#include <alloca.h>
#include <newt.h>
#include <popt.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <unistd.h>

#include "devices.h"
#include "install.h"
#include "intl.h"
#include "kickstart.h"
#include "lilo.h"
#include "log.h"
#include "run.h"
#include "windows.h"

#ifdef __i386__
#define KERNEL_IMAGE "/boot/vmlinuz-%s"
#elif __sparc__
#define KERNEL_IMAGE "/boot/vmlinux-%s.gz"
#else
#error unsupported architecture
#endif

static int mkinitrd(char * kernelVersion, char * initrdImage) {
    char * argv[] = { "/sbin/mkinitrd", "-f", initrdImage, "--ifneeded", 
			kernelVersion, NULL };
    int rc;
    static alreadyHappened = 0;

    #ifdef __sparc__
	return 0;
    #endif

    if (alreadyHappened) return 0;

    if (loadModule("loop", DRIVER_OTHER, DRIVER_MINOR_NONE, NULL)) 
	return INST_ERROR;

    winStatus(32, 3, _("LILO"), _("Creating initial ramdisk..."));
    rc = runProgramRoot(RUN_LOG, "/mnt", "/sbin/mkinitrd", argv);
    newtPopWindow();

    removeModule("loop");

    if (rc) {
	unlink("/mnt/boot/initrd");
    } else {
	alreadyHappened = 1;
    }

    return rc;
}

static int noSpaceFilter(newtComponent entry, void * data, int ch, int cursor) {
    if (ch == ' ')
	return 0;
    return ch;
}

#define SKIP_LILO 1000

#if defined(__i386__)
static int liloWhere(char * hdName, char * bootDevice, char ** where) {
    char * format = "/dev/%-7s    %s";
    char ** items;
    int which = 0, rc;

    items = alloca(sizeof(*items) * 3);
    items[0] = alloca(80);
    items[1] = alloca(80);
    items[2] = NULL;

    sprintf(items[0], format, hdName, _("Master Boot Record"));
    sprintf(items[1], format, bootDevice, _("First sector of boot partition"));

    rc = newtWinMenu(_("Lilo Installation"), _("Where do you want to install "
		       "the bootloader?"), 50, 5, 5, 4, items, &which,
		     _("Ok"), _("Skip"), _("Back"), NULL);

    if (rc == 3) return INST_CANCEL;
    if (rc == 2) return SKIP_LILO;

    switch (which) {
      case 0: 		*where = hdName; break;
      case 1: 		*where = bootDevice; break;
    }

    return 0;
}
#elif defined(__sparc__)
static int liloWhere(char * hdName, char * bootDevice, char ** where) {
    newtComponent text, yes, no, cancel, f, answer;
    int rc;

    rc = newtWinTernary(_("SILO Installation"), _("Yes"), _("No"), 
			_("Cancel"),
	    _("Would you like to install or configure the SILO bootloader on "
	      "your system?"));

    if (rc == 0 || rc == 1) {
        *where = bootDevice; 
	rc = 0;
    } else if (rc == 3) {
	rc = INST_CANCEL;
    } else {
	rc = SKIP_LILO;
    }

    return rc;
}
#endif

