/****************************************************************************
 *
 *		CPUBLIT.C
 *
 *		(C) Copyright Eddy Carroll, 1991. Freely distributable.
 * 
 *		CpuBlit replaces the BltBitMap function in graphics.library with
 *		a version that uses the CPU where practical. This is up to 2.8
 *		times faster on a 68030 system.
 *
 *		This module installs the new blit routine, handles parsing the
 *		command line options etc. Scroll.s does the actual blitting.
 *
 ***************************************************************************/

#define DEBUG	0			/* If 1, then include debugging code			*/

#ifndef LATTICE_50
#include "system.h"
typedef void (*__fptr)();	/* The sort of thing returned by SetFunction	*/
#endif

#include "scroll.h"

#define YES		1
#define NO		0

#define NAME			"CpuBlit V1.00"
#define PORTNAME		NAME
#define SIGNON			NAME " \251 1991 Eddy Carroll.\n"
#define DATE			"Apr 1991"

#define print(s)		Write(Output(), s, strlen(s))
#define BltBitMap_LVO	(-30)	/* Offset of BltBitMap in graphics.library	*/

char HelpMsg[] =
NAME " \251 Eddy Carroll, " DATE ". Replaces blitter with 68020/68030.\n"
"Usage: CpuBlit {options} | {keywords}\n"
"\n"
"Options:\n"
"    -a    Always use CPU to do blits (default setting)\n"
"    -1    Use CPU for blits unless another task is ready to run\n"
"    -2    Use CPU for blits unless more than one task is ready to run\n"
"    -b    Use CPU even if bitmap isn't initialised correctly\n"
"    -o    Use CPU only when a single bitmap is involved\n"
"    -s    Use CPU for blits unless a blit is already in progress\n"
"    -pN   Ignore tasks with priority < N (default setting is 0)\n"
"    -q    Remove CpuBlit from the system\n"
"\n"
"Keywords: BLITMODE=[ALWAYS|ONE|TWO|SHARE] BROKEN SINGLE MINTASKPRI=n QUIT\n"
"\n"
"See the documentation for details about starting CpuBlit from Workbench.\n";

/****************************************************************************
 *
 *		Globals
 * 
 ***************************************************************************/

struct Gfxbase  	 *GfxBase;
struct IntuitionBase *IntuitionBase;
struct IconBase		 *IconBase;

/*
 *		Valid modes of operation for BlitMode
 */
#define BLIT_ALWAYS		0	/* Always use CPU for blits						*/
#define BLIT_ONE		1	/* Use CPU unless another task is ready to run	*/
#define BLIT_TWO		2	/* Use CPU unless more than one task is ready	*/
#define BLIT_SHARE		3	/* Use CPU unless CPU is already doing a blit	*/

/*
 *		This array corresponds to the above modes
 */
void (*BlitFuncs[])() = { StartBlit, Friend1, Friend2, ShareBlit };

/*
 *		All the settings that can be set by the program
 */
struct Settings {
	long	BlitMode;		/* Current mode of operation for blits  */
	long	OnlySingle;		/* True if restricting blits to 1 bmap	*/
	long	Broken;			/* True if handling broken software		*/
	BYTE	MinTaskPri;		/* All tasks less than this are ignored	*/
} Settings = {
	BLIT_ALWAYS, NO, NO, 0
};

/*
 *		Commands that we can send in a message
 */
#define MSG_GETVARS		0	/* Get copy of current CpuBlit settings	*/
#define MSG_SETVARS		1	/* Update CpuBlit settings				*/
#define MSG_QUIT		2	/* Remove background copy of CpuBlit	*/

struct MyMsg {
	struct	Message msg;	/* Standard message structure			*/
	struct	Settings *vars;	/* Settings used by CpuBlit				*/
	int		command;		/* Requested operation        			*/
	int		result;			/* True if command completed okay		*/
} MyMsg, *msg;

/*
 *		Return codes passed back in result
 */
#define MSG_OKAY	0		/* Message was handled correctly		*/
#define MSG_REMOVED	1		/* CpuBlit was removed safely			*/
#define MSG_FAILED	2		/* CpuBlit couldn't be removed			*/

/*
 *		Scalar variables
 */
struct MsgPort *LocalPort;	/* Local port for returned messages		*/
long QuitFlag;				/* True if user asks CpuBlit to quit	*/
long FromWorkbench;			/* True if started from Workbench		*/
long AlreadyRunning;		/* True if CpuBlit already installed	*/

