#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <newt.h>
#include <popt.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mount.h>
#include <unistd.h>

#include "dns.h"
#include "fs.h"
#include "hd.h"
#include "intl.h"
#include "install.h"
#include "kickstart.h"
#include "log.h"
#include "methods.h"
#include "net.h"
#include "windows.h"

/* This was split into two pieces to keep the initial install program small */

static int nfsPrepare(struct installMethod * method, struct netInfo * netc,
		       struct intfInfo * intf, int forceSupp,
		       struct driversLoaded ** dl);
static int cdromPrepare(struct installMethod * method, struct netInfo * netc,
		       struct intfInfo * intf, int forceSupp,
		       struct driversLoaded ** dl);
static int nfsGetSetup(char ** hostptr, char ** dirptr);
int floppyRoot(struct installMethod * method, struct netInfo * netc,
		       struct intfInfo * intf, int forceSupp,
		       struct driversLoaded ** dl);
static int totalMemory(void);			/* in K */

#define CDROM_METHOD_NUM 0
#define NFS_METHOD_NUM 1
/* We have a loop a bit later which translates the 'Local CDROM' language */
static struct installMethod methods[] = {
    { "Local CDROM", 		"cdrom", 0, cdromPrepare, NULL, NULL,
		NULL, NULL, NULL },
    { "NFS image", 		"nfs", 0, nfsPrepare, NULL, NULL,
		NULL, NULL, NULL },
    { "hard drive",		"hd", 0, floppyRoot, NULL, NULL,
		NULL, NULL, NULL },
    { "FTP", 			"ftp", 0, floppyRoot, NULL, NULL,
		NULL, NULL, NULL },
#ifdef __i386__
    { "SMB image", 		"smb", 0, floppyRoot, NULL, NULL,
		NULL, NULL, NULL },
#endif
#if 0
    { "SCSI Tape Install", 	"tape", 0, floppyRoot, NULL, NULL,
		NULL, NULL, NULL },
#endif
} ;
static int numMethods = sizeof(methods) / sizeof(struct installMethod);
static int isMounted = 0;

#define LOAD_BLOCK_COUNT 16
static int loadRamdisk(char * todev, char * fromdev, int blocks,
			char * label) {
    newtComponent form, scale;
    char * topath, * frompath;
    char buf[LOAD_BLOCK_COUNT * 1024];
    int rc = 0;
    int i;
    int to, from;

    if (blocks % LOAD_BLOCK_COUNT) {
	logMessage("internal error: blocks in loadRamdisk() must be "
		"divisible by %d!!", LOAD_BLOCK_COUNT);
	return 1;
    }

    topath = alloca(strlen(todev) + 8);
    sprintf(topath, "/tmp/%s", todev);

    frompath = alloca(strlen(fromdev) + 8);
    sprintf(frompath, "/tmp/%s", fromdev);

    if (devMakeInode(todev, topath)) return 1;
    if (devMakeInode(fromdev, frompath)) {
	unlink(topath);
	return 1;
    }

    to = open(topath, O_WRONLY);
    if (to < 0) {
	logMessage("failed to open %s: %s", topath, strerror(errno));
	unlink(topath);
	unlink(frompath);
	return 1;
    }

    from = open(frompath, O_RDONLY);
    if (from < 0) {
	logMessage("failed to open %s: %s", frompath, strerror(errno));
	unlink(topath);
	unlink(frompath);
	return 1;
    }

    unlink(frompath);
    unlink(topath);

    logMessage("copying %d blocks from %s to %s", blocks, fromdev, todev);

    newtCenteredWindow(60, 5, "Loading");
    
    form = newtForm(NULL, NULL, 0);
    
    newtFormAddComponent(form, newtLabel(1, 1, label));
    scale = newtScale(1, 3, 58, blocks / LOAD_BLOCK_COUNT);
    newtFormAddComponent(form, scale);
    newtDrawForm(form);
    newtRefresh();

    for (i = 0; i < (blocks / LOAD_BLOCK_COUNT) && !rc; i++) {
	newtScaleSet(scale, i);
	newtRefresh();

	if (read(from, buf, sizeof(buf)) != sizeof(buf)) {
	    logMessage("error reading from device: %s", strerror(errno));
	    rc = 1;
	} else {
	    if (write(to, buf, sizeof(buf)) != sizeof(buf)) {
		logMessage("error writing to device: %s", strerror(errno));
		rc = 1;
	    }
	}
    }

    newtPopWindow();
    newtFormDestroy(form);
    
    close(from);
    close(to);

    return rc;
}