static void editBootLabel(struct partition * item) {
    newtComponent form, entry, okay, cancel, clear, answer;
    char buf[50];
    char * entryValue;
    newtGrid subgrid, grid, buttons;
 
    form = newtForm(NULL, NULL, 0);

    strcpy(buf, "/dev/");
    strcat(buf, item->device);

    entry = newtEntry(-1, -1, item->bootLabel, 20, &entryValue, 
		      NEWT_ENTRY_SCROLL | NEWT_ENTRY_RETURNEXIT);
    newtEntrySetFilter(entry, noSpaceFilter, NULL);


    subgrid = newtCreateGrid(2, 2);
    newtGridSetField(subgrid, 0, 0, NEWT_GRID_COMPONENT,
		     newtLabel(-1, -1, _("Device:")),
		     0, 0, 0, 0, NEWT_ANCHOR_LEFT, 0);
    newtGridSetField(subgrid, 1, 0, NEWT_GRID_COMPONENT,
		     newtLabel(-1, -1, buf),
		     1, 0, 0, 0, NEWT_ANCHOR_LEFT, 0);
    newtGridSetField(subgrid, 0, 1, NEWT_GRID_COMPONENT,
		     newtLabel(-1, -1, _("Boot label:")),
		     0, 0, 0, 0, NEWT_ANCHOR_LEFT, 0);
    newtGridSetField(subgrid, 1, 1, NEWT_GRID_COMPONENT, entry,
		     1, 0, 0, 0, NEWT_ANCHOR_LEFT, 0);

    buttons = newtButtonBar(_("Ok"), &okay, _("Clear"), &clear,
			    _("Cancel"), &cancel, NULL);

    grid = newtCreateGrid(1, 2);
    newtGridSetField(grid, 0, 0, NEWT_GRID_SUBGRID, subgrid,
		     0, 0, 0, 0, 0, 0);
    newtGridSetField(grid, 0, 1, NEWT_GRID_SUBGRID, buttons,
		     0, 1, 0, 0, 0, NEWT_GRID_FLAG_GROWX);

    newtGridAddComponentsToForm(grid, form, 1);
    newtGridWrappedWindow(grid, _("Edit Boot Label"));
    newtGridFree(grid, 1);

    do {
	answer = newtRunForm(form);

	if (answer == clear)
	    newtEntrySet(entry, "", 1);
    } while (answer == clear);

    if (answer != cancel) {
	if (item->bootLabel) free(item->bootLabel);

	if (strlen(entryValue))
	    item->bootLabel = strdup(entryValue);
	else
	    item->bootLabel = NULL;
    }

    newtPopWindow();
}	    

