#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 <sys/mtio.h>
#include <unistd.h>
#include <ctype.h>

#include "hd.h"
#include "fs.h"
#include "urlmethod.h"
#include "install.h"
#include "intl.h"
#include "log.h"
#include "methods.h"
#include "net.h"
#include "scsi.h"
#include "windows.h"
#include "smb.h"

struct tapeCatalogEntry {
    char * filename;
    int size;
};

struct tapeinfo {
    int fd;
    int offset;
    int curr;
    int catalogEntries;
    struct tapeCatalogEntry * catalog;
};

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

static int urlinstGetMappedFile(struct installMethod * method, char * name, 
		                char ** realName);
static int imageGetFile(struct installMethod * method, char * name, 
			char ** realName);
static int singleimageSetSymlinks(struct installMethod * method, 
	              		  struct partitionTable table,
	              		  struct netInfo * netc, 
				  struct intfInfo * intf,
				  struct driversLoaded ** dl);
static int hdSetup(struct installMethod * method, struct partitionTable table,
	           struct netInfo * netc, struct intfInfo * intf,
	           struct driversLoaded ** dl);
static int hdPrepareMedia(struct installMethod * method,
				struct fstab * fstab);
static int hdGetPackageSet(struct installMethod * method,
			     struct pkgSet * ps);
static int hdGetComponentSet(struct installMethod * method,
			       struct pkgSet * ps,
			       struct componentSet * cs);
static int urlSetup(struct installMethod * method, struct partitionTable table,
	            struct netInfo * netc, struct intfInfo * intf,
	            struct driversLoaded ** dl);
static int urlGetPackageSet(struct installMethod * method,
			     struct pkgSet * ps);
static int urlGetComponentSet(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 inline int fileInBase(char * name);

#if 0 /* disabled for now */
#ifdef __i386__
static int smbSetup(struct installMethod * method, struct partitionTable table,
	           struct netInfo * netc, struct intfInfo * intf,
	           struct driversLoaded ** dl);
static int smbGetPackageSet(struct installMethod * method,
			     struct pkgSet * ps);
static int smbGetComponentSet(struct installMethod * method,
			       struct pkgSet * ps,
			       struct componentSet * cs);
#endif
#endif /* disabled */

static struct installMethod methods[] = {
    { "Local CDROM", 		"cdrom", 0, LOCAL, NULL,
                singleimageSetSymlinks, imageGetFile, 
		imageGetPackageSet, imageGetComponentSet, NULL, NULL },
    { "NFS image", 		"nfs", 0, NETWORK, NULL,
                singleimageSetSymlinks, imageGetFile, 
		imageGetPackageSet, imageGetComponentSet, NULL, NULL },
    { "hard drive",		"hd",  0, LOCAL, NULL, hdSetup, imageGetFile,
		hdGetPackageSet, hdGetComponentSet, hdPrepareMedia, NULL },
    { "FTP",			"ftp", 1, NETWORK, NULL, urlSetup,
                urlinstGetMappedFile, urlGetPackageSet, urlGetComponentSet,
                NULL, NULL },
    { "HTTP",			"http", 1, NETWORK, NULL, urlSetup,
                urlinstGetMappedFile, urlGetPackageSet, urlGetComponentSet,
                NULL, NULL },
#if 0 /* disabled for now */
#ifdef __i386__
    { "SMB image", 		"smb", 0, NETWORK, NULL, smbSetup, 
		imageGetFile, 
		smbGetPackageSet, smbGetComponentSet, NULL, NULL },
#endif
#endif
} ;
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 inline int fileInBase(char * name) {
    if (!strcmp(name, "hdlist") || !strcmp(name, "rpmconvert"))
	return 1;
    if (!strncmp(name, "install", 7) && !strcmp(name + 8, ".tr"))
	return 1;
    return 0;
}

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

    if (fileInBase(name))
	strcpy(buf, "/tmp/rhimage/RedHat/base/");
    else
	strcpy(buf, "/tmp/rhimage/RedHat/RPMS/");

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

    return 0;
}

