/*  
 * parse.c - iffparse file IO support module
 *   based on some of looki.c by Leo Schwab
 *
 * The filename for clipboard is -c or -cUnit as in -c0 -c1 etc. (default 0)
 */

#include <exec/types.h>

#include "iffp/iff.h"

/* local function prototypes */

static LONG stdio_stream(struct Hook *, struct IFFHandle *, struct IFFStreamCmd *);

UBYTE *omodes[2] = {"r","w"};


/* openifile
 *
 * Passed a ParseInfo structure with a not-in-use IFFHandle, filename
 *   ("-c" or -cUnit like "-c1" for clipboard), and IFF open mode
 *   (IFFF_READ or IFFF_WRITE) opens file or clipboard for use with
 *   iffparse.library support modules.
 *
 * Returns 0 for success or an IFFERR (libraries/iffparse.h)
 */

LONG openifile(struct ParseInfo *pi, UBYTE *filename, ULONG iffopenmode)
{
	struct IFFHandle	*iff;
	BOOL	cboard;
	ULONG 	unit = PRIMARY_CLIP;
	LONG 	error;

	if(!pi)			return(CLIENT_ERROR);
        if(!(iff=pi->iff))	return(CLIENT_ERROR);

	cboard = (*filename == '-'  &&  filename[1] == 'c');
 	if(cboard && filename[2])	unit = atoi(&filename[2]);

	if (cboard)
		{
		/*
		 * Set up IFFHandle for Clipboard I/O.
		 */
		pi->clipboard = TRUE;
		if (!(iff->iff_Stream =
				(ULONG)OpenClipboard(unit)))
			{
			message(SI(MSG_IFFP_NOCLIP_D),unit);
			return(NOFILE);
			}
		InitIFFasClip(iff);
		}
	else
		{
		pi->clipboard = FALSE;
		/*
		 * Set up IFFHandle for buffered stdio I/O.
		 */
		if (!(iff->iff_Stream = (ULONG)
		   fopen(filename, omodes[iffopenmode & 1])))
			{
			message(SI(MSG_IFFP_NOFILE_S),filename);
			return(NOFILE);
			}
		else initiffasstdio(iff);
		}

	D(bug("%s file opened: \n", cboard ? "[Clipboard]" : filename));

	pi->filename = filename;

	error=OpenIFF(iff, iffopenmode);

	pi->opened = error ? FALSE : TRUE;	/* currently open handle */

	D(bug("OpenIFF error = %ld\n",error));
	return(error);
}


/* closeifile
 *
 * closes file or clipboard opened with openifile, and frees all
 *   iffparse context parsed by parseifile.
 *
 * Note - You should closeifile as soon as possible if using clipboard
 *   ("-c[n]").  You also need to closeifile if, for example, you wish to
 *   reopen the file to write changes back out.  See the copychunks.c
 *   module for routines which allow you clone the chunks iffparse has
 *   gathered so that you can closeifile and still be able to modify and
 *   write back out gathered chunks.
 *   
 */

void closeifile(struct ParseInfo *pi)
{
struct IFFHandle *iff;

	D(bug("closeifile:\n"));

	if(!pi)			return;
    	if(!(iff=pi->iff))	return;

	DD(bug("closeifile: About to CloseIFF if open, iff=$%lx, opened=%ld\n",
			iff, pi->opened));

	if(pi->opened)	CloseIFF(iff);

	DD(bug("closeifile: About to close %s, stream=$%lx\n",
			pi->clipboard ? "clipboard" : "file", iff->iff_Stream));
	if(iff->iff_Stream)
		{
		if (pi->clipboard)
		   CloseClipboard((struct ClipHandle *)(iff->iff_Stream));
		else
		   fclose ((FILE *)(iff->iff_Stream));
		}

	iff->iff_Stream = NULL;
	pi->clipboard = NULL;
	pi->opened = NULL;
}


