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

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

#ifdef __i386__
static char * kernelPath = "/boot/vmlinuz";
#elif __sparc__
static char * kernelPath = "/boot/vmlinux.gz";
#else
#error unsupported architecture
#endif

static int mkinitrd(void) {
    char * argv[] = { "/sbin/mkinitrd", "/boot/initrd", "*", NULL };
    int rc;

    #ifdef __sparc__
	return 0;
    #endif

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

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

    removeModule("loop");

    return rc;
}

#define SKIP_LILO 1000

static int liloWhere(char * hdName, char * rootDevice, char ** where) {
    newtComponent form, okay, listbox, cancel, answer, skip;
    char * format = "/dev/%-7s    %s";
    char buf[200];
    void * which;

    #ifdef __sparc__
	newtOpenWindow(12, 6, 55, 11, "Silo Installation");
    #else
	newtOpenWindow(12, 6, 55, 11, "Lilo Installation");
    #endif

    form = newtForm(NULL, NULL, 0);
    newtFormAddComponent(form, 
		newtLabel(1, 1, "Where do you want to install "
				"the bootloader?"));

    listbox = newtListbox(2, 3, 3, NEWT_LISTBOX_RETURNEXIT);
    sprintf(buf, format, hdName, 
            "Master Boot Record");
    newtListboxAddEntry(listbox, buf, (void *) 1);
    sprintf(buf, format, rootDevice, 
            "First sector of root partition");
    newtListboxAddEntry(listbox, buf, (void *) 2);
    sprintf(buf, format, "fd0", 
            "First sector of a floppy disk");
    newtListboxAddEntry(listbox, buf, (void *) 3);

    okay = newtButton(6, 7, "Ok");
    skip = newtButton(22, 7, "Skip");
    cancel = newtButton(38, 7, "Cancel");
    newtFormAddComponents(form, listbox, okay, skip, cancel, NULL);

    answer = newtRunForm(form);

    which = newtListboxGetCurrent(listbox);
    
    newtFormDestroy(form);
    newtPopWindow();

    if (answer == cancel) return INST_CANCEL;
    if (answer == skip) return SKIP_LILO;

    switch ((int) which) {
      case 1: 		*where = hdName; break;
      case 2: 		*where = rootDevice; break;
      case 3: 		*where = "fd0"; break;
    }

    return 0;
}