static void getConfig(char *first, char **firstData, ...) {
    char *env, **data, *ptr;
    va_list ap;
    
    ptr = getenv(first);
    *firstData = ptr ? strdup(ptr) : ptr;
    
    va_start(ap, firstData);
    
    while ((env = va_arg(ap, char *))) {
	data = va_arg(ap, char **);
	ptr = getenv(env);
	*data = ptr ? strdup(getenv(env)) : ptr;
    }
}

static int hdSetup(struct installMethod * method, struct partitionTable table,
	           struct netInfo * netc, struct intfInfo * intf,
	           struct driversLoaded ** dl) {
    struct hdinfo * hdi;
    
    hdi = malloc(sizeof(*hdi));

    getConfig("DEVICE", &hdi->device, "TYPE", &hdi->type,
	      "DIR", &hdi->dir, NULL);

    method->data = hdi;

    return INST_OKAY;
}

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

    umount("/tmp/hdimage");

    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++) {
	logMessage("looking: %s ?= %s", fstab->entries[i].device, hdi->device);
	if (fstab->entries[i].isMounted &&
	    !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));
	    newtWinMessage(_("Error"), _("Ok"), 
			   _("Failed to create symlink for package source."));
	    return INST_ERROR;
	}
    } else {
	logMessage("mounting device which contains packages");
	umount("/tmp/hdimage");
	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 netInfo * netc, 
				  struct intfInfo * intf,
				  struct driversLoaded ** dl) {
    logMessage("making symlink from /tmp/rhimage to image");
    unlink("/tmp/image");
    symlink("rhimage", "/tmp/image");
    return 0;
}

static int urlSetup(struct installMethod * method, struct partitionTable table,
	            struct netInfo * netc, struct intfInfo * intf,
	            struct driversLoaded ** dl) {
    struct iurlinfo ui;
    enum { URL_SETUP_NET, URL_SETUP_HOST1, URL_SETUP_HOST2, URL_SETUP_CHECK, 
		URL_SETUP_DONE } step = URL_SETUP_NET;
    int rc;
    FD_t fd;
    char doMore;
    char buf[256];
    urlprotocol protocol=URL_METHOD_FTP;
    
    if (!strncmp(method->abbrev, "ftp", 3))
	protocol = URL_METHOD_FTP;
    if (!strncmp(method->abbrev, "http", 4))
	protocol = URL_METHOD_HTTP;
    
    memset(&ui, 0, sizeof(ui));

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

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

	case URL_SETUP_NET:
	    getConfig("URLPREFIX", &ui.urlprefix, "HOST", &ui.address,
		      "PREFIX", &ui.prefix, "LOGIN", &ui.login,
		      "PASSWORD", &ui.password, "PROXY", &ui.proxy,
		      "PROXYPORT", &ui.proxyPort, NULL);
	    if (protocol == URL_METHOD_FTP) {
		if (ui.proxy)
		    addMacro(NULL, "_ftpproxy", NULL,
			     ui.proxy, RMIL_RPMRC);
		if (ui.proxyPort)
		    addMacro(NULL, "_ftpproxyport", NULL,
			     ui.proxyPort, RMIL_RPMRC);
	    } else {
		if (ui.proxy)
		    addMacro(NULL, "_ftpproxy", NULL,
			     ui.proxy, RMIL_RPMRC);
		if (ui.proxyPort)
		    addMacro(NULL, "_ftpproxyport", NULL,
			     ui.proxyPort, RMIL_RPMRC);
	    }
	    if (ui.urlprefix != NULL)
		step = URL_SETUP_CHECK;
	    else
		step = URL_SETUP_HOST1;
	    break;

	  case URL_SETUP_HOST1:
	    rc = urlMainSetupPanel(&ui, protocol, &doMore);
	    if (rc == INST_ERROR) 
		return rc;
	    else if (rc)
		step = URL_SETUP_NET;
	    else if (doMore == ' ')
		step = URL_SETUP_CHECK;
	    else 
		step = URL_SETUP_HOST2;
	    break;

	  case URL_SETUP_HOST2:
	    rc = urlSecondarySetupPanel(&ui, protocol);
	    if (rc == INST_ERROR) 
		return rc;
	    else if (rc)
		step = URL_SETUP_HOST1;
	    else
		step = URL_SETUP_CHECK;
	    break;

	  case URL_SETUP_CHECK:
	    fd = urlinstStartTransfer(&ui, "base/hdlist");
	    if (fd == NULL || fdFileno(fd) < 0) {
		snprintf(buf, sizeof(buf), "%s/RedHat/base/hdlist",
			 ui.urlprefix);
		newtPopWindow();
		newtWinMessage(_("Error"), _("Ok"),
			       "Error transferring %s:\n%s",
			       buf, urlStrerror(buf));
		step = URL_SETUP_HOST1;
		ufdClose(fd);
		break;
	    }

	    if (psFromHeaderListDesc(fd, &ui.ps, 1)) {
		step = URL_SETUP_HOST1;
		ufdClose(fd);
		break;
	    }

	    urlinstFinishTransfer(fd);

	    if (urlinstGetFile(&ui, "base/comps", "/tmp/comps")) {
		step = URL_SETUP_HOST1;
		break;
	    }
	    
	    step = URL_SETUP_DONE;
	    break;

	  case URL_SETUP_DONE:
	    break;
	}
    }

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

    return 0;
}

