/*----------------------------------------------------------------------*
 * IFFR.C  Support routines for reading 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.
 *
 * Uses "gio".  Either link with gio.c, or set the GIO_ACTIVE flag to 0
 * in gio.h.
 *----------------------------------------------------------------------*/

#include <libraries/dosextens.h>

#include "gio.h"
#include "iff.h"

/* ----- Private subroutine FileLength() --------------------------------*/
/* Returns the length of the file or else a negative IFFP error code
 * (NO_FILE or DOS_ERROR). AmigaDOS-specific implementation.
 * SIDE EFFECT: Thanks to AmigaDOS, we have to change the file's position
 * to find its length.
 * Now if Amiga DOS maintained fh_End, we'd just do this:
 *    fileLength = (FileHandle *)BADDR(file)->fh_End; */
STATIC LONG FileLength(file)  BPTR file;  {
    LONG fileLength = NO_FILE;

    if (file > 0)  {
	Seek(file, 0, OFFSET_END);	/* Seek to end of file.*/
	fileLength = Seek(file, 0, OFFSET_CURRENT);
	    /* Returns position BEFORE the seek, which is #bytes in file. */
	if (fileLength < 0)
	    fileLength = DOS_ERROR;	/* DOS being absurd.*/
	}
    return(fileLength);
    }

/* ---------- Read -----------------------------------------------------*/

/* ---------- OpenRIFF --------------------------------------------------*/
STATIC IFFP OpenRIFF(file, new, clientFrame)
	BPTR file;   GroupContext *new;  ClientFrame *clientFrame; {
    IFFP iffp = IFF_OKAY;

    new->parent       = NULL;		/* "whole file" has no parent.*/
    new->clientFrame  = clientFrame;
    new->file         = file;
    new->position     = 0;
    new->ckHdr.ckID   = new->subtype    = NULL_CHUNK;
    new->ckHdr.ckSize = new->bytesSoFar = 0;

    /* Set new->bound and go to the file's beginning. */
    new->bound = FileLength(file);
    if (new->bound < 0)
	iffp = new->bound;		   /* File system error! */
    else if ( new->bound < sizeof(ChunkHeader) )
	iffp = NOT_IFF;			   /* Too small for an IFF file. */
    else
	GSeek(file, 0, OFFSET_BEGINNING);  /* Go to file start. */
    return(iffp);
    }

/* ---------- SkipFwd --------------------------------------------------*/
/* Skip over bytes in a context. Won't go backwards.*/
/* Updates context->position but not context->bytesSoFar.*/
/* This implementation is AmigaDOS specific.*/
STATIC IFFP SkipFwd(context, bytes)   GroupContext *context;  LONG bytes; {
    IFFP iffp = IFF_OKAY;

    if (bytes > 0) {
	if (-1 == GSeek(context->file, bytes, OFFSET_CURRENT))
	    iffp = BAD_IFF;	/* Ran out of bytes before chunk complete.*/
	else
	    context->position += bytes;
	}
    return(iffp);
    }

/* ---------- OpenRGroup -----------------------------------------------*/
IFFP OpenRGroup(parent, new)   GroupContext *parent, *new; {
    IFFP iffp = IFF_OKAY;

    new->parent       = parent;
    new->clientFrame  = parent->clientFrame;
    new->file         = parent->file;
    new->position     = parent->position;
    new->bound        = parent->position + ChunkMoreBytes(parent);
    new->ckHdr.ckID   = new->subtype    = NULL_CHUNK;
    new->ckHdr.ckSize = new->bytesSoFar = 0;

    if ( new->bound > parent->bound  ||  IS_ODD(new->bound) )
	iffp = BAD_IFF;
    return(iffp);
    }

/* ---------- CloseRGroup -----------------------------------------------*/
IFFP CloseRGroup(context)   GroupContext *context; {
    LONG position;

    if (context->parent == NULL) {
	}  /* Context for whole file.*/
    else {
	position = context->position;
	context->parent->bytesSoFar += position - context->parent->position;
	context->parent->position = position;
	}
    return(IFF_OKAY);
    }

