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

#include "hd.h"
#include "fs.h"
#include "ftp.h"
#include "install.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 ftpinstGetMappedFile(struct installMethod * method, char * name, 
		                char ** realName, int isPreskel);
static int imageGetFile(struct installMethod * method, char * name, 
			char ** realName, int isPreskel);
static int singleimageSetSymlinks(struct installMethod * method, 
	              		  struct partitionTable table,
	              		  struct netConfig * netc, 
				  struct netInterface * intf,
				  struct driversLoaded ** dl);
static int hdMount(struct installMethod * method, struct partitionTable table,
	           struct netConfig * netc, struct netInterface * intf,
	           struct driversLoaded ** dl);
static int ftpSetup(struct installMethod * method, struct partitionTable table,
	            struct netConfig * netc, struct netInterface * intf,
	            struct driversLoaded ** dl);
static int fileGetPackageSet(struct installMethod * method,
			     struct pkgSet * ps);
static int fileGetComponentSet(struct installMethod * method,
			       struct pkgSet * ps,
			       struct componentSet * cs);
static int scanGetPackageSet(struct installMethod * method,
			     struct pkgSet * ps);
static int imageGetPackageSet(struct installMethod * method,
			      struct pkgSet * ps);
static int imageGetComponentSet(struct installMethod * method,
			     struct pkgSet * ps,
			     struct componentSet * cs);

static struct installMethod methods[] = {
    { "Local CDROM", 		"cdrom", 0, NULL, singleimageSetSymlinks, 
		imageGetFile, 
		imageGetPackageSet, imageGetComponentSet, NULL },
    { "NFS image", 		"nfs", 0, NULL, singleimageSetSymlinks, 
		imageGetFile, 
		imageGetPackageSet, imageGetComponentSet, NULL },
    { "hard drive",		"hd",  0, NULL, hdMount, imageGetFile,
		scanGetPackageSet, imageGetComponentSet, NULL },
    { "FTP",			"ftp", 1, NULL, ftpSetup, ftpinstGetMappedFile,
		fileGetPackageSet, fileGetComponentSet, NULL },
} ;
static int numMethods = sizeof(methods) / sizeof(struct installMethod);

struct ftpinfo {
    char * address;
    char * login;
    char * password;
    char * prefix;
    int sock;
};

struct installMethod * findInstallMethod(char * argptr) {
    int i;

    for (i = 0; i < numMethods; i++) 
	if (!strcmp(argptr, methods[i].abbrev)) return (methods + i);

    return NULL;
}

static int imageGetFile(struct installMethod * method, char * name, 
			char ** realName, int isPreskel) {
    static char buf[300];

    if (!strcmp(name, "skeleton.cgz") || !strcmp(name, "uglist") ||
	!strcmp(name, "rpmconvert"))
	strcpy(buf, "/tmp/rhimage/RedHat/base/");
    else
	strcpy(buf, "/tmp/rhimage/RedHat/RPMS/");

    strcat(buf, name);
    *realName = buf;

    return 0;
}

static int hdMount(struct installMethod * method, struct partitionTable table,
	           struct netConfig * netc, struct netInterface * intf,
	           struct driversLoaded ** dl) {
    newtComponent okay, cancel, form, text, listbox, label, answer;
    int i;
    char buf[80];
    struct partition * part = NULL;
    char * type;

    newtOpenWindow(10, 2, 64, 19, "Select Partition");
    text = newtTextbox(1, 1, 62, 4, NEWT_TEXTBOX_WRAP);
    newtTextboxSetText(text, "What partition holds the RedHat/RPMS and "
			"RedHat/base directories?");
    okay = newtButton(15, 15, "Ok");
    cancel = newtButton(38, 15, "Cancel");

    form = newtForm(NULL, NULL, 0);

    listbox = newtListbox(7, 7, 7, NEWT_LISTBOX_RETURNEXIT);

    label = newtLabel(5, 6, "  Device          Begin        End   Size (k)");

    for (i = 0; i < table.count; i++) {
	if (table.parts[i].type == PART_EXT2 ||
	    table.parts[i].type == PART_DOS) {
	    sprintf(buf, "/dev/%-5s  %9d  %9d  %9d", table.parts[i].device, 
		    table.parts[i].begin, table.parts[i].end, 
		    table.parts[i].size);
	    newtListboxAddEntry(listbox, buf, &table.parts[i]);
	}
    }

    newtFormAddComponents(form, text, label, listbox, okay, cancel, NULL);
    answer = newtRunForm(form);
			
    if (answer != cancel) {
	part = newtListboxGetCurrent(listbox);
    }

    newtFormDestroy(form);
    newtPopWindow();

    if (answer == cancel) return INST_CANCEL;

    switch (part->type) {
      case PART_EXT2:	type = "ext2"; 		break;
      case PART_DOS:	type = "msdos"; 	break;
      default:	return INST_ERROR;
    }

    if (doMount(part->device, "/tmp/rhimage", type, 1))
	return INST_ERROR;

    return 0;
}