static int urlGetPackageSet(struct installMethod * method,
			     struct pkgSet * ps) {
    struct iurlinfo * ui = method->data;

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

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

extern int isPackage; 

static int urlinstGetMappedFile(struct installMethod * method, char * name, 
		                char ** realName) {
    static char sbuf[300];
    char * buf;
    int rc;
    struct iurlinfo * ui = method->data;

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

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

    buf = alloca(strlen(name) + 30);
    if (fileInBase(name))
	strcpy(buf, "base/");
    else
	strcpy(buf, "RPMS/");
    strcat(buf, name);

    rc = urlinstGetFile(ui, buf, *realName);
    if (!rc) return 0;

    while (rc) {
	rc = newtWinChoice(_("ftp"), _("Yes"), _("No"),
			   _("I'm having trouble getting %s. Would you "
			     "like to retry?"), name);
	if (rc == 2) return INST_ERROR;
	rc = urlinstGetFile(ui, buf, *realName);
    }
    
    return 0;
}

#if 0 /* disabled for now */
#ifdef __i386__
static int smbSetup(struct installMethod * method, struct partitionTable table,
	           struct netInfo * netc, struct intfInfo * intf,
	           struct driversLoaded ** dl) {
    char * host = NULL, * dir = NULL, * acct = NULL, * pass = NULL;
    char * buf;
    static int moduleLoaded = 0;
    enum { SMB_STEP_NET, SMB_STEP_INFO, SMB_STEP_MOUNT, SMB_STEP_DONE }
		step = SMB_STEP_NET;
    int rc;

    while (step != SMB_STEP_DONE) {
	switch (step) {
	  case SMB_STEP_NET:
	      getConfig("HOST", &host, "DIR", &dir, "ACCT", &acct,
			"PASS", &pass, NULL);
	    step = SMB_STEP_MOUNT;
	    break;

	  case SMB_STEP_INFO:
	    rc = smbGetSetup(&host, &dir, &acct, &pass);
	    if (rc == INST_CANCEL)
		step = SMB_STEP_NET;
	    else if (rc == INST_ERROR)
		return INST_ERROR;
	    else
		step = SMB_STEP_MOUNT;
	    break;

	  case SMB_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 (!moduleLoaded) {
		    rc = loadModule("smbfs", DRIVER_FS, DRIVER_MINOR_NONE, dl);
		    if (rc) return rc;
		    moduleLoaded = 1;
		}

		rc = doPwMount(buf, "/tmp/rhimage", "smb", 1, 0, acct, pass);

		free(buf);
	    }

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

	    break;

	  case SMB_STEP_DONE:
	    break;
	}
    }

    free(host);
    free(dir);

    return 0;
}

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

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

#endif
#endif /* disabled */
