/*	Print-Handler.c v1.1, 30 September 1989
 *
 *	This  printer  device handler implements the single sheet support
 *	and data-spooling facility the usual PRT:  device does not have.
 *
 *	Each time a page has been successfully transferred to the printer
 *	and  single paper sheets are selected in Preferences, a requestor
 *	pops  up  on  the Workbench screen telling the user to insert the
 *	next  sheet  of  paper.   If a process addresses this handler via
 *	Open()  and  Write()s  data  to it, the incoming text is buffered
 *	until the data stream is Close()d.  After that the buffer sent to
 *	the  printer  device and finally gets discarded.  Note that there
 *	will  be  a buffer for each calling process (up to twenty callers
 *	are  allowed),  so  you may get low on memory.  This handler will
 *	notice low memory situations and return a write error in critical
 *	situations.   This handler does no first-in-first-out or last-in-
 *	first-out data stack handling, the first data to come out is from
 *	the first buffer to be closed
 *
 *	Don't  be  confused if the requestor pops up while the printer is
 *	still  working.  Since most printers have a write buffer of their
 *	own true synchronous printer I/O is almost impossible.
 *
 *	Installation is as follows:
 *
 *	* Add the following lines to your DEVS:Mountlist
 *
 *	  PRT:	Handler		= L:Print-Handler
 *	  	Stacksize	= 3000
 *	  	Priority	= 5
 *	  	GlobVec		= 1
 *	  #
 *
 *	* Add the following lines to your S:Startup-Sequence
 *
 *	  Assign PRT: Remove
 *	  Mount PRT:
 *
 *	* Copy Print-Handler to your L:
 *
 *	Skeleton handler code by Phillip Lindsay (C) 1986 Commodore
 *	You may freely distribute this source and use it for Amiga Development,
 *	as long as the Copyright notice is left intact.
 *
 *	Print-Handler (C) Copyright 1989 by Olaf Barthel & ED Hannover
 *
 *	Contact:	Olaf Barthel, Electronic Design Hannover
 *			Brabeckstrasse 35
 *			D-3000 Hannover 71
 *
 *			Federal Republic of Germany
 */

#include <intuition/intuitionbase.h>
#include <libraries/filehandler.h>
#include <libraries/dosextens.h>
#include <devices/prtbase.h>
#include <devices/printer.h>
#include <exec/memory.h>

	/* This version of BADDR() has no problems with castings. */

#undef  BADDR
#define BADDR(x) ((APTR)((long)x << 2))

	/* Define the packet types which are not available
	 * in the dos 1.1/1.2 include files.
	 */

#define ACTION_FIND_INPUT	1005
#define ACTION_FIND_OUTPUT	1006
#define ACTION_END		1007

	/* Some BCPL boolean definitions. */

#define DOS_FALSE	 0
#define DOS_TRUE	-1

	/* Forward declarations. */

extern struct Library		*OpenLibrary();
extern struct Process		*FindTask();
extern struct MsgPort		*CreatePort();
extern struct MsgPort		*FindPort();
extern struct Message		*GetMsg();
extern struct IOStdReq		*CreateStdIO();
extern void			*AllocMem();

	/* This is a data segment, a part of a larger printer
	 * buffer used for data-spooling.
	 */

struct DataSeg
{
	struct DataSeg	*NextSeg;	/* Next segment. */

	APTR		Buffer;		/* Data array. */
	long		Length;		/* Length of array. */
};

	/* Some global data. */

struct IntuitionBase	*IntuitionBase;
struct Preferences	*Preferences;
struct DataSeg		*PrintSlot[20];
struct IOStdReq		*PrinterDevice;
struct MsgPort		*PrinterPort;

	/* Position counters to take care of the current page length. */

long LinesDone = 0;
long ColumnsDone = 0;

	/* CreateSeg(Buffer,Length):
	 *
	 *	Creates a new segment entry for a linked list of
	 *	buffers.
	 */

struct DataSeg *
CreateSeg(Buffer,Length)
register APTR Buffer;
register long Length;
{
	register struct DataSeg *NewSeg;

		/* Allocate memory for the list element. */

	if(NewSeg = (struct DataSeg *)AllocMem(sizeof(struct DataSeg),MEMF_PUBLIC | MEMF_CLEAR))
	{
			/* Allocate memory for the data buffer. */

		if(NewSeg -> Buffer = (APTR)AllocMem(Length,MEMF_PUBLIC | MEMF_CLEAR))
		{
				/* Copy the data and set the buffer size. */

			CopyMem(Buffer,NewSeg -> Buffer,Length);
			NewSeg -> Length = Length;

				/* Return the new segment. */

			return(NewSeg);
		}

			/* Free the segment data. */

		FreeMem(NewSeg,sizeof(struct DataSeg));
	}

		/* We failed. */

	return(NULL);
}

	/* DeleteSeg(OldSeg):
	 *
	 *	Deletes both the contents and the memory occupied
	 *	by a data segment and returns a pointer to the
	 *	next segment.
	 */

