#include <alloca.h>
#include <ctype.h>
#include <fcntl.h>
#include <newt.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

#include "devices.h"
#include "isys/imount.h"
#include "../isys/isys.h"
#include "lang.h"
#include "loader.h"
#include "misc.h"
#include "modules.h"
#include "windows.h"
#include "../kudzu/kudzu.h"

static int getModuleArgs(struct moduleInfo * mod, char *** argPtr) {
    struct newtWinEntry * entries;
    int i;
    int numArgs;
    char ** values;
    char * chptr, * end;
    int misc = -1;
    char ** args;
    int argc;
    int rc;
    char * text;

    entries = alloca(sizeof(*entries) * (mod->numArgs + 2));
    values = alloca(sizeof(*values) * (mod->numArgs + 2));

    for (i = 0; i < mod->numArgs; i++) {
    	entries[i].text = mod->args[i].description;
	if (mod->args[i].arg) {
	    values[i] = malloc(strlen(mod->args[i].arg) + 2);
	    strcpy(values[i], mod->args[i].arg);
	    strcat(values[i], "=");
	} else {
	    values[i] = NULL;
	}
	entries[i].value = values + i;
	entries[i].flags = NEWT_FLAG_SCROLL;
    }

    numArgs = i;

    if (!(mod->flags & MI_FLAG_NOMISCARGS)) {
    	values[i] = NULL;
    	entries[i].text = _("Miscellaneous");
    	entries[i].value = values + i;
	entries[i].flags = NEWT_FLAG_SCROLL;
	misc = i;
	i++;
    }

    entries[i].text = (void *) entries[i].value = NULL;

    text = _("This module can take parameters which affects its "
		    "operation. If you don't know what parameters to supply, "
		    "just skip this screen by pressing the \"OK\" button "
		    "now.");

    rc = newtWinEntries(_("Module Parameters"), text,
		        40, 5, 15, 20, entries, _("OK"), 
		        _("Back"), NULL);

    if (rc == 2) {
        for (i = 0; i < numArgs; i++)
	    if (values[i]) free(values[i]);
	return LOADER_BACK;
    }

    /* we keep args big enough for the args we know about, plus a NULL */

    args = malloc(sizeof(*args) * (numArgs + 1));
    argc = 0;

    for (i = 0; i < numArgs; i++) {
    	if (values[i] && *values[i]) {
	    chptr = values[i] + strlen(values[i]) - 1;
	    while (isspace(*chptr)) chptr--;
	    if (*chptr != '=')
		args[argc++] = values[i];
	}
    }

    if (misc >= 0 && values[misc]) {
    	chptr = values[misc];
	i = 1;
	while (*chptr) {
	    if (isspace(*chptr)) i++;
	    chptr++;
	}

	args = realloc(args, sizeof(*args) * (argc + i + 1));
	chptr = values[misc];
	while (*chptr) {
	    while (*chptr && isspace(*chptr)) chptr++;
	    if (!*chptr) break;

	    end = chptr;
	    while (!isspace(*end) && *end) end++;
	    args[argc] = malloc(end - chptr + 1);
	    memcpy(args[argc], chptr, end - chptr);
	    args[argc][end - chptr] = '\0';
	    argc++;
	    chptr = end;
	}

	free(values[misc]);
    }

    args[argc] = NULL;
    *argPtr = args;

    return 0;
}

int devCopyDriverDisk(moduleInfoSet modInfo, moduleList modLoaded, 
		      moduleDeps *modDepsPtr, int flags, char * mntPoint) {
    char * files[] = { "pcitable", "modules.cgz", "modinfo", "modules.dep", 
			NULL };
    char * dirName;
    char ** file;
    int badDisk = 0;
    static int diskNum = 0;
    char from[200], to[200];

    sprintf(from, "%s/rhdd-6.1", mntPoint);
    if (access(from, R_OK))
	badDisk = 1;

    dirName = malloc(80);
    sprintf(dirName, "/tmp/DD-%d", diskNum);
    mkdir(dirName, 0755);
    for (file = files; *file; file++) {
	sprintf(from, "%s/%s", mntPoint, *file);
	sprintf(to, "%s/%s", dirName, *file);

	if (copyFile(from, to))
	    badDisk = 1;
    }

    umount("/tmp/drivers");

    if (badDisk) {
	return 1;
    }

    sprintf(from, "%s/modinfo", dirName);
    isysReadModuleInfo(from, modInfo, dirName);
    sprintf(from, "%s/modules.dep", dirName);
    mlLoadDeps(modDepsPtr, from);
    sprintf(from, "%s/pcitable", dirName);
    pciReadDrivers(from);

    diskNum++;

    return 0;
}

int devLoadDriverDisk(moduleInfoSet modInfo, moduleList modLoaded,
		      moduleDeps *modDepsPtr, int flags, int cancelNotBack) {
    int rc;
    int done = 0;

    do { 
	rc = newtWinChoice(_("Devices"), _("OK"), 
		cancelNotBack ? _("Cancel") : _("Back"),
		_("Insert your driver disk and press \"OK\" to continue."));

	if (rc == 2) return LOADER_BACK;

	mlLoadModule("vfat", NULL, modLoaded, (*modDepsPtr), NULL, 
		     modInfo, flags);

	devMakeInode("fd0", "/tmp/fd0");

	if (doPwMount("/tmp/fd0", "/tmp/drivers", "vfat", 1, 0, NULL, NULL))
	    if (doPwMount("/tmp/fd0", "/tmp/drivers", "ext2", 1, 0, NULL, NULL))
		newtWinMessage(_("Error"), _("OK"), 
			       _("Failed to mount floppy disk."));

	if (devCopyDriverDisk(modInfo, modLoaded, modDepsPtr, 
			      flags, "/tmp/drivers"))
	    newtWinMessage(_("Error"), _("OK"),
		_("The floppy disk you inserted is not a valid driver disk "
		  "for this release of Red Hat Linux."));
	else
	    done = 1;
    } while (!done);

    return 0;
}

