/*
**	clip-handler.c
**	DOS handler for Clipboard access
**	Copyright © 1995 Michael Letowski
*/

#define __USE_SYSBASE

#include <exec/types.h>
#include <exec/execbase.h>
#include <exec/memory.h>
#include <dos/dos.h>
#include <dos/dosextens.h>
#include <dos/filehandler.h>
#include <libraries/iffparse.h>
#include <devices/clipboard.h>
#include <support/types.h>
#include <support/exec.h>
#include <support/dos.h>

#include <string.h>
#include <stdlib.h>

#include <proto/exec.h>
#include <proto/iffparse.h>

#include "clip-handler.rev.h"

#define DOS_NAME		"dos.library"
#define DOS_VERN 		37L
#define IFFP_NAME		"iffparse.library"
#define IFFP_VERN		37L
#define CLIP_NAME		"clipboard.device"

STATIC CONST TEXT VersionString[]=
	VERSION(PROG_NAME,PROG_VERSION,PROG_REVISION,PROG_DATE);

#define ID_FTXT			MAKE_ID('F','T','X','T')
#define ID_CHRS			MAKE_ID('C','H','R','S')

#define PATH_SIZE		256

#define SetPktRes(pkt,res1,res2)	((pkt)->dp_Res1=(res1),(pkt)->dp_Res2=(res2))

/* Library bases */
struct ExecBase *SysBase;
struct Library *IFFParseBase;

/*	Packet routines	*/
STATIC struct DosPacket *WaitPacket(struct MsgPort *port);
STATIC VOID ReplyPacket(struct DosPacket *packet, struct MsgPort *port);
STATIC VOID ReplyPacketRes(struct DosPacket *packet, struct MsgPort *port,
														LONG res1, LONG res2);
STATIC VOID FlushPackets(struct MsgPort *port);

/*	clip-handler routines	*/
STATIC struct IFFHandle *OpenCH(struct DosPacket *packet);
STATIC VOID CloseCH(struct IFFHandle *iff);
STATIC LONG OpenInputCH(struct DosPacket *packet);
STATIC LONG OpenOutputCH(struct DosPacket *packet);
STATIC LONG OpenUpdateCH(struct DosPacket *packet);
STATIC LONG CloseIOUCH(struct DosPacket *packet);
STATIC VOID ReadClipboard(struct DosPacket *packet);
STATIC VOID WriteClipboard(struct DosPacket *packet);
STATIC VOID SeekClipboard(struct DosPacket *packet);
STATIC LONG IFF2DOS(LONG iffError);