struct DataSeg *
DeleteSeg(OldSeg)
register struct DataSeg *OldSeg;
{
	register struct DataSeg *NextSeg = NULL;

		/* Valid pointer given? */

	if(OldSeg)
	{
			/* Remember this. */

		NextSeg = OldSeg -> NextSeg;

			/* Free the contents of the buffer. */

		if(OldSeg -> Buffer && OldSeg -> Length)
		{
			FreeMem(OldSeg -> Buffer,OldSeg -> Length);

				/* Zero this out. */

			OldSeg -> Buffer = NULL;
			OldSeg -> Length = 0;
		}

			/* Free the segment data. */

		FreeMem(OldSeg,sizeof(struct DataSeg));
	}

		/* Return pointer to next segment or null. */

	return(NextSeg);
}

	/* ReturnPacket(Packet,res1,res2):
	 *
	 *	This one returns an AmigaDOS packet we have just
	 *	received.
	 */

void
ReturnPacket(Packet,res1,res2)
register struct DosPacket *Packet;
register ULONG res1,res2;
{
	register struct Message	*Message;
	register struct MsgPort	*ReplyPort;
	register struct Process	*ThisProg;
 
	Packet -> dp_Res1		= res1;
	Packet -> dp_Res2		= res2; 
	ReplyPort			= Packet -> dp_Port;
	Message				= Packet -> dp_Link;
	ThisProg			= (struct Process *)FindTask(NULL);
	Packet -> dp_Port		= &ThisProg -> pr_MsgPort;

	Message -> mn_Node . ln_Name	= (char *)Packet;
	Message -> mn_Node . ln_Succ	= NULL;
	Message -> mn_Node . ln_Pred	= NULL;
 
	PutMsg(ReplyPort,Message); 
}

	/* TaskWait():
	 *
	 *	This one waits for an AmigaDOS packet to arrive
	 *	and returns a pointer to it.
	 */

struct DosPacket *
TaskWait()
{
	register struct Process	*ThisProg;
	register struct MsgPort	*MyPort;
	register struct Message	*MyMessage;

	ThisProg = (struct Process *)FindTask(NULL);
	MyPort = &ThisProg -> pr_MsgPort;

	WaitPort(MyPort);
	MyMessage = (struct Message *)GetMsg(MyPort);

	return((struct DosPacket *)MyMessage -> mn_Node . ln_Name);
} 

	/* PrintIt(Buffer,Length):
	 *
	 *	This function handles the printing. We could as well
	 *	replace the data <-> printer transfer by a short call
	 *	to Write(); probably the easiest way to implement
	 *	output redirection.
	 */

long
PrintIt(Buffer,Length)
register APTR Buffer;
register long Length;
{
		/* Are buffer and size both valid? */

	if(Buffer && Length)
	{
			/* Send the text to the printer. */

		PrinterDevice -> io_Command	= CMD_WRITE;
		PrinterDevice -> io_Data	= Buffer;
		PrinterDevice -> io_Length	= Length;

		return(DoIO(PrinterDevice));
	}

	return(-1);
}

	/* DoRequest():
	 *
	 *	Displays the requestor asking to insert the next
	 *	sheet of paper and returns the result.
	 */

BOOL
DoRequest()
{
	static struct TextAttr DefaultFont =
	{
		(UBYTE *)"topaz.font",9,FS_NORMAL,FPF_ROMFONT
	};

	static struct IntuiText RequestTxt[] =
	{
		{0,1,JAM1,18, 4,(struct TextAttr *)&DefaultFont,(UBYTE *)"Finished with current page,",	&RequestTxt[1]},
		{0,1,JAM1,18,14,(struct TextAttr *)&DefaultFont,(UBYTE *)"please insert next sheet.",	NULL},
		{0,1,JAM1, 5, 3,(struct TextAttr *)&DefaultFont,(UBYTE *)"Ready",			NULL},
		{0,1,JAM1, 5, 3,(struct TextAttr *)&DefaultFont,(UBYTE *)"Abort",			NULL}
	};

		/* Open the Workbench first, we don't want to have a dead
		 * Workbench screen hanging around if a task preferred to
		 * close it.
		 */

	OpenWorkBench();

	return(AutoRequest(NULL,&RequestTxt[0],&RequestTxt[2],&RequestTxt[3],NULL,NULL,326,59));
}

	/* PrintData(Buffer,Length):
	 *
	 *	This is the main interface processing the incoming
	 *	data.
	 */

