/*
 * Commands relating to Yak hotkey types
 * mws, 27 April 1993
 */
#include <exec/types.h>
#include <exec/libraries.h>
#include <exec/memory.h>
#include <devices/inputevent.h>
#include <dos/dos.h>
#include <dos/datetime.h>
#include <dos/dostags.h>
#include <graphics/displayinfo.h>
#include <libraries/commodities.h>
#include <libraries/reqtools.h>
#include <libraries/locale.h>
#include <intuition/intuitionbase.h>

#include <proto/exec.h>
#include <proto/dos.h>
#include <proto/graphics.h>
#include <proto/commodities.h>
#include <proto/intuition.h>
#include <proto/reqtools.h>
#include <proto/locale.h>

#include <string.h>

#include "hotkey_types.h"
#include "yak.h"
#include "localprotos.h"
#ifndef YAK_LOCALE_STRINGS
#include "yak_locale_strings.h"
#endif

extern struct IntuitionBase *IntuitionBase;
extern struct Library *LocaleBase;

UWORD num_hkeys;

/*
 * Utility functions
 */
#define HOW_ZIP		0
#define HOW_SHRINK 	1
#define HOW_ENLARGE	2

static void HandleWindowKey(int how);
static void __saveds __asm putcharfunc( register __a0 struct Hook *h,
					register __a2 void *object,
					register __a1 char c);
static struct InputEvent *FlipIEvents(struct InputEvent *);
static int AddToStream(char *str); 
static struct Window * FindWBWindow(void);
static void __saveds DoPalette(void);
static int palette_count;
static LONG beginCommand(char *command);


/*
 * HotKey Action function prototypes
 */
static void 	act_DosCommand(YakHotKey *yhk), act_CloseWindow(YakHotKey *yhk),
		act_ZipWindow(YakHotKey *yhk), act_ShrinkWindow(YakHotKey *yhk),
		act_ExpandWindow(YakHotKey *yhk), act_CycleWindows(YakHotKey *yhk),
		act_OpenPalette(YakHotKey *yhk), act_ScreenToFront(YakHotKey *yhk),
		act_ScreenToBack(YakHotKey *yhk), act_ActivateWorkbench(YakHotKey *yhk),
		act_CentreScreen(YakHotKey *yhk), act_BlankDisplay(YakHotKey *yhk),
		act_InsertText(YakHotKey *yhk), act_InsertDate(YakHotKey *yhk),
		act_ShowInterface(YakHotKey *yhk);

#define EMPTY_MINLIST {NULL,NULL,NULL}
YakHotKeyType yhktypes[NUM_HOTKEY_TYPES] = {

	{ EMPTY_MINLIST, act_DosCommand, 0, TAKES_ARGUMENT|HAS_OPTIONS },
	{ EMPTY_MINLIST, act_CloseWindow, 0, 0L },
	{ EMPTY_MINLIST, act_ZipWindow, 0, 0L },
	{ EMPTY_MINLIST, act_ShrinkWindow, 0, 0L },
	{ EMPTY_MINLIST, act_ExpandWindow, 0, 0L },
	{ EMPTY_MINLIST, act_CycleWindows, 0, 0L },
	{ EMPTY_MINLIST, act_OpenPalette, 0, HAS_OPTIONS },
	{ EMPTY_MINLIST, act_ScreenToFront, 0, 0L },
	{ EMPTY_MINLIST, act_ScreenToBack, 0, 0L },
	{ EMPTY_MINLIST, act_ActivateWorkbench, 0, 0L },
	{ EMPTY_MINLIST, act_CentreScreen, 0, 0L },
	{ EMPTY_MINLIST, act_BlankDisplay, 0, 0L },
	{ EMPTY_MINLIST, act_InsertText, 0, TAKES_ARGUMENT },
	{ EMPTY_MINLIST, act_InsertDate, 0, TAKES_ARGUMENT },
	{ EMPTY_MINLIST, act_ShowInterface, 0, 0L }
};


