/* Copyright 1998 Acorn Computers Ltd
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <stdbool.h>

#include "Unicode/iso10646.h"

#include "unicdata.h"
#include "structures.h"
#include "throwback.h"

#define MAX_FILE_DEPTH 16
FILE *sourcefile[MAX_FILE_DEPTH];
char *sourcefilename[MAX_FILE_DEPTH];
int sourceline[MAX_FILE_DEPTH];
int filedepth;

const char *ExtraKeys, *FNKey;

char keypad_codes[]={
          0x23, 0x24, 0x25,
    0x37, 0x38, 0x39, 0x3A,
    0x48, 0x49, 0x4A, 0x4B,
    0x5A, 0x5B, 0x5C,
    0x65,       0x66, 0x67,
    0
};

typedef struct charinfo
{
    UCS4 code;
    char *name;
} charinfo;

/* Must be sorted */
charinfo funclist[] =
{
    0xC0, "ACORN",
    0xC8, "ALL CANDIDATES",
    0x08, "BACKSPACE",
    0x8A, "BREAK",
    0xC6, "CONVERT",
    0x8B, "COPY",
    0x7F, "DELETE",
    0x8E, "DOWN",
    0xC4, "EISUU",
    0x8B, "END",
    0x0D, "ENTER",
    0x1B, "ESCAPE",
    0x81, "F1",
    0xCA, "F10",
    0xCB, "F11",
    0xCC, "F12",
    0x82, "F2",
    0x83, "F3",
    0x84, "F4",
    0x85, "F5",
    0x86, "F6",
    0x87, "F7",
    0x88, "F8",
    0x89, "F9",
    0x1E, "HOME",
    0xCD, "INSERT",
    0xC7, "KANA",
    0xC3, "KANJI",
    0xC9, "KANJI NUMBER",
    0x8C, "LEFT",
    0xC1, "MENU",
    0xC5, "NO CONVERSION",
    0x9E, "PAGE DOWN",
    0x9F, "PAGE UP",
    0x80, "PRINT",
    0x8D, "RIGHT",
    0x8F, "UP",
    0xC2, "WIDTH",
};

#define FUNCTIONS (sizeof funclist / sizeof funclist[0])

charinfo speclist[] =
{
    0x03, "CAPS LOCK",
    0x07, "CONFIGURED KEYBOARD",
    0x06, "DEFAULT KEYBOARD",
    0x09, "DELETE",
    0x08, "DIAL KEYBOARD",
    0x04, "KANA LOCK",
    0x01, "NUM LOCK",
    0x00, "SCROLL LOCK",
    0x05, "SHIFT-CAPS LOCK",
    0x02, "TAB",
};

#define SPECIALS (sizeof speclist / sizeof speclist[0])

charinfo deadlist[] =
{
     2, "ACUTE ACCENT",
     6, "BREVE",
    11, "CARON",
    13, "CEDILLA",
     3, "CIRCUMFLEX ACCENT",
     8, "DIAERESIS",
     7, "DOT ABOVE",
    10, "DOUBLE ACUTE ACCENT",
     1, "GRAVE ACCENT",
     8, "GREEK DIALYTIKA",
    15, "GREEK DIALYTIKA TONOS",
    12, "GREEK TONOS",
     5, "MACRON",
    14, "OGONEK",
     9, "RING ABOVE",
    16, "STROKE",
     4, "TILDE",
    12, "VERTICAL LINE ABOVE",
};

#define DEADS (sizeof deadlist / sizeof deadlist[0])

const char *current_output_file;
FILE *current_output_handle;
static int throwback, depend;

void error(const char *p)
{
    fprintf(stderr, "%s, line %d: %s\n", sourcefilename[filedepth],  sourceline[filedepth], p);
    if (throwback)
        throwback_send(1, sourceline[filedepth], p, sourcefilename[0], sourcefilename[filedepth]);
    fclose(current_output_handle);
    remove(current_output_file);
    exit(1);
}


static int cmp_name2(const void *p1, const void *p2)
{
    char *n = (char *) p1;
    charinfo *c2 = (charinfo *) p2;

    return strcmp(n, c2->name);
}