BOOL
PrintData(Buffer,Length)
register char *Buffer;
register long Length;
{
	register long i,j,InBuff = 0,PaperWidth = Preferences -> PrintRightMargin - Preferences -> PrintLeftMargin;
	char LineStack[1024];

		/* Clear out the contents of the line buffer. */

	for(i = 0 ; i < 1024 ; i++)
		LineStack[i] = 0;

	if(Buffer)
	{
		for(i = 0 ; i < Length ; i++)
		{
				/* Put the data into the line buffer. */

			LineStack[InBuff++] = Buffer[i];
			ColumnsDone++;

				/* Right margin reached/newline encountered? */

			if(ColumnsDone == PaperWidth || Buffer[i] == '\n')
			{
					/* Overriding the paper width
					 * does not necessarily include a
					 * newline.
					 */

				if(ColumnsDone == PaperWidth || ColumnsDone == 1024)
				{
					LineStack[InBuff++] = '\n';
					ColumnsDone++;
				}

					/* Send the line to the printer. */

				PrintIt(LineStack,InBuff);

					/* Did we reach the bottom line? */

				if((++LinesDone) == Preferences -> PaperLength)
				{
					LinesDone = 0;

						/* Inform the user about it. */

					if(Preferences -> PaperType == SINGLE)
						if(!DoRequest())
							return(FALSE);
				}

					/* Clear the rest. */

				ColumnsDone = InBuff = 0;

				for(j = 0 ; j < 1024 ; j++)
					LineStack[j] = 0;
			}
		}

			/* Is there still something in the line buffer? */

		if(InBuff)
			PrintIt(LineStack,InBuff);
	}

		/* We succeeded and nobody cancelled us. */

	return(TRUE);
}

	/* _main():
	 *
	 *	Handler main routine, bypasses all typical Aztec
	 *	startup code.
	 */

