#include <errno.h>
#include <fcntl.h>
#include <newt.h>
#include <rpmerr.h>
#include <rpmlib.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>		/* for mkdir(2) ?!? */
#include <sys/time.h>
#include <sys/wait.h>
#include <unistd.h>

#include "config.h"
#include "doit.h"
#include "install.h"
#include "log.h"
#include "windows.h"

extern int testing;
FILE * logFile = NULL;

static int installPackage(rpmdb db, struct installMethod * method, 
			  struct packageInfo * pkg, int isPreskel, 
			  int errfd, int flags);
static void setupXfree(struct installMethod * method, rpmdb db, 
		       struct pkgSet * psp, int errfd);
static void setupXmetro(struct installMethod * method, rpmdb db, 
		        struct packageInfo * pkg, int errfd);
static void setupX(struct installMethod * method, rpmdb db, 
		   struct pkgSet * psp, int errfd);
static void rpmerror(void);

static void swOpen(int numPackages, int sizePackages);
static void swPackage(Header h);
static void swPackageComplete();
static void swClose(void);
static void swCallback(const unsigned long amount, const unsigned long total);
static void formatTime(char * buf, time_t t);

static int installCpioArchive(struct installMethod * method, char * name) {
    char * realName;
    char cmd[500];
    int olderrno;
    int rc;

    if (method->getFile(method, name, &realName, 1)) {
	if (logFile)
	    fprintf(logFile, "Failed to get cpio archive %s.\n", name);
	return INST_ERROR;
    }

    chdir("/mnt");
    sprintf(cmd, "gunzip < %s | cpio -iumd --quiet 2>/dev/tty5 >/dev/tty5", 
		realName);

    logMessage("running: \"%s\"", cmd);

    if (testing) {
	messageWindow("would run: %s", cmd);
	return 0;
    }

    rc = system(cmd);

    if (method->rmFiles) unlink(realName);

    if (rc) {
	olderrno = errno;
	logMessage("    command failed: %s rc = %d\n", strerror(olderrno), rc);
	olderrno = errno;
	errorWindow("error unpacking archive: %s");
	return INST_ERROR;
    }

    return 0;
}

int doInstall(struct installMethod * method, struct component * preskel, 
	      struct pkgSet * psp, int upgrade) {
    int i, totalNumPackages, totalSizePackages;
    int flags = 0;
    rpmdb db;
    int errfd;

    mkdir("/mnt/tmp", 0755);
    mkdir("/mnt/var", 0755);
    mkdir("/mnt/var/lib", 0755);
    mkdir("/mnt/var/lib/rpm", 0755);

    if (upgrade) {
	flags |= INSTALL_UPGRADE | INSTALL_REPLACEFILES;
	logFile = fopen("/mnt/tmp/upgrade.log", "w");
    } else
	logFile = fopen("/mnt/tmp/install.log", "w");
    if (logFile) {
	if (upgrade) {
	    logMessage("opened /mnt/tmp/upgrade.log");
	} else {
	    logMessage("opened /mnt/tmp/install.log");
	}
    } else {
	if (upgrade) {
	    logMessage("failed to open /mnt/tmp/upgrade.log :-(");
	    errorWindow("Failed to open /mnt/tmp/upgrade.log. No upgrade log "
			    "will be kept.");
	} else {
	    logMessage("failed to open /mnt/tmp/install.log :-(");
	    errorWindow("Failed to open /mnt/tmp/install.log. No install log "
			    "will be kept.");
	}
    }

    errfd = open("/dev/tty5", O_RDWR);
    logMessage("failed to open /dev/tty5!");
    if (errfd < 0) errfd = 2;

    errSetCallback(rpmerror);

    logMessage("reading /usr/lib/rpmrc");
    rpmReadConfigFiles(NULL, NULL, NULL, 0);
    logMessage("\tdone");

    if (testing) {
	messageWindow("Status", "Packages would be installed now");
	return 0;
    }

    if (rpmdbOpen("/mnt", &db, O_RDWR | O_CREAT, 0644)) {
	errorWindow("Fatal error opening RPM database");
	return INST_ERROR;
    }
    logMessage("opened rpm database");

    /* It's safer to *always* install the base system */
    winStatus(45, 3, "Base system", "Installing base system...");

    if (installCpioArchive(method, "skeleton.cgz"))
	return INST_ERROR;

    newtPopWindow();

    logMessage("installing initial packages (%d of them)", 
		preskel->ps.numPackages);

    totalNumPackages = 0, totalSizePackages = 0;
    for (i = 0; i < psp->numPackages; i++) {
	if (psp->packages[i]->selected) {
	    totalSizePackages += psp->packages[i]->size;
	    totalNumPackages++;
	}
    }

    swOpen(totalNumPackages, totalSizePackages);

    for (i = 0; i < preskel->ps.numPackages; i++) {
	if (!preskel->ps.packages[i]->selected) continue;
	logMessage("	installing %s", preskel->ps.packages[i]->name);
	installPackage(db, method, preskel->ps.packages[i], 1, errfd, flags);
	preskel->ps.packages[i]->selected = 0;	/* don't install it later */
    }

    logMessage("installing packages");
    for (i = 0; i < psp->numPackages; i++) {
	if (!psp->packages[i]->selected) continue;
	installPackage(db, method, psp->packages[i], 0, errfd, flags);
    }
    swClose();

    if (!upgrade) {
	mouseConfig();
        setupX(method, db, psp, errfd);
    }

    rpmdbClose(db);
    close(errfd);

    if (logFile) fclose(logFile);

    logMessage("rpm database closed");

    return 0;
}