static int scanGetPackageSet(struct installMethod * method,
			     struct pkgSet * ps) {
    return psUsingDirectory("/tmp/rhimage/RedHat/RPMS", ps);
}

static int imageGetPackageSet(struct installMethod * method,
			      struct pkgSet * ps) {
    return psFromHeaderList("/tmp/rhimage/RedHat/base/hdlist", ps);
}

static int imageGetComponentSet(struct installMethod * method,
			        struct pkgSet * ps,
			        struct componentSet * cs) {
    return psReadComponentsFile("/tmp/rhimage/RedHat/base/comps", ps, cs);
}

static int singleimageSetSymlinks(struct installMethod * method, 
	              		  struct partitionTable table,
	              		  struct netConfig * netc, 
				  struct netInterface * intf,
				  struct driversLoaded ** dl) {
    logMessage("making symlink from /tmp/rhimage to image");
    symlink("rhimage", "/tmp/image");
    return 0;
}

static int ftpinstGetFile(struct ftpinfo * fi, char * filename, char * dest) {
    char * buf;
    newtComponent form;
    int fd, rc;

    logMessage("ftping %s as %s", filename, dest);

    newtOpenWindow(10, 10, 60, 3, "Installing");

    buf = alloca(strlen(fi->prefix) + strlen(filename) + 30);
    sprintf(buf, "Retrieving %s...", filename);
    form = newtForm(NULL, NULL, 0);
    newtFormAddComponent(form, newtLabel(1, 1, buf));

    newtDrawForm(form);
    newtRefresh();
    
    fd = open(dest, O_WRONLY | O_CREAT, 0644);
    if (fd < 0) {
	messageWindow("Error", "open of %s failed: %s\n", dest, 
			strerror(errno));
	return INST_ERROR;
    }

    strcpy(buf, fi->prefix);
    strcat(buf, "/RedHat/");
    strcat(buf, filename);
    rc = ftpGetFile(fi->sock, buf, fd);

    close(fd);

    newtFormDestroy(form);
    newtPopWindow();

    if (rc) {
	messageWindow("ftp", "I cannot get file %s: %s\n", buf,
			ftpStrerror(rc));
	return INST_ERROR;
    }

    return 0;
}

static int ftpSetupPanel(struct ftpinfo * fi) {
    newtComponent form, okay, cancel, siteEntry, dirEntry, answer, text;
    char * site, * dir;

    if (fi->address) {
	site = fi->address;
	dir = fi->prefix;
    } else {
	site = "";
	dir = "";
    }

    newtOpenWindow(15, 4, 50, 14, "FTP Setup");

    form = newtForm(NULL, NULL, 0);
    okay = newtButton(10, 10, "Ok");
    cancel = newtButton(30, 10, "Cancel");

    text = newtTextbox(1, 1, 47, 5, NEWT_TEXTBOX_WRAP);
    newtTextboxSetText(text,
	"Please enter the following information:\n"
	"\n"
	"    o the name or IP number of your FTP server\n"
	"    o the directory on that server containing\n"
	"      Red Hat Linux for your architecture");

    newtFormAddComponent(form, newtLabel(3, 7, "FTP site name    :"));
    newtFormAddComponent(form, newtLabel(3, 8, "Red Hat directory:"));

    siteEntry = newtEntry(22, 7, site, 24, &site, NEWT_ENTRY_SCROLL);
    dirEntry = newtEntry(22, 8, dir, 24, &dir, NEWT_ENTRY_SCROLL);

    newtFormAddComponents(form, text, siteEntry, dirEntry, okay, cancel, NULL);

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

    fi->login = "ftp";
    fi->password = "rhinstall@unnamed.machine";
    fi->address = strdup(site);
    fi->prefix = strdup(dir);

    newtFormDestroy(form);
    newtPopWindow();

    return 0;
}

