/*
 *  was "keymap.c" from DevCon S88 (Washington DC)
 *
 *  this version "clone.c" Jan 1990 has been fixed to:
 *    - correctly allocate space for maps with double dead keys.
 *    - be more conservative about how string and dead keys are arranged
 *	and how big they are, ensuring that they get copied without
 *	overflowing to outside the signed byte offset.
 *    - demonstrate (in F8 ...Down) overlapping strings.
 */
#include	"exec/types.h"
#include	"exec/memory.h"
#include	"devices/keymap.h"
#include	"devices/console.h"
#include	"libraries/dosextens.h"

#ifdef	MANX
#include	"functions.h"
#endif

ULONG *AllocMem();
struct Task *FindTask();

struct KeyMapResource *OpenResource();
struct KeyMapNode *FindName();

struct KeyMap *
FindKeyMap(name)
char *name;
{
    struct KeyMapResource *kmr;
    struct KeyMapNode *kmn;

    kmr = (struct KeyMapResource *) OpenResource("keymap.resource");
    if (kmr == NULL) return(NULL);
    kmn = (struct KeyMapNode *) FindName(&kmr->kr_List, name);
    return(&kmn->kn_KeyMap);
}

int QualifierOnesCount[] = {
    /* number of bits set in a nibble */
    0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4
};
int QualifierExtraBytes[] = {
    /* number of extra bytes associated with non-special qualifier patterns */
    0, 0, 0, 0, 0, 0, 0, 0,	/* the last is the special VANILLA case */
    0, 0, 0, 8, 0, 8, 8, 16
};

int DeadFactor;		/* to determine size of lookup tables at end */
int DoubleDeadFactor;	/* (only used for double dead keys) */


MaxDeadIndex(type, entry)
UBYTE type;
UBYTE *entry;	/* if it needs to be looked at, it's a pointer */
{
    int count;
    int i;

    if ((type & KCF_DEAD) && (!(type & KCF_NOP))) {
	/* dead or deadable */
	/* there are 2 bytes for each qualifier combination at *entry */
	count = 1<<(QualifierOnesCount[type & 0xf]+1);
	for (i = 0; i < count; i+=2) {
	    if (entry[i] & DPF_DEAD) {
		/* see if this index is a maximum */
		if (DeadFactor < (entry[i+1] & DP_2DINDEXMASK))
		    DeadFactor = entry[i+1] & DP_2DINDEXMASK;
		if ((entry[i+1] >> DP_2DFACSHIFT) &&
		    /* this index participates in double dead keys */
			(DoubleDeadFactor < (entry[i+1] & DP_2DINDEXMASK)))
		    /* this participant in double dead keys is a maximum */
		    DoubleDeadFactor = entry[i+1] & DP_2DINDEXMASK;
	    }
	}
    }
}


int
SizeType(type, entry)
UBYTE type;
UBYTE *entry;	/* if it needs to be looked at, it's a pointer */
{
    int count, size, min, max, offset, length;
    int i, j;

    if (type & KCF_NOP) {
	/* NOP */
	return(0);
    }

    if (type & (KCF_DEAD | KCF_STRING)) {
	/* there are 2 bytes for each qualifier combination at *entry */
	count = 1<<(QualifierOnesCount[type & 0xf]+1);
	/* determine min & max of copy area */
	min = 0;
	max = count;
	for (i = 0; i < count; i+=2) {
	    if ((type & KCF_STRING) || (entry[i] & DPF_MOD)) {
		offset = (BYTE) entry[i+1];
		if (type & KCF_STRING)
		    length = entry[i];
		else
		    length = DeadFactor;
		if (offset < min)
		    min = offset;
		if ((offset + length) > max)
		    max = offset + length;
	    }
	}
	return(max-min);
    }


    /* number of extra bytes needed to index all the qualifiers set */
    return(QualifierExtraBytes[type & 0xf]);
}

