/**************************************************************************
*
*	ikm.c	- Olsen's magical key inversion routines.
*
*		  You may use this piece of code in any application
*		  without requesting permission of the original author.
*
*		  It is strongly recommended to use cx.lib/InvertString
*		  instead of the following set of routines when running
*		  under v37 or higher.
*
*		  Basically, the following code is derived from the
*		  keymap inversion routines employed by KeyMacro 1.9,
*		  strange enough the german keymap would never be
*		  read correctly to generate the letter `f'. This and
*		  a lot of other problems have been fixed (keep your
*		  fingers crossed!) and single-depth-dead-key
*		  conversion was added on the fly (i.e. accented
*		  characters can be generated with keyboard layouts
*		  which do not contain accent keys).
*
*		  Jimm Mackraz' original ikm.c code, Bill Hawes'
*		  cmap.asm example keymap, and the `International
*		  Keyboard Input' article by Eric Cotton and Carolyn
*		  Scheppner published in Amiga-Mail provided enough
*		  help to create the current release of this package
*		  (which also is significantly shorter than Jimm's
*		  original ikm.c code).
*
*		  A potential problem still exists: what happens if
*		  a dead-key-modifiable key sports less than three
*		  qualifiers (shift, alternate, control)? The
*		  `International Keyboard Input' article states that
*		  the number of qualifiers determines the format and the
*		  length of the corresponding mk_Hi/LowKeyMap entry.
*		  Unfortunately, no word is lost on how a keymap entry
*		  of such kind would look like. For now my code
*		  determines the number of table entries to look up
*		  by counting the qualifiers listed in
*		  km_Hi/LowKeyMapTypes which will probably find the
*		  approriate key but will fail to generate a matching
*		  qualifier (do YOU know of a fix?).
*
*		  Anyway, keymap inversion is a weird topic which
*		  had better been covered in the early days of
*		  the Amiga.
*
*		  A note to code hackers:
*
*			Sometimes InvertKeyMap() will not return the
*			proper raw key value for a character but
*			rather the raw key value of the base character
*			(example: character = á, base character = a).
*			To avoid unpleasant sideeffects, just remove
*			the dead key conversion in lines 214-225
*			(delete the loop, only check the first character
*			of the array).
*/

	/* PackQualifiers(BYTE Bits):
	 *
	 *	Piece together a valid pack of qualifiers from
	 *	a bitmapped key position.
	 */

STATIC UWORD
PackQualifiers(BYTE Bits)
{
	UWORD Qualifier = 0;

	if(Bits & KCF_SHIFT)
		Qualifier |= IEQUALIFIER_LSHIFT;

	if(Bits & KCF_ALT)
		Qualifier |= IEQUALIFIER_LALT;

	if(Bits & KCF_CONTROL)
		Qualifier |= IEQUALIFIER_CONTROL;

	return(Qualifier);
}

	/* CountPairs(BYTE Qualifiers):
	 *
	 *	Count the number (not the one from Sesame Street) of
	 *	qualifiers given in a KeyMapTypes entry.
	 */

STATIC BYTE
CountPairs(BYTE Qualifiers)
{
	BYTE Bits = 1;

	if(Qualifiers & KCF_SHIFT)
		Bits++;

	if(Qualifiers & KCF_ALT)
		Bits++;

	if(Qualifiers & KCF_CONTROL)
		Bits++;

	return(Bits);
}

	/* FindIndexKey():
	 *
	 *	Find the key which if `preceding' the inverted base key
	 *	will generate the accented character (example: character
	 *	to be inverted = á, inverted base key = a, the preceding
	 *	key we are trying to find in this routine will put the
	 *	accent ´ on top of the vowel).
	 */