/****************************************************************************
 *
 *		myexit(err)
 *
 *		Performs a small amount of cleanup and then exits to AmigaDos.
 *		Principally, handles cleaning up if we were run from Workbench.
 *
 ***************************************************************************/

void myexit(err)
{
	if (LocalPort)
		DeletePort(LocalPort);

	exit(err);
}

/****************************************************************************
 *
 *		SetVars(settings)
 *
 *		Sets the various CpuBlit flags according to the values in the
 *		supplied Settings structure.
 *
 ***************************************************************************/

void SetVars(struct Settings *settings)
{
	BlitFunc	= BlitFuncs[settings->BlitMode];
	OnlySingle	= settings->OnlySingle;
	Broken		= settings->Broken;
	MinTaskPri	= settings->MinTaskPri;
}

/****************************************************************************
 *
 *		ParseOption()
 *
 *		Parses an option string, setting the appropriate field in the
 *		master Settings structure. This routine handles both Unix-style
 *		-opts and also ReadArgs/ToolTypes keywords. Returns true if
 *		the option string made sense, false otherwise.
 *
 ***************************************************************************/

int ParseOption(char *opt)
{
#define MATCHSTR(s1,s2)		(!strnicmp(s1, s2, sizeof(s2)-1))

	if MATCHSTR(opt, "-a") {
		Settings.BlitMode	= BLIT_ALWAYS;
		Settings.OnlySingle	= NO;
		Settings.Broken		= NO;
	}
	else if MATCHSTR(opt, "-1")		Settings.BlitMode	= BLIT_ONE;
	else if MATCHSTR(opt, "-2")		Settings.BlitMode	= BLIT_TWO;
	else if MATCHSTR(opt, "-s")		Settings.BlitMode	= BLIT_SHARE;
	else if MATCHSTR(opt, "-b")		Settings.Broken		= YES;
	else if MATCHSTR(opt, "-o")		Settings.OnlySingle	= YES;
	else if MATCHSTR(opt, "-q")		QuitFlag            = YES;
	else if (MATCHSTR(opt, "-p") && opt[2])
		Settings.MinTaskPri = atoi(opt+2);
	else if MATCHSTR(opt, "BLITMODE") {
		char *p = opt + 8;
		if (*p++ && *p) {
			if		MATCHSTR(p, "ALWAYS")	Settings.BlitMode	= BLIT_ALWAYS;
			else if MATCHSTR(p, "ONE")		Settings.BlitMode	= BLIT_ONE;
			else if MATCHSTR(p, "TWO")		Settings.BlitMode	= BLIT_TWO;
			else if MATCHSTR(p, "SHARE")	Settings.BlitMode	= BLIT_SHARE;
		} else return (0);
	}
	else if MATCHSTR(opt, "BROKEN") {
		char *p = opt + 6;
		if (*p++ && MATCHSTR(p, "NO"))		Settings.Broken		= NO;
		else 								Settings.Broken		= YES;
	}
	else if MATCHSTR(opt, "SINGLE") {
		char *p = opt + 6;
		if (*p++ && MATCHSTR(p, "NO"))		Settings.OnlySingle	= NO;
		else 								Settings.OnlySingle = YES;
	}
	else if MATCHSTR(opt, "MINTASKPRI") {
		char *p = opt + 10;
		if (*p++ && *p)						Settings.MinTaskPri = atoi(p);
		else return (0);
	}
	else if MATCHSTR(opt, "QUIT")			QuitFlag = YES;
	else  return (0);

	return (1);
}

/****************************************************************************
 *
 *		MyFindPort(name)
 *
 *		Replacement for the FindPort() in exec.library. Under 1.3, FindPort()
 *		will cause Enforcer hits since FFS partitions create public ports
 *		that have no name (this is a no-no), and FindPort() doesn't check
 *		for null names.
 *		
 *		Even though this isn't really CpuBlit's problem, I had quite a few
 *		reports of CpuBlit causing Enforcer hits which turned out to be
 *		because of this, so I've added this workaround to keep people
 *		happy.
 *
 *		Commodore made FindPort() (actually FindName()) a bit more robust
 *		under Kickstart 2.0, so in that case we can safely use the standard
 *		routine.
 *
 ***************************************************************************/

struct MsgPort *MyFindPort(char *name)
{
	struct Node *nd;
	extern struct ExecBase *SysBase;