/* SizeKeyMap does a KeyMap, not a KeyMapNode */
int
SizeKeyMap(km, newLo, newHi)
struct KeyMap *km;
UBYTE *newLo, *newHi;
{
    UBYTE type;
    int size;
    int i;

    /*
     * fixed byte size associated with 
     *   Keymap structure itself
     *     (8 pointers)			 32
     *   Lo entries for 64 codes
     *     km_LoKeyMapTypes		 64
     *     km_LoKeyMap			256
     *     km_LoCapsable		  8
     *     km_LoRepeatable		  8
     *   Hi entries for 56 codes
     *     km_HiKeyMapTypes		 56
     *     km_HiKeyMap			224
     *     km_HiCapsable		  7
     *     km_HiRepeatable		  7
     *					---
     * total				662
     */
    size = 662;

    /* first need to find the number of dead keys */
    DeadFactor = DoubleDeadFactor = 0;
    for (i = 0; i < 64; i++) {
	MaxDeadIndex(km->km_LoKeyMapTypes[i], km->km_LoKeyMap[i]);
    }
    for (i = 0; i < 56; i++) {
	MaxDeadIndex(km->km_HiKeyMapTypes[i], km->km_HiKeyMap[i]);
    }
    if (DeadFactor) DeadFactor++;
    if (DoubleDeadFactor) DeadFactor *= (DoubleDeadFactor+1);

    /* now accumulate all applicable keymap trails */
    for (i = 0; i < 64; i++) {
	if (!(newLo[i/8] & (1<<(i&7))))
	    size += SizeType(km->km_LoKeyMapTypes[i], km->km_LoKeyMap[i]);
    }
    for (i = 0; i < 56; i++) {
	if (!(newHi[i/8] & (1<<(i&7))))
	    size += SizeType(km->km_HiKeyMapTypes[i], km->km_HiKeyMap[i]);
    }

    return(size);
}

UBYTE *NextNewMapEntry;

CopyEntry(type, oentry, nentry)
UBYTE type;
UBYTE *oentry;
UBYTE **nentry;
{
    short count, min, max, offset, length;
    int i, j, k;

    if (type & KCF_NOP) {
	/* NOP */
	return;
    }

    if (type & (KCF_DEAD | KCF_STRING)) {
	/* there are 2 bytes for each qualifier combination at *entry */
	count = 1<<(QualifierOnesCount[type & 0xf]+1);
	/* determine min & max of copy area */
	min = 0;
	max = count;
	for (i = 0; i < count; i+=2) {
	    if ((type & KCF_STRING) || (oentry[i] & DPF_MOD)) {
		offset = (BYTE) oentry[i+1];
		if (type & KCF_STRING)
		    length = oentry[i];
		else
		    length = DeadFactor;
		if (offset < min)
		    min = offset;
		if ((offset + length) > max)
		    max = offset + length;
	    }
	}
	NextNewMapEntry -= min;
	*nentry = NextNewMapEntry;
	for (i = min; i < max; i++)
	    NextNewMapEntry[i] = oentry[i];
	NextNewMapEntry += max;
	return;
    }


    /* check if all the qualified bytes fit in the entry longword */
    count = QualifierExtraBytes[type & 0xf];
    if (count > 0) {
	/* clone a new array for all the qualified bytes */
	*nentry = NextNewMapEntry;
	for (i = 0; i < count; i++) {
	    *NextNewMapEntry++ = oentry[i];
	}
    }
}


struct KeyMap *
NewKeyMap(okm, newLo, newHi)
struct KeyMap *okm;
UBYTE *newLo, *newHi;
{
    ULONG *memChunk;
    struct KeyMap *nkm;
    int size;
    int i;

    size = SizeKeyMap(okm, newLo, newHi);

    /* NewKeyMap has a longword header describing the size */
    memChunk = (ULONG *) AllocMem(size+4, MEMF_PUBLIC);
    if (memChunk == NULL)
	return(NULL);
    *memChunk++ = size;
    nkm = (struct KeyMap *) memChunk;

    /* parcel out space for fixed size tables in allocated memory */
    nkm->km_LoKeyMapTypes = ((UBYTE *) nkm) + sizeof(struct KeyMap);
    nkm->km_LoKeyMap = (ULONG *) &nkm->km_LoKeyMapTypes[64];
    nkm->km_LoCapsable = (UBYTE *) &nkm->km_LoKeyMap[64];
    nkm->km_LoRepeatable = &nkm->km_LoCapsable[8];
    nkm->km_HiKeyMapTypes = &nkm->km_LoRepeatable[8];
    nkm->km_HiKeyMap = (ULONG *) &nkm->km_HiKeyMapTypes[56];
    nkm->km_HiCapsable = (UBYTE *) &nkm->km_HiKeyMap[56];
    nkm->km_HiRepeatable = &nkm->km_HiCapsable[7];
    NextNewMapEntry = &nkm->km_HiRepeatable[7];

    /* copy the fixed size keymap data to the new keymap copy */
    for (i = 0; i < 64; i++) {
	nkm->km_LoKeyMapTypes[i] = okm->km_LoKeyMapTypes[i];
	nkm->km_LoKeyMap[i] = okm->km_LoKeyMap[i];
    }
    for (i = 0; i < 8; i++) {
	nkm->km_LoCapsable[i] = okm->km_LoCapsable[i];
	nkm->km_LoRepeatable[i] = okm->km_LoRepeatable[i];
    }
    for (i = 0; i < 56; i++) {
	nkm->km_HiKeyMapTypes[i] = okm->km_HiKeyMapTypes[i];
	nkm->km_HiKeyMap[i] = okm->km_HiKeyMap[i];
    }
    for (i = 0; i < 7; i++) {
	nkm->km_HiCapsable[i] = okm->km_HiCapsable[i];
	nkm->km_HiRepeatable[i] = okm->km_HiRepeatable[i];
    }

    /* now follow all applicable keymap trails */
    for (i = 0; i < 64; i++) {
	if (!(newLo[i/8] & (1<<(i&7))))
	    CopyEntry(okm->km_LoKeyMapTypes[i], okm->km_LoKeyMap[i],
		    &nkm->km_LoKeyMap[i]);
    }
    for (i = 0; i < 56; i++) {
	if (!(newHi[i/8] & (1<<(i&7))))
	    CopyEntry(okm->km_HiKeyMapTypes[i], okm->km_HiKeyMap[i],
		    &nkm->km_HiKeyMap[i]);
    }
    return(nkm);
}

