/** PreLoadAnim.c ***********************************************************
 *
 * Load an ILBM raster image file into memory structures
 * This is a modified version of ReadPict.c dated 23-Jan-86.
 *
 * Modified by Gary Bonham, SPARTA, Inc.  15 Aug 1986
 * Modified by Olaf `Olsen' Barthel, 16-Sep-1991
 *
 ***************************************************************************/

#include <intuition/intuition.h>
#include <libraries/dosextens.h>
#include <exec/memory.h>

#include "ilbm.h"
#include "readpict.h"
#include "preloadanim.h"

/* This example's max number of planes in a bitmap. Could use MaxAmDepth. */

#define EXDepth 5
#define maxColorReg (1 << EXDepth)

#define GetANHD(context,anHdr) IFFReadBytes(context,(BYTE *)anHdr,sizeof(AnimationHeader))

STATIC IFFP		 GetCMAP(GroupContext *ilbmContext,WORD *colorMap,UBYTE *pNColorRegs);
STATIC IFFP		 GetPrILBM(GroupContext *parent);
STATIC IFFP		 GetLiILBM(GroupContext *parent);
STATIC IFFP		 GetFoANIM(GroupContext *parent);
STATIC IFFP __regargs	 PreLoadAnimation(BPTR file,ILBMFrame *iFrame);
VOID			 ClosePreLoadIFF(VOID);
BYTE			 OpenPreLoadIFF(BPTR file);

extern struct Screen	*Screen;
struct IFFfile		*IFFfileList;

/*------------ ILBM reader -----------------------------------------------*/
/* ILBMFrame is our "client frame" for reading FORMs ILBM in an IFF file.
 * We allocate one of these on the stack for every LIST or FORM encountered
 * in the file and use it to hold BMHD & CMAP properties. We also allocate
 * an initial one for the whole file.
 * We allocate a new GroupContext (and initialize it by OpenRIFF or
 * OpenRGroup) for every group (FORM, CAT, LIST, or PROP) encountered. It's
 * just a context for reading (nested) chunks.
 *
 * If we were to scan the entire example file outlined below:
 *    reading          proc(s)                new               new
 *
 * --whole file--   ReadPicture+ReadIFF   GroupContext        ILBMFrame
 * CAT              ReadICat                GroupContext
 *   LIST           GetLiILBM+ReadIList       GroupContext        ILBMFrame
 *     PROP ILBM    GetPrILBM                   GroupContext
 *       CMAP       GetCMAP
 *       BMHD       GetBMHD
 *     FORM ILBM    GetFoILBM                   GroupContext        ILBMFrame
 *       BODY       GetBODY
 *     FORM ILBM    GetFoILBM                   GroupContext        ILBMFrame
 *       BODY       GetBODY
 *   FORM ILBM      GetFoILBM                 GroupContext        ILBMFrame
 */

/* ---------- GetCMAP ------------------------------------------------*/
/* pNColorRegs is passed in as a pointer to the number of ColorRegisters
 * caller has space to hold.  GetCMAP sets to the number actually read.*/

STATIC IFFP
GetCMAP(GroupContext *ilbmContext,WORD *colorMap,UBYTE *pNColorRegs)
{
	ColorRegister	colorReg;
	LONG		nColorRegs;
	IFFP		iffp;

	nColorRegs = ilbmContext -> ckHdr . ckSize / sizeof(ColorRegister);

	if(*pNColorRegs < nColorRegs)
		nColorRegs = *pNColorRegs;

	*pNColorRegs = nColorRegs;	/* Set to the number actually there.*/

	while(nColorRegs--)
	{
		iffp = IFFReadBytes(ilbmContext,(BYTE *)&colorReg,sizeof(ColorRegister));

		CheckIFFP();

		*colorMap++ = ((UWORD)(colorReg . red >> 4 ) << 8 ) | ((UWORD)(colorReg . green & 0x0F0)) | ((UWORD)(colorReg . blue >> 4));
	}

	return(IFF_OKAY);
}

/** GetPrILBM() *************************************************************
 *
 * Called via ReadPicture to handle every PROP encountered in an IFF file.
 * Reads PROPs ILBM and skips all others.
 *
 ****************************************************************************/

STATIC IFFP
GetPrILBM(GroupContext *parent)
{
	IFFP		 iffp;
	GroupContext	 propContext;
	ILBMFrame	*ilbmFrame = (ILBMFrame *)parent -> clientFrame;

	if(parent -> subtype != ID_ILBM)
		return(IFF_OKAY);	/* just continue scanning the file */

	iffp = OpenRGroup(parent,&propContext);

	CheckIFFP();

	do
	{
		switch(iffp = GetPChunkHdr(&propContext))
		{
			case ID_BMHD:
			{
				ilbmFrame -> foundBMHD = TRUE;

				iffp = GetBMHD(&propContext,&ilbmFrame -> bmHdr);
				break;
			}

			case ID_CMAP:
			{
				ilbmFrame -> nColorRegs = maxColorReg;

				iffp = GetCMAP(&propContext,(WORD *)&ilbmFrame -> colorMap,&ilbmFrame -> nColorRegs);
				break;
			}
		}
	}
	while(iffp >= IFF_OKAY);	/* loop if valid ID of ignored chunk or a
					 * subroutine returned IFF_OKAY (no errors).
					 */

	CloseRGroup(&propContext);

	return(iffp == END_MARK ? IFF_OKAY : iffp);
}

