/*
 * handler.c
 *
 * Routines to set up handler.
 * Part of Yak.
 *
 * Martin W. Scott, 9/92.
 */
#include <exec/types.h>
#include <exec/exec.h>
#include <hardware/custom.h>
#include <hardware/dmabits.h>
#include <devices/console.h>
#include <devices/input.h>
#include <devices/inputevent.h>
#include <libraries/commodities.h>
#include <graphics/gfxbase.h>
#include <graphics/gfxmacros.h>
#include <intuition/intuition.h>
#include <intuition/intuitionbase.h>
#include <clib/alib_protos.h>
#include <proto/exec.h>
#include <proto/dos.h>
#include <proto/layers.h>
#include <proto/intuition.h>
#include <proto/console.h>
#include <string.h>

#include "yak.h"

extern struct Custom __far custom;

#define TIMERCOUNT	2	/* how long mouse must stop before autopoint */

void (*intui_routine)(APTR);	/* for intui_op's */
APTR intui_parameter;
CxObj *clickobj;
ULONG clicksigflag, intuiopsigflag;
static BYTE clicksigbit, intuiopsigbit = -1;
static struct Task *thistask;
static BOOL misspop;

#ifdef LESS_CLICK

static struct ConsoleDevice *ConsoleDevice;
static struct IOStdReq *conreq;

void
FreeConsoleDevice()
{
	if (conreq)
	{
		if (conreq->io_Device)
			CloseDevice(conreq);
		FreeMem(conreq, sizeof(struct IOStdReq));
	}
}

BOOL
InitConsoleDevice()
{
	if (conreq = AllocMem(sizeof(struct IOStdReq), MEMF_CLEAR|MEMF_PUBLIC))
	{
		if (OpenDevice("console.device", -1L, conreq, 0L) == 0)
		{
			ConsoleDevice = (struct ConsoleDevice *)conreq->io_Device;
			return TRUE;
		}
	}
	FreeConsoleDevice();
	return FALSE;
}

/* return TRUE if input-event generates text */
BOOL
AnsiEvent(struct InputEvent *event)
{
	char buf[80];
	UWORD keys;

	keys = RawKeyConvert(event, buf, 80, NULL);
	if (keys && buf[0] != 0x9b)
		return TRUE;
	return FALSE;
}

#endif

/* Stub for Intuition routines - passes request on to main task.
 * DO NOT CALL WHILE FORBID()ING!
 * Thanks to Eddy Carroll for this.
 */
#define WTB(win)	IntuiOp(WindowToBack, win)
#define WTF(win)	IntuiOp(WindowToFront, win)
#define WACT(win)	IntuiOp((void (*))ActivateWindow, win)
/*extern void DoActiveWindow(APTR);
#define WACT(win)	IntuiOp(DoActiveWindow, win)*/
#define STB(scr)	IntuiOp(ScreenToBack, scr)

void
IntuiOp(void (*routine)(APTR), APTR parameter)
{
	BYTE oldpri = SetTaskPri(thistask, 21);

	intui_routine = routine;
	intui_parameter = parameter;
	Signal(thistask, intuiopsigflag);
	SetTaskPri(thistask, oldpri);
}


/* pattern-matching on screens */
#define IsXXXScreen(scr, pat)	(scr && MatchPattern(pat, scr->Title))
#define IsClickScreen(scr)	IsXXXScreen(scr, clickscrpat)
#define IsAutoScreen(scr)	IsXXXScreen(scr, autoscrpat)
#define IsPopWindow(win)	MatchPattern(popwinpat, win->Title)
#define IsClickWindow(win)	MatchPattern(clickwinpat, win->Title)

#define INTERRUPT void __interrupt __saveds

/* when is a window safe to bring to front and not already at front? */
#define OkayToPop(win)	(!win->ReqCount && !(win->Flags & (WFLG_MENUSTATE|WFLG_BACKDROP)) \
			 && win->WLayer->ClipRect && win->WLayer->ClipRect->Next)

/* WindowToFront only if no requester, not backdrop, not already front... */
void
PopToFront(struct Window *win)
{
    /* want to avoid popping immediately after mousebutton/keyboard */
    if (misspop)
	misspop = FALSE;
    else if (OkayToPop(win))
    {
	/* Does it pass pattern? */
	if (IsPopWindow(win))
	    WTF(win);
    }
}