static int installPackage(rpmdb db, struct installMethod * method, 
			  struct packageInfo * pkg, int isPreskel, 
			  int errfd, int flags) {
    int fd, olderr;
    char * realName;
    int olderrno, rc;

    if (flags & INSTALL_UPGRADE) {
	if (logFile)
	    fprintf(logFile, "Upgrading %s.\n", pkg->name);
    } else {
	if (logFile)
	    fprintf(logFile, "Installing %s.\n", pkg->name);
    }

    swPackage(pkg->h);

    if (method->getFile(method, pkg->data, &realName, isPreskel)) {
	logMessage("getFile method failed for %s", pkg->data);
	if (logFile)
	    fprintf(logFile, "Failed to get file for package %s.\n", pkg->name);
	swPackageComplete();
	return 1;
    }

    fd = open(realName, O_RDONLY);
    if (fd < 0) {
	olderrno = errno;
	logMessage("cannot open RPM file %s: %s", pkg->data,
		    strerror(olderrno));
	messageWindow("Error", "Error installing package: cannot open RPM file "
			"for %s: %s", pkg->data, strerror(errno));
	if (logFile)
	    fprintf(logFile, "\tcannot open RPM file %s: %s\n", 
			(char *) pkg->data, strerror(olderrno));

	swPackageComplete();

	return 1;
    }

    olderr = dup(2);
    dup2(errfd, 2);
    rc = rpmInstallPackage("/mnt", db, fd, NULL, 
		flags | INSTALL_REPLACEPKG | INSTALL_REPLACEFILES, 
	        swCallback, NULL);
    dup2(olderr, 2);
    close(olderr);

    if (rc) {
	olderrno = errno;
	logMessage("Error installing package: package install of %s failed", 
			pkg->name);
	messageWindow("Error", "RPM install of %s failed", pkg->name);
	if (logFile)
	    fprintf(logFile, "\tcannot open RPM file %s: %s\n", 
			(char *) pkg->data, strerror(olderrno));
    } 

    close(fd);
    swPackageComplete();

    if (method->rmFiles) unlink(realName);

    return 0;
}

static void setupX(struct installMethod * method, rpmdb db, 
		   struct pkgSet * psp, int errfd) {
    int hasMetro = 0, i;
    newtComponent text, yes, no, form, answer;
    struct packageInfo * metroPackage = NULL;

    /* This is a cheap trick to see if our X component was installed */
    if (access("/mnt/usr/X11R6/bin/Xconfigurator", X_OK)) {
	logMessage("/mnt/usr/X11R6/bin/Xconfigurator cannot be run");
	return;
    }

    logMessage("looking for metrox");
    for (i = 0; i < psp->numPackages; i++) {
	if (!strcmp(psp->packages[i]->name, "metroess")) {
	    logMessage("\tfound metrolink!");
	    metroPackage = psp->packages[i];
	    hasMetro = 1;
	    break;
	}
    }

    if (!hasMetro)
	return setupXfree(method, db, psp, errfd);

    newtOpenWindow(10, 4, 60, 15, "Metro-X");

    text = newtTextbox(1, 1, 58, 8, NEWT_TEXTBOX_WRAP);
    newtTextboxSetText(text, 
	"This copy of Red Hat Linux includes MetroX from MetroLink.\n\n" 
	"MetroX is LICENSED SOFTWARE.  Your purchase of Red Hat Linux "
	"entitles you to one (1) user license for this software.  You "
	"may not install and run MetroX on more than one computer "
	"without purchasing additional licenses, which are available "
	"from Red Hat Software (800) 454-5502.");
    yes = newtButton(13, 10, "Yes");
    no = newtButton(36, 10, "No");
    
    form = newtForm(NULL, NULL, 0);
    newtFormAddComponents(form, text, yes, no, NULL);

    newtRunForm(form);
    answer = newtFormGetCurrent(form);

    newtFormDestroy(form);
    newtPopWindow();

    if (answer == yes) {
	setupXmetro(method, db, metroPackage, errfd);
    } else {
	setupXfree(method, db, psp, errfd);
    }
}