STATIC APTR
FindIndexKey(UBYTE Hi,UBYTE Offset,UBYTE *KeyTable,UBYTE *KeyTypes,APTR Index)
{
	UBYTE	*String;
	SHORT	 i,j;

	for(i = 0 ; i < Hi ; i++)
	{
			/* Examine only the dead key entries. */

		if(KeyTypes[i] & KCF_DEAD)
		{
			String = (UBYTE *)(((ULONG *)KeyTable)[i]);

				/* Look for a dead-key index. */

			for(j = 0 ; j < CountPairs(KeyTypes[i]) ; j++)
			{
					/* This comes close to real magic:
					 *
					 * if the corresponding InputEvent
					 * is of type RAWKEY, the EventAddress
					 * pointer contains the packed
					 * codes and qualifiers of the
					 * previous key(s). The v37 header
					 * files reflect this, but alas,
					 * some guys will want to recompile
					 * this code using v34 (and below)
					 * header files.
					 */

				if(String[2 * j] == DPF_DEAD && (String[2 * j + 1] & 0x1F) == ((ULONG)Index & BYTEMASK))
					return((APTR)(((ULONG)(Offset + i) << 24) | (ULONG)PackQualifiers(j) << 16));
			}
		}
	}

	return(NULL);
}

	/* ScanKeyMap():
	 *
	 *	Scan a given keymap for an ANSI key and try to generate
	 *	an InputEvent which will -- if processed -- create the
	 *	given character.
	 */

STATIC BYTE
ScanKeyMap(UBYTE Hi,UBYTE Offset,UBYTE *KeyTable,UBYTE *KeyTypes,UBYTE AnsiKey,struct InputEvent *Event,BYTE Depth)
{
		/* A bunch of qualifiers associated with KeyMapType bits. */

	STATIC struct {
		UBYTE	QualBits;
		UWORD	Qualifiers[4];
	} QualType[8] = {
		KC_NOQUAL,		0,					0,			0,			0,
		KCF_SHIFT,		0,					0,			IEQUALIFIER_LSHIFT,	0,
		KCF_ALT,		0,					0,			IEQUALIFIER_LALT,	0,
		KCF_CONTROL,		0,					0,			IEQUALIFIER_CONTROL,	0,
		KCF_ALT|KCF_SHIFT,	IEQUALIFIER_LSHIFT|IEQUALIFIER_LALT,	IEQUALIFIER_LALT,	IEQUALIFIER_LSHIFT,	0,
		KCF_CONTROL|KCF_ALT,	IEQUALIFIER_CONTROL|IEQUALIFIER_LALT,	IEQUALIFIER_CONTROL,	IEQUALIFIER_LALT,	0,
		KCF_CONTROL|KCF_SHIFT,	IEQUALIFIER_CONTROL|IEQUALIFIER_LSHIFT,	IEQUALIFIER_CONTROL,	IEQUALIFIER_LSHIFT,	0,
		KC_VANILLA,		IEQUALIFIER_LSHIFT|IEQUALIFIER_LALT,	IEQUALIFIER_LALT,	IEQUALIFIER_LSHIFT,	0
	};

	BYTE	*String;
	SHORT	 i,j,k;

		/* Scan the whole area. */

	for(i = 0 ; i < Hi ; i++)
	{
			/* This looks like a dead key. */

		if(KeyTypes[i] & KCF_DEAD)
		{
			String = (BYTE *)(((ULONG *)KeyTable)[i]);

				/* Check all table entries. */

			for(j = 0 ; j < CountPairs(KeyTypes[i]) ; j++)
			{
				switch(String[2 * j])
				{
						/* A simple dead key. */

					case 0:		if((UBYTE)String[2 * j + 1] == AnsiKey)
							{
								Event -> ie_Qualifier	= PackQualifiers(j);
								Event -> ie_Code	= Offset + i;

								return(TRUE);
							}

							break;

						/* A dead-key-modifiable key. */

					case DPF_MOD:	for(k = 0 ; k < Depth ; k++)
							{
								if((UBYTE)String[String[2 * j + 1] + k] == AnsiKey)
								{
									Event -> ie_Qualifier	= PackQualifiers(j);
									Event -> ie_Code	= Offset + i;

									Event -> ie_EventAddress = (APTR)k;

									return(TRUE);
								}
							}

							break;

					default:	break;
				}
			}
		}

			/* This looks like a string. */

		if(KeyTypes[i] & KCF_STRING)
		{
			String = (BYTE *)(((ULONG *)KeyTable)[i]);

				/* Only single character strings are
				 * accepted, check the `no qualifier'
				 * entry first.
				 */

			if(String[0] == 1)
			{
				if((UBYTE)String[String[1]] == AnsiKey)
				{
						/* Try to find the approriate
						 * qualifier.
						 */

					for(k = 0 ; k < 8 ; k++)
					{
						if(QualType[k] . QualBits == (KeyTypes[i] & KC_VANILLA))
						{
							Event -> ie_Qualifier	= QualType[k] . Qualifiers[j];
							Event -> ie_Code	= Offset + i;

							return(TRUE);
						}
					}
				}
			}

				/* Are there any strings left which
				 * require a qualifier key to be
				 * pressed?
				 */

			for(j = 0 ; j < 3 ; j++)
			{
				if(KeyTypes[i] & (1 << j))
				{
					if(String[2 + (2 * j)] == 1)
					{
						if((UBYTE)String[String[3 + (2 * j)]] == AnsiKey)
						{
							for(k = 0 ; k < 8 ; k++)
							{
								if(QualType[k] . QualBits == (KeyTypes[i] & KC_VANILLA))
								{
									Event -> ie_Qualifier	= QualType[k] . Qualifiers[j];
									Event -> ie_Code	= Offset + i;

									return(TRUE);
								}
							}
						}
					}
				}
			}
		}
		else
		{
				/* At last, something sensible, check
				 * the remaining vanilla type keys.
				 */

			for(j = 3 ; j >= 0 ; j--)
			{
				if(AnsiKey == KeyTable[4 * i + j])
				{
					for(k = 0 ; k < 8 ; k++)
					{
						if(QualType[k] . QualBits == KeyTypes[i])
						{
							Event -> ie_Code = Offset + i;

							if(QualType[k] . QualBits == KC_VANILLA)
							{
								if(AnsiKey & 96)
									Event -> ie_Qualifier = QualType[k] . Qualifiers[j];
								else
									Event -> ie_Qualifier = IEQUALIFIER_CONTROL;
							}
							else
								Event -> ie_Qualifier = QualType[k] . Qualifiers[j];

							return(TRUE);
						}
					}
				}
			}
		}
	}

	return(FALSE);
}

	/* InvertKeyMap():
	 *
	 *	Invert a given character.
	 */