struct sortModuleList {
    int index;
    moduleInfoSet modInfo;
};

static int sortDrivers(const void * a, const void * b) {
    const struct sortModuleList * one = a;
    const struct sortModuleList * two = b;

    return strcmp(one->modInfo->moduleList[one->index].description,
		  one->modInfo->moduleList[two->index].description);
}

static int pickModule(moduleInfoSet modInfo, enum driverMajor type,
		      moduleList modLoaded, moduleDeps modDeps, 
		      struct moduleInfo * suggestion,
		      struct moduleInfo ** modp, int * specifyParams,
		      int flags) {
    int i;
    newtComponent form, text, listbox, checkbox, ok, back;
    newtGrid buttons, grid, subgrid;
    char specifyParameters = *specifyParams ? '*' : ' ';
    struct newtExitStruct es;
    struct sortModuleList * sortedOrder;
    int numSorted = 0;

    sortedOrder = alloca(sizeof(*sortedOrder) * modInfo->numModules);

    for (i = 0; i < modInfo->numModules; i++) {
	if (modInfo->moduleList[i].major == type && 
	    !mlModuleInList(modInfo->moduleList[i].moduleName, modLoaded)) {
	    sortedOrder[numSorted].index = i;
	    sortedOrder[numSorted++].modInfo = modInfo;
	}
    }	

    qsort(sortedOrder, numSorted, sizeof(*sortedOrder), sortDrivers);

    do {
	if (FL_MODDISK(flags)) {
	    text = newtTextboxReflowed(-1, -1, _("Which driver should I try?. "
		    "If the driver you need does not appear in this list, and "
		    "you have a separate driver disk, please press F2."),
					30, 0, 10, 0);
	} else {
	    text = newtTextboxReflowed(-1, -1, _("Which driver should I try?"),
					20, 0, 10, 0);
	}

	listbox = newtListbox(-1, -1, 6, 
			NEWT_FLAG_SCROLL | NEWT_FLAG_RETURNEXIT);

	buttons = newtButtonBar(_("OK"), &ok, _("Back"), &back, NULL);
	checkbox = newtCheckbox(-1, -1, _("Specify module parameters"),
				specifyParameters, NULL, &specifyParameters);

	form = newtForm(NULL, NULL, 0);

	if (FL_MODDISK(flags))
	    newtFormAddHotKey(form, NEWT_KEY_F2);

	for (i = 0; i < numSorted; i++) {
	    int num = sortedOrder[i].index;

	    newtListboxAppendEntry(listbox, 
				   modInfo->moduleList[num].description,
				   (void *) num);
	    if (modp && (modInfo->moduleList + num) == *modp)
		newtListboxSetCurrentByKey(listbox, (void *) num);
	}

	subgrid = newtGridVStacked(NEWT_GRID_COMPONENT, listbox,
				   NEWT_GRID_COMPONENT, checkbox, NULL);
	grid = newtGridBasicWindow(text, subgrid, buttons);
	newtGridAddComponentsToForm(grid, form, 1);
	newtGridWrappedWindow(grid, _("Devices"));

	newtFormRun(form, &es);

	i = (int) newtListboxGetCurrent(listbox);

	newtGridFree(grid, 1);
	newtFormDestroy(form);
	newtPopWindow();

	if (es.reason == NEWT_EXIT_COMPONENT && es.u.co == back) {
	    return LOADER_BACK;
	} else if (es.reason == NEWT_EXIT_HOTKEY && es.u.key == NEWT_KEY_F2) {
	    devLoadDriverDisk(modInfo, modLoaded, &modDeps, flags, 0);
	    continue;
	} else {
	    break;
	}
    } while (1);

    *specifyParams = (specifyParameters != ' ');
    *modp = modInfo->moduleList + i;

    return 0;
}

int devDeviceMenu(enum driverMajor type, moduleInfoSet modInfo, 
		  moduleList modLoaded, moduleDeps modDeps, int flags,
		  char ** moduleName) {
    struct moduleInfo * mod = NULL;
    enum { S_MODULE, S_ARGS, S_DONE } stage = S_MODULE;
    int rc;
    char ** args = NULL, ** arg;
    int specifyArgs = 0;

    while (stage != S_DONE) {
    	switch (stage) {
	  case S_MODULE:
	    if ((rc = pickModule(modInfo, type, modLoaded, modDeps, mod, &mod, 
				 &specifyArgs, flags)))
		return LOADER_BACK;
	    stage = S_ARGS;
	    break;

	  case S_ARGS:
	    if (specifyArgs) {
		rc = getModuleArgs(mod, &args);
		if (rc) {
		    stage = S_MODULE;
		    break;
		}
	    }
	    stage = S_DONE;
	    break;

	  case S_DONE:
	}
    }

    if (mod->major == DRIVER_SCSI) {
	scsiWindow(mod->moduleName);
	sleep(1);
    }
    rc = mlLoadModule(mod->moduleName, mod->path, modLoaded, modDeps, args,
		      modInfo, flags);
    if (mod->major == DRIVER_SCSI) newtPopWindow();

    if (args) {
	for (arg = args; *arg; arg++)
	    free(*arg);
	free(args);
    }

    if (!rc && moduleName)
        *moduleName = mod->moduleName;
    
    return rc;
}