/*
 *	Handler.c - Copyright © 1991 by S.R. & P.C.
 *
 *	Created:	30 Jun 1991  19:44:45
 *	Modified:	18 Feb 1992  19:51:36
 *
 *	Make>> make
 */

#include "ParMBase.h"

void geta4(void);

/*#define DEBUG */
/*#define DMOUSE_PTR */

#define MIN(x,y)	((x) < (y) ? (x) : (y))
#define ABS(x)		((x) < 0 ? (-x) : (x))

#define HANDLER_TASK_PRI	5
#define HANDLER_TASK_STACK	2048
#define HANDLER_TASK_NAME	"ParMHandlerTask"
#define HANDLER_NAME		"ParM Handler"

extern struct IntuitionBase *IntuitionBase;
extern struct GfxBase *GfxBase;
extern struct ExecBase *SysBase;
extern struct MouseOpt MouseOpt;
extern char ParMLibName[];

struct ParMEvent {
	struct ParMEvent *pe_Next;
	struct Window *pe_Owner;
	USHORT pe_Code;
	USHORT pe_Qual;
	USHORT pe_Flags;
};

struct HandlerMsg {
	struct Message hm_Message;
	short hm_Action;
	union {
		struct {
			ULONG Class;		/* Allways RAWMOUSE actually */
			USHORT Code;
			USHORT Qual;
			struct timeval TimeStamp;
		} ev;
		struct Screen *Screen;
		struct Window *Win;
	} hm_Data;
};

/* Actions for Handler Task (Passed by HandlerMessages) */
#define HMA_START_TASK				0		/* StartUp Message */
#define HMA_FINISH_TASK				1
#define HMA_HANDLE_EV				2
#define HMA_SCREENTOBACK			3
#define HMA_SCREENTOFRONT_ACTIVATE	4		/* ScreenToFront() and ActivateWindow() */
#define HMA_ACTIVATEWINDOW			5

void DummySegment(void);	/* for CreateProc() */

static struct MsgPort *InputDevPort;
static struct IOStdReq *InputRequestBlock;
static struct Interrupt HandlerStuff;
static struct ParMEvent *ParMEventList;
static struct MsgPort *Process, *StartupMsgReplyPort;
static struct Task *Task;
static struct HandlerMsg StartupMsg;
static BOOL PointerOFF, ScreenOFF;
static long MouseTimeOut, ScreenTimeOut;
static short OSVersion;

#ifdef DMOUSE_PTR
static UWORD *NoSprData, *SprSavePtr;
#endif

#ifdef DEBUG

static char output_buffer[128];

static struct IntuiText IText1 = {
	3, 0, JAM2,		/* front and back text pens, drawmode and fill byte */
	7, 13,			/* XY origin relative to container TopLeft */
	NULL,			/* font pointer or NULL for default */
	(UBYTE *)output_buffer,/* pointer to text */
	NULL			/* next IntuiText structure */
};

static struct NewWindow NWS = {
	40, 30,			/* window XY origin relative to TopLeft of screen */
	200, 30,		/* window width and height */
	0, 1,			/* detail and block pens */
	NULL,			/* IDCMP flags */
	WINDOWSIZING|WINDOWDRAG|WINDOWDEPTH|SIMPLE_REFRESH|NOCAREREFRESH,	/* other window flags */
	NULL,			/* first gadget in gadget list */
	NULL,			/* custom CHECKMARK imagery */
	(UBYTE *)"Handler Window",	/* window title */
	NULL,			/* custom screen pointer */
	NULL,			/* custom bitmap */
	5, 5,			/* minimum width and height */
	-1, -1,			/* maximum width and height */
	WBENCHSCREEN	/* destination screen type */
};

static struct Window *DebugW;

#endif


static struct ParMEvent *FindParMEvent(USHORT Code, USHORT Qual, USHORT ExcludeFlags)
{
	struct ParMEvent *ParMEvent;

	ParMEvent = ParMEventList;
	while (ParMEvent) {
		if (ParMEvent->pe_Code == Code && ParMEvent->pe_Qual == Qual && !(ParMEvent->pe_Flags & ExcludeFlags))
			return ParMEvent;
		ParMEvent = ParMEvent->pe_Next;
	}
	return NULL;
}