/* ---------- GetChunkHdr ----------------------------------------------*/
ID GetChunkHdr(context)   GroupContext *context;  {
    IFFP iffp;
    LONG remaining;

    /* Skip remainder of previous chunk & padding. */
    iffp = SkipFwd(context,
	(ChunkMoreBytes(context) + IS_ODD(context->ckHdr.ckSize)));
    CheckIFFP();

    /* Set up to read the new header. */
    context->ckHdr.ckID = BAD_IFF;	/* Until we know it's okay, mark it BAD.*/
    context->subtype    = NULL_CHUNK;
    context->bytesSoFar = 0;

    /* Generate a psuedo-chunk if at end-of-context. */
    remaining = context->bound - context->position;
    if (remaining == 0) {
	context->ckHdr.ckSize = 0;
	context->ckHdr.ckID   = END_MARK;
	}

    /* BAD_IFF if not enough bytes in the context for a ChunkHeader.*/
    else if (sizeof(ChunkHeader) > remaining) {
	context->ckHdr.ckSize = remaining;
	}

    /* Read the chunk header (finally). */
    else {
        switch (
	    GRead(context->file, (BYTE *)&context->ckHdr, sizeof(ChunkHeader))
	    ) {
	    case -1: return((ID)(context->ckHdr.ckID = DOS_ERROR));
	    case 0:  return((ID)(context->ckHdr.ckID = BAD_IFF));
	    }

	/* Check: Top level chunk must be LIST or FORM or CAT. */
	if (context->parent == NULL)
	    switch(context->ckHdr.ckID) {
		case FORM:  case LIST:  case CAT:  break;
		default:    return((ID)(context->ckHdr.ckID = NOT_IFF));
		}

	/* Update the context. */
	context->position += sizeof(ChunkHeader);
	remaining         -= sizeof(ChunkHeader);

	/* Non-positive ID values are illegal and used for error codes.*/
	/* We could check for other illegal IDs...*/
	if (context->ckHdr.ckID <= 0)
	     context->ckHdr.ckID = BAD_IFF;

	/* Check: ckSize negative or larger than # bytes left in context? */
	else if (context->ckHdr.ckSize < 0  ||
		 context->ckHdr.ckSize > remaining) {
	    context->ckHdr.ckSize = remaining;
	    context->ckHdr.ckID   = BAD_IFF;
	    }

	/* Automatically read the LIST, FORM, PROP, or CAT subtype ID */
	else switch (context->ckHdr.ckID) {
	    case LIST:  case FORM:  case PROP:  case CAT:  {
		iffp = IFFReadBytes(context,
				    (BYTE *)&context->subtype,
				    sizeof(ID));
		if (iffp != IFF_OKAY)
		    context->ckHdr.ckID = iffp;
		break; }
	    }

	}
    return(context->ckHdr.ckID);
    }

/* ---------- IFFReadBytes ---------------------------------------------*/
IFFP IFFReadBytes(context, buffer, nBytes)
    GroupContext *context;   BYTE *buffer;   LONG nBytes; {
    IFFP iffp = IFF_OKAY;

    if (nBytes < 0)
	iffp = CLIENT_ERROR;
    else if (nBytes > 0) {
        if (nBytes > ChunkMoreBytes(context)) nBytes = ChunkMoreBytes(context);
	switch ( GRead(context->file, buffer, nBytes) ) {
	    case -1: {iffp = DOS_ERROR; break; }
	    case 0:  {iffp = BAD_IFF;   break; }
	    default: {
		context->position   += nBytes;
		context->bytesSoFar += nBytes;
		}
        }
    }
    return(iffp);
    }