static void setupXmetro(struct installMethod * method, rpmdb db, 
		        struct packageInfo * pkg, int errfd) {
    int childpid;
    int status;

    swOpen(1, pkg->size);
    installPackage(db, method, pkg, 0, errfd, 0);
    swClose();

    symlink("../../usr/X11R6/bin/Xmetro", "/mnt/etc/X11/X");

    newtSuspend();
    if (!(childpid = fork())) {
	chroot("/mnt");
	putenv("PATH=/bin:/usr/bin:/sbin:/usr/sbin:/usr/X11R6/bin");
	chdir("/");
	execl("/usr/X11R6/bin/configX", "/usr/X11R6/bin/configX", NULL);
    }

    waitpid(childpid, &status, 0);

    newtResume();
}

static void setupXfree(struct installMethod * method, rpmdb db, 
		       struct pkgSet * psp, int errfd) {
    int childpid;
    int status;
    int fd, i;
    char buf[200], * chptr;
    char server[50];

    newtSuspend();
    if (!(childpid = fork())) {
	chroot("/mnt");
	putenv("PATH=/bin:/usr/bin:/sbin:/usr/sbin:/usr/X11R6/bin");
	chdir("/");
	execl("/usr/X11R6/bin/Xconfigurator", "/usr/X11R6/bin/Xconfigurator",
	      "--pick", NULL);
    }

    waitpid(childpid, &status, 0);

    newtResume();

    if ((fd = open("/mnt/tmp/SERVER", O_RDONLY)) < 0) {
	logMessage("failed to open /mnt/tmp/SERVER: %s", strerror(errno));
	return;
    }
 
    buf[0] = '\0';
    read(fd, buf, sizeof(buf));
    close(fd);
    chptr = buf;
    while (chptr < (buf + sizeof(buf) - 1) && *chptr && *chptr != ' ')
	chptr++;

    if (chptr >= (buf + sizeof(buf) - 1) || *chptr != ' ') {
	logMessage("couldn't find ' ' in /mnt/tmp/SERVER");
	return;
    }

    *chptr = '\0';
    strcpy(server, "XFree86-");
    strcat(server, buf);

    logMessage("I will install the %s package", server);

    for (i = 0; i < psp->numPackages; i++) {
	if (!strcmp(psp->packages[i]->name, server)) {
	    logMessage("\tfound package: %s", psp->packages[i]->name);
	    swOpen(1, psp->packages[i]->size);
	    installPackage(db, method, psp->packages[i], 0, errfd, 0);
	    swClose();
	    break;
	}
    }

    newtSuspend();
    if (!(childpid = fork())) {
	chroot("/mnt");
	chdir("/");
	execl("/usr/X11R6/bin/Xconfigurator", "/usr/X11R6/bin/Xconfigurator",
	      "--continue", NULL);
    }

    waitpid(childpid, &status, 0);

    newtResume();
}

static void rpmerror(void) {
    int code;

    code = errCode();
    if (code != RPMERR_UNLINK && code != RPMERR_RMDIR) {
	if (logFile)
	    fprintf(logFile, "%s\n", errString());
	else
	    logMessage(errString());
    }
}

static struct statusWindowInfo {
    newtComponent form, packageLabel, sizeLabel, summaryText;
    newtComponent pkgScale, globalScale;
    newtComponent pkgDoneLabel, pkgRemainsLabel;
    newtComponent sizeDoneLabel, sizeRemainsLabel;
    newtComponent timeDoneLabel, timeRemainsLabel, timeTotalLabel;
    int numPackages, packagesDone;
    unsigned int sizePackages, sizeDone;
    int thisPackageSize;
    time_t timeStarted;
} si;