BOOL AddParMEvent(struct Window *Win, USHORT Code, USHORT Qual, USHORT Flags)
{
	struct ParMEvent *ParMEvent, *LastPE;
	BOOL Ok = TRUE;

	Qual |= IEQUALIFIER_RELATIVEMOUSE;	/* All events have this flag set */
	if (!(Flags & PEF_NOCHECK) && FindParMEvent(Code, Qual, PEF_NOCHECK))
		Ok = FALSE;
	if (ParMEvent = AllocMem(sizeof(struct ParMEvent), MEMF_PUBLIC | MEMF_CLEAR)) {
		ParMEvent->pe_Code = Code;
		ParMEvent->pe_Qual = Qual;
		ParMEvent->pe_Owner = Win;
		ParMEvent->pe_Flags = Flags;
		/*
		 *	Insert event in first position if NOCHECK (highest priority)
		 *	else, insert event in last position (will become active when sooner
		 *	inserted events with same hot-key will be gone).
		 */
		Forbid();
		if (!ParMEventList || (Flags & PEF_NOCHECK)) {
			ParMEvent->pe_Next = ParMEventList;
			ParMEventList = ParMEvent;
		}
		else {
			LastPE = ParMEventList;
			while (LastPE->pe_Next)
				LastPE = LastPE->pe_Next;
			LastPE->pe_Next = ParMEvent;
		}
		Permit();
	}
	return Ok;
}


void RemParMEvents(struct Window *Win)
{
	struct ParMEvent *ParMEvent = ParMEventList, *LastEv;

	Forbid();
	LastEv = (struct ParMEvent *)&ParMEventList;
	while (ParMEvent) {
		if (ParMEvent->pe_Owner == Win) {
			LastEv->pe_Next = ParMEvent->pe_Next;
			FreeMem(ParMEvent, sizeof(struct ParMEvent));
		}
		else
			LastEv = ParMEvent;
		ParMEvent = LastEv->pe_Next;
	}
	Permit();
}


void UpdateParMEventsWindow(struct Window *OldWindow, struct Window *NewWindow)
{
	struct ParMEvent *ParMEvent;

	ParMEvent = ParMEventList;
	while (ParMEvent) {
		if (ParMEvent->pe_Owner == OldWindow) {
			if (NewWindow) {
				ParMEvent->pe_Owner = NewWindow;
				ParMEvent->pe_Flags &= ~PEF_OUT_OF_ORDER;
			}
			else
				ParMEvent->pe_Flags |= PEF_OUT_OF_ORDER;
		}
		ParMEvent = ParMEvent->pe_Next;
	}
}


void HandlerInterface(void);
static void HandlerTask(void);
static void ResetTimeOuts(void);

void InstallHandler(void)
{
	BPTR Segment;

#ifdef DEBUG
	DebugW = OpenWindow(&NWS);
#endif

#ifdef DMOUSE_PTR
	NoSprData = AllocMem(12, MEMF_PUBLIC|MEMF_CHIP|MEMF_CLEAR);
	NoSprData[0] = 0xFE00;
	NoSprData[1] = 0xFF00;
#endif

	OSVersion = IntuitionBase->LibNode.lib_Version;
	StartupMsgReplyPort = CreatePort(0, 0);
	StartupMsg.hm_Message.mn_Length = sizeof(struct HandlerMsg);
	StartupMsg.hm_Message.mn_ReplyPort = StartupMsgReplyPort;
	Segment = (long)DummySegment >> 2;
	if (Process = CreateProc(HANDLER_TASK_NAME, HANDLER_TASK_PRI, Segment, HANDLER_TASK_STACK))
		PutMsg(Process, (struct Message *)&StartupMsg);
	else {
		SimpleRequest(ParMLibName, "Couldn't create %s", HANDLER_TASK_NAME);
		return;
	}
	ResetTimeOuts();	/* init blank stuff */
	InputDevPort = CreatePort(0, 0);
	InputRequestBlock = (struct IOStdReq *) CreateExtIO(InputDevPort, sizeof(struct IOStdReq));
	OpenDevice("input.device", 0, (struct IORequest *)InputRequestBlock, 0);
	HandlerStuff.is_Code = HandlerInterface;
	HandlerStuff.is_Data = (APTR)&MouseOpt;
	HandlerStuff.is_Node.ln_Name = HANDLER_NAME;
	HandlerStuff.is_Node.ln_Pri = MouseOpt.HandlerPri;
	InputRequestBlock->io_Command = IND_ADDHANDLER;
	InputRequestBlock->io_Data = (APTR) & HandlerStuff;
	DoIO((struct IORequest *)InputRequestBlock);
}


