/*
 * 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 <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 REBLANKCOUNT	10	/* 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;


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

  if (!ns)
	  ns = IntuitionBase->FirstScreen;

  if (ns != scr)
  {
      ScreenToBack (scr);
	  if (scractivate && ns)
	  {
		  win = WindowUnderMouse();
		  if (win)
			  ActivateWindow (win);
		  else 
			  if (ns->FirstWindow)
				  ActivateWindow (ns->FirstWindow);
	  }
  }
}


/* 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 (*)(APTR))ActivateWindow, win)
#define STB(scr)	IntuiOp(MyScreenToBack, scr)
#define STF(scr)	IntuiOp(ScreenToFront, 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 */


void
ScreenUnderMouse( void )
{
  for (mousescr = IntuitionBase->FirstScreen ; 
	   mousescr && mousescr->TopEdge > 0 && mousescr->MouseY < 0; 
	   mousescr = mousescr->NextScreen);
}


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

  for (mousescr = IntuitionBase->FirstScreen ; 
	   mousescr && mousescr->TopEdge > 0 && mousescr->MouseY < 0; 
	   mousescr = mousescr->NextScreen);
  
  if (mousescr)
	  layer = WhichLayer(&mousescr->LayerInfo, mousescr->MouseX, mousescr->MouseY);

  return (layer ? (struct Window *) 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)

/* 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 = autopoint_delay; /* 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)
							{
								Permit (), forbidden = FALSE;
								mousescr = win->WScreen;
								
								if (IsClickScreen ( mousescr ))
								{
									if (!(win->Flags & WFLG_BACKDROP) 
										&& IsClickWindow (win) && OkayToPop (win))
									{
										WTF (win); /* musn't be Forbid()en here */
									}
									else 
										if (screencycle)
										{
											STF (mousescr); /* musn't be Forbid()en here */
										}
								}
							}
							else 
							{
								ScreenUnderMouse();
								Permit (), forbidden = FALSE;
								if (IsClickScreen ( mousescr ) && screencycle)
								{
									STF (mousescr); /* 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)
							{
								Permit (), forbidden = FALSE;
								mousescr = win->WScreen;
								if (IsClickScreen ( mousescr ))
								{
									if (!(win->Flags & WFLG_BACKDROP) 
										&& (win->NextWindow || win->WScreen->FirstWindow != win))
									{
										WTB(win); /* musn't be Forbid()en here */
									}
									else 
										if (screencycle)
										{
											STB (mousescr); /* musn't be Forbid()en here */
										}
								}	
							}
							else 
							{
								ScreenUnderMouse();
								Permit (), forbidden = FALSE;
								if (IsClickScreen ( mousescr ) && screencycle)
								{
									ev->ie_Class = IECLASS_NULL;
									STB (mousescr);
								}
							}
						}
						else 
							if ((rmbactivate && (ev->ie_Code == IECODE_RBUTTON)) || 
								(mmbactivate && (ev->ie_Code == IECODE_MBUTTON)))
							{
								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;
}
