/*
 * 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 <graphics/displayinfo.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/graphics.h>
#include <proto/intuition.h>
#include <proto/commodities.h>
#include <string.h>

#include "yak.h"
#include "beep.h"

static void __inline TurnMouseOn(void);
static void __inline TurnMouseOff(void);
static void __regargs IntuiOp(void (*routine)(APTR), APTR parameter);
static void __regargs PopToFront(struct Window *win);
static struct Window *WindowUnderMouse(void);
static BOOL __regargs StrGadgetActive(struct Window *w);

extern struct Custom __far custom;

#define TIMERCOUNT	2	/* how long mouse must stop before autopoint */
#define REBLANKCOUNT	20	/* how long to wait to reblank mouse */

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;


/* Stub for Intuition routines - passes request on to main task.
 * DO NOT CALL WHILE FORBID()ING!
 * Thanks to Eddy Carroll for this.
 */

/* for screen click-to-back */
void __stdargs
MyScreenToBack(struct Screen *scr)
{
	struct Layer *layer;
	struct Screen *ns = scr->NextScreen;
	struct Window *win;

	if (!ns) ns = IntuitionBase->FirstScreen;
	if (ns != scr)
	{
		ScreenToBack(scr);

		if (scractivate && ns)
		{
			layer = WhichLayer(&ns->LayerInfo, ns->MouseX, ns->MouseY);
			if (win = (layer ? layer->Window : NULL))
				ActivateWindow(win);
			else if (ns->FirstWindow)
				ActivateWindow(ns->FirstWindow);
		}
	}
}

#define WTB(win)	IntuiOp(WindowToBack, win)
#define WTF(win)	IntuiOp(WindowToFront, win)
#define WACT(win)	IntuiOp((void (*)(APTR))ActivateWindow, win)
#define STB(scr)	IntuiOp(MyScreenToBack, scr)

static void __regargs
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 screen/window titles */
#define IsXXXScreen(scr, pat)	(scr && (!scr->Title || MatchPattern(pat, scr->Title)))
#define IsClickScreen(scr)	IsXXXScreen(scr, clickscrpat)
#define IsAutoScreen(scr)	IsXXXScreen(scr, autoscrpat)
#define IsXXXWindow(win, pat)	(!win->Title || MatchPattern(pat, win->Title))
#define IsPopWindow(win)	IsXXXWindow(win,popwinpat)
#define IsClickWindow(win)	IsXXXWindow(win,clickwinpat)

#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... */
static void __regargs
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 */

static struct Screen *mousescr;		/* the screen under the mouse */

static struct Window *
WindowUnderMouse()
{
    struct Layer *layer = NULL;

    mousescr = IntuitionBase->FirstScreen;
    for (; mousescr; mousescr = mousescr->NextScreen)
    {
	if (layer = WhichLayer(&mousescr->LayerInfo,
			       mousescr->MouseX, mousescr->MouseY))
	    break;
	if (mousescr->MouseY >= mousescr->ViewPort.DyOffset)
	    break;
    }

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

/* does active window have an active string gadget? */
static BOOL __regargs
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, RMBACT */
#define AW	IntuitionBase->ActiveWindow
#define AWS	IntuitionBase->ActiveWindow->WScreen
#define FS	IntuitionBase->FirstScreen
#define FSW	IntuitionBase->FirstScreen->FirstWindow
void __regargs
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 (mousescr && mousescr->FirstWindow &&
	    ((why == SCREEN) || (why == RMBACT && AW && AWS != mousescr)))
	{
	    Permit(), forbidden = FALSE;
	    WACT(mousescr->FirstWindow);	/* ...activate window */
	}
    }
    if (forbidden) Permit();
}


static UWORD __chip posctldata[4];
static struct SimpleSprite simplesprite = { posctldata, 0, 0, 0, 0 };
static BOOL mouseoff;	/* is mouse off? (MB_SPRITES only) */