static UCS4 function_key_code(const char *p)
{
    UCS4 c, c2;
    int ctrl=0, shift=0;
    charinfo *ci;

    /* Check for raw &8A */
    if (*p == '&' && *(p+1) != '\0')
    {
        char *end;
        c = (UCS4) strtol(p+1, &end, 16);
        if (*end != '\0' || c < 0x80 || c >= 0xFF)
            error("Bad hex");
        return 0x80000000 + c;
    }

    /* Check for CTRL-B etc */
    if (strncmp(p, "CTRL-", 5) == 0)
    {
        c = p[5];
        c2 = p[6];
        if (c >= '@' && c <= '_' && c2 == '\0')
            return c-'@';
        if (c == '?' && c2 == '\0')
            return 127;
    }

    /* CTRL and SHIFT modifiers for other keys */

    if (strncmp(p, "CTRL-", 5) == 0)
    {
        ctrl = 0x20;
        p+=5;
    }

    if (strncmp(p, "SHIFT-", 6) == 0)
    {
        shift = 0x10;
        p+=6;
    }

    ci = (charinfo *) bsearch(p, funclist, FUNCTIONS, sizeof funclist[0], cmp_name2);
    if (ci == NULL)
    {
        error("Unknown function key");
        return NULL_UCS4;
    }

    if (ci->code < 0x80)
    {
        /* no shift/ctrl variants of these codes, and bit 31 needn't be set */
        return ci->code;
    }

    return (0x80000000 + ci->code) ^ ctrl ^ shift;
}

static UCS4 special_key_code(const char *p)
{
    charinfo *ci;

    ci = (charinfo *) bsearch(p, speclist, SPECIALS, sizeof speclist[0], cmp_name2);
    if (ci == NULL)
    {
        error("Unknown special key");
        return NULL_UCS4;
    }

    return 0x80010000 + ci->code;
}

static UCS4 dead_key_code(const char *p)
{
    charinfo *ci;

    ci = (charinfo *) bsearch(p, deadlist, DEADS, sizeof deadlist[0], cmp_name2);
    if (ci == NULL)
    {
        error("Unknown dead key");
        return NULL_UCS4;
    }

    return 0x80020000 + ci->code;
}

UCS4 keycode_from_name(const char *name)
{
    if (name[0]=='-' && name[1]=='\0')
        return NULL_UCS4;

    if (strncmp(name, "FUNCTION KEY ", 13)==0)
        return function_key_code(name+13);

    if (strncmp(name, "SPECIAL KEY ", 12)==0)
        return special_key_code(name+12);

    if (strncmp(name, "DEAD KEY ", 9)==0)
        return dead_key_code(name+9);

    return UCS_from_name(name);
}

char *getline(char *p, size_t plen)
{
    char *ret;
    int l;

  start_again:

    do
    {
        sourceline[filedepth]++;
        ret = fgets(p, plen, sourcefile[filedepth]);
        if (!ret)
        {
            /* EOF - either return from the current include file, or give up */
            if (filedepth)
            {
                free(sourcefilename[filedepth]);
                fclose(sourcefile[filedepth]);
                sourcefile[filedepth--] = NULL;
                l = 0;
                continue;
            }
            else
                return NULL;
        }

        l = strlen(p);
        if (p[l-1] == '\n')
            p[--l] = '\0';

        while (l && p[l-1] == ' ')
            p[--l] = '\0';

    } while (l == 0 || p[0] == '#');

    if (p[0] == '%' && memcmp(p, "%Include ", 9) == 0)
    {
        if (filedepth == MAX_FILE_DEPTH-1)
            error("%Include depth too great");
        else
        {
            const char *newinclude;
            if (strcmp(p+9, "@ExtraKeys@") == 0)
            {
                if (ExtraKeys)
                    newinclude = ExtraKeys;
                else
                    goto start_again;
            }
            else if (strcmp(p+9, "@FNKey@") == 0)
            {
                if (FNKey)
                    newinclude = FNKey;
                else
                    goto start_again;
            }
            else
                newinclude = p+9;

            /* Mustn't increment filedepth until all errors cleared */
            if ((sourcefile[filedepth+1] = fopen(newinclude, "r")) == NULL)
                error("Unable to open %Include file");
            if ((sourcefilename[filedepth+1]=malloc(strlen(newinclude)+1)) == NULL)
                error("Out of memory");
            strcpy(sourcefilename[++filedepth], newinclude);
            sourceline[filedepth]=0;

            if (depend)
                add_dependency(current_output_file, sourcefilename[filedepth]);

            goto start_again;
        }
    }

    return p;
}

#define K_ScrollLock  0x0E
#define K_Break       0x0F
#define K_NumLock     0x22
#define K_NumPadSlash 0x23
#define K_NumPadStar  0x24
#define K_NumPadHash  0x25
#define K_Tab         0x26
#define K_NumPad7     0x37
#define K_NumPad8     0x38
#define K_NumPad9     0x39
#define K_NumPadMinus 0x3A
#define K_CtrlL       0x3B
#define K_NumPad4     0x48
#define K_NumPad5     0x49
#define K_NumPad6     0x4A
#define K_NumPadPlus  0x4B
#define K_ShiftL      0x4C
#define K_ShiftR      0x58
#define K_NumPad1     0x5A
#define K_NumPad2     0x5B
#define K_NumPad3     0x5C
#define K_CapsLock    0x5D
#define K_AltL        0x5E
#define K_AltR        0x60
#define K_CtrlR       0x61
#define K_NumPad0     0x65
#define K_NumPadDot   0x66
#define K_NumPadEnter 0x67
#define K_FN          0x6F
#define K_MSelect     0x70
#define K_MMenu       0x71
#define K_MAdjust     0x72

