#include <errno.h>
#include <fcntl.h>
#include <newt.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 <sys/mount.h>
#include <unistd.h>
#include <popt.h>

#include "config.h"
#include "doit.h"
#include "install.h"
#include "intl.h"
#include "kbd.h"
#include "kickstart.h"
#include "log.h"
#include "syslog.h"
#include "windows.h"


extern int testing;
FILE * logFile = NULL;

static int installPackage(rpmdb db, struct installMethod * method, 
			  struct packageInfo * pkg,
			  int errfd, int flags);
static void setupXfree(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);

int doInstall(struct installMethod * method, 
	      struct pkgSet * psp, char * netSharedPath, char * keymap,
	      int upgrade) {
    int i, totalNumPackages, totalSizePackages;
    struct packageInfo ** orderedPackages;
    int flags = 0;
    rpmdb db;
    int errfd;
    char * logFileName = "/dev/tty5";
    rpmDependencies rpmdeps, rpmorder;
    char * hostEntry = 
		"127.0.0.1		localhost localhost.localdomain\n";
    int fd;

    if (testing) return 0;

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

    handleSyslogSocket();

    if (!upgrade) {
	fd = open("/mnt/etc/hosts", O_CREAT | O_RDWR, 0644);
	if (fd < 0) {
	    errorWindow("Failed to create /mnt/etc/hosts: %s.");
	    return INST_ERROR;
	}

	write(fd, hostEntry, strlen(hostEntry));
	close(fd);
    }

    if (upgrade) {
	flags |= RPMINSTALL_UPGRADE | RPMINSTALL_REPLACEFILES;
	logFile = fopen("/mnt/tmp/upgrade.log", "w");
    } else
	logFile = fopen("/mnt/tmp/install.log", "w");
    if (logFile) {
	setlinebuf(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(logFileName, O_APPEND | O_CREAT, 0644);
    if (errfd < 0) {
	logMessage("failed to open /dev/tty5!");
	logFileName = "/tmp/exec.log";
	errfd = open(logFileName, O_APPEND | O_CREAT, 0644);
	if (errfd < 0) {
	    logMessage("failed to open %s: %s!\n", logFileName, 
			strerror(errno));
	    errfd = 2;
	}
    }

    rpmErrorSetCallback(rpmerror);

    /* FIXME: we ought to read /mnt/us/lib/rpmrc if we're in the midst of an
       upgrade, but it's not obvious how to get RPM to do that. */
    rpmSetVar(RPMVAR_NETSHAREDPATH, netSharedPath);

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

    rpmdeps = rpmdepDependencies(db);
    rpmorder = rpmdepDependencies(NULL);

    for (i = 0; i < psp->numPackages; i++) {
	if (!strcmp(psp->packages[i]->name, "basesystem")) {
	    rpmdepAddPackage(rpmdeps, psp->packages[i]->h, psp->packages[i]);
	    rpmdepAddPackage(rpmorder, psp->packages[i]->h, psp->packages[i]);
	}
    }

    totalNumPackages = 0, totalSizePackages = 0;
    for (i = 0; i < psp->numPackages; i++) {
	if (psp->packages[i]->selected) {
	    if (strcmp(psp->packages[i]->name, "basesystem")) {
		rpmdepAddPackage(rpmdeps, psp->packages[i]->h, 
				 psp->packages[i]);
		rpmdepAddPackage(rpmorder, psp->packages[i]->h, 
				 psp->packages[i]);
	    }

	    totalSizePackages += psp->packages[i]->size;
	    totalNumPackages++;
	}
    }

    if (rpmdepOrder(rpmorder, (void ***) &orderedPackages)) {
	rpmdbClose(db);
	newtWinMessage(_("Error"), _("Ok"), 
			_("Error ordering package list: %s"), rpmErrorString());
	close(errfd);
	return 1;
    }

    rpmdepDone(rpmdeps);
    rpmdepDone(rpmorder);

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

    swOpen(totalNumPackages, totalSizePackages);

    logMessage("installing %d packages", totalNumPackages);
    for (i = 0; i < totalNumPackages; i++) {
	installPackage(db, method, orderedPackages[i], errfd, flags);
    }
    swClose();

    free(orderedPackages);

    #ifndef __sparc__
    if (!upgrade) {
	mkdir("/mnt/etc/sysconfig", 0755);
	writeKbdConfig("/mnt/etc/sysconfig", keymap);
    }
    #endif

    if (!upgrade) {
	mouseConfig();
	writeLangInfo();
        setupXfree(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 errfd, int flags) {
    int fd, olderr;
    char * realName;
    int olderrno, rc;

    if (logFile)
	fprintf(logFile, "%s %s.\n", 
		(flags & RPMINSTALL_UPGRADE) ? 
		    _("Upgrading") : _("Installing"), pkg->name);

    swPackage(pkg->h);

    if (method->getFile(method, pkg->data, &realName)) {
	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));
	newtWinMessage(_("Error"), _("Ok"), 
			_("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;
    }

    /* this is a hack */
    if (!strcmp(pkg->name, "lilo") || !strcmp(pkg->name, "kernel"))
	flags |= RPMINSTALL_NOSCRIPTS;

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

    if (rc) {
	olderrno = errno;
	logMessage("Error installing package: package install of "
			"%s failed: %s", pkg->name, rpmErrorString());
	newtWinMessage(_("Error"), _("Ok"), 
			_("RPM install of %s failed: %s"), pkg->name,
			rpmErrorString());
	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 setupXfree(struct installMethod * method, rpmdb db, 
		       struct pkgSet * psp, int errfd) {
    int fd, i;
    char buf[200], * chptr;
    char server[50];
    int rc;

    /* 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;
    }

    /* need proc to do pci probing */
    if ((rc = doMount("/proc", "/mnt/proc", "proc", 0, 0))) {
	return;
    }

    /* this handles kickstart and normal/expert modes */
    if ((rc=xfree86Config("--pick")))
	return;
    
    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], errfd, 0);
	    swClose();
	    break;
	}
    }

    /* this handles kickstart and normal/expert modes */
    if ((rc=xfree86Config("--continue")))
	return;

    /* done with proc now */
    umount("/mnt/proc");
}

static void rpmerror(void) {
    int code;

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

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];

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

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

    if (!headerGetEntry(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);
}