/* modified from DMouse */
/* expects multitasking to be Forbid()en */
struct Window *
WindowUnderMouse()
{
    struct Layer *layer;
    struct Screen *scr = IntuitionBase->FirstScreen;
    WORD mousex, mousey;

    for (; scr; scr = scr->NextScreen)
    {
	/* these wont change since this routine called inside Forbid()/Permit() */
	mousey = scr->MouseY;
	mousex = scr->MouseX;

	if (layer = WhichLayer(&scr->LayerInfo, mousex, mousey))
	    break;
	if (mousey >= scr->ViewPort.DyOffset)
	    break;
    }

    return (layer ? layer->Window : NULL);
}

/* does active window have an active string gadget? */
BOOL
StrGadgetActive(struct Window * w)
{
    struct Gadget *g = w->FirstGadget;

    for (; g; g = g->NextGadget)
	if ((g->GadgetType & STRGADGET) && (g->Flags & GFLG_SELECTED))
	    return TRUE;
    return FALSE;
}

/* activate window under mouse */
/* context sensitive; why tells routine how to behave */
/* can be AUTO, KEY, SCREEN, CSCREEN */
#define AW	IntuitionBase->ActiveWindow
#define FS	IntuitionBase->FirstScreen
#define FSW	IntuitionBase->FirstScreen->FirstWindow
void
ActivateMouseWindow(BYTE why)
{
    struct Window *win;
    BOOL forbidden = TRUE;

    Forbid();

    if (win = WindowUnderMouse())	/* window exists to activate */
    {
	/* either window is not active or auto-activating - need to pop? */

	if (win->Flags & WFLG_WINDOWACTIVE)	/* already active - needs popped? */
	{
		if (why == AUTO && autopop && IsAutoScreen(win->WScreen))
		{
		        Permit(), forbidden = FALSE;
			PopToFront(win);
		}
	}
	else if (why != AUTO || IsAutoScreen(win->WScreen))
	{
		/* window is not active, should we try to activate it? */

		/* AW is IntuitionBase->ActiveWindow */
		if (!AW ||
		    !(AW->Flags & WFLG_MENUSTATE) &&		/* not showing menus */
		    !(why == KEY && StrGadgetActive(AW)))	/* no str gad active */
		{
		    /* finally... */
		    Permit(), forbidden = FALSE;

		    /* do autopop? */
		    if (why == AUTO && autopop)
			PopToFront(win);

		    if (why == KEY)		
			ActivateWindow(win);	/* need this to avoid losing keys */
		    else
			WACT(win);		/* ...activate window */
		}
	}
    }
    else /* no window under mouse... */
    {
	if (why == SCREEN && FS && FSW)
	{
	    Permit(), forbidden = FALSE;
	    WACT(FSW);				/* ...activate window */
	}
    }
    if (forbidden) Permit();
}


#include <graphics/gfxbase.h>

#define SPR0PTH 0x120	/* hard-addresses of sprite pointer registers */
#define SPR0PTL 0x122

static UWORD __chip empty[6];			/* empty sprite */
static UWORD blank_sprptL, blank_sprptH;	/* initialised in InitHandler */
static UWORD old_sprptL, old_sprptH;		/* old pointer parts */
static BOOL mouseoff;				/* is mouse off? (MB_SPRITES only) */

void
TurnMouseOn()	/* restore mouse-pointer */
{
	UWORD i, *sprcoplist;

	if (mouseblank == MB_SPRITES)		/* really dirty blanking */
	{					/* but guaranteed to work... */
		if (mouseoff)
		{
			mouseoff = FALSE;
			WaitTOF();
			ON_SPRITE;
		}
	}
	else if (old_sprptL || old_sprptH)	/* mouse is really blanked */
	{
		Forbid();
		sprcoplist = &GfxBase->copinit->sprstrtup[0];

		for (i = 0; i < 8; i++)
		{
			if (sprcoplist[0] == SPR0PTH && sprcoplist[2] == SPR0PTL)
			{	
				/* found sprite zero */
				if (sprcoplist[1] == blank_sprptH && sprcoplist[3] == blank_sprptL)
				{
					/* copper list changed by us... */
					sprcoplist[1] = old_sprptH;
					sprcoplist[3] = old_sprptL;
				}
				old_sprptH = old_sprptL = 0;
				break;
			}
			sprcoplist += 4;
		}
		Permit();
	}
}