SAVEDS LONG ClipHandler(VOID)
{
	struct Library *LocalIFFParseBase;

	struct Process *PR;
	struct MsgPort *MP,*PacketPort;
	struct DosPacket *Pkt;
	struct DeviceNode *DevNode;
	LBOOL Done=FALSE;
	ULONG OpenCount=0;

	SysBase=*((struct ExecBase **)4);							/* Set up SysBase */
	PR=ThisProcessS;
	MP=&PR->pr_MsgPort;

	if(PR->pr_CLI)																/* Run from shell */
		return(RETURN_FAIL);												/* Failure */

	Pkt=WaitPacket(MP);														/* Get startup message */
	try(DevNode=(struct DeviceNode *)BADDR(Pkt->dp_Arg3),NO_DEV);

	/* Open IFFParse */
	unless(LocalIFFParseBase=OpenLibrary(IFFP_NAME,IFFP_VERN))
		throw2(ReplyPacketRes(Pkt,MP,DOSFALSE,ERROR_INVALID_RESIDENT_LIBRARY),	NO_IFFPARSE);
	/* Create port */
	unless(PacketPort=CreateMsgPort())
		throw2(ReplyPacketRes(Pkt,MP,DOSFALSE,ERROR_NO_FREE_STORE),	NO_PORT);

	/* Set up global bases */
	IFFParseBase=LocalIFFParseBase;								/* Set up global bases */
	DevNode->dn_Task=PacketPort;
	Pkt->dp_Arg4=(LONG)PacketPort;
	ReplyPacketRes(Pkt,PacketPort,DOSTRUE,Pkt->dp_Res2);

	while(!Done)
	{
		Pkt=WaitPacket(PacketPort);
		switch(Pkt->dp_Type)
		{
			case ACTION_FINDINPUT:
				OpenCount+=OpenInputCH(Pkt);
				break;
			case ACTION_FINDOUTPUT:
				OpenCount+=OpenOutputCH(Pkt);
				break;
/*			case ACTION_FINDUPDATE:
				OpenCount+=OpenUpdateCH(Pkt);*/
				break;
			case ACTION_END:
				OpenCount-=CloseIOUCH(Pkt);
				break;
			case ACTION_READ:
				ReadClipboard(Pkt);
				break;
			case ACTION_WRITE:
				WriteClipboard(Pkt);
				break;
/*			case ACTION_SEEK:
				SeekClipboard(Pkt);*/
				break;
			case ACTION_DIE:
				if(OpenCount)
					SetPktRes(Pkt,DOSFALSE,ERROR_OBJECT_IN_USE);
				else
				{
					SetPktRes(Pkt,DOSTRUE,0);
					Done=TRUE;
				}
				break;
			case ACTION_IS_FILESYSTEM:
				SetPktRes(Pkt,DOSFALSE,0);
				break;
			default:
				SetPktRes(Pkt,DOSFALSE,ERROR_ACTION_NOT_KNOWN);
				break;
		}
		ReplyPacket(Pkt,PacketPort);
	}
	Forbid();																			/* Disable task switching */
	DevNode->dn_Task=NULL;												/* Switch off device */
	FlushPackets(PacketPort);											/* Discard waiting packets */
	UnLoadSeg(DevNode->dn_SegList);								/* Unload code */
	DevNode->dn_SegList=NULL;											/* And mark this fact */
	Permit();																			/* Enable task switching */

	catch(NO_PORT,			DeleteMsgPort(PacketPort));
	catch(NO_IFFPARSE,	CloseLibrary(LocalIFFParseBase));
	catch(NO_DEV,				);
	return(0);
}	/* ClipHandler */

STATIC struct DosPacket *WaitPacket(struct MsgPort *port)
{
	WaitPort(port);
	return((struct DosPacket *)GetMsg(port)->mn_Node.ln_Name);
}	/* WaitPacket */

STATIC VOID ReplyPacket(struct DosPacket *packet, struct MsgPort *port)
{
	struct MsgPort *ReplyPort=packet->dp_Port;

	packet->dp_Port=port;
	packet->dp_Link->mn_Node.ln_Name=(STRPTR)packet;
	PutMsg(ReplyPort,packet->dp_Link);
}	/* ReplyPacket */

STATIC VOID ReplyPacketRes(struct DosPacket *packet, struct MsgPort *port,
														LONG res1, LONG res2)
{
	SetPktRes(packet,res1,res2);
	ReplyPacket(packet,port);
}	/* ReplyPacketRes */

STATIC VOID FlushPackets(struct MsgPort *port)
{
	struct DosPacket *Packet;

	while(Packet=(struct DosPacket *)GetMsg(port)->mn_Node.ln_Name)
		ReplyPacketRes(Packet,port,DOSFALSE,ERROR_ACTION_NOT_KNOWN);
}	/* FlushPackets */

