/****************************************************************************
*
*	KeyMacro.c ------------	KeyMacro main process.
*
*	Author ----------------	Olaf Barthel, MXM
*				Brabeckstrasse 35
*				D-3000 Hannover 71
*
*	KeyMacro  ©  Copyright  1990  by  MXM;  Executable  program,
*	documentation  and  source  code are shareware.  If you like
*	this  program  a  small donation will entitle you to receive
*	updates and new programs from MXM.
*
****************************************************************************/

#include <libraries/arpbase.h>

	/* Function prototypes. */

VOID *			SendMacroMsg(struct MacroMessage *);
UBYTE *			GetToken(UBYTE *,LONG *);
struct MacroKey *	AddMacroKey(struct MacroKey *);
LONG			Interprete(UBYTE *,LONG);
LONG			UpdateList(char *);

VOID			main(LONG,char **);

	/* The Arp CLI-Interface data. */

char *CLI_Template	= "STARTUP/K,QUIT/S,INFO/S";
char *CLI_Help		= "\nUsage: \33[1mKeyMacro\33[0m [STARTUP <File>] [QUIT] [INFO]\n";

#define ARG_STARTUP	1
#define ARG_QUIT	2
#define ARG_INFO	3
#define ARG_UPDATE	4

	/* Easy macro. */

#define From_CLI	(ThatsMe -> pr_CLI)

	/* Global and shared data structures. */

struct MXMBase		*MXMBase;
extern struct ArpBase	*ArpBase;
struct MSeg		*MSeg;
struct MacroKey		*KeyList;
extern struct ExecBase	*SysBase;

	/* We use this list to identify the non-ascii keys. */

struct KeyAlias KeyTab[22] =
{
	{"TAB",		0x42},
	{"ESC",		0x45},
	{"SPACE",	0x40},
	{"RETURN",	0x44},
	{"ENTER",	0x43},
	{"DEL",		0x46},
	{"BACKSPACE",	0x41},
	{"HELP",	0x5F},
	{"LEFT",	0x4F},
	{"RIGHT",	0x4E},
	{"UP",		0x4C},
	{"DOWN",	0x4D},

	{"F1",		0x50},
	{"F2",		0x51},
	{"F3",		0x52},
	{"F4",		0x53},
	{"F5",		0x54},
	{"F6",		0x55},
	{"F7",		0x56},
	{"F8",		0x57},
	{"F9",		0x58},
	{"F10",		0x59}
};

	/* These are the qualifiers. */

struct KeyAlias QualifierTab[9] =
{
	{"NONE",	0},
	{"CTRL",	IEQUALIFIER_CONTROL},
	{"NUMPAD",	IEQUALIFIER_NUMERICPAD},
	{"LSHIFT",	IEQUALIFIER_LSHIFT},
	{"RSHIFT",	IEQUALIFIER_RSHIFT},
	{"LALT",	IEQUALIFIER_LALT},
	{"RALT",	IEQUALIFIER_RALT},
	{"LAMIGA",	IEQUALIFIER_LCOMMAND},
	{"RAMIGA",	IEQUALIFIER_RCOMMAND}
};

	/* SendMacroMsg(scm_Msg,scm_Port):
	 *
	 *	Post a cloned macro message to a MsgPort.
	 */

VOID *
SendMacroMsg(struct MacroMessage *scm_Msg)
{
	struct MacroMessage *scm_TempMsg = (struct MacroMessage *)AllocRem(sizeof(struct MacroMessage),MEMF_PUBLIC | MEMF_CLEAR);

	if(scm_TempMsg)
	{
		CopyMem(scm_Msg,scm_TempMsg,sizeof(struct MacroMessage));

		scm_TempMsg -> mm_Message . mn_Node . ln_Name	= (char *)scm_TempMsg;
		scm_TempMsg -> mm_Message . mn_ReplyPort	= NULL;
		scm_TempMsg -> mm_Message . mn_Length		= sizeof(struct MacroMessage);

		PutMsg(&MSeg -> Port,(struct Message *)scm_TempMsg);
	}

	return((VOID *)scm_TempMsg);
}

	/* GetToken(s,start):
	 *
	 *	Parse a string and split it into single tokens.
	 */