int is_modifier_key(int key)
{
    return key == K_Break || key == K_CtrlL || key == K_ShiftL ||
           key == K_ShiftR || key == K_AltL || key == K_AltR ||
           key == K_CtrlR || key == K_FN ||
           key == K_MSelect || key == K_MMenu || key == K_MAdjust;
}

int is_keypad_key(int key)
{
    return key != 0 && strchr(keypad_codes, key) != NULL;
}

int is_special_key(int key)
{
    return key == K_ScrollLock || key == K_NumLock || key == K_Tab ||
           key == K_CapsLock || is_modifier_key(key);
}

void read_keypad(Keyboard *kb)
{
    int i=0;
    char buffer[256];
    char *second, *p;
    UCS4 k, k2;

    while (getline(buffer, 256))
    {
        if (buffer[0] == '$')
        {
            if (strcmp(buffer, "$EndKeypad")==0)
            {
                if (i < 17)
                    error("Too few keypad keys");
                return;
            }
            else
                error("Unexpected directive");
        }

        if (i >= 17)
            error("Too many keypad keys");

        second = strchr(buffer, ';');
        if (second)
        {
            *second='\0';
            p = (second++)-1;
            while (p>=buffer && *p==' ')
                *p--='\0';
            while (*second==' ')
                second++;
        }

        k = keycode_from_name(buffer);
        if (second)
            k2 = keycode_from_name(second);
        else
            k2 = k;

        if ((k >= 0x80 && k < 0x80000080) || (k >= 0x800000FF && k < 0xFFFFFFFF) ||
            (k2 >= 0x80 && k2 < 0x80000080) || (k2 >= 0x800000FF && k2 < 0xFFFFFFFF))
            error("I'm afraid you can't put that character on the keypad");

        kb->keypad[0][i]=(char)k;
        kb->keypad[1][i++]=(char)k2;
    }

    error("Unexpected end of file");
}

void read_fnkey(Keyboard *kb)
{
    int i=0;
    char buffer[256];
    char *p;
    unsigned long k, k2;

    for (i=0; i<MAXKEYS; i++)
        kb->key[i].fn = -1;

    kb->fnused = false;
    i = 0;

    while (getline(buffer, 256))
    {
        if (buffer[0] == '$')
        {
            if (strcmp(buffer, "$EndFNKey")==0)
            {
                return;
            }
            else
                error("Unexpected directive");
        }

        k = strtoul(buffer, &p, 16);
        if (k >= MAXKEYS)
            error("Expected key number");

        k2 = strtoul(p, NULL, 16);
        if (k2 >= MAXKEYS || k == k2)
            error("Expected key number");

        /* Can only map non-modifier keys to other non-modifier keys */
        if (is_modifier_key((int) k) && !kb->key[k].defined ||
            is_modifier_key((int) k2) && !kb->key[k2].defined)
            error("I'm sorry, I'm afraid I can't do that");

        kb->key[k].defined = 1;
        kb->key[k2].defined = 1;

        kb->key[k].fn = (int) k2;
        kb->fnused = true;
        if (++i > 0xFF)
            error("Too many FN mappings");
    }

    error("Unexpected end of file");
}

