#include <alloca.h>
#include <errno.h>
#include <fcntl.h>
#include <newt.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mount.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"

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

struct hdinfo {
    char * device;
    char * type;
    char * dir;
};

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

static int ftpinstStartTransfer(struct ftpinfo * fi, char * filename);
static int ftpinstFinishTransfer(struct ftpinfo * fi);
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 hdSetup(struct installMethod * method, struct partitionTable table,
	           struct netConfig * netc, struct netInterface * intf,
	           struct driversLoaded ** dl);
static int hdGetPackageSet(struct installMethod * method,
			     struct pkgSet * ps);
static int hdPrepareMedia(struct installMethod * method,
				struct fstab * fstab);
static int hdGetComponentSet(struct installMethod * method,
			       struct pkgSet * ps,
			       struct componentSet * cs);
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 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, NULL },
    { "NFS image", 		"nfs", 0, NULL, singleimageSetSymlinks, 
		imageGetFile, 
		imageGetPackageSet, imageGetComponentSet, NULL, NULL },
    { "hard drive",		"hd",  0, NULL, hdSetup, imageGetFile,
		hdGetPackageSet, hdGetComponentSet, hdPrepareMedia, NULL },
    { "FTP",			"ftp", 1, NULL, ftpSetup, ftpinstGetMappedFile,
		fileGetPackageSet, fileGetComponentSet, NULL, NULL },
} ;
static int numMethods = sizeof(methods) / sizeof(struct installMethod);

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 hdSetup(struct installMethod * method, struct partitionTable table,
	           struct netConfig * netc, struct netInterface * intf,
	           struct driversLoaded ** dl) {
    newtComponent okay, cancel, form, text, listbox, label, answer, dirEntry;
    int i, j;
    char buf[80];
    struct partition * part = NULL;
    char * type;
    char * dir;
    char * dest;
    char * defaultDevice;
    char * defaultDir;
    int done = 0;
    struct hdinfo * hdi;

    if (method->data) {
	hdi = method->data;
	defaultDir = strdup(hdi->dir);
	defaultDevice = hdi->device;
    } else {
	defaultDir = strdup("/");
	defaultDevice = "";
    }

    while (!done) {
	newtOpenWindow(10, 4, 64, 17, "Select Partition");
	text = newtTextbox(1, 1, 62, 4, NEWT_TEXTBOX_WRAP);
	newtTextboxSetText(text, "What partition and directory on that "
			    "partition hold the RedHat/RPMS and "
			    "RedHat/base directories?");
	okay = newtButton(15, 13, "Ok");
	cancel = newtButton(38, 13, "Cancel");

	form = newtForm(NULL, NULL, 0);

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

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

	for (i = 0, j = 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]);
	
		if (!strcmp(table.parts[i].device, defaultDevice)) {
		    newtListboxSetCurrent(listbox, j);
		}

		j++;
	    }
	}

	newtFormAddComponent(form, 
				newtLabel(1, 11, "Directory holding Red Hat:"));
	dirEntry = newtEntry(28, 11, defaultDir, 28, &dir, NEWT_ENTRY_SCROLL);

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

	dir = strdup(dir);
	free(defaultDir);
	defaultDir = dir;
	defaultDevice = part->device;

	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:	continue;
	}

	if (doMount(part->device, "/tmp/hdimage", type, 1, 0))
	    continue;

	unlink("/tmp/rhimage");
	
	/* the physical device is mounted on /tmp/hdimage, but all
	   access are through /tmp/rhimage which points to the RedHat
	   directory in /tmp/hdimage */

	dest = alloca(strlen(dir) + 20);
	sprintf(dest, "/tmp/hdimage/%s", dir);

	if (symlink(dest, "/tmp/rhimage")) {
	    messageWindow("Error", "Failed to create /tmp/rhimage "
			    "symlink: %s", strerror(errno));
	    umount("/tmp/hdimage");
	    continue;
	} 

	if (access("/tmp/rhimage/RedHat/base/hdlist", R_OK)) {
	    messageWindow("Error", "Device %s does not appear to contain "
			"a Red Hat insallation tree.", part->device);
	    umount("/tmp/hdimage");
	    continue;
	} 

	if (method->data) {
	    hdi = method->data;
	    free(hdi->dir);
	}
    
	hdi = malloc(sizeof(*hdi));
	hdi->device = part->device;
	hdi->type = type;
	hdi->dir = strdup(dir);
	method->data = hdi;

	done = 1; 

	umount("/tmp/hdimage");
    }

    return 0;
}