DisposeKeyMap(km)
ULONG *km;
{
    km--;
    FreeMem(km, (*km)+4);
}

struct MsgPort IORP = {
    {0, 0, NT_MSGPORT, 0, 0}, 0, SIGF_SINGLE, 0, { 0, 0, 0, 0, 0 }
};

struct IOStdReq IOR = {
    { {0, 0, NT_MESSAGE, 0, 0},	/* Message Succ, Pred, Type, Pri, Name */
    &IORP, 0 },			/* Message ReplyPort, Length */
    0, 0, CMD_READ, 0, 0,	/* IO Device, Unit, Command, Flags, Error */
    0, 0, 0, 0			/* IO Actual, Length, Data, Offset */
};

/*
 *  indicate replacement of function keys (0x50-0x59) will occur
 */
UBYTE NoNew[] = {
    0, 0, 0, 0, 0, 0, 0, 0
};
UBYTE NewHi[] = {
    0, 0, 0xff, 0x03, 0, 0, 0, 0
};
UBYTE NewFncTypes[] = {
    KC_NOQUAL, KCF_SHIFT, KCF_SHIFT+KCF_ALT, KC_VANILLA,
	KCF_SHIFT+KCF_ALT+KCF_CONTROL+KCF_DOWNUP,
    KCF_STRING, KCF_SHIFT+KCF_STRING,
	KCF_SHIFT+KCF_ALT+KCF_CONTROL+KCF_DOWNUP+KCF_STRING,
    KCF_SHIFT+KCF_ALT+KCF_DEAD, KCF_STRING
};
UBYTE NewFnc1_4[] = {
    0, 0, 0, '1',
    0, 0, 0xb2, '2',
    'F', 'f', 0xb3, '3',
    'm', '!', 'i', 'h' 
};
UWORD NewFncOff5_10[] = {
    /* offsets into the NewFncData area */
    0, 16, 20, 76, 172, 192
};
BYTE NewFncData[] = {
    /* 0 :f5 */
	'a','b','c','d','e','f','g','h','A','B','C','D','E','F','G','H',
    /* 16:f6 */
	2,2,'f','6',
    /* 20:f7 */
	2,4,2,6,'f','7','F','7',
    /* 76:f8 */
	/* more than 128 bytes of data, so some data must be a negative */
	/* offset from the length/offset pairs */
	'C','t','r','l','D','o','w','n',
	'C','t','r','l','S','h','i','f','t','D','o','w','n',
	'C','t','r','l','A','l','t','D','o','w','n',
	'C','t','r','l','S','h','i','f','t','A','l','t','D','o','w','n',
	/* length/offset pairs for strings for f8 */
	4,-4, 9,-36, 7,-7, 12,-12, 8,-48, 13,-40, 11,-27, 16,-16,
	2,32, 7,34, 5,41, 10,46, 6,56, 11,62, 9,73, 14,82,
	/* more data for strings for f8 */
	'U','p',
	'S','h','i','f','t','U','p',
	'A','l','t','U','p',
	'S','h','i','f','t','A','l','t','U','p',
	'C','t','r','l','U','p',
	'C','t','r','l','S','h','i','f','t','U','p',
	'C','t','r','l','A','l','t','U','p',
	'C','t','r','l','S','h','i','f','t','A','l','t','U','p',
    /*172:f9 */
	DPF_MOD,8, DPF_MOD,14, DPF_DEAD, 2, 0, '@',
	'a',0xe1,0xe0,0xe2,0xe3,0xe4,
	'A',0xc1,0xc0,0xc2,0xc3,0xc4,
    /*192:f10*/
	4, 2, 0x18, 0x0c, 0x08, 0x0d
};
UBYTE NewFncCapsable[] = {
    0x04, 0x00
};
UBYTE NewFncRepeatable[] = {
    0x00, 0x00
};

