/*
 * Yak version 1.3f
 * ----------------
 * [Yak == Yet Another K(?)ommodity
 *
 * There seems to be a profusion of commodities doing this or that.
 * Heres mine, to do what I want it to:
 *
 *	AutoActivate windows (SunMouse)
 *	ClickToFront, ClickToBack, ScreenCycle
 *	Close/Zip/Shrink/Zoom/Turn a window via keyboard.
 *	Bring up a palette on front screen.
 *	Insert date into read-stream.
 *	Produce key-click (like my keyclick program).
 *	Some other things...
 *
 * Martin W. Scott, 9/92.
 */
#include <exec/types.h>
#include <exec/libraries.h>
#include <exec/memory.h>
#include <devices/inputevent.h>
#include <dos/dos.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 "yak.h"
#include "beep.h"
#include "icon.h"
#include "version.h"

#include "WB2CLI.h"	/* we'll be a shell process */
#define DEF_CURRENTDIR	"SYS:"

/* local prototypes for main.c */
static void CloseResources(void);
static BOOL OpenResources(void);
static void __regargs EasyEasyRequest(char *str);
static void FreePatterns(void);
static void DoCloseWindow(void);
static void __regargs HandleWindowKey(LONG what);
static void InsertDate(void);
static void DoCycleWindows(void);
static void DoCycleScreens(void);
static void DoCenterScreen(void);
static struct Window *FindWBWindow(void);
static LONG ProcessMsg(void);
static void __saveds DoPalette(void);

void __saveds __asm putcharfunc(register __a0 struct Hook * h,
				register __a2 void * object,
				register __a1 char c);
void __main(void);



#ifdef __SASC_60	/* save some bytes */
void _MemCleanup(void){}
#else
void MemCleanup(void){}
#endif

/* global data - library bases and the like */
extern struct WBStartup *_WBenchMsg;
struct Library	*CxBase, *IconBase,
		*GadToolsBase, *LayersBase,
		*WorkbenchBase, *LocaleBase;
struct IntuitionBase *IntuitionBase;
struct GfxBase *GfxBase;
struct Locale *locale;
struct MsgPort *broker_mp;
CxObj *broker;

char *versionstr="\0$VER: "VERSION_NAME;

struct NewBroker newbroker = {
    NB_VERSION,
    "Yak",           /* string to identify this broker */
    VERSION_STR,
    "Multi-purpose commodity",
    NBU_UNIQUE | NBU_NOTIFY,      /* Don't want any new commodities
                                   * starting with this name.  If someone
                                   * tries it, let me know */
    COF_SHOW_HIDE
};

ULONG		wndsigflag;		/* here for overlay purposes */
ULONG		cxsigflag;
ULONG		invariantsigflag;
BYTE		palette_count;		/* how many palettes are open */

/* from handler.c */
extern ULONG	clicksigflag, intuiopsigflag;
extern void (*intui_routine)(APTR);	/* for intui_op's */
extern APTR intui_parameter;

/* from icon.c */
extern ULONG	appsigflag;

/* close what we opened */
static void
CloseResources()
{
	if (IntuitionBase) CloseLibrary((struct Library *)IntuitionBase);
	if (GfxBase) CloseLibrary((struct Library *)GfxBase);
	if (CxBase) CloseLibrary(CxBase);
	if (LayersBase) CloseLibrary(LayersBase);
	if (IconBase) CloseLibrary(IconBase);
	if (GadToolsBase) CloseLibrary(GadToolsBase);
	if (WorkbenchBase) CloseLibrary(WorkbenchBase);
	if (LocaleBase)
	{
		CloseLocale(locale);
		CloseLibrary(LocaleBase);
	}
}

/* open libraries, devices that we need */
static BOOL
OpenResources()
{
	if ((IntuitionBase = (void *)OpenLibrary("intuition.library", 37L)) &&
	    (GfxBase = (void *)OpenLibrary("graphics.library", 37L)) &&
	    (CxBase = OpenLibrary("commodities.library", 37L)) &&
	    (LayersBase = OpenLibrary("layers.library", 37L)) &&
	    (IconBase = OpenLibrary("icon.library", 37L)) &&
	    (GadToolsBase = OpenLibrary("gadtools.library", 37L)) &&
	    (WorkbenchBase = OpenLibrary("workbench.library", 37L)))
        {
		if (LocaleBase = OpenLibrary("locale.library", 38L))
			if (!(locale = OpenLocale(NULL)))
			{
				PostError("No locale set!\n");
				CloseLibrary(LocaleBase);
				LocaleBase = NULL;
			}

		return TRUE;
	}
	CloseResources();
	return FALSE;
}

/* pop up a requester */
static void __regargs
EasyEasyRequest(char *str)
{
	struct EasyStruct es;

	es.es_StructSize = sizeof(struct EasyStruct);
	es.es_Flags = 0L;
	es.es_Title = "Yak Message";
	es.es_TextFormat = str;
	es.es_GadgetFormat = "OK";
	EasyRequestArgs(NULL, &es, NULL, NULL);
}