/* ---------- ReadIFF --------------------------------------------------*/
IFFP ReadIFF(file, clientFrame)  BPTR file;  ClientFrame *clientFrame;  {
    IFFP iffp;
    GroupContext context;

    iffp = OpenRIFF(file, &context,clientFrame);

    if (iffp == IFF_OKAY)
	switch (iffp = GetChunkHdr(&context)) {
	    case FORM: { iffp = (*clientFrame->getForm)(&context); break; }
	    case LIST: { iffp = (*clientFrame->getList)(&context); break; }
	    case CAT : { iffp = (*clientFrame->getCat )(&context); break; }
	    /* default: Includes IFF_DONE, BAD_IFF, NOT_IFF... */
	    }

    CloseRGroup(&context);

    if (iffp > 0)		/* Make sure we don't return an ID.*/
	iffp = NOT_IFF;		/* GetChunkHdr should've caught this.*/
    return(iffp);
    }

/* ---------- ReadIList ------------------------------------------------*/
IFFP ReadIList(parent, clientFrame)
    GroupContext *parent;  ClientFrame *clientFrame; {
    GroupContext listContext;
    IFFP iffp;
    BOOL propOk = TRUE;

    iffp = OpenRGroup(parent, &listContext);
    CheckIFFP();

    /* One special case test lets us handle CATs as well as LISTs.*/
    if (parent->ckHdr.ckID == CAT)
	propOk = FALSE;
    else
	listContext.clientFrame = clientFrame;

    do {
	switch (iffp = GetChunkHdr(&listContext)) {
	    case PROP: {
		if (propOk)
		    iffp = (*clientFrame->getProp)(&listContext);
		else
		    iffp = BAD_IFF;
		break;
		}
	    case FORM: { iffp = (*clientFrame->getForm)(&listContext); break; }
	    case LIST: { iffp = (*clientFrame->getList)(&listContext); break; }
	    case CAT : { iffp = (*clientFrame->getCat )(&listContext); break; }
	    /* default: Includes END_MARK, IFF_DONE, BAD_IFF, NOT_IFF... */
	    }
	if (listContext.ckHdr.ckID != PROP)
	    propOk = FALSE;	/* No PROPs allowed after this point.*/
	} while (iffp == IFF_OKAY);

    CloseRGroup(&listContext);

    if (iffp > 0)	/* Only chunk types above are allowed in a LIST/CAT.*/
	iffp = BAD_IFF;
    return(iffp == END_MARK ? IFF_OKAY : iffp);
    }

/* ---------- ReadICat -------------------------------------------------*/
/* By special arrangement with the ReadIList implement'n, this is trivial.*/
IFFP ReadICat(parent)  GroupContext *parent;  {
    return( ReadIList(parent, NULL) );
    }

/* ---------- GetFChunkHdr ---------------------------------------------*/
ID GetFChunkHdr(context)   GroupContext *context; {
    ID id;

    id = GetChunkHdr(context);
    if (id == PROP)
	context->ckHdr.ckID = id = BAD_IFF;
    return(id);
    }

/* ---------- GetF1ChunkHdr ---------------------------------------------*/
ID GetF1ChunkHdr(context)   GroupContext *context; {
    ID id;
    ClientFrame *clientFrame = context->clientFrame;

    switch (id = GetChunkHdr(context))  {
	case PROP: { id = BAD_IFF; break; }
	case FORM: { id = (*clientFrame->getForm)(context); break; }
	case LIST: { id = (*clientFrame->getList)(context); break; }
	case CAT : { id = (*clientFrame->getCat )(context); break; }
	/* Default: let the caller handle other chunks */
	}
    return(context->ckHdr.ckID = id);
    }

/* ---------- GetPChunkHdr ---------------------------------------------*/
ID GetPChunkHdr(context)   GroupContext *context; {
    ID id;

    id = GetChunkHdr(context);
    switch (id) {
	case LIST:  case FORM:  case PROP:  case CAT:  {
	    id = context->ckHdr.ckID = BAD_IFF;
	    break; }
	}
    return(id);
    }
