#include <libraries/arpbase.h>
#include <arpfunctions.h>
#include <stdio.h>
#include <ctype.h>

#include "KeyMacro.h"

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

#define From_CLI	(ThatsMe -> pr_CLI)

struct MXMBase	*MXMBase;
struct MSeg	*MSeg;
struct MacroKey	*KeyList;

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}
};

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}
};

#asm
_LVORawDoFmt	EQU	$FFFFFDF6

StuffChar:	MOVE.B	D0,(A3)+
		RTS

		XDEF	_Format

_Format:	MOVE.L	 4(SP),D1
		MOVE.L	 8(SP),D0
		LEA	12(SP),A1

		MOVEM.L	A0-A3,-(SP)

		MOVE.L	D1,A3
		MOVE.L	D0,A0
		LEA	StuffChar(PC),A2
		MOVE.L	4,A6
		JSR	_LVORawDoFmt(A6)

		MOVEM.L	(SP)+,A0-A3
		CLR.L	D0

		RTS
#endasm

void *
SendCustomMsg(scm_Msg)
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,scm_TempMsg);
	}

	return((void *)scm_TempMsg);
}

UBYTE *
GetToken(s,start)
UBYTE *s;
long *start;
{
	static UBYTE buffer[256];
	long i,j,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);
}

struct MacroKey *
AddMacroKey(MacroKey)
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);
}

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

	long Start = 0,Key = FALSE,QuitLoop = FALSE,i,KeyCount = 0;
	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)
		{
			Format(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)
	{
		Format(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)
	{
		Format(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
		{
			Format(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
	{
		Format(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))
	{
		Format(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)))
	{
		Format(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);

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

	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);

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

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

BOOL
UpdateList(Name)
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);
}

Chk_Abort() { return(0); }

void
main(argc,argv)
long argc;
char *argv[];
{
	struct Process *ThatsMe = (struct Process *)FindTask(NULL);
	BOOL Created = FALSE;
	char *FileName = argv[ARG_STARTUP];
	long i;

	if(!From_CLI)
		FileName = NULL;

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

		exit(5);
	}

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

	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("         re-distributed!\n\n");

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

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

		CloseLibrary(MXMBase);

		exit(0);
	}

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

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

			CloseLibrary(MXMBase);

			exit(0);
		}

		MSeg -> Father = (struct Task *)FindTask(NULL);

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

		RemPort(&MSeg -> Port);
		FreeRem(MSeg -> Port . mp_Node . ln_Name);

		if(MSeg -> Segment)
			UnLoadSeg(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(MXMBase);

		exit(0);
	}

	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= AllocRem(sizeof(PORTNAME),MEMF_PUBLIC);
			MSeg -> Child			= NULL;
			MSeg -> Father			= (struct Task *)FindTask(NULL);
			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(MXMBase);

				exit(20);
			}

			MSeg -> Segment = (BPTR)LoadSeg("KeyMacro-Handler");

			if(!MSeg -> Segment)
				MSeg -> Segment = (BPTR)LoadSeg("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(&MSeg -> Port);

				CreateProc("KeyMacro-Handler",10,MSeg -> Segment,4096);

				Wait(SIGBREAKF_CTRL_C);

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

					RemPort(&MSeg -> Port);
					FreeRem(MSeg -> Port . mp_Node . ln_Name);

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

					FreeRem(MSeg);

					CloseLibrary(MXMBase);

					exit(20);
				}
				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;
				}
			}
		}
	}

	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;

			SendCustomMsg(&UpdateMsg);

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

	CloseLibrary(MXMBase);
}