void read_layer(Keyboard *kb, int layerno)
{
    char buffer[256];
    char scaps[8], caps[8];
    int key, i, replace=0;
    char *p;

    while (getline(buffer, 256))
    {
        char uc[4];

        if (buffer[0] == '$')
        {
            if (strcmp(buffer, "$EndLayer")==0)
                return;
        }

        key = (int) strtol(buffer, &p, 16);
        if (p == buffer || key < 0 || key >= MAXKEYS)
            error("Expected key number");

        while (*p <= ' ' && *p != '\0')
            p++;

        if (*p != '\0')
        {
            if (strcmp(p, "replace") == 0)
                replace = 1;
            else
                error("Unexpected trailing garbage after key number");
        }

        if (!replace && kb->key[key].definedinlayer[layerno])
            error("Key already defined");

        if (is_modifier_key(key))
            kb->needshiftinglist = 1;

        if (is_keypad_key(key))
            error("Use $Keypad to alter keypad keys");

        if (is_special_key(key))
            kb->needcodetable = 1;

        kb->key[key].defined = 1;
        kb->key[key].definedinlayer[layerno] = 1;

        if (key >= kb->numkeys)
        {
            kb->numkeys = key + 1;
        }

        for (i=0; i<8; i++)
        {
            char *p=buffer, *q;
            static const char brack[] = "-[]-{}-()-<>";

            if (getline(buffer, 256) == NULL)
                error("Error reading key");

            q = *p != '-' ? strchr(brack, *p) : NULL;
            if (q)
            {
                uc[i] = q-brack+1;
                p++;
            }
            else
                uc[i] = 0;

            kb->key[key].character[layerno][i] = keycode_from_name(p);
        }

        /* Now we need to figure out the Caps Lock and Shift-Caps lock mappings.
         * The logic here is that two parts of a key may be an upper/lower pair,
         * marked with [ ] or { }. When Caps Lock is in force, you should always
         * get the upper case form of anything you press. When Shift-Caps is in
         * force, you should get the opposite case of anything you press.
         */
        kb->key[key].caps[layerno] = 0;
        for (i=0; i<8; i++)
        {
            int j;
            caps[i]=scaps[i]=i;
            for (j=0; j<8; j++)
            {
                if (uc[j] == uc[i] + 1)
                {
                    caps[i] = j;
                    scaps[i] = j;
                }
                else if (uc[j] == uc[i] - 1)
                    scaps[i] = j;
            }
            if (caps[i] == scaps[i])
            {
                kb->key[key].caps[layerno] |= caps[i] << i*4;
            }
            else
            {
                if (caps[i] == i)
                    kb->key[key].caps[layerno] |= (scaps[i]|8) << i*4;
                else
                    error("Inconsistent Caps Lock pairs");
            }
        }

        /* Now we work out if this is an "easy" key, ie one that the
         * kernel can handle itself.
         */
        kb->key[key].easylayer[layerno] = 0;
        for (i=0; i<8; i++)
        {
            UCS4 c = kb->key[key].character[layerno][i];
            kb->key[key].easych[layerno][i]=0;
            if ((i < 4 && (c <= 0x7F || c >= 0x80000080 && c <= 0x800000FE)) || c == 0xFFFFFFFF)
            {
                UCS4 kc, ksc;
                /* Okay, it's one of the kernel's 00-FF codes. What will the kernel
                 * do with Caps-Lock? */
                if ((c &~ 0x20) >= 'A' && (c &~ 0x20) <= 'Z')
                {
                    ksc = c ^ 0x20;
                    kc = c &~ 0x20;
                }
                else
                    kc = ksc = c;

                /* Does it match what we want? */
                if (ksc == kb->key[key].character[layerno][scaps[i]] &&
                    kc == kb->key[key].character[layerno][caps[i]])
                {
                    kb->key[key].easylayer[layerno]++;
                    kb->key[key].easych[layerno][i]=1;
                }
            }
        }
        if (kb->key[key].easylayer[layerno] < 8) kb->key[key].easylayer[layerno] = 0;
    }
    error("Unexpected end of file");
}

void *xmalloc(size_t s)
{
    void *ret = malloc(s);
    if (!ret) error("Out of memory");
    return ret;
}

void read_layout_file(Keyboard *kb)
{
    char buffer[256];
    int layer = -1;

    while (getline(buffer, 256))
    {
        if (buffer[0] == '$')
        {
            if (memcmp(buffer, "$Country ", 9)==0)
                kb->country = atoi(buffer + 9);
            else if (strcmp(buffer, "$LeftAltLayerSwitch")==0)
            {
                kb->leftaltlayerswitch = 1;
                kb->needcodetable = 1;
            }
            else if (memcmp(buffer, "$Layer ", 7)==0)
            {
                layer = atoi(buffer + 7);
                if (layer < 0 || layer >= MAXLAYERS)
                    error("Bad layer number");

                if (layer < kb->layers)
                    error("Layer already defined");

                read_layer(kb, layer);

                kb->layers++;
            }
            else if (strcmp(buffer, "$Keypad")==0)
            {
                if (kb->custompad)
                    error("Keypad already defined");
                kb->custompad=1;
                read_keypad(kb);
            }
            else if (strcmp(buffer, "$FNKey")==0)
                read_fnkey(kb);
            else
                error("Unknown directive");
        }
        else
            error("Expecting a $ directive");
    }

}

