/*----------------------------------------------------------------------*
 * IFFW.C  Support routines for writing IFF-85 files.          1/23/86
 * (IFF is Interchange Format File.)
 *
 * By Jerry Morrison and Steve Shaw, Electronic Arts.
 * This software is in the public domain.
 *
 * This version for the Commodore-Amiga computer.
 *----------------------------------------------------------------------*/
#include "iff/iff.h"
#include "iff/gio.h"
#include "proto/dos.h"

/* ---------- IFF Writer -----------------------------------------------*/

/* A macro to test if a chunk size is definite, i.e. not szNotYetKnown.*/
#define Known(size)   ( (size) != szNotYetKnown )

/* Yet another weird macro to make the source code simpler...*/
#define IfIffp(expr)  {if (iffp == IFF_OKAY)  iffp = (expr);}

/* ---------- OpenWIFF -------------------------------------------------*/

IFFP 
OpenWIFF (BPTR file, GroupContext *new0, LONG limit)
{
	register GroupContext *new = new0;
	register IFFP iffp = IFF_OKAY;

	new->parent = NULL;
	new->clientFrame = NULL;
	new->file = file;
	new->position = 0;
	new->bound = limit;
	new->ckHdr.ckID = NULL_CHUNK;	/* indicates no current chunk */
	new->ckHdr.ckSize = new->bytesSoFar = 0;

	if (0 > Seek (file, 0, OFFSET_BEGINNING))	/* Go to start of the file.*/
		iffp = DOS_ERROR;
	else if (Known (limit) && IS_ODD (limit))
		iffp = CLIENT_ERROR;
	return (iffp);
}

/* ---------- StartWGroup ----------------------------------------------*/
IFFP 
StartWGroup (GroupContext *parent, ID groupType, LONG groupSize, ID subtype,
	     GroupContext *new)
{
	register IFFP iffp;

	iffp = PutCkHdr (parent, groupType, groupSize);
	IfIffp (IFFWriteBytes (parent, (BYTE *) & subtype, sizeof (ID)));
	IfIffp (OpenWGroup (parent, new));
	return (iffp);
}

/* ---------- OpenWGroup -----------------------------------------------*/
IFFP 
OpenWGroup (GroupContext *parent0, GroupContext *new0)
{
	register GroupContext *parent = parent0;
	register GroupContext *new = new0;
	register LONG ckEnd;
	register IFFP iffp = IFF_OKAY;

	new->parent = parent;
	new->clientFrame = parent->clientFrame;
	new->file = parent->file;
	new->position = parent->position;
	new->bound = parent->bound;
	new->ckHdr.ckID = NULL_CHUNK;
	new->ckHdr.ckSize = new->bytesSoFar = 0;

	if (Known (parent->ckHdr.ckSize))
	{
		ckEnd = new->position + ChunkMoreBytes (parent);
		if (new->bound == szNotYetKnown || new->bound > ckEnd)
			new->bound = ckEnd;
	};

	if (parent->ckHdr.ckID == NULL_CHUNK ||	/* not currently writing a chunk*/
	    IS_ODD (new->position) ||
	    (Known (new->bound) && IS_ODD (new->bound)))
		iffp = CLIENT_ERROR;
	return (iffp);
}

/* ---------- CloseWGroup ----------------------------------------------*/
IFFP 
CloseWGroup (GroupContext *old0)
{
	register GroupContext *old = old0;
	IFFP iffp = IFF_OKAY;

	if (old->ckHdr.ckID != NULL_CHUNK)	/* didn't close the last chunk */
		iffp = CLIENT_ERROR;
	else if (old->parent == NULL)
	{			/* top level file context */
		if (GWriteFlush (old->file) < 0)
			iffp = DOS_ERROR;
	}
	else
	{			/* update parent context */
		old->parent->bytesSoFar += old->position - old->parent->position;
		old->parent->position = old->position;
	};
	return (iffp);
}

/* ---------- EndWGroup ------------------------------------------------*/
IFFP 
EndWGroup (GroupContext *old)
{
	register GroupContext *parent = old->parent;
	register IFFP iffp;

	iffp = CloseWGroup (old);
	IfIffp (PutCkEnd (parent));
	return (iffp);
}

