#include "KeyMacro.h"

struct MXMBase		*MXMBase;
struct IntuitionBase	*IntuitionBase;

struct MSeg		*MSeg;
LONG			 MSignal = -1;
struct KeyEquivalent	*EquList;

struct Process		*ExecuteProc;
struct MsgPort		*ExecutePort;

struct MsgPort		*InputDevPort;
struct IOStdReq		*InputRequestBlock;
struct Interrupt	 HandlerStuff;

long			 ConsoleDevice = NULL;
struct IOStdReq		 ConStdReq;

	/* FreeString(Byte):
	 *
	 *	Frees the memory occupied by the contents of a BSTR.
	 */

void
FreeString(Byte)
BPTR Byte;
{
	long *Ptr = (long *)BADDR(Byte);

	FreeMem(Ptr - 1,Ptr[-1]);
}

	/* CreateBSTR(s):
	 *
	 *	Allocates enough memory to hold the contents of
	 *	a given string and makes it a BSTR.
	 */

BPTR
CreateBSTR(s)
char *s;
{
	long Length = strlen(s);
	long BlockLength = (Length + 8) & ~3;
	char *Byte;

	if(!(Byte = (char *)AllocMem(BlockLength,MEMF_PUBLIC | MEMF_CLEAR)))
		return(NULL);

	*(long *)Byte = BlockLength;

	Byte[4] = Length;
	strncpy(Byte + 5,s,Length);

	return((long)(Byte + 4) >> 2);
}

	/* CopyPath():
	 *
	 *	Builds a fake pathlist inherited from any valid
	 *	CLI process or Workbench.
	 */

BPTR
CopyPath()
{
	struct Process *Father;
	struct CommandLineInterface *CLI;
	BPTR *Next1,*Next2,*Last,NewPath = NULL;

	Last = &NewPath;

		/* If using ARP this will also give us a valid
		 * pathlist.
		 */

	if(!(Father = (struct Process *)FindTask("Workbench")))
		if(!(Father = (struct Process *)FindTask("ARP Shell Process")))
			if(!(Father = (struct Process *)FindTask("New CLI")))
				if(!(Father = (struct Process *)FindTask("Initial CLI")))
					return(NULL);

	if(!(CLI = (struct CommandLineInterface *)BADDR(Father -> pr_CLI)))
		return(NULL);

	for(Next1 = (BPTR *)BADDR(CLI -> cli_CommandDir) ; Next1 ; Next1 = (BPTR *)BADDR(*Next1))
	{
		if(!(Next2 = (BPTR *)AllocMem(2 * sizeof(BPTR),MEMF_PUBLIC | MEMF_CLEAR)))
			break;

		*Last = (long)Next2 >> 2;
		Last = Next2;

		Next2[1] = (BPTR)DupLock(Next1[1]);
		Next2[0] = NULL;
	}

	return(NewPath);
}

	/* FakeCLI():
	 *
	 *	Creates a fake CLI structure for out process. This
	 *	includes pathlist, currentdir, prompt and stack.
	 */

void
FakeCLI()
{
	struct CommandLineInterface *CLI;
	struct Process *MyProcess = (struct Process *)FindTask(NULL);

	if(!(CLI = (struct CommandLineInterface *)AllocMem(sizeof(struct CommandLineInterface),MEMF_PUBLIC | MEMF_CLEAR)))
		return;

	MyProcess -> pr_CLI = (long)CLI >> 2;

	CLI -> cli_SetName	= CreateBSTR("SYS:");
	CLI -> cli_Prompt	= CreateBSTR("%N> ");
	CLI -> cli_DefaultStack	= 4000;

	CurrentDir(Lock("SYS:",ACCESS_READ));

	CLI -> cli_CommandDir	= CopyPath();
}

	/* ClearPath(InitPath):
	 *
	 *	Frees the contents of our fake pathlist.
	 */

void
ClearPath(InitPath)
BPTR InitPath;
{
	BPTR *Next,*Path;

	for(Path = (BPTR *)BADDR(InitPath) ; Path ; Path = Next)
	{
		Next = (BPTR *)BADDR(Path[0]);

		if(Path[1])
			UnLock(Path[1]);

		FreeMem(Path,2 * sizeof(BPTR));
	}
}

	/* StopFakery():
	 *
	 *	Removes the contents of our fake CLI structure.
	 */

void
StopFakery()
{
	BPTR MyCD = (BPTR)CurrentDir(NULL);
	struct Process *MyProcess = (struct Process *)FindTask(NULL);
	struct CommandLineInterface *CLI = (struct CommandLineInterface *)BADDR(MyProcess -> pr_CLI);

	if(!CLI)
		return;

	if(MyCD)
		UnLock(MyCD);

	FreeString(CLI -> cli_SetName);
	FreeString(CLI -> cli_Prompt);

	ClearPath(CLI -> cli_CommandDir);

	MyProcess -> pr_CLI = NULL;

	FreeMem(CLI,sizeof(struct CommandLineInterface));
}

