#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[] = {
    { "de", "de-latin1" }, 
    { "fi", "fi-latin1" },   
    { "se", "se-latin1" },   
    { "no", "no-latin1" },   
    { "cs", "cz-lat2" },
    { "tr", "trq" },
    { NULL, NULL } };

#ifdef __sparc__
struct defaultKeyboardByLang
defaultSunKeyboards[] = {
    { "fi", "sunt5-fi-latin1" },   
    { "cs", "sunt5-cz-us" },
    { NULL, NULL } };
#endif

/* 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, char ** kbdtypep) {
    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;

#ifdef __sparc__
#define KBDTYPE_SUN            0
#define KBDTYPE_PC             1
    int kbdtype = -1;
    int j;
#endif    

    /*if (testing) return 0;*/

#ifdef __sparc__
    if (kickstart) {
    	kbdtype = KBDTYPE_SUN;
	if (!ksGetCommand(KS_CMD_KBDTYPE, NULL, &argc, &argv)) {
	    if (argc < 2) {
		logMessage("no argument passed to keyboard "
				"kickstart command");
	    } else {
	        if (!strcasecmp (argv[1], "sun"))
	            kbdtype = KBDTYPE_SUN;
	        else if (!strcasecmp (argv[1], "pc"))
	            kbdtype = KBDTYPE_PC;
	    }
	}
    } else {
        char twelve = 12;
        int fd;
        
        if (ioctl (0, TIOCLINUX, &twelve) < 0)
            kbdtype = KBDTYPE_SUN; /* probably serial console, but one should not call us in such a case */
        else {
            fd = open("/dev/kbd", O_RDWR);
            if (fd < 0)
            	kbdtype = KBDTYPE_PC; /* if PC keyboard, then there is no driver for /dev/kbd */
            else {
            	close(fd);
                kbdtype = KBDTYPE_SUN;
            }
        }
    }
#endif
    
    if (!defkbd && getenv("LANG")) {
	kbdEntry = defaultKeyboards;
#ifdef __sparc__
	if (kbdtype == KBDTYPE_SUN)
	    kbdEntry = defaultSunKeyboards;
#endif	
	while (kbdEntry->lang && 
	       strcmp(kbdEntry->lang, getenv("LANG")))
	     kbdEntry++;
	if (kbdEntry->keyboard) defkbd = kbdEntry->keyboard;
    }
    if (!defkbd)
#ifdef __sparc__
	if (kbdtype == KBDTYPE_SUN)
	    defkbd = "sunkeymap";
	else
#endif
	    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;
#ifdef __sparc__
		if (i < hdr.numEntries) {
		    if (kbdtype == KBDTYPE_SUN && strncmp (argv[1], "sun", 3))
		    	i = hdr.numEntries;
		    else if (kbdtype == KBDTYPE_PC && !strncmp (argv[1], "sun", 3))
		    	i = hdr.numEntries;
		}
#endif
		if (i < hdr.numEntries)
		    num = i;
		else 
		    newtWinMessage("Kickstart Error", "Ok", "Bad keymap "
				   "name %s passed to kickstart command.",
				   argv[1]);
	    }
	}
    }

    if (num == -1 ) {
#ifdef __sparc__
	kbds = alloca(sizeof(*kbds) * (hdr.numEntries + 1));
	for (j = 0, i = 0; j < hdr.numEntries; j++) {
	    if (kbdtype == KBDTYPE_SUN && strncmp (infoTable[j].name, "sun", 3))
		continue;
	    else if (kbdtype == KBDTYPE_PC && !strncmp (infoTable[j].name, "sun", 3))
	        continue;
	    kbds[i] = infoTable[j].name;
	    if (!strcmp(infoTable[j].name, defkbd))
		num = i;
	    i++;
	}
#else	
	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;
	}
#endif

	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;

#ifdef __sparc__
    for (j = 0, i = 0; i < hdr.numEntries; i++) {
	if (kbdtype == KBDTYPE_SUN && strncmp (infoTable[i].name, "sun", 3))
		continue;
	if (kbdtype == KBDTYPE_PC && !strncmp (infoTable[i].name, "sun", 3))
		continue;
	if (j == num) {
		num = i;
		break;
	}
	j++;
    }
#endif	
    
    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, 
#ifdef __sparc__    
    			   kbdtype == KBDTYPE_SUN ? "sun" : "pci"
#else
			   NULL
#endif    			   
    			   );


    if (keymap) *keymap = strdup(infoTable[num].name);

#ifdef __sparc__
    if (kbdtypep) *kbdtypep = (kbdtype == KBDTYPE_SUN) ? "sun" : "pci";
#endif

    return rc;
}

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

    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;
    }
    
#ifdef __sparc__
    if (fprintf(f, "KEYBOARDTYPE=%s\n", kbdtype) < 0) {
	errorWindow("failed to write keyboard configuration: %s");
	return INST_ERROR;
    }
#endif

    fclose(f);

    /* write default keymap */
    if (strlen(prefix)>=14) {
	rootpath = alloca(strlen(prefix));
	rootpath = strncpy(rootpath,prefix,strlen(prefix)-14);
	rootpath[strlen(prefix)-14]='\0';
	if ((pid=fork())!=-1) {
	    if (pid) {
		wait(&pid);
	    } else {
		chroot(rootpath);
		system("/usr/bin/dumpkeys > /etc/sysconfig/console/default.kmap 2>/dev/null");
		exit(0);
	    }
	}
    }

    return 0;
}

int readKbdConfig(char * prefix, char ** keymap, char ** kbdtype) {
    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;
    }

    if (strncmp("KEYTABLE=", buf, 9)) {
	errorWindow("unrecognized entry in keyboard configuration file");
        fclose(f);
	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);

#ifdef __sparc__
    if (!fgets(buf, sizeof(buf) - 1, f)) {
	errorWindow("empty keyboard configuration file");
	fclose(f);
	return INST_ERROR;
    }

    if (strncmp("KEYBOARDTYPE=", buf, 13)) {
	errorWindow("unrecognized entry in keyboard configuration file");
        fclose(f);
	return INST_ERROR;
    }
    
    for (chptr = buf + 13; *chptr == ' ' || *chptr == '"' || *chptr == '\t'; chptr++);
    if (!strcasecmp (chptr, "pc"))
        *kbdtype = "pc";
    else
        *kbdtype = "sun";
#endif

    fclose(f);

    return 0;
}