UBYTE *
GetToken(UBYTE *s,LONG *start)
{
	static UBYTE buffer[256];
	LONG i,end = 0,quote = FALSE,maxlen = strlen(s);
	char t;

	if(maxlen > 255)
		maxlen = 255;

	if(*start > strlen(s) - 1 || !strlen(s) || !s)
		return(NULL);

	for(i = *start ; i <= maxlen ; i++)
	{
		if(!end && (s[i] == ' ' || s[i] == '\t'))
		{
			while((s[i] == ' ' || s[i] == '\t') && i < maxlen)
			{
				i++;
				(*start)++;
			}
		}

		t = s[i];

		if(!end && t == '+')
		{
			(*start)++;
			continue;
		}

		if(!end && t == '=')
		{
			strcpy(buffer,"=");
			(*start)++;

			return(buffer);
		}

		if(s[i] == '\\' && s[i + 1] == '\"')
		{
			i += 2;

			end = i - *start + 1;

			t = s[i];
		}

		if(t == '\"' && !quote)
		{
			quote = TRUE;

			(*start)++;

			end++;

			continue;
		}

		if((t == '+' || t == '=' || t == ' ' || t == '\t' || t == ';') && quote)
		{
			end++;
			continue;
		}

		if((t == '+' || t == '\n' || t == '=' || t == ' ' || t == 0) || (t == '\"' && quote) || (t == ';' && !quote))
		{
			if(t == ';' && !end)
				return(NULL);

			if(t == '\"')
			{
				strncpy(buffer,s + *start,end - 1);
				buffer[end - 1] = 0;
			}
			else
			{
				strncpy(buffer,s + *start,end);
				buffer[end] = 0;
			}

			(*start) += end;

			return(buffer);
		}

		end++;
	}

	return(NULL);
}

	/* AddMacroKey(MacroKey):
	 *
	 *	Add a macro key to the big list.
	 */

struct MacroKey *
AddMacroKey(struct MacroKey *MacroKey)
{
	struct MacroKey *TheKey = NULL;
	LONG i;

	for(i = 0 ; i < MSeg -> NumMacros ; i++)
	{
		if(KeyList[i] . mk_Type == MK_UNUSED)
		{
			TheKey = &KeyList[i];
			break;
		}
	}

	if(!TheKey)
		return(NULL);

	CopyMem(MacroKey,TheKey,sizeof(struct MacroKey));

	return(TheKey);
}

	/* Interprete(String,Line):
	 *
	 *	Interprete a command line from the config file.
	 */