static int doinstallLilo(char * prefix, char * dev, char * rootdev,
			 char * bootdev,
			 struct partitionTable table,
			 char * append, char * kernelVersion, 
			 char * hdname, int linear) {
    char filename[100];
    FILE * f;
    char * argv[] = { "/mnt/sbin/lilo", NULL };
    int i;
    int rc;
    struct stat sb;
    int useInitrd = 0;
    char relinitrdImage[50], absinitrdImage[55];
    int pass;
    static int firstTime = 1;

    sprintf(relinitrdImage, "/boot/initrd-%s.img", kernelVersion);
    strcpy(absinitrdImage, "/mnt");
    strcat(absinitrdImage, relinitrdImage);

    if (mkinitrd(kernelVersion, relinitrdImage))
	return INST_ERROR;

    if (testing) return 0;

    if (!stat(absinitrdImage, &sb))
	useInitrd = 1;

    #ifdef __sparc__
	sprintf(filename, "%s/silo.conf", prefix);
    #else
	sprintf(filename, "%s/lilo.conf", prefix);
    #endif

    /* why not? */
    if (firstTime) {
	rename("/mnt/etc/lilo.conf", "/mnt/etc/lilo.conf.orig");
	rename("/mnt/etc/silo.conf", "/mnt/etc/silo.conf.orig");
	firstTime = 0;
    }
    
    f = fopen(filename, "w");
    if (!f) {
	errorWindow("cannot create [ls]ilo config file: %s");
	return INST_ERROR;
    }

    logMessage("writing [sl]ilo config to %s", filename);

    #ifdef __i386__
	fprintf(f, "boot=/dev/%s\n", dev);
	fprintf(f, "map=/boot/map\n");
	fprintf(f, "install=/boot/boot.b\n");
	fprintf(f, "prompt\n");
	if (linear) fprintf(f, "linear\n");
	fprintf(f, "timeout=50\n");
    #elif __sparc__
	fprintf(f, "timeout=50\n");
	fprintf(f, "partition=%s\n", rootdev + 3);
	fprintf(f, "root=/dev/%s\n", rootdev);
    #else
	#error "unsupported architecture";
    #endif

    for (pass = 0; pass < 2; pass++) {
	for (i = 0; i < table.count; i++) {
	    if (!table.parts[i].bootLabel) continue;
	    if (pass == 0 && !table.parts[i].defaultBoot) continue;
	    if (pass == 1 && table.parts[i].defaultBoot) continue;

	    if (table.parts[i].type == PART_EXT2) {
		fprintf(f, "image=" KERNEL_IMAGE "\n", kernelVersion);
		fprintf(f, "\tlabel=%s\n", table.parts[i].bootLabel);

		/* remap /boot to root=/, but make sure we set up booting
		   from old root partitions as well */
		if (!strcmp(bootdev, table.parts[i].device))
		    fprintf(f, "\troot=/dev/%s\n", rootdev);
		else
		    fprintf(f, "\troot=/dev/%s\n", table.parts[i].device);

		if (useInitrd)
		    fprintf(f, "\tinitrd=%s\n", relinitrdImage);
		if (append) fprintf(f, "\tappend=\"%s\"\n", append);
		fprintf(f, "\tread-only\n");
	  #ifdef __i386__
	    } else {
		fprintf(f, "other=/dev/%s\n", table.parts[i].device);
		fprintf(f, "\tlabel=%s\n", table.parts[i].bootLabel);
		fprintf(f, "\ttable=/dev/%.3s\n", table.parts[i].device);

		if (strncmp(table.parts[i].device, hdname, 3)) {
		    /* boot off the second drive, so reverse the BIOS maps */
		    fprintf(f, "\tmap-drive=0x80\n");
		    fprintf(f, "\t   to = 0x81\n");
		    fprintf(f, "\tmap-drive=0x81\n");
		    fprintf(f, "\t   to = 0x80\n");
		}
	  #endif
	    }
	}
    }

    fclose(f);

    winStatus(35, 3, _("Running"), _("Installing boot loader..."));

    #ifdef __i386__
	rc = runProgramRoot(RUN_LOG, "/mnt", "sbin/lilo", argv);
    #elif __sparc__ 
	rc = doMount("/proc", "/mnt/proc", "proc", 0, 0);
	if (rc) {
	    newtPopWindow();
	    return rc;
	}
	rc = runProgramRoot(RUN_LOG, "/mnt", "sbin/silo", argv);
	umount("/mnt/proc");
    #else
	#error unsupported architectures
    #endif

    newtPopWindow();

    if (rc)
	return INST_ERROR;

    return 0;
}

static void formatEntry(char * buf, struct partition * part) {
    sprintf(buf, "/dev/%-5s  %-25s %-7s %-10s", part->device, 
	    part->tagName, 
	    part->defaultBoot ? "   *" : "",
	    part->bootLabel ? part->bootLabel : "");
}