/** GetLiILBM() *************************************************************
 *
 * Called via ReadPicture to handle every LIST encountered in an IFF file.
 *
 ****************************************************************************/

STATIC IFFP
GetLiILBM(GroupContext *parent)
{
	ILBMFrame newFrame;	/* allocate a new Frame */

	newFrame = *(ILBMFrame *)parent -> clientFrame;	/* copy parent frame */

	return(ReadIList(parent,(ClientFrame *)&newFrame));
}

/** GetFoANIM() *************************************************************
 *
 * Called via ReadPicture to handle every FORM encountered in an IFF file.
 * Reads FORMs ILBM and skips all others.
 * Inside a FORM ILBM, it stops once it reads a BODY. It complains if it
 * finds no BODY or if it has no BMHD to decode the BODY.
 *
 * Once we find a BODY chunk, we'll allocate the BitMap and read the image.
 *
 ****************************************************************************/

STATIC IFFP
GetFoANIM(GroupContext *parent)
{
	IFFP		iffp;
	GroupContext	formContext;
	ILBMFrame	ilbmFrame;	/* only used for non-clientFrame fields.*/

	/* Handle a non-ILBM FORM. */

	if(parent -> subtype != ID_ILBM)
	{
		/* Open a non-ILBM FORM and recursively scan it for ILBMs.*/

		iffp = OpenRGroup(parent,&formContext);

		CheckIFFP();

		do
		{
			iffp = GetF1ChunkHdr(&formContext);

			if(iffp == IFF_DONE)
				iffp = IFF_OKAY;
		}
		while(iffp >= IFF_OKAY);

		if(iffp == END_MARK)
			iffp = IFF_DONE;	/* then continue scanning the file */

		CloseRGroup(&formContext);
		return(iffp);
	}

	ilbmFrame = *(ILBMFrame *)parent -> clientFrame;

	iffp = OpenRGroup(parent,&formContext);

	CheckIFFP();

	if(!IFFfileList -> lastframe)
	{
		IFFfileList -> lastframe	= (struct FrameHD *)AllocMem(sizeof(struct FrameHD),MEMF_PUBLIC|MEMF_CLEAR);
		IFFfileList -> firstframe	= IFFfileList -> lastframe;
	}
	else
	{
		if(IFFfileList -> lastframe -> next = (struct FrameHD *)AllocMem(sizeof(struct FrameHD),MEMF_PUBLIC|MEMF_CLEAR))
			IFFfileList -> lastframe -> next -> prev = IFFfileList -> lastframe;

		IFFfileList -> lastframe	= IFFfileList -> lastframe -> next;
	}

	if(!IFFfileList -> lastframe)
		return(END_MARK);

	do
	{
		switch(iffp = GetFChunkHdr(&formContext))
		{
			case ID_ANHD:
			{
				ilbmFrame . foundBMHD = TRUE;

				if(!(IFFfileList -> lastframe -> anhd = (AnimationHeader *)AllocMem(sizeof(AnimationHeader),MEMF_PUBLIC|MEMF_CLEAR)))
				{
					iffp = DOS_ERROR;
					break;
				}

				iffp = GetANHD(&formContext,IFFfileList -> lastframe -> anhd);
				break;
			}

			case ID_BMHD:
			{
				ilbmFrame . foundBMHD = TRUE;

				if(!(IFFfileList -> lastframe -> bmhd = (BitMapHeader *)AllocMem(sizeof(BitMapHeader),MEMF_PUBLIC|MEMF_CLEAR)))
				{
					iffp = DOS_ERROR;

					break;
				}
				else
				{
					iffp = GetBMHD(&formContext,IFFfileList -> lastframe -> bmhd);

					if(IFFfileList -> lastframe -> bmhd -> pageHeight < IFFfileList -> lastframe -> bmhd -> h)
						IFFfileList -> lastframe -> bmhd -> pageHeight = IFFfileList -> lastframe -> bmhd -> h;

					if(IFFfileList -> lastframe -> bmhd -> pageWidth < IFFfileList -> lastframe -> bmhd -> w)
						IFFfileList -> lastframe -> bmhd -> pageWidth = IFFfileList -> lastframe -> bmhd -> w;

					if(IFFfileList -> lastframe -> bmhd -> pageHeight != Screen -> Height || IFFfileList -> lastframe -> bmhd -> pageWidth != Screen -> Width || IFFfileList -> lastframe -> bmhd -> nPlanes != Screen -> RastPort . BitMap -> Depth)
						iffp = DOS_ERROR;

					break;
				}
			}

			case ID_CAMG:
			{
				ilbmFrame . foundCAMG = TRUE;

				if(!(IFFfileList -> lastframe -> camg = (CamgChunk *)AllocMem(sizeof(CamgChunk),MEMF_PUBLIC|MEMF_CLEAR)))
				{
					iffp = DOS_ERROR;

					break;
				}
				else
				{
					iffp = GetCAMG(&formContext,IFFfileList -> lastframe -> camg);
					break;
				}
			}

			case ID_CMAP:
			{
				IFFfileList -> lastframe -> nColorRegs = maxColorReg;	/* we have room for this many */

				iffp = GetCMAP(&formContext,&IFFfileList -> lastframe -> cmap[0],&IFFfileList -> lastframe -> nColorRegs);
				break;
			}

			case ID_DLTA:	/* at this point just read in delta chunk */
			case ID_BODY:
			{
				if(!ilbmFrame . foundBMHD)
					return(BAD_FORM);	/* No BMHD chunk! */

				if(IFFfileList -> lastframe -> bodylength = formContext . ckHdr . ckSize)
				{
					if(!(IFFfileList -> lastframe -> body = (ULONG *)AllocMem(IFFfileList -> lastframe -> bodylength,MEMF_PUBLIC|MEMF_CLEAR)))
					{
						iffp = END_MARK;

						break;
					}

					IFFReadBytes(&formContext,(BYTE *)IFFfileList -> lastframe -> body,IFFfileList -> lastframe -> bodylength);
				}

				break;
			}

			case END_MARK:	/* No BODY chunk! */
			{
				iffp = IFF_DONE;
				break;
			}
		}
	}
	while(iffp >= IFF_OKAY);	/* loop if valid ID of ignored chunk or a
					 * subroutine returned IFF_OKAY (no errors).
					 */

	if(iffp != IFF_DONE)
		return(iffp);

	/* If we get this far, there were no errors. */

	CloseRGroup(&formContext);

	return(iffp);
}