main(argc, argv)
int argc;
char *argv[];
{
    struct KeyMap *ukm;		/* usa keymap */
    struct KeyMap ckm;		/* console keymap */
    struct KeyMap *nkm;		/* new keymap */
    int uSize, cSize;
    struct MsgPort *con;
    struct StandardPacket *packet;
    struct InfoData *id;
    BYTE *f;
    int i;

    ukm = FindKeyMap("usa");
    if (ukm == NULL) {
	printf("usa map not found\n");
	exit(20);
    }
    uSize = SizeKeyMap(ukm, NoNew, NoNew);
    printf("usa map size %ld (0x%lx)\n", uSize, uSize);

    NewList(&IORP.mp_MsgList);
    IORP.mp_SigTask = (struct Task *) FindTask((char *) NULL);
	con = (struct MsgPort *)
		((struct Process *) IORP.mp_SigTask) -> pr_ConsoleTask;
	if (con != NULL) {
	    packet = (struct StandardPacket *)
		    AllocMem(sizeof(*packet), MEMF_CLEAR);
	    if (packet != NULL) {
		id = (struct InfoData *) AllocMem(sizeof(*id), MEMF_CLEAR);
		if (id != NULL) {
		    /* this is the console handlers packet port */
		    packet->sp_Msg.mn_Node.ln_Name = (char *) &(packet->sp_Pkt);
		    packet->sp_Pkt.dp_Link = &(packet->sp_Msg);
		    packet->sp_Pkt.dp_Port = &IORP;
		    packet->sp_Pkt.dp_Type = ACTION_DISK_INFO;
		    packet->sp_Pkt.dp_Arg1 = ((ULONG) id) >> 2;
		    PutMsg(con, packet);
		    WaitPort(&IORP);
		    if (id->id_InUse) {
			/* clone iorequest and get keymap */
			IOR.io_Unit =
				((struct IOStdReq *) id->id_InUse)->io_Unit;
			IOR.io_Device =
				((struct IOStdReq *) id->id_InUse)->io_Device;
			IOR.io_Command = CD_ASKKEYMAP;
			IOR.io_Data = (APTR) &ckm;
			IOR.io_Length = sizeof(struct KeyMap);
			DoIO(&IOR);

			cSize = SizeKeyMap(&ckm, NoNew, NoNew);
			printf("current map size %ld (0x%lx)\n", cSize, cSize);

			/* default: go back to the usa keymap */
			nkm = ukm;
			/*
			 *  if cSize == uSize, assume this is a usa keymap
			 *  else assume this has already been modified
			 *  (crude, but this is demo code)
			 */
			if (cSize == uSize) {
			    f = (BYTE *)
				    AllocMem(sizeof(NewFncData), MEMF_PUBLIC);
			    if (f) {
				/* make a new keymap */
				nkm = NewKeyMap(ukm, NoNew, NewHi);

				/* replace the function key descriptions */
				for (i = 0; i < 10; i++)
				    nkm->km_HiKeyMapTypes[i+0x10] =
					    NewFncTypes[i];
				for (i = 0; i < 4; i++)
				    nkm->km_HiKeyMap[i+0x10] =
					((ULONG *) NewFnc1_4)[i];
				for (i = 0; i < 6; i++)
				    nkm->km_HiKeyMap[i+0x14] =
					    (ULONG) (f + NewFncOff5_10[i]);
				for (i = 0; i < 2; i++) {
				    nkm->km_HiCapsable[i+2] = ((~NewHi[i+2]) &
					    nkm->km_HiCapsable[i+2]) |
					    NewFncCapsable[i];
				    nkm->km_HiRepeatable[i+2] = ((~NewHi[i+2]) &
					    nkm->km_HiRepeatable[i+2]) |
					    NewFncRepeatable[i];
				}
				for (i = 0; i < sizeof(NewFncData); i++)
				    f[i] = NewFncData[i];
			    }
			}
			if (nkm == ukm)
			    printf("installing usa keymap 0x%lx\n", nkm);
			else
			    printf("installing custom KeyMap 0x%lx\n", nkm);
			/* set the keymap */
			IOR.io_Command = CD_SETKEYMAP;
			IOR.io_Data = (APTR) nkm;
			IOR.io_Length = sizeof(struct KeyMap);
			DoIO(&IOR);

			if (cSize != uSize) {
			    /* release the old keymap: oh, this IS demo code! */
			    DisposeKeyMap(ckm.km_LoKeyMapTypes -
				    sizeof(struct KeyMap));
			}
		    }
		    FreeMem(id, sizeof(*id));
		}
		FreeMem(packet, sizeof(*packet));
	    }
	}
}