BYTE
KeyInvert(UBYTE AnsiKey,struct InputEvent *Event,struct KeyMap *KeyMap,BYTE Depth)
{
	BYTE Result;

	Event -> ie_Class		= IECLASS_RAWKEY;
	Event -> ie_EventAddress	= NULL;

		/* Check the high keymap types first to include control keys
		 * such as backspace, return, delete, etc. instead of the
		 * control+? combination given in the low keymap entries.
		 */

	if(!(Result = ScanKeyMap(0x28,0x40,(UBYTE *)KeyMap -> km_HiKeyMap,(UBYTE *)KeyMap -> km_HiKeyMapTypes,AnsiKey & BYTEMASK,Event,Depth)))
		Result = ScanKeyMap(0x40,0x00,(UBYTE *)KeyMap -> km_LoKeyMap,(UBYTE *)KeyMap -> km_LoKeyMapTypes,AnsiKey & BYTEMASK,Event,Depth);

		/* See if we are to generate a preceding keystroke. */

	if(Result && Event -> ie_EventAddress)
	{
		APTR Address;

			/* Find the preceding keystroke. */

		if(!(Address = FindIndexKey(0x28,0x40,(UBYTE *)KeyMap -> km_HiKeyMap,(UBYTE *)KeyMap -> km_HiKeyMapTypes,Event -> ie_EventAddress)))
			Address = FindIndexKey(0x40,0x00,(UBYTE *)KeyMap -> km_LoKeyMap,(UBYTE *)KeyMap -> km_LoKeyMapTypes,Event -> ie_EventAddress);

			/* Heavy magic, don't touch! */

		if(!(Event -> ie_EventAddress = Address))
			return(FALSE);
	}

	return(Result);
}