STATIC struct IFFHandle *OpenCH(struct DosPacket *packet)
{
	CHAR Name[PATH_SIZE];
	STRPTR Str=(STRPTR)BADDR(packet->dp_Arg3);
	struct FileHandle *Handle=FH(packet->dp_Arg1);
	struct IFFHandle *IFF;
	LONG Unit;

	SetPktRes(packet,DOSFALSE,0);
	Handle->fh_Arg1=0;
	Handle->fh_Port=NULL;													/* Non-interactive file */

	memcpy(Name,&Str[1],Str[0]);									/* Copy data */
	Name[Str[0]]='\0';														/* NULL-terminate */

	Str=Name;
	if(Str=strchr(Str,':'))												/* Device name given */
		Str++;																			/* Skip it */
	Unit=atol(Str);																/* Convert to unit number */

	unless(IFF=AllocIFF())
		throw2(packet->dp_Res2=ERROR_NO_FREE_STORE,	NO_IFF);
	unless(IFF->iff_Stream=(ULONG)OpenClipboard(Unit))
		throw2(packet->dp_Res2=ERROR_NO_FREE_STORE,	NO_CLIPBOARD);	/* FIX: error */
	InitIFFasClip(IFF);
	Handle->fh_Arg1=(LONG)IFF;
	packet->dp_Res1=DOSTRUE;
	return(IFF);

	catch(FOO,					);
	catch(NO_CLIPBOARD,	CloseClipboard((struct ClipboardHandle *)IFF->iff_Stream));
	catch(NO_IFF,				FreeIFF(IFF));
	return(NULL);
}	/* OpenCH */

STATIC VOID CloseCH(struct IFFHandle *iff)
{
	CloseClipboard((struct ClipboardHandle *)iff->iff_Stream);
	FreeIFF(iff);
}	/* CloseCH */

STATIC LONG OpenInputCH(struct DosPacket *packet)
{
	struct IFFHandle *IFF;
	LONG Error;

	try(IFF=OpenCH(packet),												NO_HANDLE);
	try(!(Error=OpenIFF(IFF,IFFF_READ)),					NO_IFF);
	try(!(Error=StopChunk(IFF,ID_FTXT,ID_CHRS)),	IFF_ERROR);
	try(!(Error=ParseIFF(IFF,IFFPARSE_SCAN)),			IFF_ERROR);
	return(1);																		/* Open succesfull */

	catch(IFF_ERROR,	);
	catch(NO_IFF,			CloseIFF(IFF));
	SetPktRes(packet,DOSFALSE,IFF2DOS(Error));		/* Set result */
	catch(NO_HANDLE,	CloseCH(IFF));
	return(0);																		/* Open failed */
}	/* OpenInputCH */

STATIC LONG OpenOutputCH(struct DosPacket *packet)
{
	struct IFFHandle *IFF;
	LONG Error;

	try(IFF=OpenCH(packet),																				NO_HANDLE);
	try(!(Error=OpenIFF(IFF,IFFF_WRITE)),													NO_IFF);
	try(!(Error=PushChunk(IFF,ID_FTXT,ID_FORM,IFFSIZE_UNKNOWN)),	IFF_ERROR);
	try(!(Error=PushChunk(IFF,0,ID_CHRS,IFFSIZE_UNKNOWN)),				IFF_ERROR);
	return(1);																		/* Open succesfull */

	catch(IFF_ERROR,	);
	catch(NO_IFF,			CloseIFF(IFF));
	SetPktRes(packet,DOSFALSE,IFF2DOS(Error));		/* Set result */
	catch(NO_HANDLE,	CloseCH(IFF));
	return(0);																		/* Open failed */
}	/* OpenOutputCH */

STATIC LONG OpenUpdateCH(struct DosPacket *packet)
{
	struct IFFHandle *IFF;
	LONG Error;

	try(IFF=OpenCH(packet),												NO_HANDLE);
	try(!(Error=OpenIFF(IFF,IFFF_WRITE)),					NO_IFF);
	try(!(Error=StopChunk(IFF,ID_FTXT,ID_CHRS)),	IFF_ERROR);
		/* FIX: Should use write mode here */
	try(!(Error=ParseIFF(IFF,IFFPARSE_SCAN)),			IFF_ERROR);
	return(1);																		/* Open succesfull */

	catch(IFF_ERROR,	);
	catch(NO_IFF,			CloseIFF(IFF));
	SetPktRes(packet,DOSFALSE,IFF2DOS(Error));		/* Set result */
	catch(NO_HANDLE,	CloseCH(IFF));
	return(0);																		/* Open failed */
}	/* OpenUpdateCH */