LONG
Interprete(UBYTE *String,LONG Line)
{
	ULONG Qualifier = 0;
	ULONG Code = -1;
	struct InputEvent FakeEvent;
	struct MacroKey NewKey;

	LONG Start = 0,Key = FALSE,i,KeyCount = 0;
	volatile LONG QuitLoop;
	UBYTE *Token,*CommandString,*WindowName = NULL,Recognized = FALSE;

	UBYTE MessBuff[256],KeyBuff1[40],KeyBuff2[40];

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

	if(Token = GetToken(String,&Start))
	{
		if(!UStrCmp("KEY",Token))
			Key = TRUE;

		if(UStrCmp("COMMAND",Token) && !Key)
		{
			SPrintf(MessBuff,"Line %ld: Unknown keyword:\n\n'%s'",Line,String);

			PopRequest(NULL,"KeyMacro Problem:",MessBuff,NULL,"Continue?",FALSE,NULL);
			return(FALSE);
		}
	}
	else
		return(TRUE);

	FOREVER
	{
		if(Token = GetToken(String,&Start))
		{
			QuitLoop = TRUE;

			for(i = 0 ; i < 9 ; i++)
			{
				if(!UStrCmp(QualifierTab[i] . ka_Name,Token))
				{
					Recognized = TRUE;
					QuitLoop = FALSE;

					Qualifier |= QualifierTab[i] . ka_Key;
				}
			}
		}
		else
			break;

		if(QuitLoop)
			break;
	}

	if(!Recognized)
	{
		SPrintf(MessBuff,"Line %ld: Didn't recognize qualifier:\n\n'%s'",Line,String);

		PopRequest(NULL,"KeyMacro Problem:",MessBuff,NULL,"Continue?",FALSE,NULL);
		return(FALSE);
	}

	if(Token)
		goto JumpIn;

	if(Token = GetToken(String,&Start))
	{
JumpIn:		for(i = 0 ; i < 22 ; i++)
		{
			if(!UStrCmp(KeyTab[i] . ka_Name,Token))
			{
				Code = KeyTab[i] . ka_Key;
				goto Next;
			}
		}

		if(InvertKey(Token[0],&FakeEvent,IK_USEIKM,NULL))
			Code = FakeEvent . ie_Code;
	}

	if(Code == -1)
	{
		SPrintf(MessBuff,"Line %ld: Didn't recognize key:\n\n'%s'",Line,String);

		PopRequest(NULL,"KeyMacro Problem:",MessBuff,NULL,"Continue?",FALSE,NULL);
		return(FALSE);
	}

Next:	FOREVER
	{
		if(Token = GetToken(String,&Start))
		{
			if(!UStrCmp("=",Token))
				break;
		}
		else
		{
			SPrintf(MessBuff,"Line %ld: Statement '=' missing:\n\n'%s'",Line,String);

			PopRequest(NULL,"KeyMacro Problem:",MessBuff,NULL,"Continue?",FALSE,NULL);
			return(FALSE);
		}
	}

	if(Token = GetToken(String,&Start))
		strcpy(KeyBuff1,Token);
	else
	{
		SPrintf(MessBuff,"Line %ld: Didn't find macro:\n\n'%s'",Line,String);

		PopRequest(NULL,"KeyMacro Problem:",MessBuff,NULL,"Continue?",FALSE,NULL);
		return(FALSE);
	}

	if(Key)
		goto AddIt;

	if(!(Token = GetToken(String,&Start)))
		goto AddIt;

	if(UStrCmp("WINDOW",Token))
	{
		SPrintf(MessBuff,"Line %ld: Didn't recognize 'WINDOW' statement:\n\n'%s'",Line,String);

		PopRequest(NULL,"KeyMacro Problem:",MessBuff,NULL,"Continue?",FALSE,NULL);
		return(FALSE);
	}

	if(!(Token = GetToken(String,&Start)))
	{
		SPrintf(MessBuff,"Line %ld: Didn't find window title:\n\n'%s'",Line,String);

		PopRequest(NULL,"KeyMacro Problem:",MessBuff,NULL,"Continue?",FALSE,NULL);
		return(FALSE);
	}

	if(!(WindowName = (UBYTE *)AllocRem(strlen(Token) + 1,MEMF_PUBLIC)))
	{
		PopRequest(NULL,"KeyMacro Problem:","Can't allocate memory chunk!",NULL,"Continue?",FALSE,NULL);
		return(FALSE);
	}

	strcpy(WindowName,Token);

AddIt:	for(i = 0 ; i < strlen(KeyBuff1) ; i++)
	{
		UBYTE c;

		if(KeyBuff1[i] != '\\')
		{
			KeyBuff2[KeyCount++] = KeyBuff1[i];
			continue;
		}

		if(i == strlen(KeyBuff1) - 1)
			break;

		i++;

		c = 0;

		switch(ToUpper(KeyBuff1[i]))
		{
			case 'U':	c = KC_CURSORUP;
					break;

			case 'D':	c = KC_CURSORDOWN;
					break;

			case 'L':	c = KC_CURSORLEFT;
					break;

			case 'R':	c = KC_CURSORRIGHT;
					break;

			case 'H':	c = KC_HELP;
					break;

			case 'B':	c = 8;
					break;

			case 'E':	c = 127;
					break;

			case 'F':	if(i == strlen(KeyBuff1) - 1)
						break;

					i++;

					if(!isdigit(KeyBuff1[i]))
						break;

					if(!KeyBuff1[i] == '1')
					{
						c = KC_FKEY1 + KeyBuff1[i] - '1';
						break;
					}

					if(i == strlen(KeyBuff1) - 1)
						break;

					i++;

					if(!isdigit(KeyBuff1[i]))
					{
						c = KC_FKEY1;
						break;
					}

					if(KeyBuff1[i] != '0')
						break;

					c = KC_FKEY10;
					break;

			case 'N':	c = '\n';
					break;

			case '\\':	c = '\\';
					break;

			default:	c = KeyBuff1[i];

					break;
		}

		if(c)
			KeyBuff2[KeyCount++] = c;
	}

	KeyBuff2[KeyCount] = 0;

	if(!(CommandString = (UBYTE *)AllocRem(strlen(KeyBuff2) + 1,MEMF_PUBLIC)))
	{
		PopRequest(NULL,"KeyMacro Problem:","Can't allocate memory chunk!",NULL,"Continue?",FALSE,NULL);
		FreeRem(WindowName);

		return(FALSE);
	}

	strcpy(CommandString,KeyBuff2);

	memset(&NewKey,0,sizeof(struct MacroKey));

	NewKey . mk_CommandKey		= Code;
	NewKey . mk_CommandQualifier	= Qualifier;

	NewKey . mk_String		= CommandString;
	NewKey . mk_Window		= WindowName;

	if(Key)
		NewKey . mk_Type	= MK_WORD;
	else
		NewKey . mk_Type	= MK_COMMAND;

	if(AddMacroKey(&NewKey))
		return(TRUE);

	SPrintf(MessBuff,"Line %ld: Key macro table full.",Line);

	PopRequest(NULL,"KeyMacro Problem:",MessBuff,NULL,"Continue?",FALSE,NULL);
	return(FALSE);
}

	/* UpdateList(Name):
	 *
	 *	Update the big macro key list.
	 */