void output_layer(FILE *out, Keyboard *kb, int layerno)
{
    int i;

    fprintf(out, "\nUCSTable%d_%d\n", kb->country, layerno);

    for (i=0; i < MAXKEYS; i++)
    {
        if (kb->key[i].defined && !kb->key[i].easy)
            fprintf(out, "        &       &%08X,&%08X,&%08X,&%08X ; &%02X\n"
                         "        &       &%08X,&%08X,&%08X,&%08X, &%08X\n",
                         kb->key[i].character[layerno][0],
                         kb->key[i].character[layerno][1],
                         kb->key[i].character[layerno][2],
                         kb->key[i].character[layerno][3],
                         i,
                         kb->key[i].character[layerno][4],
                         kb->key[i].character[layerno][5],
                         kb->key[i].character[layerno][6],
                         kb->key[i].character[layerno][7],
                         kb->key[i].caps[layerno]);
    }
}

void output_simples(FILE *out, Keyboard *kb)
{
    int i,j,c, best;
    int t;

    if (kb->fnused)
    {
        /* Sorry lads, if FN's in use we can't have any simple keys */
        for (i=0; i<MAXKEYS; i++)
            kb->key[i].easy = 0;
        best = 0;
    }
    else
    {
        int bestmem;
        int specials;

        for (i=0; i<MAXKEYS; i++)
        {
            kb->key[i].easy = 1;
            for (j=0; j<kb->layers; j++)
            {
                if (!kb->key[i].easylayer[j])
                {
                    kb->key[i].easy = 0;
                    break;
                }
                for (c=0; c<8; c++)
                    if (kb->key[i].character[j][c] != kb->key[i].character[0][c])
                    {
                        kb->key[i].easy = 0;
                        break;
                    }
            }
        }

        /* How many should we put in the "easy" array? Want to
         * minimise memory usage. The memory usage is basically
         *      4 * (highest easy key+1)     for the KeyTran table
         *    + 36 * special keys * layers + special keys.    for SpecialList + UCSList
         *
         * Start off supposing every defined key is special,
         * then try effect of gradually extending the simple table.
         */

        for (i=0, specials=0; i < MAXKEYS; i++)
            if (kb->key[i].defined)
                specials++;

        bestmem = 36*specials*kb->layers + specials;
        best = 0;

        for (i=0; i < MAXKEYS; i++)
        {
            int mem;
            if (kb->key[i].easy && kb->key[i].defined) // we've saved one special code!!
                specials--;
            mem = (i+1)*4 + 36*specials*kb->layers + specials;
            /*printf("%d simple, %d special, mem = %d\n", i, specials, mem);*/
            if (mem<bestmem)
            {
                best = i+1;
                bestmem = mem;
            }
        }

        for (i=best; i < MAXKEYS; i++)
            kb->key[i].easy=0;
    }

    fprintf(out, "KeyTran%d\n", kb->country);
    for (i=0; i < best; i++)
        if (kb->key[i].easy)
            fprintf(out, "        =       &%02X, &%02X, &%02X, &%02X\n",
                         kb->key[i].easych[0][0] ? kb->key[i].character[0][0]&0xFF : 0xFF,
                         kb->key[i].easych[0][1] ? kb->key[i].character[0][1]&0xFF : 0xFF,
                         kb->key[i].easych[0][2] ? kb->key[i].character[0][2]&0xFF : 0xFF,
                         kb->key[i].easych[0][3] ? kb->key[i].character[0][3]&0xFF : 0xFF);
        else
            fprintf(out, "        =       &FF, &FF, &FF, &FF\n");

    fprintf(out, "KeyTran%dEnd\n\n", kb->country);

    fprintf(out, "\nSpecialList%d\n"
                 "        =       SpecialList%dEnd - SpecialList%d - 1\n",
                 kb->country, kb->country, kb->country);

    t=1;

    if (!kb->key[K_ShiftL].defined)     { fprintf(out, "        =       K1ShiftLeft\n");   t++; }
    if (!kb->key[K_ShiftR].defined)     { fprintf(out, "        =       K1ShiftRight\n");  t++; }
    if (!kb->key[K_CtrlL].defined)      { fprintf(out, "        =       K1CtrlLeft\n");    t++; }
    if (!kb->key[K_CtrlR].defined)      { fprintf(out, "        =       K1CtrlRight\n");   t++; }
    if (!kb->key[K_AltL].defined)       { fprintf(out, "        =       K1AltLeft\n");     t++; }
    if (!kb->key[K_AltR].defined)       { fprintf(out, "        =       K1AltRight\n");    t++; }
    if (!kb->key[K_FN].defined)         { fprintf(out, "        =       K1FN\n");          t++; }
    if (!kb->key[K_MSelect].defined)    { fprintf(out, "        =       K1LeftMouse\n");   t++; }
    if (!kb->key[K_MMenu].defined)      { fprintf(out, "        =       K1CentreMouse\n"); t++; }
    if (!kb->key[K_MAdjust].defined)    { fprintf(out, "        =       K1RightMouse\n");  t++; }
    if (!kb->key[K_Break].defined)      { fprintf(out, "        =       K1Break\n");       kb->key[K_Break].tablenum=t++; }

    fprintf(out, "SpecialList%dPad\n"
                 "        =       K1NumPadSlash, K1NumPadStar, K1NumPadHash\n"
                 "        =       K1NumPad7, K1NumPad8, K1NumPad9, K1NumPadMinus\n"
                 "        =       K1NumPad4, K1NumPad5, K1NumPad6, K1NumPadPlus\n"
                 "        =       K1NumPad1, K1NumPad2, K1NumPad3\n"
                 "        =       K1NumPad0, K1NumPadDot, K1NumPadEnter\n",
                 kb->country);

    kb->key[K_NumPadSlash].tablenum=t++;
    kb->key[K_NumPadStar].tablenum=t++;
    kb->key[K_NumPadHash].tablenum=t++;
    kb->key[K_NumPad7].tablenum=t++;
    kb->key[K_NumPad8].tablenum=t++;
    kb->key[K_NumPad9].tablenum=t++;
    kb->key[K_NumPadMinus].tablenum=t++;
    kb->key[K_NumPad4].tablenum=t++;
    kb->key[K_NumPad5].tablenum=t++;
    kb->key[K_NumPad6].tablenum=t++;
    kb->key[K_NumPadPlus].tablenum=t++;
    kb->key[K_NumPad1].tablenum=t++;
    kb->key[K_NumPad2].tablenum=t++;
    kb->key[K_NumPad3].tablenum=t++;
    kb->key[K_NumPad0].tablenum=t++;
    kb->key[K_NumPadDot].tablenum=t++;
    kb->key[K_NumPadEnter].tablenum=t++;

    if (!kb->key[K_ScrollLock].defined) { fprintf(out, "        =       K1ScrollLock\n");  kb->key[K_ScrollLock].tablenum=t++; }
    if (!kb->key[K_NumLock].defined)    { fprintf(out, "        =       K1NumLock\n");     kb->key[K_NumLock].tablenum=t++; }
    if (!kb->key[K_Tab].defined)        { fprintf(out, "        =       K1Tab\n");         kb->key[K_Tab].tablenum=t++; }
    if (!kb->key[K_CapsLock].defined)   { fprintf(out, "        =       K1CapsLock\n");    kb->key[K_CapsLock].tablenum=t++; }

    fprintf(out, "SpecialList%dUCS", kb->country);

    for (j=0, i=0; i<MAXKEYS; i++)
        if (kb->key[i].defined && !kb->key[i].easy)
        {
            fprintf(out, "%s&%02X", j==0 ? "\n        =       " : ", ", i);
            j = (j+1)%8;
            kb->key[i].tablenum = t++;
        }

    fprintf(out, "\nSpecialList%dEnd\n"
                 "        ALIGN\n",
                 kb->country);
}