static int totalMemory(void) {
    int fd;
    int bytesRead;
    char buf[4096];
    char * chptr, * start;
    int total = 0;

    fd = open("/proc/meminfo", O_RDONLY);
    if (fd < 0) {
	logMessage("failed to open /proc/meminfo: %s", strerror(errno));
	return 0;
    }

    bytesRead = read(fd, buf, sizeof(buf) - 1);
    if (bytesRead < 0) {
	logMessage("failed to read from /proc/meminfo: %s", strerror(errno));
	close(fd);
	return 0;
    }

    close(fd);
    buf[bytesRead] = '\0';

    chptr = buf;
    while (*chptr && !total) {
	if (*chptr != '\n' || strncmp(chptr + 1, "MemTotal:", 9)) {
	    chptr++;
	    continue;
	}

	start = ++chptr ;
	while (*chptr && *chptr != '\n') chptr++;

	*chptr = '\0';

	logMessage("found total memory tag: \"%s\"", start);
	
	while (!isdigit(*start) && *start) start++;
	if (!*start) {
	    logMessage("no number appears after MemTotal tag");
	    return 0;
	}

	chptr = start;
	while (*chptr && isdigit(*chptr)) {
	    total = (total * 10) + (*chptr - '0');
	    chptr++;
	}
    }

    logMessage("%d kB are available", total);

    return total;
}

static int installMethodWindow(struct installMethod ** method,
			       int showSuppBox,
			       int * forceSuppPtr) {
    int i;
    newtComponent textbox, listbox, form, okay, back, result;
    newtGrid buttonBar, grid, middle;
    char forceSupp = showSuppBox ? '*' : ' ';

    textbox = newtTextboxReflowed(-1, -1, _("What type of media contains "
				  "the packages to be installed?"), 30, 10, 
				  0, 0);

    listbox = newtListbox(-1, -1, numMethods, NEWT_FLAG_RETURNEXIT);

    for (i = 0; i < numMethods; i++) {
	newtListboxAddEntry(listbox, _(methods[i].name), methods + i);
    }

    buttonBar = newtButtonBar(_("Ok"), &okay, _("Back"), &back, NULL);
    
    middle = newtCreateGrid(1, 2);
    newtGridSetField(middle, 0, 0, NEWT_GRID_COMPONENT, listbox,
		     0, 0, 0, 0, 0, 0);

#ifdef __i386__
    if (expert || showSuppBox)
	newtGridSetField(middle, 0, 1, NEWT_GRID_COMPONENT,
			    newtCheckbox(-1, -1, _("Force supplemental disk"),
				         forceSupp, NULL, &forceSupp),
			 0, 1, 0, 0, NEWT_ANCHOR_LEFT, 0);
#endif

    grid = newtGridBasicWindow(textbox, middle, buttonBar);
    newtGridWrappedWindow(grid, _("Installation Method"));

    form = newtForm(NULL, 0, 0);
    newtGridAddComponentsToForm(grid, form, 1);
    newtGridFree(grid, 1);

    result = newtRunForm(form);
    newtPopWindow();
    if (result == back) {
	newtFormDestroy(form);
	return INST_CANCEL;
    }

    *method = newtListboxGetCurrent(listbox);
    newtFormDestroy(form);

    *forceSuppPtr = (forceSupp != ' ');

    return 0;
}

int chooseInstallMethod(int showSuppBox, struct installMethod ** method, 
			struct netInfo * netc, struct intfInfo * intf, 
			struct driversLoaded ** dl) {
    int rc;
    int forceSupp = 0;

    if (kickstart) {
	if (!ksGetCommand(KS_CMD_NFS, NULL, NULL, NULL)) {
	    *method = methods + NFS_METHOD_NUM;
	    return (*method)->prepareImage((*method), netc, intf, 0, dl);
	} else if (!ksGetCommand(KS_CMD_CDROM, NULL, NULL, NULL)) {
	    *method = methods + CDROM_METHOD_NUM;
	    return (*method)->prepareImage((*method), netc, intf, 0, dl);
	} else {
	    logMessage("No kickstart method was specified.");
	    kickstart = 0;
	}
    }

    do {
	rc = installMethodWindow(method, showSuppBox, &forceSupp);
	if (rc) return rc;

	if ((*method)->prepareImage) {
	    rc = (*method)->prepareImage((*method), netc, intf, forceSupp, dl);
	    if (rc == INST_ERROR) return rc;
	}
    } while (rc);

    return 0;
}

/* We could use newtWinEntries() here, if we didn't like those clever
   callbacks */
