/*----------------------------------------------------------------------*
 * 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 "iff/intuall.h"
#include "iff/gio.h"
#include "iff/iff.h"
#include "functions.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; */
LONG FileLength(file)  BPTR file;  {
    LONG fileLength = NO_FILE;

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

    return(fileLength);
    }

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

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

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

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

    return(iffp);
    }

/* ---------- OpenRGroup -----------------------------------------------*/
IFFP OpenRGroup(parent0, new0)   GroupContext *parent0, *new0; {
    register GroupContext *parent = parent0;
    register GroupContext *new    = new0;
    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 = 0L;

    if ( new->bound > parent->bound  ||  IS_ODD(new->bound) )
        iffp = BAD_IFF;
    return(iffp);
    }
    
/* ---------- CloseRGroup -----------------------------------------------*/
IFFP CloseRGroup(context)   GroupContext *context; {
    register 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);
    }

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

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

/* ---------- GetChunkHdr ----------------------------------------------*/
ID GetChunkHdr(context0)   GroupContext *context0;  {
    register GroupContext *context = context0;
    register 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 = 0L;

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

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

    /* Read the chunk header (finally). */
    else {
        switch (
            GRead(context->file, (BYTE *)&context->ckHdr,(long)sizeof(ChunkHeader))
            ) {
            case -1: return(context->ckHdr.ckID = DOS_ERROR);
            case 0:  return(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(context->ckHdr.ckID = NOT_IFF);
                }
        /* Update the context. */
        context->position += (long)sizeof(ChunkHeader);
        remaining         -= (long)sizeof(ChunkHeader);

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

        /* Check: ckSize negative or larger than # bytes left in context? */
        else if (context->ckHdr.ckSize < 0L  ||
                 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,
                                    (long)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; {
    register IFFP iffp = IFF_OKAY;

    if (nBytes < 0L)
        iffp = CLIENT_ERROR;
    else if (nBytes > ChunkMoreBytes(context))
        iffp = SHORT_CHUNK;
    else if (nBytes > 0L) {
        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);
    }

/* ---------- SkipGroup ------------------------------------------------*/
IFFP SkipGroup(context)  GroupContext *context;  {
    }   /* Nothing to do, thanks to GetChunkHdr */

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

    iffp = OpenRIFF(file, &context);
    context.clientFrame = 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 > 0L)              /* 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 > 0L)  /* 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; {
    register ID id;

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

/* ---------- GetF1ChunkHdr ---------------------------------------------*/
ID GetF1ChunkHdr(context)   GroupContext *context; {
    register ID id;
    register 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; {
    register ID id;

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