void process_keyboard(FILE *in, FILE *out)
{
    int i, k, c;
    Keyboard *kb = xmalloc(sizeof *kb);
    kb->country = -1;
    kb->layers = 0;
    kb->numkeys = 0;
    kb->fnused = false;
    kb->needcodetable = 0;
    kb->needshiftinglist = 0;
    kb->custompad = 0;
    kb->leftaltlayerswitch = 0;

    sourcefile[0] = in;

    for (k=0; k<MAXKEYS; k++)
    {
        kb->key[k].defined = 0;
        kb->key[k].tablenum = 0;
        kb->key[k].easy = 1;
        for (i=0; i<MAXLAYERS; i++)
        {
            for (c=0; c<8; c++)
            {
                kb->key[k].character[i][c] = NULL_UCS4;
                kb->key[k].easych[i][c]=1;
            }
            kb->key[k].caps[i]=(0<<0)|(1<<4)|(2<<8)|(3<<12)|(4<<16)|(5<<20)|(6<<24)|(7<<28);
            kb->key[k].definedinlayer[i]=0;
            kb->key[k].easylayer[i]=1;
        }
    }

    read_layout_file(kb);

    fprintf(out, "KeyStruct%d\n"
                 "        &       KeyTran%d-KeyStruct%d\n"
                 "        &       (KeyTran%dEnd-KeyTran%d) :SHR: 2\n"
                 "        &       InkeyTran-KeyStruct%d\n",
                 kb->country, kb->country, kb->country, kb->country, kb->country,
                 kb->country);

    if (kb->needshiftinglist)
        fprintf(out, "        &       ShiftingKeyList%d-KeyStruct%d\n", kb->country, kb->country);
    else
        fprintf(out, "        &       ShiftingKeyList-KeyStruct%d\n", kb->country);

    fprintf(out, "        &       SpecialList%d-KeyStruct%d\n",
                 kb->country, kb->country);

    if (kb->needcodetable)
        fprintf(out, "        &       SpecialCodeTable%d-KeyStruct%d\n", kb->country, kb->country);
    else
        fprintf(out, "        &       SpecialCodeTable-KeyStruct%d\n", kb->country);

    fprintf(out, "        &       KeyStructInit-KeyStruct%d\n"
                 "        &       PendingAltCode-KeyStruct%d\n",
                 kb->country, kb->country);

    if (kb->custompad)
        fprintf(out,
               "        &       PadK%dNumTran-KeyStruct%d-(SpecialList%dPad-SpecialList%d)\n"
               "        &       PadK%dCurTran-KeyStruct%d-(SpecialList%dPad-SpecialList%d)\n",
               kb->country, kb->country, kb->country, kb->country,
               kb->country, kb->country, kb->country, kb->country);
    else
        fprintf(out,
               "        &       PadKNumTran-KeyStruct%d-(SpecialList%dPad-SpecialList%d)\n"
               "        &       PadKCurTran-KeyStruct%d-(SpecialList%dPad-SpecialList%d)\n",
               kb->country, kb->country, kb->country,
               kb->country, kb->country, kb->country);

    if (kb->fnused)
        fprintf(out, "        &       FNTable%d-KeyStruct%d\n", kb->country, kb->country);
    else
        fprintf(out, "        &       0\n");

    for (i=0; i < MAXLAYERS; i++)
    {
        int j = i < kb->layers ? i : 0;
        fprintf(out, "        &       UCSTable%d_%d-KeyStruct%d-(SpecialList%dUCS-SpecialList%d)*36\n", kb->country, j, kb->country, kb->country, kb->country);
    }

    fprintf(out, "\n");

    output_simples(out, kb);

    if (kb->needcodetable)
    {
        fprintf(out, "\nSpecialCodeTable%d\n", kb->country);

        if (!kb->key[K_ShiftL].defined)
            fprintf(out, "        &       ProcessKShift - SpecialCodeTable%d\n", kb->country);
        if (!kb->key[K_ShiftR].defined)
            fprintf(out, "        &       ProcessKShift - SpecialCodeTable%d\n", kb->country);
        if (!kb->key[K_CtrlL].defined)
            fprintf(out, "        &       ProcessKCtrl - SpecialCodeTable%d\n", kb->country);
        if (!kb->key[K_CtrlR].defined)
            fprintf(out, "        &       ProcessKCtrl - SpecialCodeTable%d\n", kb->country);
        if (!kb->key[K_AltL].defined)
        {
            if (kb->leftaltlayerswitch)
                fprintf(out, "        &       ProcessKAltLeft - SpecialCodeTable%d\n", kb->country);
            else
                fprintf(out, "        &       ProcessKAlt - SpecialCodeTable%d\n", kb->country);
        }
        if (!kb->key[K_AltR].defined)
            fprintf(out, "        &       ProcessKAlt - SpecialCodeTable%d\n", kb->country);
        if (!kb->key[K_FN].defined)
            fprintf(out, "        &       ProcessKFN - SpecialCodeTable%d\n", kb->country);
        if (!kb->key[K_MSelect].defined)
            fprintf(out, "        &       ProcessKLeft - SpecialCodeTable%d\n", kb->country);
        if (!kb->key[K_MMenu].defined)
            fprintf(out, "        &       ProcessKCentre - SpecialCodeTable%d\n", kb->country);
        if (!kb->key[K_MAdjust].defined)
            fprintf(out, "        &       ProcessKRight - SpecialCodeTable%d\n", kb->country);
        if (!kb->key[K_Break].defined)
            fprintf(out, "        &       ProcessKBreak - SpecialCodeTable%d\n", kb->country);
        for (i=0; i<17; i++)
            fprintf(out, "        &       ProcessK1Pad - SpecialCodeTable%d\n", kb->country);
        if (!kb->key[K_ScrollLock].defined)
            fprintf(out, "        &       ProcessKScroll - SpecialCodeTable%d\n", kb->country);
        if (!kb->key[K_NumLock].defined)
            fprintf(out, "        &       ProcessKNum - SpecialCodeTable%d\n", kb->country);
        if (!kb->key[K_Tab].defined)
            fprintf(out, "        &       ProcessKTab - SpecialCodeTable%d\n", kb->country);
        if (!kb->key[K_CapsLock].defined)
            fprintf(out, "        &       ProcessKCaps - SpecialCodeTable%d\n", kb->country);
        for (i=0; i<MAXKEYS; i++)
            if (kb->key[i].defined && !kb->key[i].easy)
                fprintf(out, "        &       ProcessUCS - SpecialCodeTable%d\n", kb->country);
    }

    if (kb->needshiftinglist)
    {
        fprintf(out, "\nShiftingKeyList%d\n"
                     "        =       ShiftingKeyList%dEnd - ShiftingKeyList%d - 1\n",
                     kb->country, kb->country, kb->country);

        if (!kb->key[K_ShiftL].defined)  fprintf(out, "        =       K1ShiftLeft\n");
        if (!kb->key[K_ShiftR].defined)  fprintf(out, "        =       K1ShiftRight\n");
        if (!kb->key[K_CtrlL].defined)   fprintf(out, "        =       K1CtrlLeft\n");
        if (!kb->key[K_CtrlR].defined)   fprintf(out, "        =       K1CtrlRight\n");
        if (!kb->key[K_AltL].defined)    fprintf(out, "        =       K1AltLeft\n");
        if (!kb->key[K_AltR].defined)    fprintf(out, "        =       K1AltRight\n");
        if (!kb->key[K_FN].defined)      fprintf(out, "        =       K1FN\n");
        if (!kb->key[K_MAdjust].defined) fprintf(out, "        =       K1RightMouse\n");
        if (!kb->key[K_MMenu].defined)   fprintf(out, "        =       K1CentreMouse\n");
        if (!kb->key[K_MSelect].defined) fprintf(out, "        =       K1LeftMouse\n");
        if (!kb->key[K_Break].defined)   fprintf(out, "        =       K1Break\n");

        fprintf(out, "ShiftingKeyList%dEnd\n",
                     kb->country);
    }

    for (i=0; i<kb->layers; i++)
        output_layer(out, kb, i);

    if (kb->fnused)
    {
        fprintf(out, "\nFNTable%d\n"
                     "        =       (FNTable%dEnd - FNTable%d - 1) / 2\n",
                     kb->country, kb->country, kb->country);
        for (i=0; i<MAXKEYS; i++)
            if (kb->key[i].fn != -1)
            {
                if (!kb->key[i].tablenum || !kb->key[kb->key[i].fn].tablenum)
                {
                    char buffer[128];
                    sprintf(buffer, "Consistency failure in FN map (%02X->%02X)", i, kb->key[i].fn);
                    error(buffer);
                }
                fprintf(out, "        =       &%02X, &%02X\n", kb->key[i].tablenum,
                                                               kb->key[kb->key[i].fn].tablenum);
            }
        fprintf(out, "FNTable%dEnd\n", kb->country);
    }

    if (kb->custompad)
    {
        int j, n;
        for (n=0; n<=1; n++)
        {
            fprintf(out, n ? "\nPadK%dCurTran" : "\nPadK%dNumTran", kb->country);
            for (j=0, i=0; i<17; i++)
            {
                fprintf(out, "%s&%02X", j==0 ? "\n        =       " : ", ", kb->keypad[n][i]);
                j = (j+1)%8;
            }
        }
        putc('\n', out);
    }

    fprintf(out, "\n        ALIGN\n");
    fprintf(out, "\n        END\n");
}