_main()
{
	struct Process		*ThisProg = (struct Process *)FindTask(NULL);
	struct DosPacket	*MyPacket;
	struct DeviceNode	*MyNode;	/* Our device node passed in parmpkt Arg3. */
	long			OpenCount = 0;	/* Handler open flag. */
	BOOL			Running = TRUE;	/* Handler main loop flag. */

	register long NextSlot = 0;
	register long i;

		/* The list of available buffers. */

	UBYTE Available[20];

		/* Since we were started as a non-BCPL module we get sent the
		 * parameter packet (i.e. parameter packet not in D1).
		 */

	MyPacket = TaskWait();	/* Wait for parameter packet. */

		/* Mark all buffers as empty. */

	for(i = 0 ; i < 20 ; i++)
	{
		Available[i] = TRUE;
		PrintSlot[i] = NULL;
	}

		/* Get a pointer to our device node. */

	MyNode = (struct DeviceNode *)BADDR(MyPacket -> dp_Arg3);

		/* Do the main handler initialization. */

	if(!(IntuitionBase = (struct IntuitionBase *)OpenLibrary("intuition.library",33)))
	{
		ReturnPacket(MyPacket,DOS_FALSE,MyPacket -> dp_Res2);
		goto FallOff;
	}

	if(!(PrinterPort = (struct MsgPort *)CreatePort(NULL,0)))
	{
		ReturnPacket(MyPacket,DOS_FALSE,MyPacket -> dp_Res2);
		goto FallOff;
	}

	if(!(PrinterDevice = (struct IOStdReq *)CreateStdIO(PrinterPort)))
	{
		ReturnPacket(MyPacket,DOS_FALSE,MyPacket -> dp_Res2);
		goto FallOff;
	}

	if(OpenDevice("printer.device",0,PrinterDevice,0))
	{
		ReturnPacket(MyPacket,DOS_FALSE,MyPacket -> dp_Res2);
		goto FallOff;
	}

		/* Initialize the Preferences pointer to
		 * reflect current printer.device settings.
		 */

	Preferences = &((struct PrinterData *)PrinterDevice -> io_Device) -> pd_Preferences;

		/* If initialization was possible then we install our
		 * taskid.   If we don't...for every reference to our
		 * handler  a  new  process will be created.  This is
		 * fine  for  things like CON:  (console handler) but
		 * if  you  plan  to be the only dude on block  (like
		 * the file-system handler or SER:)   you should fill
		 * the    task    field   with   your   taskid  (i.e.
		 * &(pr_MsgPort))    Note:  remember that shared code
		 * has  to  be reentrant.  (like CON:  handler), keep
		 * your  variables on the stack [autos], and allocate
		 * memory  for  larger  data  structures  and  "FLAG"
		 * global   data   structures   that   need  only  be
		 * intialized once.
		 */

	MyNode -> dn_Task = &ThisProg -> pr_MsgPort;

	ReturnPacket(MyPacket,DOS_TRUE,MyPacket -> dp_Res2);

	while(Running)	/* Start of the real work. */
	{
		MyPacket = TaskWait();	/* Wait for a packet. */
 
		switch(MyPacket -> dp_Type)
		{
				/* Somebody Open()ed us. */

			case ACTION_FIND_INPUT:
			case ACTION_FIND_OUTPUT:
			{
				struct FileHandle *FileHandle = (struct FileHandle *)BADDR(MyPacket -> dp_Arg1);

					/* Assume failure. */

				FileHandle -> fh_Port = DOS_FALSE;

				for(i = 0 ; i < 20 ; i++)
				{
						/* Any buffer available? */

					if(Available[i])
					{
							/* We didn't fail. */

						FileHandle -> fh_Port = DOS_TRUE;
						FileHandle -> fh_Arg1 = i;

						Available[i] = FALSE;

							/* Increment usercount. */

						OpenCount++;

						break;
					}
				}

					/* Return the compliment. */	

				ReturnPacket(MyPacket,FileHandle -> fh_Port,MyPacket -> dp_Res2);
				break;
			}

				/* Someone Close()d the file. */

			case ACTION_END:
			{
				long TheSlotIs = MyPacket -> dp_Arg1;
				BOOL GoOn = TRUE;

					/* We want to fall out of the loop if not OPEN. */

				if((--OpenCount) <= 0)
					Running = FALSE;

				ReturnPacket(MyPacket,DOS_TRUE,MyPacket -> dp_Res2);

					/* Print the current buffer. */

				while(PrintSlot[TheSlotIs])
				{
					if(GoOn)
						if(!PrintData(PrintSlot[TheSlotIs] -> Buffer,PrintSlot[TheSlotIs] -> Length))
							GoOn = FALSE;

					PrintSlot[TheSlotIs] = DeleteSeg(PrintSlot[TheSlotIs]);
				}

				Available[TheSlotIs] = TRUE;

					/* Reset the line counters. */

				LinesDone = ColumnsDone = 0;

				break;
			}

				/* Someone tries to Read() us. */

			case ACTION_READ:
			{
					/* We *always* read nothing. */

				ReturnPacket(MyPacket,NULL,MyPacket -> dp_Res2);
				break;
			}

				/* Someone tries to Write() to us. */

			case ACTION_WRITE:
			{
				long TheSlotIs = MyPacket -> dp_Arg1;
				char *Buffer = (char *)MyPacket -> dp_Arg2;
				struct DataSeg *NextSlot;

				MyPacket -> dp_Res1 = MyPacket -> dp_Arg3;

					/* Buffer not initialized yet? */

				if(!PrintSlot[TheSlotIs])
				{
					if(!(PrintSlot[TheSlotIs] = CreateSeg(Buffer,MyPacket -> dp_Res1)))
						MyPacket -> dp_Res1 = -1;
				}
				else
				{
						/* Add a new buffer to the current list. */

					NextSlot = PrintSlot[TheSlotIs];

					while(NextSlot -> NextSeg)
						NextSlot = NextSlot -> NextSeg;

					if(!(NextSlot -> NextSeg = CreateSeg(Buffer,MyPacket -> dp_Res1)))
						MyPacket -> dp_Res1 = -1;
				}

					/* We *always* write everything. */

				ReturnPacket(MyPacket,MyPacket -> dp_Arg3,MyPacket -> dp_Res2);
				break;
			}

				/* Someone wants us the leave the town. */

			case ACTION_DIE:
			{
					/* Empty all buffers. */

				for(i = 0 ; i < 20 ; i++)
					while(PrintSlot[i])
						PrintSlot[i] = DeleteSeg(PrintSlot[i]);

					/* Result will be ignored anyway. */

				ReturnPacket(MyPacket,DOS_FALSE,ERROR_ACTION_NOT_KNOWN);

				goto FallOff;
			}

				/* For any other purpose: ignore the message. */

			default:
			{
					/* Say what? */

				ReturnPacket(MyPacket,DOS_FALSE,ERROR_ACTION_NOT_KNOWN);

				break;
			}
		}
	}

		/* Fall off the edge of the world. */

FallOff:MyNode -> dn_Task = FALSE; /* Zero the TaskID field of device node. */

		/* Free all our data. */

	if(PrinterDevice)
	{
		if(PrinterDevice -> io_Device)
			CloseDevice(PrinterDevice);

		DeleteStdIO(PrinterDevice);
	}

	if(PrinterPort)
		DeletePort(PrinterPort);

	if(IntuitionBase)
		CloseLibrary(IntuitionBase);

		/* This is truly the end. */
}
