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