#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 <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <zlib.h>

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

struct defaultKeyboardByLang {
    char * lang, * keyboard;
} defaultKeyboards[] = {
    { "fi", "fi-latin1" },   
    { "se", "se-latin1" },   
    { "no", "no-latin1" },   
    { "cz", "cz-lat2" },
    { "tr", "trq" },
    { NULL, NULL } };

/* 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(char ** keymap) {
    int num = -1;
    int rc;
    gzFile f;
    struct kmapHeader hdr;
    struct kmapInfo * infoTable;
    char ** argv;
    int argc;
    char ** kbds;
    char buf[16384]; 			/* I hope this is big enough */
    int i;
    char * defkbd = keymap ? *keymap : NULL;
    struct defaultKeyboardByLang * kbdEntry;

    /*if (testing) return 0;*/

    if (!defkbd && getenv("LANG")) {
	kbdEntry = defaultKeyboards;
	while (kbdEntry->lang && 
	       strcmp(kbdEntry->lang, getenv("LANG")))
	     kbdEntry++;
	if (kbdEntry->keyboard) defkbd = kbdEntry->keyboard;
    }
    if (!defkbd) defkbd = "us";

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

    if (kickstart) {
	if (!ksGetCommand(KS_CMD_KEYBOARD, NULL, &argc, &argv)) {
	    if (argc < 2) {
		logMessage("no argument passed to keyboard "
				"kickstart command");
	    } else {
		for (i = 0; i < hdr.numEntries; i++) 
		    if (!strcmp(infoTable[i].name, argv[1])) break;
		if (i < hdr.numEntries)
		    num = i;
		else 
		    newtWinMessage("Kickstart Error", "Ok", "Bad keymap "
				   "name %s passed to kickstart command.",
				   argv[1]);
	    }
	}
    }

    if (num == -1 ) {
	kbds = alloca(sizeof(*kbds) * (hdr.numEntries + 1));
	for (i = 0; i < hdr.numEntries; i++)  {
	    kbds[i] = infoTable[i].name;
	    if (!strcmp(infoTable[i].name, defkbd)) 
		num = i;
	}

	kbds[i] = NULL;

	rc = newtWinMenu(_("Keyboard Type"), 
			_("What type of keyboard do you have?"),
		        40, 5, 5, 8, kbds, &num, _("Ok"), /*_("Back"),*/ NULL);
	if (rc == 2) return INST_CANCEL;
    }

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

    if (keymap) *keymap = strdup(infoTable[num].name);
 
    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=%s\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 != '=')
	chptr--;
    if (*chptr == '.')
	*chptr-- = '\0';

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

    *keymap = strdup(chptr);

    return 0;
}