/* ---------- PutCk ----------------------------------------------------*/
IFFP 
PutCk (GroupContext *context, ID ckID, LONG ckSize, BYTE *data)
{
	register IFFP iffp = IFF_OKAY;

	if (ckSize == szNotYetKnown)
		iffp = CLIENT_ERROR;
	IfIffp (PutCkHdr (context, ckID, ckSize));
	IfIffp (IFFWriteBytes (context, data, ckSize));
	IfIffp (PutCkEnd (context));
	return (iffp);
}

/* ---------- PutCkHdr -------------------------------------------------*/
IFFP 
PutCkHdr (GroupContext *context0, ID ckID, LONG ckSize)
{
	register GroupContext *context = context0;
	LONG minPSize = sizeof (ChunkHeader);	/* physical chunk >= minPSize bytes*/

	/*
	 * CLIENT_ERROR if we're already inside a chunk or asked to
	 * write other than one FORM, LIST, or CAT at the top level of
	 * a file.
	 * Also, non-positive ID values are illegal and used for error
	 * codes. (We could check for other illegal IDs...)
	 */
	if (context->ckHdr.ckID != NULL_CHUNK || ckID <= 0)
		return (CLIENT_ERROR);
	else if (context->parent == NULL)
	{
		switch (ckID)
		{
		case FORM:
		case LIST:
		case CAT:
			break;
		default:
			return (CLIENT_ERROR);
		}
		if (context->position != 0)
			return (CLIENT_ERROR);
	}

	if (Known (ckSize))
	{
		if (ckSize < 0)
			return (CLIENT_ERROR);
		minPSize += ckSize;
	};
	if (Known (context->bound) &&
	    context->position + minPSize > context->bound)
		return (CLIENT_ERROR);

	context->ckHdr.ckID = ckID;
	context->ckHdr.ckSize = ckSize;
	context->bytesSoFar = 0;
	if (0 > GWrite (context->file,
			(BYTE *) & context->ckHdr,
			sizeof (ChunkHeader)) )
		return (DOS_ERROR);
	context->position += sizeof (ChunkHeader);
	return (IFF_OKAY);
}

/* ---------- IFFWriteBytes ---------------------------------------------*/
IFFP 
IFFWriteBytes (GroupContext *context0, BYTE *data, LONG nBytes)
{
	register GroupContext *context = context0;

	if (context->ckHdr.ckID == NULL_CHUNK	/* not in a chunk */
	    || nBytes < 0			/* negative nBytes */
	    || (Known (context->bound)		/* overflow context */
	        && context->position + nBytes > context->bound)
	    || (Known (context->ckHdr.ckSize)	/* overflow chunk */
	        && context->bytesSoFar + nBytes > context->ckHdr.ckSize)
	   )
		return (CLIENT_ERROR);

	if (0 > GWrite (context->file, data, nBytes))
		return (DOS_ERROR);

	context->bytesSoFar += nBytes;
	context->position += nBytes;
	return (IFF_OKAY);
}

/* ---------- PutCkEnd -------------------------------------------------*/
IFFP 
PutCkEnd (GroupContext *context0)
{
	register GroupContext *context = context0;
	WORD zero = 0;		/* padding source */

	if (context->ckHdr.ckID == NULL_CHUNK)	/* not in a chunk */
		return (CLIENT_ERROR);

	if (Known(context->ckHdr.ckSize))
	{
		/*
		 * make sure the client wrote as many bytes as planned
		 */
		if (context->ckHdr.ckSize != context->bytesSoFar)
			return (CLIENT_ERROR);
	}

	/*
	 * go back and set the chunk size to bytesSoFar
	 */
	else if (0 > GSeek (context->file,
			    -(context->bytesSoFar + sizeof (LONG)),
			    OFFSET_CURRENT) )
		return (DOS_ERROR);

	else if (0 > GWrite (context->file,
			     (BYTE *) & context->bytesSoFar,
			      sizeof (LONG)) )
		return (DOS_ERROR);

	else if (0 > GSeek (context->file,
			    context->bytesSoFar,
			    OFFSET_CURRENT) )
		return (DOS_ERROR);

	/*
	 * Write a pad byte if needed to bring us up to an even
	 * boundary. Since the context end must be even, and since we
	 * haven't overwritten the context, if we're on an odd position
	 * there must be room for a pad byte.
	 */

	if (IS_ODD (context->bytesSoFar))
	{
		if (0 > GWrite (context->file, (BYTE *) & zero, 1))
			return (DOS_ERROR);
		context->position += 1;
	};

	context->ckHdr.ckID = NULL_CHUNK;
	context->ckHdr.ckSize = context->bytesSoFar = 0;
	return (IFF_OKAY);
}