static void SendRequest(short Action, void *APtr);

void RemoveHandler(void)
{
	if (!Process)
		return;
	InputRequestBlock->io_Command = IND_REMHANDLER;
	InputRequestBlock->io_Data = (APTR)&HandlerStuff;
	DoIO((struct IORequest *)InputRequestBlock);
	CloseDevice((struct IORequest *)InputRequestBlock);
	FreeMem(InputRequestBlock, sizeof(struct IOStdReq));
	DeletePort(InputDevPort);

	SendRequest(HMA_FINISH_TASK, NULL);
	WaitPort(StartupMsgReplyPort);
	GetMsg(StartupMsgReplyPort);
	DeletePort(StartupMsgReplyPort);

#ifdef DMOUSE_PTR
	FreeMem(NoSprData, 12);
#endif

#ifdef DEBUG
	Forbid();
	CloseWindow(DebugW);
	DebugW = NULL;
	Permit();
#endif
}


void UpdateMouseOpt(struct MouseOpt *UsrMouseOpt)
{
	MouseOpt = *UsrMouseOpt;
	ResetTimeOuts();
}


void ScreenOff(void);
void ScreenOn(void);
void PointerOff(void);
void PointerOn(void);


/**************** Warning: UPDATE for new 2.0 screen modes *************/
/*
 *	On exit, if Screen is not NULL, Screen LayerInfo is locked
 */

static struct Layer *WhichMouseLayer(struct Screen **Scr)
{
	struct IntuitionBase *IBase = IntuitionBase;
	struct Screen *Screen;
	struct Layer *Layer = NULL;
	short MouseX;
	short MouseY;

	for(Screen = IBase->FirstScreen ; Screen ; Screen = Screen->NextScreen) {
		if (OSVersion < 36)
			LockLayerInfo(&Screen->LayerInfo);
		MouseX = IBase->MouseX;
		MouseY = IBase->MouseY;
		if (!(Screen->ViewPort.Modes & HIRES))
			MouseX >>= 1;
		if (!(Screen->ViewPort.Modes & LACE))
			MouseY >>= 1;
		if (Layer = WhichLayer(&Screen->LayerInfo, MouseX - Screen->ViewPort.DxOffset, MouseY - Screen->ViewPort.DyOffset))
			break;
		if (MouseY >= Screen->ViewPort.DyOffset)
			break;
		if (OSVersion < 36)
			UnlockLayerInfo(&Screen->LayerInfo);
	}
	*Scr = Screen;
	return Layer;
}