/*
 * Utility functions
 */
#define AdjustedLeftEdge(w, width) \
	(w->LeftEdge + width > w->WScreen->Width ? \
	w->WScreen->Width - width : w->LeftEdge)
#define AdjustedTopEdge(w, height) \
	(w->TopEdge + height > w->WScreen->Height ? \
	w->WScreen->Height - height : w->TopEdge)

/* zip/enlarge or whatever the currently active window */
static void
HandleWindowKey(int how)
{
	struct Window *window;
	ULONG lock;

	lock = LockIBase(0);
	if (window = IntuitionBase->ActiveWindow)
	{
		switch (how)
		{
		case HOW_ZIP:
			if (window->Flags & WFLG_HASZOOM)
				ZipWindow(window);
			break;

		case HOW_SHRINK:
			if (window->Flags & WFLG_SIZEGADGET)
				ChangeWindowBox(window,
						window->LeftEdge,
						window->TopEdge,
						window->MinWidth,
						window->MinHeight);
			break;

		case HOW_ENLARGE:
		    {	/* sometimes Max vars are -1 == NO LIMIT */
			USHORT width, height;

			width = window->MaxWidth;
			if (width == -1) width = window->WScreen->Width-width;
			height = window->MaxHeight;
			if (height == -1) height = window->WScreen->Height-height;
			
			if (window->Flags & WFLG_SIZEGADGET)
				ChangeWindowBox(window,
						AdjustedLeftEdge(window, width),
						AdjustedTopEdge(window, height),
						width,
						height);
		    }
		    break;

		} /* switch */
	}
	UnlockIBase(lock);
}

/* hook-function for inserting chars into input-stream */
static void __saveds __asm
putcharfunc(	register __a0 struct Hook *h,
		register __a2 void *object,
		register __a1 char c)
{
	struct InputEvent ev;

	if (c)	/* stop at null-terminator */
	{
		ev.ie_NextEvent = NULL;
		InvertKeyMap(c, &ev, NULL);
		AddIEvents(&ev);
	}
}
/* and its associated structure */
static struct Hook putcharhook = {
	{NULL, NULL},
	(APTR)putcharfunc,
	NULL,
	NULL
};

/* given a list of input events, reverse their order */
/* NB: the list BETTER terminate... */
static struct InputEvent *
FlipIEvents(struct InputEvent *ie)
{
	struct InputEvent *oldtail, *newtail = NULL;

	while (ie)
	{
		/* remove head, set next event */
		oldtail = ie->ie_NextEvent;	/* this will be prev event */
		ie->ie_NextEvent = newtail;	/* add to head */
		newtail = ie;
		ie = oldtail;			/* what's left */
	}
	return newtail;
}

/* adds a string to the input-stream */
static int
AddToStream(char *str)
{
	struct InputEvent *ie;

	if (ie = InvertString(str, NULL))
	{
                ie = FlipIEvents(ie);
       	        AddIEvents(ie);
		FreeIEvents(ie);
		return TRUE;
	}
	return FALSE;
}

/* find (a) Workbench window */
static struct Window *
FindWBWindow()
{
    struct Screen *s = LockPubScreen("Workbench");
    if (s)
    {
	struct Window *w = s->FirstWindow;
	WBenchToFront();
	Forbid();
	for (; w; w = w->NextWindow)
		if (w->Flags & WFLG_WBENCHWINDOW)
			break;
	Permit();
	UnlockPubScreen(NULL, s);
	return w;
    }
    return NULL;
}

/* bring a palette up on frontmost screen */
/* CreateNewProc this function... */
static void __saveds
DoPalette()
{
	struct ReqToolsBase *ReqToolsBase;
	LONG tags[3];

	palette_count++;
	if (ReqToolsBase = (void *)OpenLibrary("reqtools.library", 0L))
	{
		tags[0] = RT_Screen;
		tags[1] = (LONG)ReqToolsBase->IntuitionBase->FirstScreen;
		tags[2] = TAG_DONE;
		(void) rtPaletteRequestA("Palette", NULL, (struct TagItem *)tags);
		CloseLibrary(ReqToolsBase);
	}
	palette_count--;
}