LONG
UpdateList(char *Name)
{
	char LineBuff[256];
	LONG LineNum = 1;
	FILE *ConfigFile;

	if(!Name)
		Name = "S:KeyMacro.config";

	if(!(KeyList = (struct MacroKey *)AllocRem(sizeof(struct MacroKey) * MAXMACROS,MEMF_PUBLIC | MEMF_CLEAR)))
	{
		PopRequest(NULL,"KeyMacro Problem:","Can't allocate memory chunk!",NULL,"Continue?",FALSE,NULL);
		return(FALSE);
	}

	MSeg -> NumMacros = MAXMACROS;

	if(ConfigFile = fopen(Name,"r"))
	{
		while(fgets(LineBuff,256,ConfigFile))
		{
			if(!Interprete(LineBuff,LineNum++))
			{
				fclose(ConfigFile);

				FreeRem(KeyList);

				return(FALSE);
			}
		}

		fclose(ConfigFile);
	}
	else
	{
		PopRequest(NULL,"KeyMacro Problem:","Couldn't open configuration file!",NULL,"Continue?",FALSE,NULL);
		return(FALSE);
	}

	return(TRUE);
}

	/* main(argc,argv):
	 *
	 *	The entry point to this program.
	 */

VOID
main(LONG argc,char **argv)
{
	struct Process *ThatsMe = (struct Process *)SysBase -> ThisTask;
	LONG Created = FALSE;
	char *FileName = argv[ARG_STARTUP];
	LONG i;

		/* No ^C trapping, please. */

	Enable_Abort = FALSE;

		/* Started from Workbench? */

	if(!From_CLI)
		FileName = NULL;

		/* Try to open mxm.library. */

	if(!(MXMBase = (struct MXMBase *)OpenLibrary("mxm.library",34)))
	{
		if(From_CLI)
			Puts("\33[1mKeyMacro:\33[0m You need \33[1mmxm.library\33[0m 34.12 or higher to run this program.");

		exit(RETURN_FAIL);
	}

		/* Look if handler process is already running. */

	MSeg = (struct MSeg *)FindPort(PORTNAME);

		/* Short info? */

	if(argv[ARG_INFO])
	{
		Printf("\n\33[1m\33[33mKeyMacro\33[31m\33[0m the Amiga macro key handler.\n\n");

		Printf("         This program may be non-commercially\n");
		Printf("         redistributed!\n\n");

		Printf("\33[1m\33[33mAuthor\33[31m\33[0m - Olaf Barthel, MXM\n");
		Printf("         Brabeckstrasse 35\n");
		Printf("         D-3000 Hannover 71\n\n");

		Printf("	 Federal Republic of Germany.\n\n");

		CloseLibrary((struct Library *)MXMBase);

		exit(RETURN_OK);
	}

		/* Remove the handler? */

	if(argv[ARG_QUIT])
	{
		Printf("Removing \33[1m\33[33mKeyMacro\33[31m\33[0m, ");

		if(!MSeg)
		{
			Printf("failed!\7\n");

			CloseLibrary((struct Library *)MXMBase);

			exit(RETURN_OK);
		}

		MSeg -> Father = (struct Task *)SysBase -> ThisTask;

		if(MSeg -> Child)
		{
			Signal(MSeg -> Child,SIG_CLOSE);
			Wait(SIG_CLOSE);
		}

		RemPort((struct MsgPort *)MSeg);
		FreeMem(MSeg -> Port . mp_Node . ln_Name,sizeof(PORTNAME));

		if(MSeg -> Segment)
			UnLoadPrg(MSeg -> Segment);

		if(MSeg -> MacroList)
		{
			for(i = 0 ; i < MSeg -> NumMacros ; i++)
			{
				if(MSeg -> MacroList[i] . mk_Type == MK_UNUSED)
					continue;

				if(MSeg -> MacroList[i] . mk_String)
					FreeRem(MSeg -> MacroList[i] . mk_String);

				if(MSeg -> MacroList[i] . mk_Window)
					FreeRem(MSeg -> MacroList[i] . mk_Window);
			}

			FreeRem(MSeg -> MacroList);
		}

		FreeRem(MSeg);

		Printf("OK.\n");

		CloseLibrary((struct Library *)MXMBase);

		exit(RETURN_OK);
	}

		/* Allocate the handler data. */

	if(!MSeg)
	{
		if(MSeg = (struct MSeg *)AllocRem(sizeof(struct MSeg),MEMF_PUBLIC | MEMF_CLEAR))
		{
			MSeg -> Port . mp_Flags			= PA_IGNORE;
			MSeg -> Port . mp_Node . ln_Pri		= 0;
			MSeg -> Port . mp_Node . ln_Type	= NT_MSGPORT;
			MSeg -> Port . mp_Node . ln_Name	= AllocMem(sizeof(PORTNAME),MEMF_PUBLIC);
			MSeg -> Child				= NULL;
			MSeg -> Father				= (struct Task *)SysBase -> ThisTask;
			MSeg -> SegSize				= sizeof(struct MSeg);
			MSeg -> RingBack			= SIGBREAKF_CTRL_C;
			MSeg -> Revision			= REVISION;

			NewList(&MSeg -> Port . mp_MsgList);

			if(From_CLI)
			{
				Printf("\33[1m\33[33mKeyMacro v1.%ld \33[31m\33[0m(C) Copyright 1989, 1990 by \33[4mMXM\33[0m.\n",REVISION);

				Printf("Installing \33[33m\33[1mKeyMacro\33[0m\33[31m, ");
			}

			if(MSeg -> Port . mp_Node . ln_Name)
				strcpy(MSeg -> Port . mp_Node . ln_Name,PORTNAME);
			else
			{
				if(From_CLI)
					Printf("failed!\n");

				CloseLibrary((struct Library *)MXMBase);

				exit(RETURN_FAIL);
			}

			MSeg -> Segment = LoadPrg("KeyMacro-Handler");

			if(!MSeg -> Segment)
				MSeg -> Segment = LoadPrg("L:KeyMacro-Handler");

			if(!MSeg -> Segment)
			{
				if(From_CLI)
					Printf("unable to find \33[33mL:KeyMacro-Handler\33[31m\7!\n");

				FreeRem(MSeg -> Port . mp_Node . ln_Name);
				FreeRem(MSeg);
			}
			else
			{
				AddPort((struct MsgPort *)MSeg);

				if(!CreateProc("KeyMacro-Handler",10,MSeg -> Segment,4096))
					goto NoMem;

				Wait(SIGBREAKF_CTRL_C);

				if(!MSeg -> Child)
				{
NoMem:					if(From_CLI)
						Printf("\33[33mFAILED!\33[31m (care to retry?)\n");

					RemPort((struct MsgPort *)MSeg);
					FreeRem(MSeg -> Port . mp_Node . ln_Name);

					if(MSeg -> Segment)
						UnLoadPrg(MSeg -> Segment);

					FreeRem(MSeg);

					CloseLibrary((struct Library *)MXMBase);

					exit(RETURN_FAIL);
				}
				else
				{
					if(From_CLI)
						Printf("initializing, ");

					InvertKey(NULL,NULL,IK_USEIKM | IK_BUILDLIST,NULL);

					if(From_CLI)
						Puts("Okay.");
					else
						PopRequest(NULL,"KeyMacro Info:","\33[1mKeyMacro\33[0m installed.",NULL,"Continue?",FALSE,NULL);

					Created = TRUE;
				}
			}
		}
	}

		/* Update the macro key list. */

	if(UpdateList(FileName))
	{
		if(Created)
		{
			MSeg -> NumMacros = MAXMACROS;
			MSeg -> MacroList = KeyList;
		}
		else
		{
			struct MacroMessage UpdateMsg;

			UpdateMsg . mm_Type	= MM_UPDATE;
			UpdateMsg . mm_NumMacros= MAXMACROS;
			UpdateMsg . mm_MacroList= KeyList;

			SendMacroMsg(&UpdateMsg);

			if(From_CLI)
				Printf("\33[1mKeyMacro:\33[0m Updating macro keys...\n");
		}
	}
	else
	{
		CloseLibrary((struct Library *)MXMBase);
		exit(RETURN_ERROR);
	}

	CloseLibrary((struct Library *)MXMBase);

	exit(RETURN_OK);
}