static void HandlerTask(void)
{
	struct IntuitionBase *IBase;
	struct HandlerMsg *Msg;
	struct Screen *Screen;
	struct Layer *Layer;
	struct Window *Win, *ActiveWin, *LastClickWin = NULL;
	ULONG OldSec=0, OldMic=0, Sec, Mic;
	BOOL End = FALSE;
	long ILock;

	geta4();
	Task = SysBase->ThisTask;
	WaitPort(Process);
	GetMsg(Process);
	IBase = IntuitionBase;
	if (OSVersion < 36)
		Forbid();
	while(!End) {
		WaitPort(Process);
		if (OSVersion >= 36)
			ILock = LockIBase(0);
		while(Msg = (struct HandlerMsg *)GetMsg(Process)) {
			switch(Msg->hm_Action) {
			case HMA_HANDLE_EV:
				Sec = Msg->hm_Data.ev.TimeStamp.tv_secs;
				Mic = Msg->hm_Data.ev.TimeStamp.tv_micro;
				Layer = WhichMouseLayer(&Screen);
				Win = (Layer) ? (struct Window *)Layer->Window : NULL;
				if (Msg->hm_Data.ev.Class == IECLASS_TIMER) {
					ActiveWin = IBase->ActiveWindow;
					if (Win && Win != ActiveWin && (!ActiveWin || !ActiveWin->FirstRequest))
						ActivateWindow(Win);
				}
				else if (Msg->hm_Data.ev.Class == IECLASS_RAWMOUSE) {
					if (Msg->hm_Data.ev.Code == IECODE_LBUTTON) {
						if (MouseOpt.NClick && Msg->hm_Data.ev.Qual == MouseOpt.WTFQual && Win && !(Win->Flags & BACKDROP) && Layer->ClipRect && Layer->ClipRect->Next) {
							if (Win == LastClickWin && DoubleClick(OldSec, OldMic, Sec, Mic)) {
								/* Do a window to front only if ActiveWindow is still the DoubleClicked */
								if (Win == IBase->ActiveWindow)
									WindowToFront(Win);
							}
							else {
								/* Record date and Window Clicked */
								OldSec = Sec;
								OldMic = Mic;
								LastClickWin = Win;
							}
						}
					}
					else if (Msg->hm_Data.ev.Code == IECODE_RBUTTON && Msg->hm_Data.ev.Qual == (IEQUALIFIER_LEFTBUTTON|IEQUALIFIER_RBUTTON|IEQUALIFIER_RELATIVEMOUSE)) {
						if ((MouseOpt.Flags & MO_WINDOWTOBACK) && Win && !(Win->Flags & BACKDROP) && (Win->NextWindow || Win->WScreen->FirstWindow != Win)) {
							WindowToBack(Win);
						}
						else if ((MouseOpt.Flags & MO_SCREENTOBACK) && Screen) {
							if (OSVersion >= 36)
								UnlockIBase(ILock);
							ScreenToBack(Screen);
							if (OSVersion >= 36)
								ILock = LockIBase(0);
						}
					}
				}
				if (OSVersion < 36 && Screen)
					UnlockLayerInfo(&Screen->LayerInfo);
				break;
			case HMA_SCREENTOBACK:
				ScreenToBack(Msg->hm_Data.Screen);
				break;
			case HMA_SCREENTOFRONT_ACTIVATE:
				ScreenToFront(Msg->hm_Data.Win->WScreen);
			case HMA_ACTIVATEWINDOW:
				ActivateWindow(Msg->hm_Data.Win);
				break;
			case HMA_FINISH_TASK:
				End = TRUE;
				break;
			}
			/* Free message now, don't reply */
			FreeMem(Msg, sizeof(struct HandlerMsg));
		}
		if (OSVersion >= 36)
			UnlockIBase(ILock);
	}
	Forbid();
	ScreenOn();
	PointerOn();
	/* Get rid of pending messages */
	while(Msg = (struct HandlerMsg *)GetMsg(Process))
		FreeMem(Msg, sizeof(struct HandlerMsg));
	/* die now */
	ReplyMsg((struct Message *)&StartupMsg);
	Exit(0);
}


/*
 *	APtr: generic pointer, may be an InputEvent, Screen, Window or NULL
 *	Action make the difference.
 */

#define EV ((struct InputEvent *)APtr)

static void SendRequest(short Action, void *APtr)
{
	struct HandlerMsg *Msg = AllocMem(sizeof(struct HandlerMsg), MEMF_PUBLIC|MEMF_CLEAR);

	if (Msg) {
		Msg->hm_Message.mn_Length = sizeof(struct HandlerMsg);
		Msg->hm_Action = Action;
		if (Action == HMA_HANDLE_EV) {
			Msg->hm_Data.ev.Class = EV->ie_Class;
			Msg->hm_Data.ev.Code = EV->ie_Code;
			Msg->hm_Data.ev.Qual = EV->ie_Qualifier & ~(IEQUALIFIER_NUMERICPAD|IEQUALIFIER_CAPSLOCK);
			Msg->hm_Data.ev.TimeStamp = EV->ie_TimeStamp;
		}
		else
			Msg->hm_Data.Screen = APtr;		/* Something else than an InputEvent */
		PutMsg(Process, (struct Message *)Msg);
	}
}


static void ResetTimeOuts(void)
{
	MouseTimeOut = MouseOpt.MouseBlank * 10;		/* 10 timer events per sec */
	ScreenTimeOut = MouseOpt.ScreenBlank * 10;
}


