/* ikm.c -- invert keymaps      */

/** NOTICE:
 * The contents of this file are copyright 1987, Jim Mackraz.  All rights
 * reserved.  No use of the contents of this file or the compilation
 * of them may be used apart from the entire Commodities Exchange
 * without written permission.
 */

#define CONTROLBITS     ( (1 << 5) | (1 << 6) )

/* return >= 0 if ok (no error checking now)    */
ULONG InvertKeyMap(ansicode, ie, km)
ULONG ansicode;
register struct InputEvent *ie;
struct KeyMap *km;
{
    ULONG   kindex;
    UBYTE   code = 0;
    extern struct KeyMap keymap;

    if (km == NULL) {
        km = &keymap;
    }

    ie->ie_Class = IECLASS_RAWKEY;
    ie->ie_EventAddress = 0;

    /* check for codes in (default) high map first  */
    switch(ansicode)
    {
        case ' ':
            code = 0x40;		/* space            */
            break;
        case 0x08:              /* backspace    */
            code = 0x41;
            break;
        case '\t':              /* tab                  */
            code = 0x42;
            break;
        case 0x0D:              /* return               */
            code = 0x44;
            break;
        case 0x1B:              /* esc                  */
            code = 0x45;
            break;
        case 0x7F:              /* del                  */
            code = 0x46;
            break;
    }

    if (code) {
        ie->ie_Code = code;
        ie->ie_Qualifier = 0;
        return (1);
    }

    if (LowKeyInvert((UBYTE) ansicode, km,
            &ie->ie_Code, &ie->ie_Qualifier, &kindex) >= 0) {
        if (kindex) {   /* was dead key, need "preceding" keystroke. */
            IndexKey(kindex, km, (ULONG *)&ie->ie_EventAddress);
        }
    }

    return (1);
}

#define KEYMAPSIZE      64

/* LowKeyInvert returns good code else <0 if no find
 *
 * regarding keymap as many-to-one mapping:
 * -entries for a given key are scanned so that
 *      the minimum number of qualifiers are associated
 *      with the keystroke.
 * -passing a character value of zero corresponds, in
 *      the default keymap, to CTRL-`, which is probably a bug.
 * -numerals are matched with numeric pad keystrokes (no
 *      qualifiers) on standard keymap.  The way to specify
 *      a key on the number row is via its shifted value;
 *      specify explicitly that the qualifier is to be unshifted,
 *      or a "don't care."
 */
UWORD LowKeyInvert(value, km, codep, qualp, indexp)
register UBYTE  value;  /* translation value from low keymap    */
struct  KeyMap  *km;
UWORD   *codep;         /* pointers where answers are to be put */
UWORD   *qualp;
ULONG   *indexp;        /* dead-key index information (put into ie?)    */
{
    register UWORD code = 0;
    register WORD type;
    register LONG *p;             /* points to four-byte lokeymap entry   */
    WORD found_it = 0;

    *indexp = *qualp = 0;

    p = (LONG *) km->km_LoKeyMap;

    do  {
        /* determine type of key        */
        if ((type = km->km_LoKeyMapTypes[code] ) == KC_VANILLA) {
            found_it = checkVanilla((UBYTE *)p, value, qualp);
        } else if (type & KCF_DEAD) {
            found_it = checkDead((UBYTE **)p, value, qualp, indexp);
        }
        /**
        else if (type & KCF_STRING) {
        }
        **/
        else if (!(type & KCF_NOP)) {
            found_it = checkNormal((LONG)p, value, (UWORD)type, qualp);
        }

        ++p;
    } while (!found_it && ++code < KEYMAPSIZE);

    *codep = code;
    return (code);
}

/*
 * packs code|qual of previous key (should be keys) in *dead_vudup
 * returns code, <0 if failure
 */
VOID IndexKey(inx, km, dead_vudup)
ULONG   inx;
struct  KeyMap  *km;
ULONG   *dead_vudup;
{
    /* find keystroke which generates index */

    register WORD code = 0;
    UWORD **p;            /* points to four-byte lokeymap entry   */
    register UWORD *deadthing;
    WORD i;
    WORD qual = 0;
    LONG vudu;

    p = (UWORD **) km->km_LoKeyMap;

    do {
        /* check each deadkey in the table      */

        if (km->km_LoKeyMapTypes[code] & KCF_DEAD) {
            /* keymap entry is pointer to eight prefix:byte pairs   */

            deadthing = *p;
            for (i = 0; i < 8; ++i, ++deadthing) {
                /* check for index prefix and correct index     */
                if (*deadthing == ((DPF_DEAD << 8) | inx)) {
                    deadQual((WORD)i, &qual);
                    goto FOUND_IT;
                }
            }
        }
        ++p;
    } while (++code < KEYMAPSIZE);

FOUND_IT:

    /* pack as follows: [pred(-1)|qual(-1)|pred(-2)|qual(-2)]       */
    /* for now, 2nd previous deadkey ignored                                        */

    if (code < 0) {
        *dead_vudup = 0;
    } else {
        vudu =  code << 8;
        vudu |= (0xFF & qual);
        vudu <<= 16;

        *dead_vudup = vudu;
    }
}