/*
 * Asynchronous external command started with its own autocon Input/Output
 * Result will only reflect whether System() call itself succeeded.
 * If System() call fails, result will be -1L; -2L for window failure.
 * We are using -2L as result if our Open of CON: fails
 */
#define AUTOCON "CON:0/25/640/150/Yak Command/AUTO/CLOSE/WAIT/ALT0/25/80/50"
static LONG
beginCommand(char *command)
{
	static ULONG stags[] = {
		SYS_Input, NULL,
		SYS_Output, NULL,
		SYS_Asynch, TRUE,
		SYS_UserShell, TRUE,
		NP_Priority, 0L,
		TAG_DONE
	};
	BPTR file;
	char *filename;

#ifdef NOTDEF	/* will this screw up sometimes? */
	char *t;
	int inquote = FALSE, usecon = TRUE;

	/* determine (simple-mindedly) whether there's redirection */
	for (t = command; *t; t++)
	{
		if (*t == '\"')
			if (t <= command || t[-1] != '*')
				inquote ^= 1;
		if (!inquote)
			if (*t == '>')
			{
				usecon = FALSE;
				break;
			}
	}

	filename = usecon ? AUTOCON : "NIL:";
#else
	filename = AUTOCON;
#endif
	if (file = Open(filename, MODE_OLDFILE))
        {
		stags[1] = (ULONG)file;
		return(SystemTagList(command, (struct TagItem *)stags));
        }
	else return(-2);
}


/*
 * the hotkey actions themselves
 */

/* asynchronously execute a dos command */
static void
act_DosCommand(YakHotKey *yhk)
{
	if (yhk->yhk_ArgStr)
	{
		LONG rc = beginCommand(yhk->yhk_ArgStr);

		if ((rc == -1) || (rc == -2))
			PostError("%s\n\"%s\"",
					  getString(Couldnt_start_ps_for_cmd_ERR), 
					  yhk->yhk_ArgStr);
	}
	else PostError("%s\"%s\".", 
				   getString(No_cmd_specified_for_hotkey_ERR), yhk->yhk_KeyDef);
}

static void
act_CloseWindow(YakHotKey *yhk)
{
	struct InputEvent ev;
	struct Window *window;
	ULONG lock;

	lock = LockIBase(0);
	window = IntuitionBase->ActiveWindow;
	UnlockIBase(lock);

	if (window && (window->IDCMPFlags & CLOSEWINDOW))
	{
		ev.ie_NextEvent = NULL;
		ev.ie_Class = IECLASS_CLOSEWINDOW;
		ev.ie_SubClass = 0;
		ev.ie_Code = 0;
		ev.ie_Qualifier = 0;
		ev.ie_EventAddress = 0;
		AddIEvents(&ev);
	}
}

static void
act_ZipWindow(YakHotKey *yhk)
{
	HandleWindowKey(HOW_ZIP);
}

static void
act_ShrinkWindow(YakHotKey *yhk)
{
	HandleWindowKey(HOW_SHRINK);
}

static void
act_ExpandWindow(YakHotKey *yhk)
{
	HandleWindowKey(HOW_ENLARGE);
}

static void
act_CycleWindows(YakHotKey *yhk)
{
#define NOCYCLEFLAGS (WFLG_BACKDROP)

	struct Screen *s;
	struct Window *w, *backmost;
	struct Layer *l;
	ULONG lock;

	lock = LockIBase(0);
	if (s = IntuitionBase->FirstScreen)
	{
	    Forbid();
		backmost = NULL;
		for (l = s->LayerInfo.top_layer; l; l = l->back)
		{
			w = (struct Window *)l->Window;
			if (w && !(w->Flags & NOCYCLEFLAGS))
				backmost = w;
		}

		if (backmost)
		{
			ScreenToFront(s);
			WindowToFront(backmost);
			ActivateWindow(backmost);
		}
	    Permit();
	}
	UnlockIBase(lock);
}