static void __inline
TurnMouseOn()	/* restore mouse-pointer */
{
	if (mouseblank == MB_SPRITES)		/* really dirty blanking */
	{					/* but guaranteed to work... */
		if (mouseoff)
		{
			Forbid();
			  WaitTOF();
			  ON_SPRITE;
			Permit();
		}
	}
	/* else using FreeSprite solution - unblanks when needed */
	mouseoff = FALSE;
}

static void __inline
TurnMouseOff()		/* blank mouse-pointer */
{
	if (!mouseoff)	/* no point in turning it off twice... */
	{
		Forbid();

		if (mouseblank == MB_SPRITES) {
			WaitTOF();
			OFF_SPRITE;
			custom.spr[0].dataa = custom.spr[0].datab=0;
		} else {
			FreeSprite(0);
			GetSprite(&simplesprite, 0);
		}
		
		Permit();

		mouseoff = TRUE;
	}
}


static BOOL blanked;
static struct Screen *blankscr;

/* blank display, by putting up a black screen */
/* this screen's displaymode is cloned from front screen */
void
BlankScreen()
{
    ULONG modeid = INVALID_ID;

    if (blankscr)
	ScreenToFront(blankscr);
    else
    {
	Forbid();
	if (FS)
		modeid = GetVPModeID(&(FS->ViewPort));
	Permit();
	if (modeid == INVALID_ID)
		modeid = LORES_KEY;

    	if (blankscr = OpenScreenTags(NULL, SA_Depth, 1,
				       SA_Quiet, TRUE,
				       SA_Behind, TRUE,
				       SA_DisplayID, modeid,
				       TAG_DONE))
	{
	    SetRGB4(&blankscr->ViewPort, 0, 0, 0, 0);
	    ScreenToFront(blankscr);
	    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)

#define sqr(x) ((x)*(x))

/* the input handler itself */
INTERRUPT
Handler(CxMsg * CxMsg, CxObj * CO)
{
    static struct timeval lastclick;	/* last left-button click */
    static WORD 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) && autopoint && !apcount--)
	{
	    ActivateMouseWindow(AUTO);
	}

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

	if (mouseblank && !--mblankcount)	/* blank mouse? */
	{
	    mouseoff = FALSE;		/* force reblank */
	    TurnMouseOff();
	    /* in case someone else turns it on, reset counter */
	    mblankcount = REBLANKCOUNT;
	}
    }
    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 */

	if (click_volume)
	    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 (mouseoff)
	    TurnMouseOn(); /* not needed for MB_COPPER */

	/* window/screen cycling... */
	/* maybe should check for depth gadgets? nah... */
	if (!(ev->ie_Qualifier & KEY_QUAL))
	{
	    if (!(ev->ie_Qualifier & ALL_BUTTONS))
	    {
		apcount = TIMERCOUNT;	/* reset auto-activate count */
	    }
	    else	/* a button - check out clicktofront etc. */
	    {
		/* mustn't be Forbid()en when calling window op */
		BOOL forbidden = TRUE;
		Forbid();

		misspop = TRUE;
		apcount = -1;	/* button - wait for move */

		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);
		    }
		}
		else if (rmbactivate && ev->ie_Code == IECODE_RBUTTON)
		{
		    Permit(), forbidden = FALSE;
		    ActivateMouseWindow(RMBACT);
		}
		if (forbidden) Permit();
	    }
	}
    }
    else if (ev->ie_Class == IECLASS_DISKINSERTED)
    {
	blankcount = blanktimeout;	/* reset blanking countdown */
	if (blanked)			/* turn off screen-blanking */
	    UnBlankScreen();
    }
}

/* close resources allocated for handler */
void
EndHandler()
{
    if (clickobj) DeleteCxObj(clickobj);
    FreeAudio();
    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) &&
	AllocAudio())
    {
	thistask = FindTask(NULL);	/* initialize liason structure */
	clicksigflag = 1 << clicksigbit;
	intuiopsigflag = 1 << intuiopsigbit;

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