static void swOpen(int numPackages, int sizePackages) {
    char buf[50];

    newtOpenWindow(10, 4, 60, 15, "Install Status");

    si.form = newtForm(NULL, NULL, 0);
    newtFormAddComponent(si.form, newtLabel(1, 1, "Package:"));
    newtFormAddComponent(si.form, newtLabel(1, 2, "Size   :"));
    newtFormAddComponent(si.form, newtLabel(1, 3, "Summary:"));

    si.packageLabel = newtLabel(13, 1, "");
    si.sizeLabel    = newtLabel(13, 2, "");
    si.summaryText  = newtTextbox(13, 3, 45, 2, NEWT_TEXTBOX_WRAP);

    si.pkgScale = newtScale(3, 6, 54, 100);

    newtFormAddComponent(si.form, 
	newtLabel(1, 8, "             Packages       Bytes           Time"));
	/*		 12345678901234567890123456789012345678901234567
				  1         2         3         4 */
    newtFormAddComponent(si.form, newtLabel(1, 9,  "Total     :"));
    newtFormAddComponent(si.form, newtLabel(1, 10, "Completed :"));
    newtFormAddComponent(si.form, newtLabel(1, 11, "Remaining :"));

    si.numPackages = numPackages;
    si.sizePackages = sizePackages;
    si.packagesDone = 0;
    si.sizeDone = 0;
    si.timeStarted = time(NULL);

    sprintf(buf, "%8d", numPackages);
    newtFormAddComponent(si.form, newtLabel(14, 9, buf));
    si.pkgDoneLabel = newtLabel(14, 10, "");
    si.pkgRemainsLabel = newtLabel(14, 11, "");

    sprintf(buf, "%4uM", sizePackages / (1024 * 1024));
    newtFormAddComponent(si.form, newtLabel(29, 9, buf));
    si.sizeDoneLabel = newtLabel(29, 10, "");
    si.sizeRemainsLabel = newtLabel(29, 11, "");

    si.timeTotalLabel = newtLabel(42, 9, "");
    si.timeDoneLabel = newtLabel(42, 10, "");
    si.timeRemainsLabel = newtLabel(42, 11, "");

    si.globalScale = newtScale(1, 13, 58, sizePackages);

    newtFormAddComponents(si.form, si.packageLabel, si.sizeLabel, 
			  si.summaryText, si.pkgScale, si.globalScale, 
			  si.pkgDoneLabel, si.pkgRemainsLabel, 
			  si.sizeDoneLabel, si.sizeRemainsLabel, 
			  si.timeDoneLabel, si.timeRemainsLabel,
			  si.timeTotalLabel, NULL);
}

static void swPackage(Header h) {
    char * name, * version, * release, * summary;
    char buf[50];
    uint_32 * size;

    getEntry(h, RPMTAG_NAME, NULL, (void *) &name, NULL);
    getEntry(h, RPMTAG_VERSION, NULL, (void *) &version, NULL);
    getEntry(h, RPMTAG_RELEASE, NULL, (void *) &release, NULL);
    getEntry(h, RPMTAG_SIZE, NULL, (void *) &size, NULL);

    if (!getEntry(h, RPMTAG_SUMMARY, NULL, (void *) &summary, NULL))
	summary = "(no summary)";

    sprintf(buf, "%s-%s-%s", name, version, release);
    newtLabelSetText(si.packageLabel, buf);

    sprintf(buf, "%dk", (*size) / 1024);
    newtLabelSetText(si.sizeLabel, buf);

    newtTextboxSetText(si.summaryText, summary);

    si.thisPackageSize = *size;

    newtScaleSet(si.pkgScale, 0);

    newtDrawForm(si.form);
    newtRefresh();
}

static void swPackageComplete(void) {
    char buf[50];
    time_t now, finishTime, elapsedTime, remainingTime;

    si.packagesDone++;
    si.sizeDone += si.thisPackageSize;

    sprintf(buf, "%8d", si.packagesDone);
    newtLabelSetText(si.pkgDoneLabel, buf);

    sprintf(buf, "%8d", si.numPackages - si.packagesDone);
    newtLabelSetText(si.pkgRemainsLabel, buf);

    sprintf(buf, "%4dM", si.sizeDone / (1024 * 1024));
    newtLabelSetText(si.sizeDoneLabel, buf);

    sprintf(buf, "%4dM", (si.sizePackages - si.sizeDone) / (1024 * 1024));
    newtLabelSetText(si.sizeRemainsLabel, buf);

    now = time(NULL);
    elapsedTime = now - si.timeStarted;
    formatTime(buf, elapsedTime);
    newtLabelSetText(si.timeDoneLabel, buf);

    finishTime = (((float) si.sizePackages) / si.sizeDone) * elapsedTime;
    formatTime(buf, finishTime);
    newtLabelSetText(si.timeTotalLabel, buf);
    
    remainingTime = finishTime - elapsedTime;
    formatTime(buf, remainingTime);
    newtLabelSetText(si.timeRemainsLabel, buf);

    newtScaleSet(si.globalScale, si.sizeDone);

    newtRefresh();
}

static void swCallback(const unsigned long amount, const unsigned long total) {
    if (total == 0)
	newtScaleSet(si.pkgScale, 100);
    else
	newtScaleSet(si.pkgScale, (amount * 100) / total);

    newtRefresh();
}

static void swClose(void) {
    newtPopWindow();
}

static void formatTime(char * buf, time_t t) {
    int hours, minutes, secs;

    hours = t / 60 / 60;
    t %= (60 * 60);

    minutes = t / 60;
    t %= 60;

    secs = t;

    sprintf(buf, "%01d:%02d.%02d", hours, minutes, secs);
}