/* parseifile
 *
 * Passed a ParseInfo with an initialized and open IFFHandle,
 *  grouptype (like ID_FORM), groupid (like ID_ILBM),
 *  and TAG_DONE terminated longword arrays of type,id
 *  for chunks to be grabbed, gathered, and stopped on
 *  (like { ID_ILBM, ID_BMHD, ID_ILBM, ID_CAMG, TAG_DONE })
 *  will parse an IFF file, grabbing/gathering and stopping
 *  on specified chunk.
 *
 * Note - you can call getcontext() (to continue after a stop chunk) or
 *  nextcontext() (after IFFERR_EOC, to parse next form in the same file)
 *  if you wish to continue parsing the same IFF file.  If parseifile()
 *  has to delve into a complex format to find your desired FORM, the
 *  pi->hunt flag will be set.  This should be a signal to you that
 *  you may not have the capability to simply modify and rewrite
 *  the data you have gathered.
 *
 * Returns 0 for success else and IFFERR (libraries/iffparse.h)
 */ 

LONG parseifile(pi,groupid,grouptype,propchks,collectchks,stopchks)
struct	ParseInfo *pi;
LONG groupid, grouptype;
LONG *propchks, *collectchks, *stopchks;
{
struct IFFHandle *iff;	
register struct ContextNode	*cn;
LONG			error = 0L;


    	if(!(iff=pi->iff))	return(CLIENT_ERROR);

	if(!iff->iff_Stream)	return(IFFERR_READ);

	pi->hunt = FALSE;

	/*
	 * Declare property, collection and stop chunks.
	 */
	if (propchks)
	  if (error = PropChunks (iff, propchks, chkcnt(propchks)))
		return (error);
	if (collectchks)
	  if (error =
	      CollectionChunks(iff, collectchks, chkcnt(collectchks)))
		return (error);
	if (stopchks)
	  if (error = StopChunks (iff, stopchks, chkcnt(stopchks)))
		return (error);

	/*
	 * We want to stop at the end of an ILBM context.
	 */
	if (grouptype)
	  if (error = StopOnExit (iff, grouptype, groupid))
		return(error);

	/*
	 * Take first parse step to enter main chunk.
	 */
	if (error = ParseIFF (iff, IFFPARSE_STEP))
		return(error);

	/*
	 * Test the chunk info to see if simple form of type we want (ILBM).
	 */
	if (!(cn = CurrentChunk (iff)))
		{
		/*
		 * This really should never happen.  If it does, it means
		 * our parser is broken.
		 */
		message(SI(MSG_IFFP_NOTOP));
		return(NOFILE);
		}

	if (cn->cn_ID != groupid  ||  cn->cn_Type != grouptype)
		{
		
		D(bug("This is a(n) %.4s.%.4s.  Looking for embedded %.4s's...\n",
		  &cn->cn_Type, &cn->cn_ID, &grouptype));

		pi->hunt = TRUE;	/* Warning - this is a complex file */
		}

	if(!error)	error = getcontext(iff);
	return(error);
}

/* chkcnt
 *
 * simply counts the number of chunk pairs (type,id) in array
 */
LONG chkcnt(LONG *taggedarray)
{
LONG k = 0;

	while(taggedarray[k] != TAG_DONE) k++;
	return(k>>1);
}


/* currentchunkis
 *
 * returns the ID of the current chunk (like ID_CAMG)
 */
LONG currentchunkis(struct IFFHandle *iff, LONG type, LONG id)
{
register struct ContextNode	*cn;
LONG result = 0;

	if (cn = CurrentChunk (iff))
		{
		if((cn->cn_Type == type)&&(cn->cn_ID == id)) result = 1;
		}
	return(result);
}


/* contextis
 *
 * returns the enclosing context of the current chunk (like ID_ILBM)
 */
LONG contextis(struct IFFHandle *iff, LONG type, LONG id)
{
register struct ContextNode	*cn;
LONG result = 0;

       if (cn = (CurrentChunk (iff)))
           {
           if (cn = (ParentChunk(cn)))
               {
               if((cn->cn_Type == type)&&(cn->cn_ID == id)) result = 1;
               }
	   }

	D(bug("This is a %.4s %.4s\n",&cn->cn_Type,&cn->cn_ID));

	return(result);
}


/* getcontext()
 *
 * Continues to gather the context which was specified to parseifile(),
 *  stopping at specified stop chunk, or end of context, or EOF
 *
 * Returns 0 (stopped on a stop chunk)
 *      or IFFERR_EOC (end of context, not an error)
 *      or IFFER_EOF (end of file)
 */
LONG getcontext(iff)
struct	IFFHandle *iff;
{
	LONG error = 0L;