static short Accel(short d)
{
	short d0 = d, Threshold;
	BOOL Sgn = 1;

	if (d < 0) {
		d = -d;
		Sgn = -1;
	}
	if (d > (Threshold = MouseOpt.Threshold))
		return Sgn * ((d - Threshold) * MouseOpt.Acc + Threshold);
	return d0;
}


static void CheckParMEvent(struct InputEvent *ev)
{
	struct Window *Win;
	struct ParMEvent *ParMEvent;
	long OldPri;

	if (ParMEvent = FindParMEvent(ev->ie_Code, ev->ie_Qualifier & ~(IEQUALIFIER_CAPSLOCK|IEQUALIFIER_NUMERICPAD), PEF_OUT_OF_ORDER)) {
		Win = ParMEvent->pe_Owner;
		OldPri = SetTaskPri(Task, 21);
		if (ParMEvent->pe_Flags & PEF_SCREENTOFRONT)
			SendRequest(HMA_SCREENTOFRONT_ACTIVATE, Win);
		else
			SendRequest(HMA_ACTIVATEWINDOW, Win);
		SetTaskPri(Task, OldPri);
		if (!(ParMEvent->pe_Flags & PEF_PASSTHROUGH))
			ev->ie_Class = IECLASS_NULL;	/* Skip Event */
	}
}


static struct InputEvent *InputHandler(struct InputEvent *EventList, struct MouseOpt *MouseOpt)
{
	struct InputEvent *ev = EventList;
	struct Window *W;
	static USHORT LastMouseMoveQual, MinMove;
	static BOOL NoMouseMove;
	UWORD Code, Qual, X, Y;

	while (ev) {

#ifdef DEBUG
static short tim;

		if (DebugW && (ev->ie_Class != IECLASS_TIMER || ++tim > 5)) {
			tim = 0;
			SPrintf(output_buffer, "%lx    %lx    %lx    %04lx ",
				(long) ev->ie_Class,
				(long) ev->ie_SubClass,
				(long) ev->ie_Code,
				(long) ev->ie_Qualifier);
			PrintIText(DebugW->RPort, &IText1, 0, 0);
		}

#endif

		Code = ev->ie_Code;
		/* ignore remanent qualifiers which can disable some handler features */
		Qual = ev->ie_Qualifier & ~(IEQUALIFIER_NUMERICPAD|IEQUALIFIER_CAPSLOCK);
		switch (ev->ie_Class) {
		case IECLASS_RAWMOUSE:
			ResetTimeOuts();
			if (ScreenOFF) {
				ScreenOn();
				ScreenOFF = FALSE;
			}
			if (Code == IECODE_NOBUTTON) {
				if (PointerOFF) {
					if (W = IntuitionBase->ActiveWindow)
						WaitBOVP(&W->WScreen->ViewPort);	/* Make pointer appear smoothly */
					PointerOn();
					PointerOFF = FALSE;
				}
				NoMouseMove = FALSE;
				LastMouseMoveQual = Qual;
				if (MouseOpt->Acc) {
					ev->ie_X = Accel(ev->ie_X);
					ev->ie_Y = Accel(ev->ie_Y);
				}
				X = ABS(ev->ie_X);
				Y = ABS(ev->ie_Y);
				if (X + Y > 0)
					MinMove = MIN(MinMove, X + Y);
			}
			else {
				CheckParMEvent(ev);
				if (Code == IECODE_LBUTTON || Code == IECODE_RBUTTON) {
					SendRequest(HMA_HANDLE_EV, ev);
					if (Code == IECODE_RBUTTON && (MouseOpt->Flags & (MO_WINDOWTOBACK|MO_SCREENTOBACK)) && Qual == (IEQUALIFIER_LEFTBUTTON|IEQUALIFIER_RBUTTON|IEQUALIFIER_RELATIVEMOUSE))
						ev->ie_Class = IECLASS_NULL;	/* Remove event */
				}
			}
			break;
		case IECLASS_RAWKEY:
			ResetTimeOuts();
			if (Code & IECODE_UP_PREFIX)		/* don't handle key up */
				break;
			if (ScreenOFF) {
				ScreenOn();
				ScreenOFF = FALSE;
			}
			if (!PointerOFF && MouseOpt->MouseBlank) {
				PointerOff();
				PointerOFF = TRUE;
			}
			CheckParMEvent(ev);

			/********** REMOVE IN 2.0 (System handled & Qual parametrable) ************/
			/* LAmiga-M stuff (rawkey 'm' = $37) */
			if (OSVersion < 36 && Code == 0x37 && Qual == (IEQUALIFIER_LCOMMAND|IEQUALIFIER_RELATIVEMOUSE)) {
				SendRequest(HMA_SCREENTOBACK, IntuitionBase->FirstScreen);
				ev->ie_Class = IECLASS_NULL;	/* Skip Event */
			}
			break;
		case IECLASS_TIMER:
			MouseTimeOut--;
			ScreenTimeOut--;
			if (LastMouseMoveQual == IEQUALIFIER_RELATIVEMOUSE
				&& (MouseOpt->SunMouse > 0 && MinMove <= MouseOpt->SunMouse		/* Smallest MOUSEMOVE between two TIMER events smaller than SunMouse */
				|| MouseOpt->SunMouse >= 0 && NoMouseMove))		/* No MOUSEMOVE between two TIMER events */
			{
				SendRequest(HMA_HANDLE_EV, ev);
				LastMouseMoveQual = 0;
				MinMove = MouseOpt->SunMouse + 1;
			}
			NoMouseMove = TRUE;
			/* look if something must be blanked */
			if (!PointerOFF && MouseOpt->MouseBlank && MouseTimeOut <= 0) {
				PointerOff();
				PointerOFF = TRUE;
			}
			if (!ScreenOFF && MouseOpt->ScreenBlank && ScreenTimeOut <= 0) {
				ScreenOff();
				ScreenOFF = TRUE;
			}
			break;
		}
		ev = ev->ie_NextEvent;
	}
	return EventList;
}