static int hdGetPackageSet(struct installMethod * method,
			     struct pkgSet * ps) {
    struct hdinfo * hdi = method->data;
    int rc;

    if (doMount(hdi->device, "/tmp/hdimage", hdi->type, 1, 0))
	return INST_ERROR;

    rc = psUsingDirectory("/tmp/rhimage/RedHat/RPMS", ps);
    
    umount("/tmp/hdimage");
 
    return rc;
}

static int hdGetComponentSet(struct installMethod * method,
			       struct pkgSet * ps,
			       struct componentSet * cs) {
    struct hdinfo * hdi = method->data;
    int rc;

    if (doMount(hdi->device, "/tmp/hdimage", hdi->type, 1, 0))
	return INST_ERROR;

    rc = psReadComponentsFile("/tmp/rhimage/RedHat/base/comps", ps, cs);
    
    umount("/tmp/hdimage");
 
    return rc;
}

static int hdPrepareMedia(struct installMethod * method,
				struct fstab * fstab) {
    struct hdinfo * hdi = method->data;
    int i;
    char * buf;

    for (i = 0; i < fstab->numEntries; i++) {
	if (!strcmp(fstab->entries[i].device, hdi->device)) break;
    }

    if (i < fstab->numEntries) {
	logMessage("device %s is already mounted -- using symlink",
			fstab->entries[i].device);
	buf = alloca(strlen(fstab->entries[i].mntpoint) + 10);

	sprintf(buf, "/mnt/%s", fstab->entries[i].mntpoint);
	
	rmdir("/tmp/hdimage");
	if (symlink(buf, "/tmp/hdimage")) {
	    logMessage("failed to create symlink %s: %s\n", 
			buf, strerror(errno));
	    messageWindow("Error", "Failed to create symlink for package "
			"source.");
	    return INST_ERROR;
	}
    } else {
	logMessage("mounting device which contains packages");
	if (doMount(hdi->device, "/tmp/hdimage", hdi->type, 1, 0))
	    return INST_ERROR;
    }

    return 0;
}