void *
DeleteCustomMsg(scm_Msg)
struct MacroMessage *scm_Msg;
{
	if(scm_Msg && scm_Msg -> mm_Message . mn_Node . ln_Name == scm_Msg)
		FreeRem(scm_Msg);

	return(NULL);
}

void *
SendCustomMsg(scm_Msg,scm_Port)
struct MacroMessage *scm_Msg;
struct MsgPort *scm_Port;
{
	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(scm_Port,scm_TempMsg);
	}

	return((void *)scm_TempMsg);
}

void
Executor()
{
	ULONG SignalSet,NilHandle;
	struct MacroMessage *ExecuteMsg;
	struct Window *TheWindow;

	geta4();

	if(!(ExecutePort = (struct MsgPort *)CreatePort(NULL,0)))
		return;

	if(!(NilHandle = (ULONG)Open("NIL:",MODE_NEWFILE)))
	{
		DeletePort(ExecutePort);
		return;
	}

	FakeCLI();

	FOREVER
	{
		SignalSet = Wait(SIG_CLOSE | (1 << ExecutePort -> mp_SigBit));

		if(SignalSet & SIG_CLOSE)
		{
			StopFakery();
			Close(NilHandle);
			DeletePort(ExecutePort);

			ExecuteProc = NULL;

			return;
		}

		while(ExecuteMsg = (struct MacroMessage *)GetMsg(ExecutePort))
		{
			TheWindow = NULL;

			if(ExecuteMsg -> mm_WindowName)
			{
				ULONG IntuiLock;

				struct Screen *ExScreen;
				struct Window *ExWindow;

				IntuiLock = LockIBase(NULL);

				ExScreen = IntuitionBase -> FirstScreen;

				do
				{
					ExWindow = ExScreen -> FirstWindow;

					do
					{
						if(!UStrCmp(ExecuteMsg -> mm_WindowName,ExWindow -> Title))
						{
							UnlockIBase(IntuiLock);

							TheWindow = ExWindow;

							goto SkipLoop;
						}
					}
					while(ExWindow = ExWindow -> NextWindow);
				}
				while(ExScreen = ExScreen -> NextScreen);

				UnlockIBase(IntuiLock);
			}

			Execute(ExecuteMsg -> mm_FileName,NULL,NilHandle);

SkipLoop:		DeleteCustomMsg(ExecuteMsg);

			if(TheWindow)
			{
				WindowToFront(TheWindow);
				ScreenToFront(TheWindow -> WScreen);
				ActivateWindow(TheWindow);
			}
		}
	}
}

struct MacroKey *
FindMacroKey(Code,Qualifier)
LONG Code,Qualifier;
{
	long i;

	if(!MSeg -> MacroList)
		return(NULL);

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

		if(MSeg -> MacroList[i] . mk_CommandKey == Code && (Qualifier & MSeg -> MacroList[i] . mk_CommandQualifier) == MSeg -> MacroList[i] . mk_CommandQualifier)
			return(&MSeg -> MacroList[i]);
	}

	return(NULL);
}

#asm
_Handler:	MOVEM.L	A4,-(A7)
		MOVEM.L	A0/A1,-(A7)

		JSR	_geta4#
		JSR	_EventHandler

		ADDQ.L	#8,A7

		MOVEM.L	(A7)+,A4

		RTS
#endasm

struct InputEvent *
EventHandler(Event)
struct InputEvent *Event;
{
	register struct MacroKey *HandlerKey;

	if(Event -> ie_Class != IECLASS_RAWKEY || Event -> ie_Code & IECODE_UP_PREFIX)
		return(Event);

	if(HandlerKey = (struct MacroKey *)FindMacroKey(Event -> ie_Code,Event -> ie_Qualifier))
	{
		struct MacroMessage HandlerMsg;

		HandlerMsg . mm_Type	= MM_INPUT;
		HandlerMsg . mm_MacroKey= HandlerKey;

		SendCustomMsg(&HandlerMsg,&MSeg -> Port);

		Event -> ie_Class = IECLASS_NULL;
	}

	return(Event);
}

BOOL
InitHandler()
{
	extern void Handler();

	if(OpenDevice("console.device",-1,&ConStdReq,0))
		return(FALSE);

	ConsoleDevice = (long)ConStdReq . io_Device;

	if(!(InputDevPort = (struct MsgPort *)CreatePort(NULL,0)))
		return(FALSE);

	if(!(InputRequestBlock = (struct IOStdReq *)CreateStdIO(InputDevPort)))
		return(FALSE);

	if(OpenDevice("input.device",0,InputRequestBlock,0))
		return(FALSE);

	HandlerStuff . is_Code		= Handler;
	HandlerStuff . is_Node . ln_Pri	= 60;
	HandlerStuff . is_Node . ln_Name= "KeyMacro-Handler";

	InputRequestBlock -> io_Command	= IND_ADDHANDLER;
	InputRequestBlock -> io_Data	= (APTR)&HandlerStuff;

	DoIO(InputRequestBlock);

	return(TRUE);
}