STATIC IFFP __regargs
PreLoadAnimation(BPTR file,ILBMFrame *iFrame)
{
	iFrame -> clientFrame . getList	= GetLiILBM;
	iFrame -> clientFrame . getProp	= GetPrILBM;
	iFrame -> clientFrame . getForm	= GetFoANIM;
	iFrame -> clientFrame . getCat	= ReadICat ;

	/* Initialize the top-level client frame's property settings to the
	 * program-wide defaults. This example just records that we haven't read
	 * any BMHD property or CMAP color registers yet. For the color map, that
	 * means the default is to leave the machine's color registers alone.
	 * If you want to read a property like GRAB, init it here to (0, 0).
	 */

	iFrame -> foundBMHD	= FALSE;
	iFrame -> nColorRegs	= 0;

	/* Store a pointer to the client's frame in a global variable so that
	 * GetFoILBM can update client's frame when done.  Why do we have so
	 * many frames & frame pointers floating around causing confusion?
	 * Because IFF supports PROPs which apply to all FORMs in a LIST,
	 * unless a given FORM overrides some property.
	 * When you write code to read several FORMs,
	 * it is ssential to maintain a frame at each level of the syntax
	 * so that the properties for the LIST don't get overwritten by any
	 * properties specified by individual FORMs.
	 * We decided it was best to put that complexity into this one-FORM example,
	 * so that those who need it later will have a useful starting place.
	 */

	if(IFFfileList = (struct IFFfile *)AllocMem(sizeof(struct IFFfile),MEMF_PUBLIC|MEMF_CLEAR))
		return(ReadIFF(file,(ClientFrame *)iFrame));
	else
		return(DOS_ERROR);
}

VOID
ClosePreLoadIFF()
{
	if(IFFfileList)
	{
		struct FrameHD *currentframe,*nextframe;

		currentframe = IFFfileList -> firstframe;

		while(currentframe)
		{
			if(currentframe -> bmhd)
				FreeMem(currentframe -> bmhd,sizeof(BitMapHeader));

			if(currentframe -> anhd)
				FreeMem(currentframe -> anhd,sizeof(AnimationHeader));
			if(currentframe -> camg)
				FreeMem(currentframe -> camg,sizeof(CamgChunk));

			if(currentframe -> body && currentframe -> bodylength)
				FreeMem(currentframe -> body,currentframe -> bodylength);

			nextframe = currentframe -> next;

			FreeMem(currentframe,sizeof(struct FrameHD));

			currentframe = nextframe;
		}

		FreeMem(IFFfileList,sizeof(struct IFFfile));

		IFFfileList = NULL;
	}
}

BYTE
OpenPreLoadIFF(BPTR file)
{
	ILBMFrame iframe;

	if(PreLoadAnimation(file,&iframe) != IFF_DONE)
	{
		ClosePreLoadIFF();

		return(FALSE);
	}
	else
		return(TRUE);
}