STATIC LONG CloseIOUCH(struct DosPacket *packet)
{
	struct IFFHandle *IFF=(struct IFFHandle *)packet->dp_Arg1;

#if 0
	/* FIX:??? Check PopChunk() for errors */
	if(ftst(IFF->iff_Flags,IFFF_WRITE))						/* Write mode */
		if(PopChunk(IFF)==0)												/* Close CHRS */
			PopChunk(IFF);														/* Close FTXT */
#endif
	CloseIFF(IFF);
	CloseCH(IFF);
	SetPktRes(packet,DOSTRUE,0);									/* Set result */
	return(1);
}	/* CloseIOUCH */

STATIC VOID ReadClipboard(struct DosPacket *packet)
{
	struct IFFHandle *IFF=(struct IFFHandle *)packet->dp_Arg1;
	STRPTR Buffer=(STRPTR)packet->dp_Arg2;
	LONG Read=-1,Bytes=packet->dp_Arg3;

	SetPktRes(packet,-1,0);												/* Set result */

	Read=ReadChunkBytes(IFF,Buffer,Bytes);				/* Read into buffer */
	if(Read<0)
		packet->dp_Res2=IFF2DOS(Read);
	else
		packet->dp_Res1=Read;
}	/* ReadClipboard */

STATIC VOID WriteClipboard(struct DosPacket *packet)
{
	struct IFFHandle *IFF=(struct IFFHandle *)packet->dp_Arg1;
	STRPTR Buffer=(STRPTR)packet->dp_Arg2;
	LONG Written=-1,Bytes=packet->dp_Arg3;

	SetPktRes(packet,-1,0);												/* Set result */

	Written=WriteChunkBytes(IFF,Buffer,Bytes);		/* Write buffer */
	if(Written<0)
		packet->dp_Res2=IFF2DOS(Written);
	else
		packet->dp_Res1=Written;
}	/* WriteClipboard */

STATIC VOID SeekClipboard(struct DosPacket *packet)
{
	struct IFFHandle *IFF=(struct IFFHandle *)packet->dp_Arg1;
	struct ContextNode *CN;
	LONG Offset=packet->dp_Arg2,OffsetType=packet->dp_Arg3;
	LONG OldPos,NewPos=-1;

	SetPktRes(packet,-1,ERROR_SEEK_ERROR);				/* Set result */

	unless(CN=CurrentChunk(IFF))
		return;
	unless(CN->cn_Type==ID_FTXT && CN->cn_ID==ID_CHRS)
		return;

	switch(OffsetType)
	{
		case OFFSET_BEGINNING:
			NewPos=Offset;
			break;
		case OFFSET_CURRENT:
			NewPos=CN->cn_Scan+Offset;
			break;
		case OFFSET_END:
			NewPos=CN->cn_Size-Offset;
			break;
	}
	if(NewPos>0 && NewPos<CN->cn_Size)						/* Can seek */
	{
		OldPos=CN->cn_Scan;
		CN->cn_Scan=NewPos;
		SetPktRes(packet,OldPos,0);
	}
}	/* SeekClipboard */

STATIC LONG IFF2DOS(LONG iffError)
{
	LONG DOSError=ThisProcessS->pr_Result2;

	unless(DOSError)															/* Error not set */
		switch(iffError)
		{
			case IFFERR_NOTIFF:	DOSError=ERROR_OBJECT_NOT_FOUND;	break;
			case IFFERR_NOMEM:	DOSError=ERROR_NO_FREE_STORE;			break;
			case IFFERR_SEEK:		DOSError=ERROR_SEEK_ERROR;				break;
			case IFFERR_NOHOOK:	DOSError=ERROR_NOT_IMPLEMENTED;		break;
			default:						DOSError=ERROR_OBJECT_WRONG_TYPE;	break;
		}
	return(DOSError);
}	/* IFF2DOS */

#if 0
STATIC LONG SeekChunk(struct IFFHandle *iff, LONG offset)
{
	struct IFFStreamCmd Cmd=
	{
		IFFCMD_SEEK,	NULL,	offset
	};	/* Cmd */
	return(CallHookPkt((struct Hook *)(iff+1),iff,&Cmd));
}	/* SeekChunk */
#endif