void
TurnMouseOff()		/* blank mouse-pointer */
{
	UWORD i, *sprcoplist;

	if (mouseblank == MB_SPRITES)
	{
		if (!mouseoff)
		{
			mouseoff = TRUE;
			WaitTOF();
			OFF_SPRITE;
			custom.spr[0].dataa = custom.spr[0].datab=0;
		}
		return;
	}
	/* o.w. MB_COPPER */

	Forbid();
	sprcoplist = &GfxBase->copinit->sprstrtup[0];

	for (i = 0; i < 8; i++)
	{
		if (sprcoplist[0] == SPR0PTH && sprcoplist[2] == SPR0PTL)
		{
			if (sprcoplist[1] != blank_sprptH || 
			    sprcoplist[3] != blank_sprptL)
			{
				/* not blanked already */

				old_sprptH = sprcoplist[1];
				sprcoplist[1] = blank_sprptH;
				old_sprptL = sprcoplist[3];
				sprcoplist[3] = blank_sprptL;
			}
			break;
		}
		sprcoplist += 4;
	}
	Permit();
}


static BOOL blanked;
static struct Screen *blankscr;

/* blank display, by putting up a black screen */
void
BlankScreen()
{
    if (blankscr)
	ScreenToFront(blankscr);
    else if (blankscr = OpenScreenTags(NULL, SA_Depth, 1,
				       SA_Quiet, TRUE,
				       TAG_DONE))
    {
	SetRGB4(&blankscr->ViewPort, 0, 0, 0, 0);
	blanked = TRUE;
    }
    OFF_SPRITE; custom.spr[0].dataa = custom.spr[0].datab=0;
}

/* unblank display, i.e. close our screen */
void
UnBlankScreen()
{
    if (blankscr)
	CloseScreen(blankscr);
    blankscr = NULL;
    blanked = FALSE;
    ON_SPRITE;
}


#define ALL_BUTTONS	(IEQUALIFIER_LEFTBUTTON|IEQUALIFIER_RBUTTON|IEQUALIFIER_MIDBUTTON)
#define KEY_QUAL	(IEQUALIFIER_LSHIFT|IEQUALIFIER_RSHIFT \
			|IEQUALIFIER_CONTROL \
			|IEQUALIFIER_LALT|IEQUALIFIER_RALT \
			|IEQUALIFIER_LCOMMAND|IEQUALIFIER_RCOMMAND)
#define ALL_QUALS	(ALL_BUTTONS|KEY_QUAL)