/* taglist for CreateNewProc */
static LONG palette_taglist[] = {
	NP_Entry, (LONG)DoPalette,
	NP_Name, (LONG)"Yak Palette",
	TAG_DONE
};

static void
act_OpenPalette(YakHotKey *yhk)
{
	CreateNewProc((struct TagItem *)palette_taglist);
}

static void
act_ScreenToFront(YakHotKey *yhk)
{
	struct Screen *s;
	ULONG lock;

	lock = LockIBase(0);
	for (s = IntuitionBase->FirstScreen; s; s = s->NextScreen)
		if (!s->NextScreen)
		{
			ScreenToFront(s);
			if (scractivate)
				ActivateMouseWindow(SCREEN);
			break;
		}
	UnlockIBase(lock);
}

static void
act_ScreenToBack(YakHotKey *yhk)
{
	MyScreenToBack(IntuitionBase->FirstScreen);
}

static void
act_ActivateWorkbench(YakHotKey *yhk)
{
	struct Window *w;

	if (w = FindWBWindow())
	{
		WBenchToFront();
		ActivateWindow(w);
	}
}

static void
act_CentreScreen(YakHotKey *yhk)
{
	struct Rectangle rect;
	struct Screen *s;
	ULONG	lock;
	ULONG	modeid;
	WORD	newle;

	lock = LockIBase(0);
	if ((s = IntuitionBase->FirstScreen) &&
	    (modeid = GetVPModeID(&s->ViewPort)) != INVALID_ID &&
	    QueryOverscan(modeid, &rect, OSCAN_TEXT))
	{
		newle = (rect.MinX + rect.MaxX + 1 - s->Width) >> 1;
		MoveScreen(s, newle - s->LeftEdge, 0);
	}
	UnlockIBase(lock);
}

static void
act_BlankDisplay(YakHotKey *yhk)
{
	BlankScreen();
}

static void
act_InsertText(YakHotKey *yhk)
{
	if (yhk->yhk_ArgStr)
	{
		if (!AddToStream(yhk->yhk_ArgStr))
			PostError("%s:\n \"%s\"", 
					  getString(Text_insertion_str_invalid_ERR), yhk->yhk_ArgStr); 
	}
	else PostError("%s \"%s\" %s", 
				   getString(Yak_text_hotkey_ERR),
				   yhk->yhk_KeyDef,
				   getString(Has_no_text_specified_ERR));
}

static void
act_InsertDate(YakHotKey *yhk)
{
	struct DateTime dat;
	DateStamp(&dat.dat_Stamp);

	if (LocaleBase)
	{
		if (yhk->yhk_ArgStr)
			FormatDate(locale, yhk->yhk_ArgStr, &dat.dat_Stamp, &putcharhook);
		else PostError("%s \"%s\" %s", 
					   getString(Yak_date_hotkey_ERR),
					   yhk->yhk_KeyDef,
					   getString(Has_no_format_specified_ERR));
	}
	else
	{
		/* pre-2.1 OS */
		char datestr[LEN_DATSTRING];

		dat.dat_Format = FORMAT_DOS;
		dat.dat_Flags = 0;
		dat.dat_StrDate = datestr;
		dat.dat_StrDay = dat.dat_StrTime = NULL;
		DateToStr(&dat);
		AddToStream(datestr);
	}
}

static void
act_ShowInterface(YakHotKey *yhk)
{
	ShowWindow();
}

/*
 * the generic hotkey action stub
 */
void
PerformAction(YakHotKey *yhk)
{
	if (yhk->yhk_Options)
	{
		struct Screen *scr;
		char *scrname;

		if (yhk->yhk_Options == WB_TO_FRONT)
			scrname = "Workbench";
		else
			scrname = NULL;
		
		if (scr = LockPubScreen(scrname))
		{
			ScreenToFront(scr);
			UnlockPubScreen(NULL, scr);
		}
	}

	/* and perform action (gobbledegook!? that's C for you...) */
	(*(yhktypes[yhk->yhk_Type].yhkt_Command))(yhk);
}