static int getBootLabels(struct partitionTable table, struct fstab fstab) {
    newtComponent f, okay, text, listbox, label, cancel, edit;
    struct newtExitStruct answer;
    char buf[80];
    int i, j;
    int foundDos = 0;
    int mustAsk = 0;
    int * map;
    struct partition * curr;
    int * currNum;
    int count;
    int done;
    int defaultBootPart = 0;
    char * reflowedText;
    int width, height;
    newtGrid buttons, grid;

    reflowedText = newtReflowText(
		       _("The boot manager Red Hat uses can boot other " 
                         "operating systems as well. You need to tell me " 
                         "what partitions you would like to be able to boot " 
                         "and what label you want to use for each of them."),
			 60, -5, -5, &width, &height);
    text = newtTextbox(-1, -1, width, height, NEWT_TEXTBOX_WRAP);
    newtTextboxSetText(text, reflowedText);
    free(reflowedText);

    f = newtForm(NULL, NULL, 0);

    sprintf(buf, "%-10s  %-25s %-7s %-10s", _("Device"), _("Partition type"), 
			_("Default"), _("Boot label"));
    label = newtLabel(-1, -1, buf);

    listbox = newtListbox(-1, -1, 10 - height, NEWT_LISTBOX_RETURNEXIT);
    map = alloca(sizeof(int) * table.count);
    
    for (i = 0, count = 0; i < table.count; i++) {
	if (table.parts[i].type != PART_SWAP && 
	    table.parts[i].type != PART_IGNORE &&
#ifdef __sparc__
	    table.parts[i].type != PART_OTHER &&
#endif
	    (table.parts[i].type != PART_FAT32 || !foundDos) &&
	    (table.parts[i].type != PART_DOS || !foundDos)) {

	    if (table.parts[i].type == PART_DOS || 
	        table.parts[i].type == PART_FAT32) {
		table.parts[i].bootLabel = strdup("dos");
		foundDos = 1;
	    }

	    if (table.parts[i].type == PART_EXT2) {
		for (j = 0; j < fstab.numEntries; j++) {
		    if (!strcmp(table.parts[i].device, fstab.entries[j].device))
			break;
		}

		if (j < fstab.numEntries && !table.parts[i].bootLabel) 
		    continue;
	    }

	    if (!table.parts[i].bootLabel ||
		strcmp(table.parts[i].bootLabel, "linux")) mustAsk = 1;

	    if (table.parts[i].defaultBoot)
		defaultBootPart = count;

	    map[count] = i;
	    formatEntry(buf, table.parts + i);
	    newtListboxAddEntry(listbox, buf, map + count++);
	} 
    }

    /* we add stuff to the form here so we can actually destroy it if
       we don't need this window after all */
    newtFormAddComponents(f, text, label, listbox, NULL);

    if (!mustAsk) {
	newtFormDestroy(f);
	return 0;
    }

    newtPushHelpLine("<F2> Selects the default partition");

    buttons = newtButtonBar(_("Ok"), &okay, _("Edit"), &edit,
			    _("Back"), &cancel, NULL);

    newtFormAddComponents(f, okay, edit, cancel, NULL);

    newtFormAddHotKey(f, NEWT_KEY_F2);

    grid = newtCreateGrid(1, 4);

    newtGridSetField(grid, 0, 0, NEWT_GRID_COMPONENT, text,
		     0, 0, 0, 1, NEWT_ANCHOR_LEFT, 0);
    newtGridSetField(grid, 0, 1, NEWT_GRID_COMPONENT, label,
		     0, 0, 0, 0, NEWT_ANCHOR_LEFT, 0);
    newtGridSetField(grid, 0, 2, NEWT_GRID_COMPONENT, listbox,
		     0, 0, 0, 0, NEWT_ANCHOR_LEFT, 0);
    newtGridSetField(grid, 0, 3,  NEWT_GRID_SUBGRID, buttons,
		     0, 1, 0, 0, 0, NEWT_GRID_FLAG_GROWX);

    newtGridWrappedWindow(grid, _("Bootable Partitions"));
    newtGridFree(grid, 1);

    done = 0;
    while (!done) {
	newtFormRun(f, &answer);

	if (answer.reason == NEWT_EXIT_HOTKEY) {
	    if (answer.u.key == NEWT_KEY_F12) {
		done = 1;
	    } else if (answer.u.key == NEWT_KEY_F2) {
		currNum = newtListboxGetCurrent(listbox);
		curr = table.parts + *currNum;

		if (!curr->bootLabel) {
		    newtWinMessage("Boot Partition", "Ok", "You cannot mark a "
				  "partition as the default partition to "
				  "boot from unless that partition has "
				  "been assigned a boot label.");
		} else{
		    for (i = 0; i < count; i++) {
			if (table.parts[map[i]].defaultBoot) {
			    table.parts[map[i]].defaultBoot = 0;
			    formatEntry(buf, table.parts + map[i]);
			    newtListboxSetEntry(listbox, i, buf);
			    break;
			}
		    }

		    curr->defaultBoot = 1;
		    formatEntry(buf, curr);
		    newtListboxSetEntry(listbox, currNum - map, buf);
		}
	    }
	} else {
	    if (answer.u.co == edit || answer.u.co== listbox) {
		currNum = newtListboxGetCurrent(listbox);
		curr = table.parts + *currNum;
		editBootLabel(curr);

		if (!curr->bootLabel && curr->defaultBoot) {
		    curr->defaultBoot = 0;
		    if (table.parts[map[defaultBootPart]].bootLabel) {
			table.parts[map[defaultBootPart]].defaultBoot = 1;

			formatEntry(buf, table.parts + map[defaultBootPart]);
			newtListboxSetEntry(listbox, defaultBootPart, buf);
		    }
		}

		formatEntry(buf, curr);
		newtListboxSetEntry(listbox, currNum - map, buf);
	    } else 
		done = 1;
	}
    }

    newtPopHelpLine();

    newtFormDestroy(f);
    newtPopWindow();

    if (answer.reason == NEWT_EXIT_COMPONENT && answer.u.co == cancel)
	return INST_CANCEL;
    else
	return 0;
}