static void editBootLabel(struct partition * item) {
    newtComponent form, entry, okay, cancel, clear, answer;
    char buf[50];
    char * entryValue;
 
    newtOpenWindow(10, 7, 50, 10, "Edit Boot Label");

    form = newtForm(NULL, NULL, 0);

    strcpy(buf,"Device      : /dev/");
    strcat(buf, item->device);
    newtFormAddComponent(form, newtLabel(1, 1, buf));
    newtFormAddComponent(form, newtLabel(1, 3, "Boot label :"));

    entry = newtEntry(17, 3, item->bootLabel, 20, &entryValue, 
		      NEWT_ENTRY_SCROLL | NEWT_ENTRY_RETURNEXIT);

    okay = newtButton(5, 6, "Ok");
    clear = newtButton(20, 6, "Clear");
    cancel = newtButton(35, 6, "Cancel");

    newtFormAddComponents(form, entry, okay, clear, cancel, NULL);

    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, 
		         struct partitionTable table) {
    char filename[100];
    FILE * f;
    char * argv[] = { "/mnt/sbin/lilo", "-r", "/mnt", NULL };
    int i;
    int rc;
    struct stat sb;
    int useInitrd = 0;
    struct partition * root = NULL;

    if (mkinitrd())
	return INST_ERROR;

    if (!stat("/mnt/boot/initrd", &sb))
	useInitrd = 1;

    #ifdef __sparc__
	sprintf(filename, "%s/silo.conf", prefix);
    #else
	sprintf(filename, "%s/lilo.conf", prefix);
    #endif
    
    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);

    for (i = 0; i < table.count; i++) {
	if (table.parts[i].bootLabel && 
		table.parts[i].type == PART_EXT2) {
	    root = table.parts + i;
	}
    }

    if (!root) {
	errorWindow("No ext2 partition is bootable");
	return INST_ERROR;
    }

    #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");
	fprintf(f, "timeout=50\n");
    #elif __sparc__
	fprintf(f, "timeout=50\n");
	fprintf(f, "partition=%s\n", root->device + 3);
	fprintf(f, "root=/dev/%s\n", root->device);
    #else
	#error "unsupported architecture";
    #endif

    for (i = 0; i < table.count; i++) {
	if (table.parts[i].bootLabel) {
	    if (table.parts[i].type == PART_EXT2) {
		fprintf(f, "image=%s\n", kernelPath);
		fprintf(f, "\tlabel=%s\n", table.parts[i].bootLabel);
		fprintf(f, "\troot=/dev/%s\n", table.parts[i].device);
		if (useInitrd)
		    fprintf(f, "\tinitrd=/boot/initrd\n");
		fprintf(f, "\tread-only\n");
	    } 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);
	    }
	}
    }

    fclose(f);

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

    #ifdef __i386__
	rc = runProgram(RUN_LOG, "/mnt/sbin/lilo", argv);
    #elif __sparc__ 
	rc = runProgram(RUN_LOG, "/mnt/sbin/silo", argv);
    #else
	#error unsupported architectures
    #endif

    newtPopWindow();

    if (rc)
	return INST_ERROR;

    return 0;
}

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

    f = newtForm(NULL, NULL, 0);
    text = newtTextbox(1, 1, 60, 4, NEWT_TEXTBOX_WRAP);
    newtTextboxSetText(text, 
		       "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.");


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

    listbox = newtListbox(1, 7, 7, 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 &&
	    (table.parts[i].type != PART_DOS || !foundDos)) {

	    if (table.parts[i].type == PART_DOS) {
		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;

	    sprintf(buf, "/dev/%-5s  %-25s %-18s", table.parts[i].device, 
		    table.parts[i].tagName, table.parts[i].bootLabel ? 
			table.parts[i].bootLabel : "");
	    map[count] = i;
	    newtListboxAddEntry(listbox, buf, map + count++);
	} 
    }

    newtFormAddComponents(f, text, label, listbox, NULL);

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

    newtOpenWindow(8, 2, 64, 19, "Bootable Partitions");

    okay = newtButton(8, 15, "Ok");
    edit = newtButton(26, 15, "Edit");
    cancel = newtButton(44, 15, "Cancel");

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

    do { 
	answer = newtRunForm(f);
	if (answer == edit || answer == listbox) {
	    currNum = newtListboxGetCurrent(listbox);
	    curr = table.parts + *currNum;
	    editBootLabel(curr);
	    sprintf(buf, "/dev/%-5s  %-25s %-18s", curr->device, 
		    curr->tagName, 
		    curr->bootLabel ? curr->bootLabel : "");

	    newtListboxSetEntry(listbox, currNum - map, buf);
	}
    } while (answer == edit || answer == listbox);

    newtFormDestroy(f);
    newtPopWindow();

    if (answer == cancel)
	return INST_CANCEL;
    else
	return 0;
}

#define LILO_WHERE 2
#define LILO_LABELS 3
#define LILO_INSTALL 4
#define LILO_DONE 20
    
int installLilo(char * prefix, struct partitionTable table, 
	        struct fstab fstab) {
    char * rootDevice;
    char * hdName;
    char * where;
    int i;
    int rc;
    int stage = LILO_WHERE;

    /* why not? */
    rename("/mnt/etc/lilo.conf", "/mnt/etc/lilo.conf.orig");
    rename("/mnt/etc/silo.conf", "/mnt/etc/silo.conf.orig");
    
    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, "/")) break;
    }

    rootDevice = fstab.entries[i].device;

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

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

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

	  case LILO_INSTALL:
	    rc = doinstallLilo(prefix, where, table);
	    if (rc == INST_ERROR) return INST_ERROR;
	    stage = LILO_DONE;
	    break;
	}
    }

    return 0;
}