int main(int argc, char **argv)
{
    FILE *in, *out;

    if (argc >= 3 && strcmp(argv[1], "-depend")==0)
    {
        depend = 1;
        dependfilename = argv[2];
        argv+=2;
        argc-=2;
    }

    if (argc >= 2 && strcmp(argv[1], "-throwback")==0)
    {
        throwback = 1;
        argv++;
        argc--;
    }

    if (argc >= 3 && strcmp(argv[1], "-extra")==0)
    {
        ExtraKeys = argv[2];
        argv+=2;
        argc-=2;
    }

    if (argc >= 3 && strcmp(argv[1], "-fn")==0)
    {
        FNKey = argv[2];
        argv+=2;
        argc-=2;
    }

    if (argc != 3)
    {
        fprintf(stderr, "Usage: keygen [-depend filename] [-throwback] [-extra filename] [-fn filename]\n"
                        "              layout-file output-file\n");
        exit(1);
    }

    in = fopen(argv[1], "r");
    if (!in)
    {
        perror(argv[1]);
        exit(1);
    }

    out = fopen(argv[2], "w");
    if (!out)
    {
        perror(argv[2]);
        exit(1);
    }

    sourcefilename[0] = argv[1];
    current_output_handle = out;
    current_output_file = argv[2];
    if (depend)
        add_dependency(current_output_file, sourcefilename[0]);

    load_unidata("UniData215");

    process_keyboard(in, out);

    return 0;
}