static int getAppendLine(char ** line, int * linear) {
    newtComponent form, text, entry, okay, cancel, answer;
    newtComponent linearCheck;
    char * result = NULL;
    char linearChar = (*linear) ? '*' : ' ';
    newtGrid grid, buttons, subgrid;

    text = newtTextboxReflowed(0, 0,
		     _("A few systems will need to pass special options "
		       "to the kernel at boot time for the system to function "
		       "properly. If you need to pass boot options to the "
		       "kernel, enter them now. If you don't need any or "
		       "aren't sure, leave this blank."), 53, 10, 0, 0);

    form = newtForm(NULL, NULL, 0);

    entry = newtEntry(-1, -1, *line, 48, &result, 
			NEWT_ENTRY_SCROLL | NEWT_ENTRY_RETURNEXIT);

    #ifndef __sparc__
	linearCheck = newtCheckbox(-1, -1, 
			     _("Use linear mode (needed for some SCSI drives)"),
			     linearChar, NULL, &linearChar);
    #endif

    buttons = newtButtonBar(_("Ok"), &okay, _("Back"), &cancel, NULL);
    subgrid = newtGridVStacked(NEWT_GRID_COMPONENT, entry,
			       #ifndef __sparc__
				  NEWT_GRID_COMPONENT, linearCheck,
			       #endif
			       NULL);
    grid = newtGridBasicWindow(text, subgrid, buttons);

    newtGridAddComponentsToForm(grid, form, 1);
    newtFormSetCurrent(form, okay);

    #ifdef __sparc__
	newtGridWrappedWindow(grid, _("Silo Installation"));
    #else
	newtGridWrappedWindow(grid, _("Lilo Installation"));
    #endif

    newtGridFree(grid, 1);

    answer = newtRunForm(form);

    newtPopWindow();

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

    *linear = linearChar != ' ';

    if (!strlen(result)) 
	*line = NULL;
    else 
	*line = strdup(result);

    newtFormDestroy(form);

    return 0;
}

#define LILO_WHERE 2
#define LILO_LABELS 3
#define LILO_INSTALL 4
#define LILO_APPEND 5
#define LILO_DONE 20
    
