/****************************************************************************
 *
 *		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 moudle installs the new blit routine, handles parsing the
 *		command line options etc. Scroll.s does the actual blitting.
 *
 ***************************************************************************/

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

#include "scroll.h"

#define NAME			"CpuBlit V0.96"
#define PORTNAME		NAME
#define SIGNON			NAME " \251 1991 Eddy Carroll.\n"

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

char HelpMsg[] =
NAME " \251 Eddy Carroll, Feb 1991. Replaces blitter with 68020/68030.\n"
"Usage: CpuBlit {option}\n"
"\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"
"    -q    Remove CpuBlit from the system\n"
"\n";

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

struct Gfxbase *GfxBase;

struct MyMsg {
	struct	Message msg;	/* Standard message structure		*/
	int		opt;			/* Requested mode of operation		*/
	int		result;			/* True if succesfully terminated	*/
} MyMsg, *msg;

#define MSG_OKAY	0		/* Message was handled correctly	*/
#define MSG_QUIT	1		/* CpuBlit was removed safely		*/
#define MSG_FAILED	2		/* CpuBlit couldn't be removed		*/


/****************************************************************************
 *
 *		Sets the blitter function to be executed according to the character
 *		option specified. Returns 1 if QUIT was requested, else zero.
 *
 ***************************************************************************/

int setopt(int opt)
{
	switch (opt) {
		case 'a':	BlitFunc   = StartBlit;
					OnlySingle = 0;
					Broken	   = 0;			return (0);
		case 'b':	Broken	   = 1;			return (0);
		case 'o':   OnlySingle = 1;			return (0);
		case 's':	BlitFunc   = ShareBlit; return (0);
		case '1':	BlitFunc   = Friend1;   return (0);
		case '2':	BlitFunc   = Friend2;   return (0);
		case 'q':	BlitFunc   = ExitBlit;  return (1);
		default:
			print(HelpMsg);
			exit(5);
	}
}

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

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

	BltBitMapAddress = 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) {
			if (setopt(msg->opt) == 0) {
				/*
				 *		Received a valid command line argument and
				 *		handled it.
				 */
				msg->result = MSG_OKAY;
			} else {
				/*
				 *		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, BltBitMapAddress);
				if (oldptr == NewBltBitMap) {
					installed = 0;
					msg->result = MSG_QUIT;
				} 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, *MyPort;
	int i;

	for (i = 1; i < argc; i++) {
		if (argv[i][0] == '-')
			setopt(argv[i][1]);		/* Check for valid command line option */
		else {
			print(HelpMsg);
			exit(5);
		}
	}

	BlitPort = FindPort(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.
		 */
		MyPort = CreatePort(NULL, 0);
		if (!MyPort) {
			print("CpuBlit: couldn't create local message port.\n");
			exit(10);
		}
		for (i = 1; i < argc; i++) {
			MyMsg.opt				= argv[i][1];
			MyMsg.msg.mn_Length		= sizeof(MyMsg);
			MyMsg.msg.mn_ReplyPort	= MyPort;
			PutMsg(BlitPort, &MyMsg);
			WaitPort(MyPort);
			switch (MyMsg.result) {
				case MSG_OKAY:
					break;

				case MSG_QUIT:
					print("CpuBlit removed successfully.\n");
					i = argc;	/* Force exit from loop */
					break;

				case 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");
					break;
			}
		}
		DeletePort(MyPort);
		exit(5);
	}

	/*
	 *		This is the first time we are being run, so parse the
	 *		command line and then detach ourselves from the CLI to run
	 *		in the background.
	 */
	for (i = 1; i < argc; i++) {
		if (setopt(argv[i][1])) {
			print("CpuBlit hasn't been installed yet.\n");
			exit(5);
		}
	}

	if (!res(NAME, 5, mainloop, 4000)) {
		print("Couldn't spawn background CpuBlit task.\n");
		exit(10);
	}
	print(SIGNON);
}
