#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 <unistd.h>

#include "config.h"
#include "doit.h"
#include "install.h"
#include "kbd.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, char * netSharedPath, int flags);
static void setupXfree(struct installMethod * method, rpmdb db,
                      struct pkgSet * psp, char * netSharedPath, int errfd);
static void setupXmetro(struct installMethod * method, rpmdb db,
                       struct packageInfo * pkg, char * netSharedPath,
                       int errfd);
static void setupX(struct installMethod * method, rpmdb db,
                  struct pkgSet * psp, char * netSharedPath, 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 * logFileName) {
   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>%s >%s",
               realName, logFileName, logFileName);

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

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

   rc = system(cmd);

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

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

       return INST_ERROR;
   }

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

   return 0;
}

int doInstall(struct installMethod * method, struct component * preskel,
             struct pkgSet * psp, char * netSharedPath, char * keymap,
             int upgrade) {
   int i, totalNumPackages, totalSizePackages;
   int flags = 0;
   rpmdb db;
   int errfd;
   char * logFileName = "/dev/tty5";

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

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

   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", logFileName))
       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,
                       netSharedPath, flags);
       preskel->ps.packages[i]->selected = 0;  /* don't install it later */
   }

   #ifndef __sparc__
       mkdir("/mnt/etc/sysconfig", 0755);
       writeKbdConfig("/mnt/etc/sysconfig", keymap);
   #endif

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

   if (!upgrade) {
       mouseConfig();
       setupX(method, db, psp, netSharedPath, 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, char * netSharedPath, int flags) {
   int fd, olderr;
   char * realName;
   int olderrno, rc;

   if (flags & RPMINSTALL_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 | RPMINSTALL_REPLACEPKG | RPMINSTALL_REPLACEFILES,
               swCallback, NULL, netSharedPath);
   dup2(olderr, 2);
   close(olderr);

   if (rc) {
       olderrno = errno;
       logMessage("Error installing package: package install of "
                       "%s failed: %s", pkg->name, rpmErrorString());
       messageWindow("Error", "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 setupX(struct installMethod * method, rpmdb db,
                  struct pkgSet * psp, char * netSharedPath, 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, netSharedPath, errfd);

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

   text = newtTextbox(1, 1, 58, 9, 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. Would you like to install "
       "MetroX on your computer?");
   yes = newtButton(13, 12, "Yes");
   no = newtButton(36, 12, "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, netSharedPath, errfd);
   } else {
       setupXfree(method, db, psp, netSharedPath, errfd);
   }
}

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

   swOpen(1, pkg->size);
   installPackage(db, method, pkg, 0, errfd, netSharedPath, 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, char * netSharedPath, 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,
                               netSharedPath, 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 = 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];

   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;

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