#include <alloca.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/keyboard.h>
#include <linux/kd.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <zlib.h>

#include "install.h"
#include "kbd.h"
#include "log.h"
#include "newt.h"
#include "windows.h"

/* the file pointer must be at the beginning of the section already! */
static int loadKeymap(gzFile stream) {
    int console;
    int kmap, key;
    struct kbentry entry;
    int keymaps[MAX_NR_KEYMAPS];
    int count = 0;
    int magic;
    short keymap[NR_KEYS];

    if (gzread(stream, &magic, sizeof(magic)) != sizeof(magic)) {
	logMessage("failed to read kmap magic: %s", strerror(errno));
	return INST_ERROR;
    }

    if (magic != KMAP_MAGIC) {
	logMessage("bad magic for keymap!");
	return INST_ERROR;
    }

    if (gzread(stream, keymaps, sizeof(keymaps)) != sizeof(keymaps)) {
	logMessage("failed to read keymap header: %s", strerror(errno));
	return INST_ERROR;
    }


    console = open("/dev/console", O_RDWR);
    if (console < 0) {
	logMessage("failed to open /dev/console: %s", strerror(errno));
	return INST_ERROR;
    }

    for (kmap = 0; kmap < MAX_NR_KEYMAPS; kmap++) {
	if (!keymaps[kmap]) continue;

	if (gzread(stream, keymap, sizeof(keymap)) != sizeof(keymap)) {
	    logMessage("failed to read keymap data: %s", strerror(errno));
	    close(console);
	    return INST_ERROR;
	}

	count++;
	for (key = 0; key < NR_KEYS; key++) {
	    entry.kb_index = key;
	    entry.kb_table = kmap;
	    entry.kb_value = keymap[key];
	    if (KTYP(entry.kb_value) != KT_SPEC) {
		if (ioctl(console, KDSKBENT, &entry)) {
		    close(console);
		    logMessage("keymap ioctl failed: %s", strerror(errno));
		}
	    }
	}
    }

    logMessage("loaded %d keymap tables", count);

    close(console);

    return 0;
}

int setupKeyboard(void) {
    newtComponent form, okay, listbox, answer;
    int num;
    int rc;
    gzFile f;
    struct kmapHeader hdr;
    struct kmapInfo * infoTable;
    char buf[16384]; 			/* I hope this is big enough */
    int i;

    f = gzopen("/etc/keymaps.gz", "r");
    if (!f) {
	errorWindow("cannot open /etc/keymaps.gz: %s");
	return INST_ERROR;
    }

    if (gzread(f, &hdr, sizeof(hdr)) != sizeof(hdr)) {
	errorWindow("failed to read keymaps header: %s");
	gzclose(f);
	return INST_ERROR;
    }

    logMessage("%d keymaps are available", hdr.numEntries);

    i = hdr.numEntries * sizeof(*infoTable);
    infoTable = alloca(i);
    if (gzread(f, infoTable, i) != i) {
	errorWindow("failed to read keymap information: %s");
	gzclose(f);
	return INST_ERROR;
    }

    newtOpenWindow(20, 3, 40, 16, "Keyboard Type");

    form = newtForm(NULL, NULL, 0);

    newtFormAddComponent(form, 
			 newtLabel(3, 1, "What type of keyboard do you have?"));

    listbox = newtListbox(8, 3, 8, NEWT_LISTBOX_RETURNEXIT);

    for (i = 0; i < hdr.numEntries; i++) {
	newtListboxAddEntry(listbox, infoTable[i].name, (void *) i);

	if (!strcmp(infoTable[i].name, "us")) 
	    newtListboxSetCurrent(listbox, i);
    }

    okay = newtButton(18, 12, "Ok");

    newtFormAddComponents(form, listbox, okay, NULL);

    answer = newtRunForm(form);

    num = (int) newtListboxGetCurrent(listbox);
    rc = 0;

    logMessage("using keymap %s", infoTable[num].name);

    for (i = 0; i < num; i++) {
	if (gzread(f, buf, infoTable[i].size) != infoTable[i].size) {
	    logMessage("error reading %d bytes from file: %s", 
			    infoTable[i].size, strerror(errno));
	    gzclose(f);
	    rc = INST_ERROR;
	}
    }

    if (!rc) rc = loadKeymap(f);

    gzclose(f);

    writeKbdConfig("/tmp", infoTable[num].name);
 
    newtFormDestroy(form);
    newtPopWindow();

    return rc;
}

int writeKbdConfig(char * prefix, char * keymap) {
    FILE * f;
    char * filename;

    if (testing || !keymap) return 0;

    filename = alloca(strlen(prefix) + 20);
    sprintf(filename, "%s/keyboard", prefix);

    f = fopen(filename, "w");
    if (!f) {
	errorWindow("failed to create keyboard configuration: %s");
	return INST_ERROR;
    }

    if (fprintf(f, "KEYTABLE=\"/usr/lib/kbd/keytables/%s.map\"\n", 
			keymap) < 0) {
	errorWindow("failed to write keyboard configuration: %s");
	return INST_ERROR;
    }

    fclose(f);

    return 0;
}

int readKbdConfig(char * prefix, char ** keymap) {
    FILE * f;
    char * filename;
    char buf[255];
    char * chptr;

    *keymap = NULL;

    if (testing) return 0;

    filename = alloca(strlen(prefix) + 20);
    sprintf(filename, "%s/keyboard", prefix);

    f = fopen(filename, "r");
    if (!f) {
	/* fail silently -- old bootdisks won't create this */
	logMessage("failed to read keyboard configuration (proably ok)");
	return 0;
    }

    /* this is a bit braindead -- we can steal better parsing from
       kbdconfig if we ever need it */
    if (!fgets(buf, sizeof(buf) - 1, f)) {
	errorWindow("empty keyboard configuration file");
	fclose(f);
	return INST_ERROR;
    }

    fclose(f);

    if (strncmp("KEYTABLE=", buf, 9)) {
	errorWindow("unrecognized entry in keyboard configuration file");
	return INST_ERROR;
    }

    chptr = buf + strlen(buf) - 1;
    /* ignore the '\n' on the end */
    *chptr-- = '\0';
    if (*chptr == '"') 
	*chptr-- = '\0';

    while (chptr > buf && *chptr != '.') chptr--;
    if (*chptr == '.') *chptr-- = '\0';

    while (chptr > buf && *chptr != '/') chptr--;
    if (*chptr == '/') chptr++;

    *keymap = strdup(chptr);

    return 0;
}