static int nfsGetSetup(char ** hostptr, char ** dirptr) {
    newtComponent form, okay, cancel, answer, text;
    newtComponent siteLabel, dirLabel;
    newtGrid buttons, entryArea, grid;
    struct nfsMountCallbackInfo cbInfo;
    char * reflowedText;
    int width, height;

    if (*hostptr) {
	cbInfo.serverVal = *hostptr;
	cbInfo.netpathVal = *dirptr;
    } else {
	cbInfo.serverVal = "";
	cbInfo.netpathVal = "";
    }

    form = newtForm(NULL, NULL, 0);
    buttons = newtButtonBar(_("Ok"), &okay, _("Back"), &cancel, NULL);

    reflowedText = newtReflowText(
		       _("Please enter the following information:\n"
		         "\n"
		         "    o the name or IP number of your NFS server\n"
		         "    o the directory on that server containing\n"
		         "      Red Hat Linux for your architecture"),
			 60, 10, 10, &width, &height);
    text = newtTextbox(-1, -1, width, height, NEWT_TEXTBOX_WRAP);
    newtTextboxSetText(text, reflowedText);

    free(reflowedText);

    entryArea = newtCreateGrid(2, 2);
    siteLabel = newtLabel(-1, -1, _("NFS server name:"));
    newtGridSetField(entryArea, 0, 0, NEWT_GRID_COMPONENT, siteLabel, 
			0, 0, 0, 0, NEWT_ANCHOR_LEFT, 0);
    dirLabel = newtLabel(-1, -1, _("Red Hat directory:"));
    newtGridSetField(entryArea, 0, 1, NEWT_GRID_COMPONENT, dirLabel, 
			0, 0, 0, 0, NEWT_ANCHOR_LEFT, 0);

    cbInfo.server = newtEntry(-1, -1, cbInfo.serverVal, 24, &cbInfo.serverVal, 
				NEWT_ENTRY_SCROLL);
    newtComponentAddCallback(cbInfo.server, nfsMountCallback, &cbInfo);
    cbInfo.netpath = newtEntry(-1, -1, cbInfo.netpathVal, 24, 
				&cbInfo.netpathVal, NEWT_ENTRY_SCROLL);
    cbInfo.mntpoint = NULL;

    newtGridSetField(entryArea, 1, 0, NEWT_GRID_COMPONENT, cbInfo.server, 
			1, 0, 0, 0, 0, 0);
    newtGridSetField(entryArea, 1, 1, NEWT_GRID_COMPONENT, cbInfo.netpath, 
			1, 0, 0, 0, 0, 0);

    grid = newtCreateGrid(1, 3);
    newtGridSetField(grid, 0, 0, NEWT_GRID_COMPONENT, text,
			0, 0, 0, 0, 0, 0);
    newtGridSetField(grid, 0, 1, NEWT_GRID_SUBGRID, entryArea,
			0, 1, 0, 1, 0, 0);
    newtGridSetField(grid, 0, 2, NEWT_GRID_SUBGRID, buttons,
			0, 0, 0, 0, 0, NEWT_GRID_FLAG_GROWX);

    newtGridWrappedWindow(grid, _("NFS Setup"));

    newtGridFree(grid, 1);

    newtFormAddComponents(form, text, cbInfo.server, cbInfo.netpath, okay, 
			  cancel, dirLabel, siteLabel, NULL);

    answer = newtRunForm(form);
    if (answer == cancel) {
	newtFormDestroy(form);
	newtPopWindow();
	
	return INST_CANCEL;
    }

    if (*hostptr) free(*hostptr);
    if (*dirptr) free(*dirptr);
    *hostptr = strdup(cbInfo.serverVal);
    *dirptr = strdup(cbInfo.netpathVal);

    newtFormDestroy(form);
    newtPopWindow();

    return 0;
}