	/* Based on our parse initialization,
	 * ParseIFF() will return on a stop chunk (error = 0)
	 * or end of context for an ILBM FORM (error = IFFERR_EOC)
	 * or end of file (error = IFFERR_EOF)
	 */
	return(error = ParseIFF(iff, IFFPARSE_SCAN));
}


/* nextcontext
 *
 * If you have finished parsing and reading your context (IFFERR_EOC),
 *   nextcontext will enter the next context contained in the file
 *   and parse it.
 *
 * Returns 0 or an IFFERR such as IFFERR_EOF (end of file)
 */

LONG nextcontext(iff)
struct	IFFHandle *iff;
{
	LONG error = 0L;

	error = ParseIFF(iff, IFFPARSE_STEP);

	D(bug("nextcontext: Got through next step\n"));

	return(error);
}


/* findpropdata
 *
 * finds specified chunk parsed from IFF file, and
 *   returns pointer to its sp_Data (or 0 for not found)
 */
UBYTE *findpropdata(iff, type, id)
struct IFFHandle	*iff;
LONG type, id;
	{
	register struct StoredProperty	*sp;

	if(sp = FindProp (iff, type, id)) return(sp->sp_Data);
	return(0);
	}


/*
 * File I/O hook functions which the IFF library will call.
 * A return of 0 indicates success (no error).
 *
 * Iffparse.library calls this code via struct Hook and Hook.asm
 */
static LONG
stdio_stream (struct Hook *hook, struct IFFHandle *iff,
			struct IFFStreamCmd *actionpkt)
{
	register FILE	*stream;
	register LONG	nbytes;
	register int	actual;
	register UBYTE	*buf;
	long	len;

	stream	= (FILE *) iff->iff_Stream;
	if(!stream)	return(1);

	nbytes	= actionpkt->sc_NBytes;
	buf	= (UBYTE *) actionpkt->sc_Buf;

	switch (actionpkt->sc_Command) {
	case IFFSCC_READ:
		do {
			actual = nbytes > 32767 ? 32767 : nbytes;
			if ((len=fread (buf, 1, actual, stream)) != actual)
				break;
			nbytes -= actual;
			buf += actual;
		} while (nbytes > 0);
		return (nbytes ? IFFERR_READ : 0 );

	case IFFSCC_WRITE:
		do {
			actual = nbytes > 32767 ? 32767 : nbytes;
			if ((len=fwrite (buf, 1, actual, stream)) != actual)
				break;
			nbytes -= actual;
			buf += actual;
		} while (nbytes > 0);
		return (nbytes ? IFFERR_WRITE : 0);

	case IFFSCC_SEEK:
		return ((fseek (stream, nbytes, 1) == -1) ? IFFERR_SEEK : 0);

	default:
		/*  No _INIT or _CLEANUP required.  */
		return (0);
	}
}

/* initiffasstdio (ie. init iff as stdio)
 *
 * sets up hook callback for the file stream handler above
 */
void initiffasstdio (iff)
struct IFFHandle *iff;
{
	extern LONG		HookEntry();
	static struct Hook	stdiohook = {
		{ NULL },
		(ULONG (*)()) HookEntry,
		(ULONG (*)()) stdio_stream,
		NULL
	};

	/*
	 * Initialize the IFF structure to point to the buffered I/O
	 * routines.  Unbuffered I/O is terribly slow.
	 */
	InitIFF (iff, IFFF_FSEEK | IFFF_RSEEK, &stdiohook);
}


/*
 * PutCk
 *
 * Writes one chunk of data to an iffhandle
 *
 */
long PutCk(struct IFFHandle *iff, long id, long size, void *data)
    {
    long error = 0, wlen;

    D(bug("PutCk: asked to push chunk \"%.4s\" ($%lx) length %ld\n",&id,id,size));

    if(error=PushChunk(iff, 0, id, size))
	{
	D(bug("PutCk: PushChunk of %.4s, error = %s, size = %ld\n",
		id, IFFerr(error), id));
	}
    else
	{
	D(bug("PutCk: PushChunk of %.4s, error = %ld\n",&id, error));

	/* Write the actual data */
	if((wlen = WriteChunkBytes(iff,data,size)) != size)
	    {
	    D(bug("WriteChunkBytes error: size = %ld, wrote %ld\n",size,wlen));
	    error = IFFERR_WRITE;
	    }
	else error = PopChunk(iff);
	D(bug("PutCk: After PopChunk - error = %ld\n",error));
	}
    return(error);
    }