void
FlushHandler()
{
	if(ConsoleDevice)
		CloseDevice(&ConStdReq);

	if(InputRequestBlock -> io_Device)
	{
		InputRequestBlock -> io_Command	= IND_REMHANDLER;
		InputRequestBlock -> io_Data	= (APTR)&HandlerStuff;

		DoIO(InputRequestBlock);

		CloseDevice(InputRequestBlock);
	}

	if(InputRequestBlock)
		DeleteStdIO(InputRequestBlock);

	if(InputDevPort)
		DeletePort(InputDevPort);
}

void
ShutDown()
{
	FlushHandler();

	if(ExecuteProc)
	{
		Signal(ExecuteProc,SIG_CLOSE);

		while(ExecuteProc)
			Delay(10);
	}

	if(MXMBase)
		CloseLibrary(MXMBase);

	if(MSignal != -1)
		FreeSignal(MSignal);

	Forbid();

	if(MSeg -> Father)
		Signal(MSeg -> Father,SIG_CLOSE);

	if(!MSeg -> Father)
	{
		register BPTR Segment = MSeg -> Segment;

		RemPort(&MSeg -> Port);
		FreeMem(MSeg -> Port . mp_Node . ln_Name,sizeof(PORTNAME));

		UnLoadSeg(Segment);

		Wait(NULL);
	}
}

long
_main()
{
	struct Process *ThatsMe = (struct Process *)FindTask(NULL);
	ULONG SignalSet;

	struct MacroMessage *MacroMsg;

	register long i;

	if(ThatsMe -> pr_CLI)
		return(10);

	if(!(MSeg = (struct MSeg *)FindPort(PORTNAME)))
		return(10);

	if(MSeg -> Port . mp_Flags == PA_SIGNAL)
		return(10);

	if(MSeg -> Revision < REVISION)
	{
		Signal(MSeg -> Father,MSeg -> RingBack);

		return(10);
	}

	if(!InitHandler())
	{
		FlushHandler();
		Signal(MSeg -> Father,MSeg -> RingBack);

		return(20);
	}

	if(!(MXMBase = (struct MXMBase *)OpenLibrary("mxm.library",0)))
	{
		FlushHandler();
		Signal(MSeg -> Father,MSeg -> RingBack);

		return(20);
	}

	IntuitionBase = (struct IntuitionBase *)MXMBase -> IntuitionBase;

	if((MSignal = AllocSignal(-1)) == -1)
	{
		CloseLibrary(MXMBase);
		FlushHandler();
		Signal(MSeg -> Father,MSeg -> RingBack);

		return(20);
	}

	if(!(ExecuteProc = (struct Process *)CreateFuncProc("KeyMacro.exec",10,Executor,4000)))
	{
		FreeSignal(MSignal);
		CloseLibrary(MXMBase);
		FlushHandler();
		Signal(MSeg -> Father,MSeg -> RingBack);

		return(20);
	}

	MSeg -> Child = (struct Task *)ThatsMe;

	Signal(MSeg -> Father,MSeg -> RingBack);

	MSeg -> Father = NULL;

	MSeg -> Port . mp_Flags		= PA_SIGNAL;
	MSeg -> Port . mp_SigBit	= MSignal;
	MSeg -> Port . mp_SigTask	= MSeg -> Child;

	FOREVER
	{
		SignalSet = Wait(SIG_CLOSE | SIG_PORT);

		if(SignalSet & SIG_CLOSE)
		{
			ShutDown();
			return(0);
		}

		if(SignalSet & SIG_PORT)
		{
			while(MacroMsg = (struct MacroMessage *)GetMsg(&MSeg -> Port))
			{
				if(MacroMsg -> mm_Type == MM_INPUT)
				{
					struct MacroKey *TempMacroKey = MacroMsg -> mm_MacroKey;

					if(TempMacroKey)
					{
						if(TempMacroKey -> mk_Type == MK_COMMAND)
						{
							struct MacroMessage CommandMsg;

							CommandMsg . mm_Type		= MM_EXECUTE;
							CommandMsg . mm_FileName	= TempMacroKey -> mk_String;
							CommandMsg . mm_WindowName	= TempMacroKey -> mk_Window;

							SendCustomMsg(&CommandMsg,ExecutePort);
						}

						if(TempMacroKey -> mk_Type == MK_WORD)
						{
							struct InputEvent FakeInputEvent;

							InputRequestBlock -> io_Command	= IND_WRITEEVENT;
							InputRequestBlock -> io_Data	= (APTR)&FakeInputEvent;

							setmem(&FakeInputEvent,sizeof(struct InputEvent),0);

							FakeInputEvent . ie_Class = IECLASS_RAWKEY;

							for(i = 0 ; i < strlen(TempMacroKey -> mk_String) ; i++)
								if(InvertKey(TempMacroKey -> mk_String[i],&FakeInputEvent,IK_USEIKM,NULL))
									DoIO(InputRequestBlock);
						}
					}
				}

				if(MacroMsg -> mm_Type == MM_UPDATE)
				{
					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);
					}

					MSeg -> NumMacros = MacroMsg -> mm_NumMacros;
					MSeg -> MacroList = MacroMsg -> mm_MacroList;
				}

				DeleteCustomMsg(MacroMsg);
			}
		}
	}
}