#ifdef DMOUSE_PTR

void PointerOff(void)
{
	struct copinit *ci = GfxBase->copinit;

	if (!SprSavePtr)
		SprSavePtr = (UWORD *)((ci->sprstrtup[1] << 16) | ci->sprstrtup[3]);
	ci->sprstrtup[1] = (ULONG)NoSprData >> 16;
	ci->sprstrtup[3] = (UWORD)(LONG)NoSprData;
}


void PointerOn(void)
{
	struct copinit *ci = GfxBase->copinit;

	if (SprSavePtr) {
		ci->sprstrtup[1] = (ULONG)SprSavePtr >> 16;
		ci->sprstrtup[3] = (UWORD)(LONG)SprSavePtr;
		SprSavePtr = NULL;
	}
}

#endif


#asm

	public _DummySegment
	public _HandlerTask

	cseg

_DummySegment:
	dc.l	0
	nop
	nop
	jmp		_HandlerTask


_HandlerInterface:
	move.l	a4,-(sp)		; save a4
	movem.l	a0/a1,-(sp)		; push args
	jsr 	_geta4			; get our private a4 to access global data
	jsr 	_InputHandler	; call handler stuff
	addq.l	#8,sp			; pop args
	move.l	(sp)+,a4		; restore a4
	rts


; blank routines
; hardware.defs
custom 		EQU $dff000
dmacon 		EQU $096
spr 		EQU $140
sd_dataa	EQU $04
color 		EQU $180

;include "hardware/dmabits.i"
DMAF_RASTER EQU $0100
DMAF_COPPER EQU $0080

_ScreenOff:
	lea 	custom+dmacon,a0
	move.w	#0+DMAF_RASTER+DMAF_COPPER,(a0)
	clr.w	color-dmacon(a0)			; SET BLACK SCREEN
	clr.l	spr+sd_dataa-dmacon(a0)		; MAKE SURE SPRITE GOES
	rts

_ScreenOn:
	lea		custom+dmacon,a0
	move.w	#$8000+DMAF_RASTER+DMAF_COPPER,(a0)		; screen on
	rts


#endasm


#ifndef DMOUSE_PTR

#asm

	public	_GfxBase

_PointerOff:
	lea		custom+dmacon,a0
	move.w	#0+$20,(a0)						; turn sprites off.
	clr.l	spr+sd_dataa-dmacon(a0)
	rts

_PointerOn:
	lea 	custom+dmacon,a0
	move.w	#$8000+$20,(a0)			; turn sprites on
	rts

#endasm

#endif