static int cdromPrepare(struct installMethod * method, struct netInfo * netc,
		        struct intfInfo * intf, int useSupp, 
			struct driversLoaded ** dl) {
    char * cddev;
    int rc;

    if (!kickstart)
	rc = newtWinChoice(_("Note"), _("Ok"), ("Back"),
			_("Insert your Red Hat CD into your CD drive now"));

    if (rc == 2) return INST_CANCEL;

    while (1) {
	/* this autoprobes already, so we don't need to do anything special
	   for the kickstart :-) */
	rc = setupCDdevice(&cddev, dl);
	if (rc) return rc;

	if ((rc = loadFilesystem("isofs", "iso9660", dl))) return rc;

	rc = doMount(cddev, "/tmp/rhimage", "iso9660", 1, 0);
	if (rc) {
	    removeCDmodule(dl);
	    newtWinMessage(_("Error"), _("Ok"), 
		    _("I could not mount a CD on device /dev/%s"), cddev);
	    return INST_CANCEL;
	}

	if (access("/tmp/rhimage/RedHat", R_OK)) {
	    umount("/tmp/rhimage");
	    removeCDmodule(dl);
	    newtWinMessage(_("Error"), _("Ok"), 
			   _("That CDROM device does "
			     "not seem to contain a Red Hat CDROM."));
	    return INST_CANCEL;
	}

	rc = 0;
	while (useSupp) {
	    rc = floppyRoot(NULL, NULL, NULL, 0, NULL);

	    logMessage("got floppy root rc=%d", rc);

	    if (rc == INST_CANCEL) {
		umount("/tmp/rhimage");
		break;
	    } else if (!rc) {
		logMessage("breaking out of loop");
		break;
	    }
	}

	if (!rc) break;
    }

    if (!useSupp) {
	if (!access("/tmp/rhimage/RedHat/instimage/lib", X_OK)) {
	    unlink("/tmp/rhimage/RedHat/instimage/lib");
	    symlink("/tmp/rhimage/RedHat/instimage/lib", "/lib");
	}

	if (!access("/tmp/rhimage/RedHat/instimage/usr/bin", X_OK)) {
	    unlink("/tmp/rhimage/RedHat/instimage/usr/bin");
	    symlink("/tmp/rhimage/RedHat/instimage/usr/bin", "/usr/bin");
	}

	if (!access("/tmp/rhimage/RedHat/instimage/usr/etc", X_OK)) {
	    unlink("/tmp/rhimage/RedHat/instimage/usr/etc");
	    symlink("/tmp/rhimage/RedHat/instimage/usr/etc", "/usr/etc");
	}
    }

    logMessage("done here");

    return 0;
}

static int nfsPrepare(struct installMethod * method, struct netInfo * netc,
		      struct intfInfo * intf, int useSupp,
		      struct driversLoaded ** dl) {
    char * host = NULL, * dir = NULL;
    char * buf;
    enum { NFS_STEP_NET, NFS_STEP_INFO, NFS_STEP_MOUNT, NFS_STEP_DONE }
		step = NFS_STEP_NET;
    int rc;
    int ksArgc;
    char ** ksArgv;
    poptContext optCon;
    int direction = 1;
    struct poptOption ksNfsOptions[] = {
	    { "server", '\0', POPT_ARG_STRING, &host, 0 },
	    { "dir", '\0', POPT_ARG_STRING, &dir, 0 },
	    { 0, 0, 0, 0, 0 }
	};


    if (kickstart) {
	/* FIXME: there must be a better test to use here */
	if (!intf->set || !netc->set) 
	    if (bringUpNetworking(intf, netc, dl, 1)) return INST_ERROR;

	ksGetCommand(KS_CMD_NFS, NULL, &ksArgc, &ksArgv);

	optCon = poptGetContext(NULL, ksArgc, ksArgv, ksNfsOptions, 0);

	if ((rc = poptGetNextOpt(optCon)) < -1) {
	    newtWinMessage(_("nfs command"),  _("Ok"),
		       _("bad argument to kickstart nfs command %s: %s"),
		       poptBadOption(optCon, POPT_BADOPTION_NOALIAS), 
		       poptStrerror(rc));
	}

	if (!host || !dir) {
	    newtWinMessage(_("nfs command"),  _("Ok"),
		       _("nfs command incomplete"));
	} else {
	    step = NFS_STEP_MOUNT;
	}
    }

    while (step != NFS_STEP_DONE) {
	switch (step) {
	  case NFS_STEP_NET:
	    rc = bringUpNetworking(intf, netc, dl, direction);
	    if (rc) return rc;
	    direction = 1;
	    step = NFS_STEP_INFO;
	    break;

	  case NFS_STEP_INFO:
	    if (!host && (intf->set & INTFINFO_HAS_BOOTSERVER))
		host = mygethostbyaddr(inet_ntoa(intf->bootServer));
	    if (!dir && (intf->set & INTFINFO_HAS_BOOTFILE))
		dir = strdup(intf->bootFile);

	    rc = nfsGetSetup(&host, &dir);
	    if (rc == INST_CANCEL) {
		step = NFS_STEP_NET;
		direction = -1;
	    } else if (rc == INST_ERROR)
		return INST_ERROR;
	    else
		step = NFS_STEP_MOUNT;
	    break;

	  case NFS_STEP_MOUNT:
	    if (!strlen(host) || !strlen(dir))
		rc = INST_ERROR;
	    else {
		buf = malloc(strlen(host) + strlen(dir) + 10);
		strcpy(buf, host);
		strcat(buf, ":");
		strcat(buf, dir);

		if ((rc = loadFilesystem("nfs", "nfs", dl))) return rc;

		rc = doMount(buf, "/tmp/rhimage", "nfs", 1, 0);
		free(buf);
	    }

	    if (rc) {
		step = NFS_STEP_INFO;
		newtWinMessage(_("Error"), _("Ok"), 
		        _("I could not mount that directory from the server"));
	    } else {
	        if (access("/tmp/rhimage/RedHat", R_OK)) {
		    step = NFS_STEP_INFO;
		    newtWinMessage(_("Error"), _("Ok"), 
				   _("That directory does not seem to contain "
				     "a Red Hat installation tree."));
		    umount("/tmp/rhimage");
		} else
		    step = NFS_STEP_DONE;
	    }

	    rc = 0;
	    while (useSupp) {
		rc = floppyRoot(NULL, NULL, NULL, 0, NULL);

		if (rc == INST_CANCEL) {
		    umount("/tmp/rhimage");
		    break;
		} else if (!rc) {
		    break;
		}
	    }

	    if (rc) {
		umount("/tmp/rhimage");
		step = NFS_STEP_INFO;
	    }

	    break;

	  case NFS_STEP_DONE:
	    break;
	}
    }

    if (!kickstart) {
	free(host);
	free(dir);
    }

    if (!useSupp) {
	if (!access("/tmp/rhimage/RedHat/instimage/lib", X_OK)) {
	    unlink("/tmp/rhimage/RedHat/instimage/lib");
	    symlink("/tmp/rhimage/RedHat/instimage/lib", "/lib");
	}

	if (!access("/tmp/rhimage/RedHat/instimage/usr/bin", X_OK)) {
	    unlink("/tmp/rhimage/RedHat/instimage/usr/bin");
	    symlink("/tmp/rhimage/RedHat/instimage/usr/bin", "/usr/bin");
	}

	if (!access("/tmp/rhimage/RedHat/instimage/usr/etc", X_OK)) {
	    unlink("/tmp/rhimage/RedHat/instimage/usr/etc");
	    symlink("/tmp/rhimage/RedHat/instimage/usr/etc", "/usr/etc");
	}
    }

    return 0;
}