	if (SysBase->LibNode.lib_Version >= 36)
		return (FindPort(name));

	Forbid();
	for (nd = SysBase->PortList.lh_Head; nd; nd = nd->ln_Succ)
		if (nd->ln_Name && !strcmp(nd->ln_Name, name))
			break;
	Permit();
	return (struct MsgPort *)nd;
}


/****************************************************************************
 *
 *		mainloop()
 *
 *		This is the main event loop. It sits waiting for a message from
 *		other invocations of CpuBlit, which tell it to either change the
 *		current settings or to remove itself.
 *
 ***************************************************************************/

void mainloop(void)
{
	struct MsgPort *MyPort;
	int installed = 1;
	__fptr *BltBitMapPtr = (__fptr *)BltBitMapAddress;

	/*
	 *		We have to create our rendezvous port here rather than in the
	 *		mainline, since the message port depends on task information etc.
	 *		This is not altogether satisfactory since if it fails, there is
	 *		no way to tell the user (as a background task, we have no stdin
	 *		or stdout). But since port creation is unlikely to fail anyway,
	 *		it's not a big problem.
	 */
	MyPort = CreatePort(PORTNAME, 0);
	if (!MyPort)
		return;

	/*
	 *		Now have to open graphics.library, so that we can add in our
	 *		new patch. As above, if this fails there is no easy way to
	 *		tell the user. However, at least it won't crash the system.
	 */
	GfxBase = OpenLibrary("graphics.library", 0);
	if (!GfxBase)
		return;

	*BltBitMapPtr = SetFunction(GfxBase, BltBitMap_LVO, NewBltBitMap);

	/*
	 *		Now wait a message from another copy of CpuBlit. This will
	 *		either contain an updated command line argument or else a
	 *		request to quit.
	 */
	do {
		__fptr oldptr;

		WaitPort(MyPort);
		while ((msg = (struct MyMsg *)GetMsg(MyPort)) != NULL) {
			switch (msg->command) {

			case MSG_GETVARS:
				memcpy(msg->vars, &Settings, sizeof(Settings));
				break;

			case MSG_SETVARS:
				memcpy(&Settings, msg->vars, sizeof(Settings));
				SetVars(&Settings);
				break;

			case MSG_QUIT:
				/*
				 *		Try and remove ourselves. We have to surround this
				 *		with Forbid() to make sure that no other tasks manage
				 *		to call BltBitMap() in the case where we restore the
				 *		original vector and then realise that its current
				 *		replacement actually pointed to something other than
				 *		CpuBlit.
				 */
				Forbid();
				oldptr = SetFunction(GfxBase, BltBitMap_LVO, *BltBitMapPtr);
				if (oldptr == NewBltBitMap) {
					installed = 0;
					msg->result = MSG_REMOVED;
				} else {
					SetFunction(GfxBase, BltBitMap_LVO, oldptr);
					msg->result = MSG_FAILED;
				}
				Permit();
			}
			ReplyMsg(msg);
		}
	} while (installed);

	/*
	 *		Now our patch has been removed, it only remains to free up the
	 *		code. It is possible that someone is still in our blitter code.
	 *		We can determine this fairly safely by looking at UsageCount;
	 *		if this is -1, nobody is in our code. Otherwise, we wait until
	 *		it is -1 (delaying for a little while inbetween to give programs
	 *		a chance to run).
	 *
	 *		We also set the blitter test function to ExitBlit, so that if
	 *		someone does slip through our test and end up inside our code,
	 *		they will get rerouted back to the normal blitter code almost
	 *		immediately.
	 */
	DeletePort(MyPort);
	BlitFunc = ExitBlit;

	while (UsageCount != -1)
		Delay(10);				/* Wait 0.2 seconds */

	/*
	 *		Now we're completely finished so we can close the libraries
	 *		and exit.
	 */
	CloseLibrary(GfxBase);
}


/****************************************************************************
 *
 *		Mainline 
 *
 ***************************************************************************/