int installLilo(char * prefix, struct partitionTable table, 
	        struct fstab fstab, char * kernelVersion) {
    char * rootDevice, * bootDevice = NULL;
    char * hdName;
    char * where = NULL;
    char * append = NULL;
    char * chptr = NULL;
    int i;
    int rc;
    int stage = LILO_WHERE;
    static int linear = 0;
    char ** argv;
    int argc;
    char * location = NULL;
    poptContext optCon;
    struct poptOption ksOptions[] = {
	    { "append", '\0', POPT_ARG_STRING, &append, 0 },
#ifdef __i386__
	    { "linear", '\0', 0, &linear, 0 },
	    { "location", '\0', POPT_ARG_STRING, &location, 0 },
#endif
	    { 0, 0, 0, 0, 0 }
	};

    hdName = alloca(4);
    strncpy(hdName, table.parts[0].device, 3);
    hdName[3] = '\0';
    
    for (i = 0; i < fstab.numEntries; i++) {
	if (!strcmp(fstab.entries[i].mntpoint, "/boot")) break;
    }

    if (i < fstab.numEntries) 
	bootDevice = fstab.entries[i].device;

    for (i = 0; i < fstab.numEntries; i++) {
	if (!strcmp(fstab.entries[i].mntpoint, "/")) break;
    }

    rootDevice = fstab.entries[i].device;
    if (!bootDevice) {
	bootDevice = rootDevice;
    }

    for (i = 0; i < table.count; i++) {
	if (!strcmp(table.parts[i].device, bootDevice)) {
	    table.parts[i].bootLabel = strdup("linux");
	    table.parts[i].defaultBoot = 1;
	    break;
	}
    }

    if (kickstart) {
	if (!ksGetCommand(KS_CMD_LILO, NULL, &argc, &argv)) {
	    optCon = poptGetContext(NULL, argc, argv, ksOptions, 0);

	    if ((rc = poptGetNextOpt(optCon)) < -1) {
		newtWinMessage(_("lilo command"),  _("Ok"),
			   _("bad argument to kickstart lilo command %s: %s"),
			   poptBadOption(optCon, POPT_BADOPTION_NOALIAS), 
			   poptStrerror(rc));
	    }
	}

	if (!location || !strcmp(location, "mbr"))
	    where = hdName;
	else if (!strcmp(location, "partition"))
	    where = bootDevice;
	else if (!strcmp(location, "none"))
	    return 0;

	if (where) {
	    rc = doinstallLilo(prefix, where, rootDevice, bootDevice, table, 
				append, kernelVersion, hdName, linear);
	    if (rc == INST_ERROR) return INST_ERROR;
	    stage = LILO_DONE;
	}
    }

    while (stage != LILO_DONE) {
	switch (stage) {
	  case LILO_WHERE:
	    rc = liloWhere(hdName, bootDevice, &where);
            if (rc == SKIP_LILO ) return 0;
	    if (rc) return rc;
	    stage = LILO_APPEND;
	    break;

	  case LILO_APPEND:
	    chptr = append;
	    rc = getAppendLine(&chptr, &linear);

	    if (rc == INST_ERROR) return INST_ERROR;
	    if (rc == INST_CANCEL)
		stage = LILO_WHERE;
	    else {
		stage = LILO_LABELS;

		if (append) free(append);
		if (chptr) {
		    append = alloca(strlen(chptr) + 1);
		    strcpy(append, chptr);
		    free(chptr);
		} else {
		    append = NULL;
		}
	    }

	    break;

	  case LILO_LABELS:
	    rc = getBootLabels(table, fstab);
	    if (rc == INST_ERROR) return INST_ERROR;
	    if (rc == INST_CANCEL)
		stage = LILO_APPEND;
	    else
		stage = LILO_INSTALL;
	    break;

	  case LILO_INSTALL:
	    rc = doinstallLilo(prefix, where, rootDevice, bootDevice,
				table, append, 
				kernelVersion, hdName, linear);
	    if (rc == INST_ERROR) return INST_ERROR;
	    stage = LILO_DONE;
	    break;
	}
    }

    return 0;
}

int makeBootdisk(char * prefix, char * kernelVersion) {
    char * argv[] = { "/sbin/mkbootidsk", "--noprompt", kernelVersion, NULL };
    int rc;

    #ifdef __sparc__
	return 0;
    #endif

    if (loadModule("loop", DRIVER_OTHER, DRIVER_MINOR_NONE, NULL)) 
	return INST_ERROR;

    winStatus(32, 3, _("Bootdisk"), _("Creating bootdisk..."));
    rc = runProgramRoot(RUN_LOG, "/mnt", "/sbin/mkbootdisk", argv);
    newtPopWindow();

    removeModule("loop");

    return rc;
}