static int imageGetPackageSet(struct installMethod * method,
			      struct pkgSet * ps) {
    return psFromHeaderListFile("/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;
}

/* returns a socket file descriptor, a ftpFinishTransfer() must occur after 
   this call */
static int ftpinstStartTransfer(struct ftpinfo * fi, char * filename) {
    char * buf;
    newtComponent form;
    int fd;

    logMessage("ftping %s to a fd", filename);

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

    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();

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

    return fd;
}

static int ftpinstFinishTransfer(struct ftpinfo * fi) {
    newtPopWindow();

    return ftpGetFileDone(fi->sock);
}

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, "Retrieving");

    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 ftpMainSetupPanel(struct ftpinfo * fi, char * doSecondarySetup) {
    newtComponent form, okay, cancel, siteEntry, dirEntry, answer, text, cb;
    char * site, * dir;

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

    if (fi->login || fi->password || fi->login)
	*doSecondarySetup = 'X';
    else
	*doSecondarySetup = ' ';

    newtOpenWindow(17, 3, 50, 17, "FTP Setup");

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

    text = newtTextbox(1, 1, 47, 8, 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 architecure\n");

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

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

    cb = newtCheckbox(3, 11, "Use non-anonymous ftp or a proxy server",
			*doSecondarySetup, NULL, doSecondarySetup);

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

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

    if (fi->address) free(fi->address);
    fi->address = strdup(site);

    if (fi->prefix) free(fi->prefix);
    fi->prefix = strdup(dir);

    newtFormDestroy(form);
    newtPopWindow();

    return 0;
}

static int ftpSecondarySetupPanel(struct ftpinfo * fi) {
    newtComponent form, okay, cancel, answer, text, accountEntry;
    newtComponent passwordEntry, proxyEntry;
    char * account, * password, * proxy;

    newtOpenWindow(17, 4, 50, 15, "Further FTP Setup");

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

    text = newtTextbox(1, 1, 47, 8, NEWT_TEXTBOX_WRAP);
    newtTextboxSetText(text,
	"If you are using non anonymous ftp, enter the account "
	"name and password you wish to use below. If you are "
	"using an FTP proxy enter the name of the FTP proxy server "
	"to use.");

    newtFormAddComponent(form, newtLabel(3, 6, "Account name :"));
    newtFormAddComponent(form, newtLabel(3, 7, "Password     :"));

    newtFormAddComponent(form, newtLabel(3, 9, "FTP Proxy    :"));

    accountEntry = newtEntry(18, 6, fi->login, 24, &account, NEWT_ENTRY_SCROLL);
    passwordEntry = newtEntry(18, 7, NULL, 24, &password, 
				NEWT_ENTRY_SCROLL | NEWT_ENTRY_HIDDEN);
    proxyEntry = newtEntry(18, 9, fi->proxy, 24, &proxy, NEWT_ENTRY_SCROLL);

    newtFormAddComponents(form, text, accountEntry, passwordEntry, proxyEntry, 
			  okay, cancel, NULL);

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

    if (fi->login) free(fi->login);
    if (strlen(account))
	fi->login = strdup(account);
    else
	fi->login = NULL;

    if (fi->password) free(fi->password);
    if (strlen(password))
	fi->password = strdup(password);
    else
	fi->password = NULL;

    if (fi->proxy) free(fi->proxy);
    if (strlen(proxy))
	fi->proxy = strdup(proxy);
    else
	fi->proxy = NULL;

    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_FTP1, FTP_SETUP_FTP2, FTP_SETUP_CHECK, 
		FTP_SETUP_DONE } step = FTP_SETUP_NET;
    int rc, fd;
    char doMore;

    memset(&fi, 0, sizeof(fi));

    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_FTP1;
	    break;

	  case FTP_SETUP_FTP1:
	    rc = ftpMainSetupPanel(&fi, &doMore);
	    if (rc == INST_ERROR) 
		return rc;
	    else if (rc)
		step = FTP_SETUP_NET;
	    else if (doMore == ' ')
		step = FTP_SETUP_CHECK;
	    else 
		step = FTP_SETUP_FTP2;
	    break;

	  case FTP_SETUP_FTP2:
	    rc = ftpSecondarySetupPanel(&fi);
	    if (rc == INST_ERROR) 
		return rc;
	    else if (rc)
		step = FTP_SETUP_FTP1;
	    else
		step = FTP_SETUP_CHECK;
	    break;

	  case FTP_SETUP_CHECK:
	    if ((fi.sock = ftpOpen(fi.address, fi.login, 
				   fi.password ? fi.password : "rhinstall@", 
				   fi.proxy, -1)) < 0) {
		messageWindow("ftp", "I cannot log into machine: %s\n",
				ftpStrerror(fi.sock));
		step = FTP_SETUP_FTP1;
		break;
	    }

	    
	    fd = ftpinstStartTransfer(&fi, "base/hdlist");
	    if (fd < 0) {
		newtPopWindow();
		ftpClose(fi.sock);
		step = FTP_SETUP_FTP1;
		break;
	    }

	    if (psFromHeaderListDesc(fd, &fi.ps)) {
		ftpClose(fi.sock);
		step = FTP_SETUP_FTP1;
		break;
	    }

	    ftpinstFinishTransfer(&fi);

	    if (ftpinstGetFile(&fi, "base/comps", "/tmp/comps")) {
		ftpClose(fi.sock);
		step = FTP_SETUP_FTP1;
		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) {
    struct ftpinfo * fi = method->data;

    *ps = fi->ps;
    return 0;
}

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 ? fi->password : "rhinstall@", 
			    fi->proxy, -1)) 
		< 0) {
	messageWindow("ftp", "I cannot log into machine: %s\n",
			ftpStrerror(fi->sock));
	free(fi);
	return INST_ERROR;
    }

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