void main(int argc, char **argv)
{
	struct MsgPort *BlitPort;
	int i;

	FromWorkbench = (argc == 0);

	/*
	 *		Now see if CpuBlit is already running in
	 *		the background. If it is, then get a copy of the settings it
	 *		is currently using.
	 */
	BlitPort = MyFindPort(PORTNAME);
	if (BlitPort) {
		/*
		 *		The new blit routine has already been installed. So, send
		 *		it a message giving the command line options (if any)
		 *		to the remote routine, telling it to update its own options.
		 */
		AlreadyRunning = YES;
		LocalPort = CreatePort(NULL, 0);
		if (!LocalPort) {
			if (!FromWorkbench)
				print("CpuBlit: couldn't create local message port.\n");
			myexit(10);
		}
		MyMsg.msg.mn_ReplyPort	= LocalPort;
		MyMsg.command			= MSG_GETVARS;
		MyMsg.vars   			= &Settings;
		PutMsg(BlitPort, &MyMsg);
		WaitPort(LocalPort);
		GetMsg(LocalPort);
	}
	/*
	 *		Now parse the command line options, modifying our local copy
	 *		of Settings accordingly.
	 */
	if (FromWorkbench) {
		extern struct WBStartup *WBenchMsg;
		struct WBArg *wbarg = WBenchMsg->sm_ArgList;

		IconBase = (struct IconBase *)OpenLibrary("icon.library", 0);
		if (!IconBase)
			myexit(5);

		/*
		 *		Now walk down all the icons we've been given (probably
		 *		just our own tool icon) and parse the arguments present
		 *		in each one.
		 */
		for (i = 0; i < WBenchMsg->sm_NumArgs; i++, wbarg++) {
			struct DiskObject *dobj;
			char **tooltypes;
			BPTR olddir;

			if (wbarg->wa_Lock && *wbarg->wa_Name) {
				olddir = CurrentDir(wbarg->wa_Lock);
				if (dobj = GetDiskObject(wbarg->wa_Name)) {
					for (tooltypes = dobj->do_ToolTypes;
											*tooltypes; tooltypes++)
						ParseOption(*tooltypes);
				}
				FreeDiskObject(dobj);
				CurrentDir(olddir);
			}
		}
		CloseLibrary(IconBase);
	} else {
		/*
		 *		Plain jane CLI startup
		 */
#if DEBUG
		if (argv[1][0] == '!') {
			static buf[1000];
			sprintf(buf,
				"Blitmode   = %d\n"
				"OnlySingle = %d\n"
				"Broken     = %d\n"
				"MinTaskPri = %d\n",
				Settings.BlitMode, Settings.OnlySingle,
				Settings.Broken, Settings.MinTaskPri);
			print(buf);
			exit(5);
		}
#endif
		for (i = 1; i < argc; i++) {
			if (!ParseOption(argv[i])) {
				print(HelpMsg);
				myexit(5);
			}
		}
	}

	/*
	 *		Now we either send the options to the remote copy of CpuBlit
	 *		or install ourselves in the background.
	 */
	if (AlreadyRunning) {
		if (QuitFlag)
			MyMsg.command = MSG_QUIT;
		else
			MyMsg.command = MSG_SETVARS;
		PutMsg(BlitPort, &MyMsg);
		WaitPort(LocalPort);
		GetMsg(LocalPort);
		if (FromWorkbench && MyMsg.result == MSG_FAILED) {
			IntuitionBase = (struct IntuitionBase *)
							OpenLibrary("intuition.library", 0);
			if (IntuitionBase) {
				DisplayBeep(0);	/* Flash all screens -- pretty rude */
				CloseLibrary(IntuitionBase);
			}
		}
		if (!FromWorkbench) {
			if (MyMsg.result == MSG_REMOVED)
				print("CpuBlit removed successfully.\n");
			else if (MyMsg.result == MSG_FAILED) {
				print(
"Couldn't remove CpuBlit; someone else has patched BltBitMap. Please remove\n"
"any other utilities you have installed and then try again.\n");
				myexit(5);
			}
		}
		myexit(0);
	}

	/*
	 *		This is the first time we are being run. If we were run from
	 *		the CLI, detach ourselves (and allow the current process to
	 *		return to the CLI immediately). If we were run from Workbench,
	 *		then just call the message handling code directly and wait
	 *		until another copy of CpuBlit asks us to return.
	 */
	if (QuitFlag) {
		if (!FromWorkbench)
			print("CpuBlit hasn't been installed yet.\n");
		myexit(5);
	}

	SetVars(&Settings);
	if (FromWorkbench)
		mainloop();
	else {
		if (!res(NAME, 5, mainloop, 4000)) {
			print("Couldn't spawn background CpuBlit task.\n");
			myexit(10);
		}
		print(SIGNON);
	}
	myexit(0);
}