int floppyRoot(struct installMethod * method, struct netInfo * netc,
	       struct intfInfo * intf, int forceSuppDisk,
	       struct driversLoaded ** dl) { 
    int rc;

    if (!access("/usr/bin/runinstall2", R_OK)) {
	isMounted = 1;
	return 0;
    }

    do {
	rc = newtWinChoice(_("Supplemental Disk"), _("Ok"), _("Back"),
		_("This install method requires a second disk. Please remove "
		  "the boot disk currently in your drive and replace it with "
		  "the Red Hat Supplementary Install disk."));
	if (rc == 2) return INST_CANCEL;

	rc = loadFloppyRoot(method);
    } while (rc);

    return 0;
}

int loadFloppyRoot(struct installMethod * method) { 
    int rc;

    if (isMounted) return 0;

    if (testing) return 0;

    while (doMount("fd0", "/tmp/image", "ext2", 1, 0) ||
	   access("/tmp/image/usr/bin/runinstall2", R_OK)) {
	/* in case the mount succeeded */
	umount("/tmp/image");

	rc = newtWinChoice(_("Supplemental Disk"), _("Ok"), _("Cancel"),
	    _("I failed to mount the floppy. Please insert the "
	      "Red Hat Supplementary Install disk, or choose "
	      "Cancel to pick a different installation process."));
	if (rc == 2) return INST_CANCEL;
    }

    if (totalMemory() > 8000) {
	umount("/tmp/image");
	loadRamdisk("ram2", "fd0", 1440, _("Loading supplemental disk..."));
	if (doMount("ram2", "/tmp/image", "ext2", 1, 0)) {
	    errorWindow("Error mounting ramdisk. This shouldn't "
			    "happen, and I'm rebooting your system now.");
	    exit(1);
	}
    } 

    symlink("/tmp/image/lib", "/lib");
    symlink("/tmp/image/etc", "/etc");
    symlink("/tmp/image/usr/bin", "/usr/bin");
    symlink("/tmp/image/usr/etc", "/usr/etc");

    isMounted = 1;

    return 0;
}