WORD checkNormal(four_bytesp, val, type, qualp)
LONG    four_bytesp;
UBYTE   val;
UWORD   type;
UWORD   *qualp;
{
    register UBYTE  *p = (UBYTE *) four_bytesp; /* codes held in long word  */
    register WORD    position;

    /* start with last of four bytes, "more vanilla"        */
    p += 3;

    for (position = 3; position >= 0; --position, --p) {
        if (*p == val) {
            switch (type) {
                case KC_NOQUAL:
                    if (position != 3) goto NOT_THIS;
                    break;

                case KCF_SHIFT:
                    if (!(position & 2)) goto NOT_THIS;
                    if (position == 2) *qualp |= IEQUALIFIER_LSHIFT;
                    break;

                case KCF_ALT:
                    if (!(position & 2)) goto NOT_THIS;
                    if (position == 2) *qualp |= IEQUALIFIER_LALT;
                    break;

                case KCF_CONTROL:
                    if (!(position & 2)) goto NOT_THIS;
                    if (position == 2) *qualp |= IEQUALIFIER_CONTROL;
                    break;

                case KCF_ALT | KCF_CONTROL:
                    if (!(position & 1)) *qualp |= IEQUALIFIER_LALT;
                    if (!(position & 2)) *qualp |= IEQUALIFIER_CONTROL;
                    break;

                case KCF_SHIFT | KCF_CONTROL:
                    if (!(position & 1)) *qualp |= IEQUALIFIER_LSHIFT;
                    if (!(position & 2)) *qualp |= IEQUALIFIER_CONTROL;
                    break;

                case KCF_SHIFT | KCF_ALT:
                    if (!(position & 1)) *qualp |= IEQUALIFIER_LSHIFT;
                    if (!(position & 2)) *qualp |= IEQUALIFIER_LALT;
                    break;
                default:
                    break;
            }
            return (1);
        }
NOT_THIS:       ;
    }
    return (0);
}

WORD checkVanilla(p, val, qualp)
UBYTE   *p;                     /* note: byte pointer   */
UBYTE   val;
UWORD   *qualp;
{
    register WORD i;

    /* only one way to match a vanilla control key  */
    if (!(val & CONTROLBITS)) {
        /* is a control code    */

        if ((p[3] & ~CONTROLBITS) == val) {
            *qualp |= IEQUALIFIER_CONTROL;
            return (1);
        }
    } else {
        /* not a control        */
        for (i = 3; i >= 0; --i) {
            if (p[i] == val) {
                if (!(i & 1)) *qualp |= IEQUALIFIER_LSHIFT;
                if (!(i & 2)) *qualp |= IEQUALIFIER_LALT;
                return (1);
            }
        }
    }
    return (0);
}


WORD checkDead(keybase, val, qualp, indexp)
UBYTE   **keybase;                      /* note: byte pointer   */
UBYTE   val;
UWORD   *qualp;
ULONG   *indexp;
{
    WORD i;
    WORD j;

    register UWORD *p = (UWORD *) *keybase;
    /* need to remember keybase for offsets */

    UBYTE *deadp;
    WORD found_it = 0;

    /* walk through eight two-byte entries, one for each qual. combo.       */
    for (i = 0; i < 8; ++i, ++p) {
        switch (*p >> 8) {
            case DPF_DEAD:       /* dead keys do not themselves map to anything */
                break;
            case DPF_MOD:        /* dead key modifiable  */
                deadp = *keybase + (*p & 0xFF);
                /* look down the string indexed by dead-key     index */
                for (j = 0; j < 6; ++j) {
                    if (deadp[j] == val) {
                        found_it = 1;
                        *indexp = j;
                        break;
                    }
                }
                break;
            case 0:                      /* normal stroke for this key   */
                if ((*p & 0xFF) == val) {
                    found_it = 1;
            }
        }

        if (found_it) {
            deadQual((WORD)i, qualp);
            return (1);
        }
    }

    return (0);
}

/* figure out qualifier from position */
WORD deadQual(wordpos, qualp)
WORD    wordpos;                /* which word in dead-key string?       */
UWORD   *qualp;
{
    if (wordpos & 1) *qualp |= IEQUALIFIER_LSHIFT;
    if (wordpos & 2) *qualp |= IEQUALIFIER_LALT;
    if (wordpos & 4) *qualp |= IEQUALIFIER_CONTROL;
    return (0);
}