/* the input handler itself */
INTERRUPT
Handler(CxMsg * CxMsg, CxObj * CO)
{
    static struct timeval lastclick;	/* last left-button click */
    static UBYTE apcount;	/* timer events since last mousemove */
    struct InputEvent *ev;


    ev = (struct InputEvent *) CxMsgData(CxMsg);

    if (ev->ie_Class == IECLASS_TIMER)
    {
	/*** AUTO-ACTIVATE/POP, SCREENBLANK, MOUSEBLANK ***/

	if (!(ev->ie_Qualifier & ALL_QUALS) &&	/* auto-activate? */
	    autopoint && apcount && !--apcount)
	{
	    ActivateMouseWindow(AUTO);
	}

	if (blanktimeout && !--blankcount)	/* blank screen? */
	{
	    BlankScreen();
	    blankcount = blanktimeout;	/* reset counter */
	}			/* in case sceen opens on top */

	if (mouseblank && !--mblankcount)	/* blank mouse? */
	{
	    TurnMouseOff();
	    /* in case someone else turns it on, reset counter */
	    mblankcount = mblanktimeout;
	}
    }
    else if (ev->ie_Class == IECLASS_DISKINSERTED)
    {
	blankcount = blanktimeout;	/* reset blanking countdown */
	if (blanked)			/* turn off screen-blanking */
	    UnBlankScreen();
    }
    else if ((ev->ie_Class == IECLASS_RAWKEY) && !(ev->ie_Code & IECODE_UP_PREFIX))
    {
	/*** MOUSEBLANK, KEYACTIVATE, KEYCLICK ***/

	blankcount = blanktimeout;	/* reset blanking countdown */
	if (blanked)			/* turn off screen-blanking */
	    UnBlankScreen();

	if (mouseblank)
	    if (ev->ie_Qualifier & IEQUALIFIER_LCOMMAND)
	    {
		/* this allows use of keyboard to move mouse */
		mblankcount = mblanktimeout;
	        TurnMouseOn();
	    }
	    else TurnMouseOff();	/* blank the mouse */
		


#ifdef LESS_CLICK
	if (click_volume && (AnsiEvent(ev) || !(ev->ie_Qualifier & IEQUALIFIER_REPEAT)))
#else
	if (click_volume)
#endif
	    Signal(thistask, clicksigflag);

	if (keyactivate)			/* perform key-activate */
	    ActivateMouseWindow(KEY);
    }
    else if (ev->ie_Class == IECLASS_RAWMOUSE)
    {
	/*** CLICKTOFRONT/BACK, AUTOACTIVATE ***/

	/* restore screen/mouse pointer */
	blankcount = blanktimeout;	/* reset blanking countdowns */
	mblankcount = mblanktimeout;
	if (blanked)		/* turn off screen-blanking */
	    UnBlankScreen();
	if (mouseblank == MB_SPRITES)
		TurnMouseOn(); /* not needed for MB_COPPER */

	/* window/screen cycling... */
	/* maybe should check for depth gadgets? nah... */

	if (!(ev->ie_Qualifier & KEY_QUAL))
	{
	    BOOL forbidden;	/* mustn't be Forbid()en when calling window op */

	    if (ev->ie_Qualifier & ALL_BUTTONS)
	    {
		misspop = TRUE;
		apcount = -1;	/* button - wait for move */
	    }
	    else
		apcount = TIMERCOUNT;	/* reset auto-activate count */

	    Forbid(), forbidden = TRUE;

	    if (clicktofront && ev->ie_Code == IECODE_LBUTTON)
	    {
		if (DoubleClick(lastclick.tv_secs,
				lastclick.tv_micro,
				ev->ie_TimeStamp.tv_secs,
				ev->ie_TimeStamp.tv_micro))
		{
		    struct Window *win = WindowUnderMouse();

		    if (win && IsClickWindow(win) &&
			IsClickScreen(win->WScreen) && OkayToPop(win))
		    {
			Permit(), forbidden = FALSE;
			WTF(win);	/* musn't be Forbid()en here */
		    }
		    lastclick.tv_secs = 0;
		    lastclick.tv_micro = 0;
		}
		else
		{
		    lastclick.tv_secs = ev->ie_TimeStamp.tv_secs;
		    lastclick.tv_micro = ev->ie_TimeStamp.tv_micro;
		}
	    }
	    else if (ev->ie_Code == IECODE_RBUTTON && (ev->ie_Qualifier & IEQUALIFIER_LEFTBUTTON))
	    {
		struct Window *win = WindowUnderMouse();

		if (win && !(win->Flags & WFLG_BACKDROP) &&
		    (win->NextWindow || win->WScreen->FirstWindow != win))
		{
		    if (clicktoback && IsClickScreen(win->WScreen) /*&& IsClickWindow(win)*/)
		    {
			Permit(), forbidden = FALSE;
			WTB(win);
		    }
		}
		else if (screencycle)
		{
		    Permit(), forbidden = FALSE;
		    ev->ie_Class = IECLASS_NULL;
		    STB(IntuitionBase->FirstScreen);
		    if (scractivate)
			ActivateMouseWindow(CSCREEN);
		}
	    }
	    if (forbidden) Permit();
	}
    }
}

/* close resources allocated for handler */
void
EndHandler()
{
    if (clickobj) DeleteCxObj(clickobj);
    FreeAudio();
#ifdef LESS_CLICK
    FreeConsoleDevice();
#endif
    if (intuiopsigbit != -1) FreeSignal(intuiopsigbit);
    if (clicksigbit != -1) FreeSignal(clicksigbit);
    UnBlankScreen();
    TurnMouseOn();
}

/* open resources needed for handler */
BOOL
InitHandler()
{
    if (((clicksigbit = AllocSignal(-1)) != -1) &&
    	((intuiopsigbit = AllocSignal(-1)) != -1) &&
#ifdef LESS_CLICK
	AllocAudio() &&
	InitConsoleDevice())
#else
	AllocAudio())
#endif
    {
	thistask = FindTask(NULL);	/* initialize liason structure */
	clicksigflag = 1 << clicksigbit;
	intuiopsigflag = 1 << intuiopsigbit;

	blank_sprptL = (LONG)empty &0xFFFF;
	blank_sprptH = (LONG)empty >> 16;

	clickobj = CxCustom(Handler, 0L);
	AttachCxObj(broker, clickobj);
	return TRUE;
    }
    EndHandler();
    return FALSE;
}
