#include <alloca.h>
#include <ctype.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>

#include <stdio.h>

#include "isys.h"

struct moduleInfo * isysGetModuleList(moduleInfoSet mis, 
				      enum driverMajor major) {
    struct moduleInfo * miList, * next;
    int i;

    next = miList = malloc(sizeof(*miList) * mis->numModules + 1);
    for (i = 0; i < mis->numModules; i++) {
	if (mis->moduleList[i].major == major || major == DRIVER_NONE) {
	    *next = mis->moduleList[i];
	    next++;
	}
    }

    if (next == miList) {
	free(next);
	return NULL;
    }

    next->moduleName = NULL;
    next++;

    miList = realloc(miList, sizeof(*miList) * (next - miList));
    return miList;
}

struct moduleInfo * isysFindModuleInfo(moduleInfoSet mis, 
				       const char * moduleName) {
    int i;
    struct moduleInfo * found = NULL;

    for (i = 0; i < mis->numModules; i++) {
	if (!strcmp(moduleName, mis->moduleList[i].moduleName)) {
	    if (!found)
		found = mis->moduleList + i;
	    else if (found->locationID && !mis->moduleList[i].locationID)
		;
	    else
		found = mis->moduleList + i;
	}
    }

    return found;
}

moduleInfoSet isysNewModuleInfoSet(void) {
    return calloc(sizeof(struct moduleInfoSet_s), 1);
}

int isysReadModuleInfo(const char * filename, moduleInfoSet mis, void * ident) {
    int fd, isIndented;
    char * buf, * start, * next, * chptr;
    struct stat sb;
    char oldch;
    struct moduleInfo * nextModule;
    int modulesAlloced;
    int i;

    fd = open(filename, O_RDONLY);
    if (fd < 0) return -1;
 
    fstat(fd, &sb);
    buf = alloca(sb.st_size + 1);
    i = read(fd, buf, sb.st_size);
    buf[sb.st_size] = '\0';
    close(fd);

    if (i != sb.st_size)
	return -1;

    nextModule = NULL;
    modulesAlloced = mis->numModules;

    if (strncmp(buf, "Version 0\n", 10)) return -1;

    start = buf + 10;
    while (start && *start) {
	chptr = strchr(start, '\n');
	if (chptr) {
	    /* slice and dice */
	    next = chptr + 1;
	} else {
	    chptr + strlen(start) - 1;
	}

	chptr--;
	while (isspace(*chptr)) chptr--;
	chptr++;
	*chptr = '\0';

	isIndented = 0;
	if (isspace(*start)) {
	    while (isspace(*start) && *start != '\n') start++;
	    isIndented = 1;
	}

	if (*start != '\n' && *start && *start != '#') {
	    if (!isIndented) {
		if (nextModule && nextModule->moduleName &&
		    nextModule == (mis->moduleList + mis->numModules)) {
			mis->numModules++; 
		}

		if (mis->numModules == modulesAlloced) {
		    modulesAlloced += 5;
		    mis->moduleList = realloc(mis->moduleList,
			modulesAlloced * sizeof(*mis->moduleList));
		}

		nextModule = NULL;
		for (i = 0; i < mis->numModules; i++) {
		    if (!strcmp(mis->moduleList[i].moduleName, start)) {
			nextModule = mis->moduleList + i;
			break;
		    }
		}

		if (!nextModule) {
		    nextModule = mis->moduleList + mis->numModules;

		    nextModule->moduleName = strdup(start);
		} 

		nextModule->major = DRIVER_NONE;
		nextModule->minor = DRIVER_MINOR_NONE;
		nextModule->description = NULL;
		nextModule->flags = 0;
		nextModule->args = NULL;
		nextModule->numArgs = 0;
		nextModule->locationID = ident;
	    } else if (!nextModule) {
		/* ACK! syntax error */
		return 1;
	    } else if (nextModule->major == DRIVER_NONE) {
		chptr = start + strlen(start) - 1;
		while (!isspace(*chptr) && chptr > start) chptr--;
		if (chptr != start) chptr++;

		if (!strcmp(chptr, "eth")) {
		    nextModule->major = DRIVER_NET;
		    nextModule->minor = DRIVER_MINOR_ETHERNET;
		} else if (!strcmp(chptr, "tr")) {
		    nextModule->major = DRIVER_NET;
		    nextModule->minor = DRIVER_MINOR_TR;
		} else if (!strcmp(chptr, "plip")) {
		    nextModule->major = DRIVER_NET;
		    nextModule->minor = DRIVER_MINOR_PLIP;
		} else if (!strcmp(chptr, "scsi_hostadapter") ||
			   !strcmp(chptr, "scsi")) {
		    nextModule->major = DRIVER_SCSI;
		} else if (!strcmp(chptr, "cdrom")) {
		    nextModule->major = DRIVER_CDROM;
		}
	    } else if (!nextModule->description) {
		chptr = start + strlen(start) - 1;
		if (*start == '"' && *chptr == '"') {
		    start++;
		    *chptr = '\0';
		    nextModule->description = strdup(start);
		}
	    } else {
		nextModule->args = realloc(nextModule->args,
			sizeof(*nextModule->args) * (nextModule->numArgs + 1));
		chptr = start;
		while (!isspace(*chptr) && *chptr) chptr++;
		if (*chptr) {
		    oldch = *chptr;
		    *chptr = '\0';
		    nextModule->args[nextModule->numArgs].arg = strdup(start);

		    start = chptr + 1;
		    while (*start && isspace(*start)) start++;

		    if (*start == '"') {
			start++;
			chptr = strchr(start, '"');
			if (chptr) {
			    *chptr = '\0';
			    nextModule->args[nextModule->numArgs].description = 
				strdup(start);
			    nextModule->numArgs++;
			}
		    }
		}
	    }
	}

	start = next;
    }

    /* do we need to add in this last module? */
    if (nextModule && ((nextModule - mis->moduleList) == mis->numModules))
	mis->numModules++;

    return 0;
}

void isysFreeModuleInfoSet(moduleInfoSet mis) {
    int i, j;

    for (i = 0; i < mis->numModules; i++) {
        if (mis->moduleList[i].moduleName) 
	    free(mis->moduleList[i].moduleName);

        if (mis->moduleList[i].description) 
	    free(mis->moduleList[i].description);

	for (j = 0; i < mis->moduleList[i].numArgs; j++) {
	    if (mis->moduleList[i].args[j].arg) 
		free(mis->moduleList[i].args[j].arg) ;
	    if (mis->moduleList[i].args[j].description) 
		free(mis->moduleList[i].args[j].description) ;
	}
    }

    free(mis);
}