/*
 * YakHotKey manipulation routines
 */
static void internal_DeleteYakHotKey(YakHotKey *yhk);

/* allocate memory region of size len+1, copy string to it */
char *
DupStr(char *str)
{
	char *newstr;
	ULONG len;

	if (newstr = AllocVec(len=(strlen(str)+1),MEMF_CLEAR))
	{
		CopyMem(str,newstr,len);
	}
	return newstr;
}

/* free fields of hotkey */
static void
internal_DeleteYakHotKey(YakHotKey *yhk)
{
	if (yhk->yhk_ArgStr)
		FreeStr(yhk->yhk_ArgStr);
	if (yhk->yhk_KeyDef)
		FreeStr(yhk->yhk_KeyDef);
	if (yhk->yhk_CxObj)
		DeleteCxObjAll(yhk->yhk_CxObj);
}

/* free ALL memory associated with a hotkey and remove from list */
void
DeleteYakHotKey(YakHotKey *yhk)
{
	numkeys(yhk->yhk_Type)--;
	num_hkeys--;
	Remove((struct Node *)yhk);
	internal_DeleteYakHotKey(yhk);
	FreeVec(yhk);
}

/* change key definition */
int
ModifyYHKKeyDef(YakHotKey *yhk, char *keystr)
{
	CxObj *filter;

	if (filter = HotKey(keystr, broker_mp, (LONG)yhk))
	{
		/* delete old filter and use new one */
		if (yhk->yhk_CxObj)
			DeleteCxObjAll(yhk->yhk_CxObj);
		yhk->yhk_CxObj = filter;
		AttachCxObj(broker, filter);

		/* and store new keystr */
		if (yhk->yhk_KeyDef)
			FreeStr(yhk->yhk_KeyDef);
		if (yhk->yhk_KeyDef = DupStr(keystr))
		{
			yhk->yhk_Node.ln_Name = yhk->yhk_KeyDef;
			return TRUE;
		}
		PostError(getString(Allocation_ERR));
	}
	else PostError("%s: \"%s\"", getString(Invalid_hotkey_specif_ERR), keystr);

	return FALSE;
}

/* change argument string */
int
ModifyYHKArgStr(YakHotKey *yhk, char *argstr)
{
	char *newstr;

	if (argstr && *argstr)
	{
		if (newstr = DupStr(argstr))
		{
			FreeStr(yhk->yhk_ArgStr);
			yhk->yhk_ArgStr = newstr;
			return TRUE;
		}
		PostError(getString(Allocation_ERR));
	}
	else PostError(getString(Arg_unspecified_for_key_ERR));

	return FALSE;
}

/* add a new hotkey of given type */
YakHotKey *
NewYakHotKey(UWORD type)
{
	YakHotKey *newhk;

	if (newhk = AllocVec(sizeof(YakHotKey), MEMF_CLEAR))
	{
		newhk->yhk_Node.ln_Name = "<unset>";
		newhk->yhk_Type = type;
		AddTail(keylist(type), (struct Node *)newhk);
		numkeys(type)++;
		num_hkeys++;
	}
	return newhk;
}

/* initialise list */
void
InitYakHotKeyList()
{
	UWORD i;

	for (i = 0; i < NUM_HOTKEY_TYPES; i++)
		NewList(keylist(i));
}

/* delete whole list */
void
DeleteYakHotKeyList()
{
	YakHotKey *yhk;
	UWORD i;

	for (i = 0; i < NUM_HOTKEY_TYPES; i++)
		while (yhk = (YakHotKey *)RemHead(keylist(i)))
			DeleteYakHotKey(yhk);
}

/* okay to quit? (not so if palettes open) */
int
OkayToExit()
{
	return (palette_count == 0);
}