/* display an error appropriately */
void __regargs
PostError(char *str)
{
	if (IntuitionBase)
		EasyEasyRequest(str);
	else
	{
		PutStr("Yak: ");
		PutStr(str);
		PutStr("\n");
	}
}

/* modify hk array with new hotkey */	
BOOL __regargs
AddHotKey(char *newstr, UWORD n)
{
	CxObj *newfilter;
	char *hotkey = newstr ? newstr : hk[n].key;

	if (!hotkey[0])		/* hotkey being cancelled */
	{
		if (hk[n].filter) DeleteCxObjAll(hk[n].filter);
		hk[n].filter = NULL;
		hk[n].key[0] = '\0';
		return TRUE;
	}
	else if (newfilter = HotKey(hotkey, broker_mp, hk[n].msgid))
	{
		if (hk[n].filter) DeleteCxObjAll(hk[n].filter);
		hk[n].filter = newfilter;
		if (newstr) strncpy(hk[n].key, newstr, MAXKEYLEN);
		AttachCxObj(broker, hk[n].filter);
		return TRUE;
	}
	PostError("Invalid hotkey definition");
	return FALSE;
}

/* parse pattern, report errors */
BOOL __regargs
InitPattern(char *newpatstr, UWORD n)
{
	char *patstr = newpatstr ? newpatstr : patterns[n].patstr;
	char *pat;
	LONG len;

	if (pat = AllocVec(len = strlen(patstr)*3+10, MEMF_CLEAR))
	{
		if (ParsePattern(patstr, pat, len) != -1)
		{
			if (newpatstr) strncpy(patterns[n].patstr, newpatstr, PATLEN);
			if (patterns[n].pat) FreeVec(patterns[n].pat);
			patterns[n].pat = pat;
			return TRUE;
		}
		PostError("Pattern parse error");
		FreeVec(pat);
	}
	else PostError("Allocation error");
	return FALSE;
}

static void
FreePatterns()
{
	UWORD i;

	for (i = 0; i < NUM_PATTERNS; i++)
		if (patterns[i].pat) FreeVec(patterns[i].pat);
}

void
__main()		/* Yak: multi-function commodity */
{
	BPTR	newdir = NULL, olddir;

	if (OpenResources())
	{
	    if (broker_mp = CreateMsgPort())
            {
                newbroker.nb_Port = broker_mp;
                cxsigflag = 1L << broker_mp->mp_SigBit;

		/* process tool-types */
		GetOurIcon(_WBenchMsg);
                newbroker.nb_Pri = (BYTE)TTInt("CX_PRIORITY", 0);

		if (_WBenchMsg)
		{
			if (newdir = Lock(DEF_CURRENTDIR, ACCESS_READ))
				olddir = CurrentDir(newdir);
			WB2CLI(_WBenchMsg,4000,DOSBase);		/* get it over with... */
		}

		if (broker = CxBroker(&newbroker, NULL))
                {
			/* HANDLER FIRST, SO IT SEES EVERYTHING!!! */
			if (InitHandler())
			{
				LoadSettings(CONFIG_FILE);
				/* tooltype overrides config file */
				AddHotKey(TTString("CX_POPKEY", NULL), HK_POPKEY);

				MyPri(ACTIVE);
				ActivateCxObj(broker, 1L);

				if (TTBool("CX_POPUP", FALSE))
					ShowWindow();

				if (!TTBool("NOICON", FALSE))
				{
					if (!MakeOurAppIcon(TTString("ICONNAME", "Yak!")))
						if (_WBenchMsg)
							PostError("Couldn't create icon");
				}
				/* WB makes a copy of icon, so can free it */
				FreeOurIcon();

				/* these are the signals waited for, + window sig */
				invariantsigflag = SIGBREAKF_CTRL_C | cxsigflag
						   | clicksigflag | intuiopsigflag
						   | appsigflag;

				while (ProcessMsg())
					;
				HideWindow();
				RemoveOurAppIcon();

				MyPri(ORIGINAL);

				EndHandler();
				FreePatterns();
			}
			else PostError("Allocation error");

                    DeleteCxObjAll(broker);
                }

		if (newdir) {
			CurrentDir(olddir);
			UnLock(newdir);
		}

                DeleteMsgPort(broker_mp);
		FreeOurIcon();	/* may already be gone, but so what? */
            }
	    else PostError("Allocation error");

	    CloseResources();
	}
	else PostError("Resource error");
}

/* simulate user clicking close-gadget */
static void
DoCloseWindow()
{
	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);
	}
}