static int ftpSetup(struct installMethod * method, struct partitionTable table,
	            struct netConfig * netc, struct netInterface * intf,
	            struct driversLoaded ** dl) {
    struct ftpinfo fi;
    enum { FTP_SETUP_NET, FTP_SETUP_FTP, FTP_SETUP_CHECK, 
		FTP_SETUP_DONE } step = FTP_SETUP_NET;
    int rc;

    if (method->data)
	memcpy(&fi, method->data, sizeof(fi));
    else
	memset(&fi, 0, sizeof(fi));

    while (step != FTP_SETUP_DONE) {
	switch (step) {

	  case FTP_SETUP_NET:
	    rc = bringUpNetworking(intf, netc, dl);
	    if (rc) return rc;
	    step = FTP_SETUP_FTP;
	    break;

	  case FTP_SETUP_FTP:
	    rc = ftpSetupPanel(&fi);
	    if (rc == INST_ERROR) 
		return rc;
	    else if (rc)
		step = FTP_SETUP_NET;
	    else
		step = FTP_SETUP_CHECK;
	    break;

	  case FTP_SETUP_CHECK:
	    if ((fi.sock = ftpOpen(fi.address, fi.login, 
				fi.password, NULL)) < 0) {
		messageWindow("ftp", "I cannot log into machine: %s\n",
				ftpStrerror(fi.sock));
		step = FTP_SETUP_FTP;
		break;
	    }

	    if (ftpinstGetFile(&fi, "base/hdlist", "/tmp/hdlist")) {
		ftpClose(fi.sock);
		step = FTP_SETUP_FTP;
		break;
	    }

	    if (ftpinstGetFile(&fi, "base/comps", "/tmp/comps")) {
		ftpClose(fi.sock);
		step = FTP_SETUP_FTP;
		break;
	    }
	    
	    step = FTP_SETUP_DONE;
	    break;

	  case FTP_SETUP_DONE:
	    break;
	}
    }

    if (method->data) free(method->data);
    method->data = malloc(sizeof(fi));
    memcpy(method->data, &fi, sizeof(fi));

    return 0;
}

static int fileGetPackageSet(struct installMethod * method,
			     struct pkgSet * ps) {
    return psFromHeaderList("/tmp/hdlist", ps);
}

static int fileGetComponentSet(struct installMethod * method,
			       struct pkgSet * ps,
			       struct componentSet * cs) {
    return psReadComponentsFile("/tmp/comps", ps, cs);
}

static int ftpinstGetMappedFile(struct installMethod * method, char * name, 
		                char ** realName, int isPreskel) {
    static char sbuf[300];
    char * buf;
    int rc;
    struct ftpinfo * fi = method->data;

    if (isPreskel)
	strcpy(sbuf, "/mnt/");
    else
	strcpy(sbuf, "/mnt/var/tmp/");

    strcat(sbuf, name);
    *realName = sbuf;

    buf = alloca(strlen(name) + 30);
    if (!strcmp(name, "skeleton.cgz") || !strcmp(name, "uglist") ||
	!strcmp(name, "rpmconvert"))
	strcpy(buf, "base/");
    else
	strcpy(buf, "RPMS/");
    strcat(buf, name);

    rc = ftpinstGetFile(fi, buf, *realName);
    if (!rc) return 0;

    /* Try again, and relogin */
    ftpClose(fi->sock);

    if ((fi->sock = ftpOpen(fi->address, fi->login, fi->password, NULL)) < 0) {
	messageWindow("ftp", "I cannot log into machine: %s\n",
			ftpStrerror(fi->sock));
	free(fi);
	return INST_ERROR;
    }

    return ftpinstGetFile(fi, buf, *realName);
}