#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 __regargs
HandleWindowKey(LONG what)
{
	struct Window *window;
	ULONG lock;

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

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

		case HK_ZOOMKEY:
		    {	/* 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);
}

/* bring a palette up on frontmost screen */
/* CreateNewProc it... */
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--;
}

/* hook-function for inserting chars into input-stream */
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);
		ev.ie_Code |= IECODE_UP_PREFIX;
		AddIEvents(&ev);
	}
}
/* and its associated structure */
struct Hook putcharhook = {
	{NULL, NULL},
	(APTR)putcharfunc,
	NULL,
	NULL
};

/* if locale present, insert date as string into read-stream */
static void
InsertDate()
{
	struct DateStamp datestamp;
	DateStamp(&datestamp);
	if (LocaleBase)
		FormatDate(locale, DateFormat, &datestamp, &putcharhook);
	else
		DisplayBeep(NULL);
}

/* cyle windows, like IHelp */
/*#define NOCYCLEFLAGS (WFLG_WBENCHWINDOW|WFLG_BACKDROP)*/
#define NOCYCLEFLAGS (WFLG_BACKDROP)
static void
DoCycleWindows()
{
	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);
}

/* cyle screens, like IHelp */
static void
DoCycleScreens()
{
	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);
}

/* center horizontally the frontmost screen */
static void
DoCenterScreen()
{
	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);
}

/* find (a) Workbench window; used below, AND in handler.c */
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;
}

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

/* monitor cx port, act on messages */
static LONG
ProcessMsg(void)
{
    CxMsg *msg;
    ULONG sigrcvd, msgid, msgtype;
    LONG returnvalue = 1L;

    sigrcvd = Wait(invariantsigflag | wndsigflag);

    if ((sigrcvd & clicksigflag) && click_volume)	/* keyclick please */
    {
	beep(click_volume);
	    Delay(1);   /* avoid ugly sound when key repeating */
    }

    if (sigrcvd & intuiopsigflag)	/* intuiop requested */
	intui_routine(intui_parameter);

    if (sigrcvd & appsigflag)		/* settings change */
    {
	RespondToAppIcon();
	ShowWindow();
    }

    if (sigrcvd & wndsigflag)		/* settings change */
    	if (HandleIDCMP() != HELP_OKAY)
		returnvalue = 0;
	else if (!wndsigflag)		/* window gone */
		DummyOverlay();		/* remove code */

    while(msg = (CxMsg *)GetMsg(broker_mp))
    {
        msgid = CxMsgID(msg);
        msgtype = CxMsgType(msg);
        ReplyMsg((struct Message *)msg);

        switch(msgtype)
        {
            case CXM_IEVENT:

                switch(msgid)
                {
		    case HK_POPKEY:
			ShowWindow();	/* check error return? */
			break;

		    case HK_CLOSEKEY:
			DoCloseWindow();
			break;

		    case HK_ZIPKEY:
		    case HK_SHRINKKEY:
		    case HK_ZOOMKEY:
			HandleWindowKey(msgid);
                        break;

		    case HK_WORKBENCH:
			{
			    struct Window *w;

			    if (w = FindWBWindow())
			    {
				WBenchToFront();
				ActivateWindow(w);
			    }
			}
			break;
				
		    case HK_POPPALKEY:
			CreateNewProc((struct TagItem *)palette_taglist);
			break;

		    case HK_POPCLI:
			{
			    struct Screen *s;
			    if (s = LockPubScreen(NULL))
			    {
				ScreenToFront(s);
				UnlockPubScreen(NULL, s);
			    }
			    MyPri(ORIGINAL);
			    SystemTagList(PopCommand, NULL);
			    MyPri(ACTIVE);
			}
			break;

		    case HK_INSDATE:
			InsertDate();
			break;

		    case HK_CYCLEWIN:
			DoCycleWindows();
			break;

		    case HK_CYCLESCR:
			DoCycleScreens();
			break;

		    case HK_CENTERSCR:
			DoCenterScreen();
			break;

		    case HK_SCRTOBACK:	/* replacement for lcommand m */
			MyScreenToBack(IntuitionBase->FirstScreen);
			break;

		    case HK_BLANK:
			BlankScreen();
			break;
                }
                break;

            case CXM_COMMAND:
                switch(msgid)
                {
                    case CXCMD_UNIQUE:
		    case CXCMD_APPEAR:
			ShowWindow();	/* check error return? */
			break;

		    case CXCMD_DISAPPEAR:
			HideWindow();
			DummyOverlay();
			break;

                    case CXCMD_DISABLE:
                        ActivateCxObj(broker, 0L);
                        break;

                    case CXCMD_ENABLE:
                        ActivateCxObj(broker, 1L);
                        break;

                    case CXCMD_KILL:
                        returnvalue = 0L;
                        break;
                }
                break;
        }
    }

    if (sigrcvd & SIGBREAKF_CTRL_C)
        returnvalue = 0L;

    if (!returnvalue && palette_count)
    {
	PostError("Cannot exit yet:\npalette(s) remain open");
	returnvalue = 1;
    }
    
    return(returnvalue);
}
