/*
**	MandelSquare - AmigaDOS 2.0/3.0 Mandelbrot set explorer
**
**	PlayAnim.c, Routines to replay IFF-ANIM files
**
**	Copyright © 1991-1992 by Olaf `Olsen' Barthel
**		All Rights Reserved
*/

	/* Animation module error codes, as returned by ValidateAnim(). */

enum	{		ANIMERR_WRONG_SEQUENCE=1,ANIMERR_NO_BODY,ANIMERR_WRONG_OPERATION,ANIMERR_NO_ANIM,
			ANIMERR_MASKING,ANIMERR_IMAGE,ANIMERR_COMPRESSION,ANIMERR_NO_HEADER,ANIMERR_WRONG_SIZE,
			ANIMERR_WRONG_MODE };

	/* Animation file list entry. */

struct AnimNode
{
	struct MinNode	 Node;

	BitMapHeader	*BitMapHeader;
	AnimationHeader	*AnimationHeader;

	APTR		 Body;

	ULONG		*ViewModes,
			 Modes;

	struct Palette	*Palette;
};

	/* Global flag. */

extern BYTE		Is39;

	/* In Double.c */

extern VOID		FreeDBI(struct DBI *Info);
extern struct DBI *	AllocDBI(struct Screen *Screen);
extern VOID		SwapDBI(struct DBI *Info,BYTE Copy);
extern struct BitMap *	GetDBI(struct DBI *Info);
extern VOID		PaletteDBI(struct DBI *Info,struct Palette *Palette);
extern VOID		StartDBI(struct DBI *Info,ULONG Jiffies);
extern VOID		WaitDBI(struct DBI *Info);

	/* In Palette.c */

extern VOID		FreePalette(struct Palette *Palette);
extern struct Palette *	AllocPalette(LONG NumColours,BYTE TrueColour);
extern struct Palette *	GetPalette(struct Screen *Screen,struct Palette *Palette);
extern VOID		LoadPalette(struct Screen *Screen,struct Palette *Palette,LONG NumColours);
extern LONG		GetPaletteSize(struct Palette *Palette);
extern BYTE		GetPaletteTriplet(struct Palette *Palette,UBYTE *Triplet,LONG Index);
extern BYTE		SetPaletteTriplet(struct Palette *Palette,UBYTE R,UBYTE G,UBYTE B,LONG Index);
extern ULONG		GetPaletteEntry(struct Palette *Palette,LONG Index);
extern BYTE		SetPaletteEntry(struct Palette *Palette,ULONG Entry,LONG Index);

	/* In unvscomp.asm */

extern VOID __asm	decode_vkplane(register __a0 UBYTE *deltabyte,register __a2 PLANEPTR plane,register __a3 UWORD *ytable,register __d2 UWORD bytesperrow);

	/* Local, static routines. */

STATIC VOID __regargs	UnPackRow(BYTE **SourcePtr,BYTE **DestinationPtr,register WORD DestinationBytes);
STATIC BYTE __regargs	GetBitMapHeader(struct AnimNode *Node,BitMapHeader *Header);
STATIC BYTE __regargs	GetAnimationHeader(struct AnimNode *Node,AnimationHeader *Header);
STATIC BYTE __regargs	GetColourMap(struct AnimNode *Node,UBYTE *Palette,LONG Size);
STATIC VOID __regargs	SetRIFF(struct BitMap *BitMap,struct AnimNode *Node,UWORD *LineTable);
STATIC VOID __regargs	SetBODY(struct BitMap *BitMap,struct AnimNode *Node);
STATIC VOID __regargs	DeleteAnimNode(struct AnimNode *Node);

	/* Exported routines. */

VOID			DeleteAnim(struct MinList *List);
struct MinList *	LoadAnim(STRPTR Name);
LONG			ValidateAnim(struct MinList *List,struct Screen *Screen);
BYTE			PlayAnim(struct MinList *List,struct Screen *Screen,BYTE (*CheckAbort)(VOID));

	/* UnPackRow(BYTE **SourcePtr,BYTE **DestinationPtr,register WORD DestinationBytes):
	 *
	 *	CmpByteRun unpacker, based on original EA IFF code.
	 */

STATIC VOID __regargs
UnPackRow(BYTE **SourcePtr,BYTE **DestinationPtr,register WORD DestinationBytes)
{
	register BYTE	*Source		= *SourcePtr,
			*Destination	= *DestinationPtr,
			Char;
	register WORD	Count;

	while(DestinationBytes > 0)
	{
		if((Count = *Source++) >= 0)
		{
			Count++;

			if((DestinationBytes -= Count) < 0)
				break;
			else
			{
				do
					*Destination++ = *Source++;
				while(--Count);
			}
		}
		else
		{
			if(Count != (WORD)(-128))
			{
				Count = -Count + 1;

				if((DestinationBytes -= Count) < 0)
					break;
				else
				{
					Char = *Source++;

					do
						*Destination++ = Char;
					while(--Count);
				}
			}
		}
	}

	*SourcePtr		= Source;
	*DestinationPtr		= Destination;
}

	/* GetBitMapHeader(struct AnimNode *Node,BitMapHeader *Header):
	 *
	 *	Attach a bitmap header to an animnode.
	 */

STATIC BYTE __regargs
GetBitMapHeader(struct AnimNode *Node,BitMapHeader *Header)
{
	if(Node -> BitMapHeader = (BitMapHeader *)AllocVec(sizeof(BitMapHeader),MEMF_ANY))
	{
		CopyMem(Header,Node -> BitMapHeader,sizeof(BitMapHeader));

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

	/* GetAnimationHeader(struct AnimNode *Node,AnimationHeader *Header):
	 *
	 *	Attach and animation header to an animnode.
	 */

STATIC BYTE __regargs
GetAnimationHeader(struct AnimNode *Node,AnimationHeader *Header)
{
	if(Node -> AnimationHeader = (AnimationHeader *)AllocVec(sizeof(AnimationHeader),MEMF_ANY))
	{
		CopyMem(Header,Node -> AnimationHeader,sizeof(AnimationHeader));

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

	/* GetColourMap(struct AnimNode *Node,UBYTE *Palette,LONG Size):
	 *
	 *	Attach a colour map to an animnode.
	 */

STATIC BYTE __regargs
GetColourMap(struct AnimNode *Node,UBYTE *Palette,LONG Size)
{
		/* Determine number of palette entries. */

	LONG Count = Size / 3;

		/* Allocate a palette. */

	if(Node -> Palette = AllocPalette(Count,Count == 256))
	{
		LONG	R,G,B,
			i;

			/* Eight bits? */

		if(Count == 256)
		{
				/* Run down the table, setting the eight bit triplets. */

			for(i = 0 ; i < Count ; i++)
			{
				R = *Palette++;
				G = *Palette++;
				B = *Palette++;

				SetPaletteTriplet(Node -> Palette,R,G,B,i);
			}
		}
		else
		{
				/* Run down the table, setting the four bit triplets. */

			for(i = 0 ; i < Count ; i++)
			{
				R = ((*Palette++) >> 4) & 0xF;
				G = ((*Palette++) >> 4) & 0xF;
				B = ((*Palette++) >> 4) & 0xF;

				SetPaletteTriplet(Node -> Palette,R,G,B,i);
			}
		}

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

	/* SetRIFF(struct BitMap *BitMap,struct AnimNode *Node,UWORD *LineTable):
	 *
	 *	Unpack RIFF style delta data.
	 */

STATIC VOID __regargs
SetRIFF(struct BitMap *BitMap,struct AnimNode *Node,UWORD *LineTable)
{
	register LONG	*DeltaData = (LONG *)Node -> Body,
			 i;

		/* Run down the planes... */

	for(i = 0 ; i < BitMap -> Depth ; i++)
	{
			/* Any data to process? If so, unpack it. */

		if(DeltaData[i])
			decode_vkplane((UBYTE *)DeltaData + DeltaData[i],BitMap -> Planes[i],LineTable,BitMap -> BytesPerRow);
	}
}

	/* SetBODY(struct BitMap *BitMap,struct AnimNode *Node):
	 *
	 *	Unpack CmpByteRun data.
	 */

STATIC VOID __regargs
SetBODY(struct BitMap *BitMap,struct AnimNode *Node)
{
	PLANEPTR	 Planes[8],
			*Destination;
	BYTE		*Source;
	LONG		 i,j;

		/* Copy the plane addresses, we will modify the pointers. */

	for(i = 0 ; i < BitMap -> Depth ; i++)
		Planes[i] = BitMap -> Planes[i];

		/* Copy the data pointer, this will be modified as well. */

	Source = (BYTE *)Node -> Body;

		/* Run down the rows... */

	for(i = 0 ; i < BitMap -> Rows ; i++)
	{
			/* Run down the planes... */

		for(j = 0 ; j < BitMap -> Depth ; j++)
		{
				/* Remember the address. */

			Destination = &Planes[j];

				/* Unpack the data. */

			UnPackRow(&Source,Destination,BitMap -> BytesPerRow);
		}
	}
}

	/* DeleteAnimNode(struct AnimNode *Node):
	 *
	 *	Free an animation list node.
	 */

STATIC VOID __regargs
DeleteAnimNode(struct AnimNode *Node)
{
	if(Node -> BitMapHeader)
		FreeVec(Node -> BitMapHeader);

	if(Node -> AnimationHeader)
		FreeVec(Node -> AnimationHeader);

	if(Node -> Body)
		FreeVec(Node -> Body);

	if(Node -> Palette)
		FreePalette(Node -> Palette);

	FreeVec(Node);
}

	/* DeleteAnim(struct MinList *List):
	 *
	 *	Delete the contents of an entire animation list.
	 */

VOID
DeleteAnim(struct MinList *List)
{
	struct AnimNode	*Node,
			*Next;

		/* Pick up the first node in the list. */

	Node = (struct AnimNode *)List -> mlh_Head;

		/* Run down the list... */

	while(Next = (struct AnimNode *)Node -> Node . mln_Succ)
	{
			/* Free the node. */

		DeleteAnimNode(Node);

			/* Skip to the next. */

		Node = Next;
	}

		/* Remove the list itself. */

	FreeVec(List);
}

	/* LoadAnim(STRPTR Name):
	 *
	 *	Load an ANIM5 animation file, creating a list suitable for
	 *	submitting to PlayAnim() or ValidateAnim().
	 */

struct MinList *
LoadAnim(STRPTR Name)
{
		/* Data we will pick up on the fly. */

	STATIC ULONG Properties[2 * 4] =
	{
		ID_ILBM,ID_BMHD,
		ID_ILBM,ID_ANHD,
		ID_ILBM,ID_CAMG,
		ID_ILBM,ID_CMAP
	};

		/* Image and delta data. */

	STATIC ULONG Stops[2 * 2] =
	{
		ID_ILBM,ID_BODY,
		ID_ILBM,ID_DLTA
	};

	struct MinList *List;

		/* Allocate the body list. */

	if(List = (struct MinList *)AllocVec(sizeof(struct MinList),MEMF_ANY))
	{
		struct IFFHandle	*Handle;
		BYTE			 Success = TRUE;

			/* Initialize the list header. */

		NewList((struct List *)List);

			/* Allocate an IFF handle. */

		if(Handle = AllocIFF())
		{
				/* Open the file for reading. */

			if(Handle -> iff_Stream = Open(Name,MODE_OLDFILE))
			{
					/* Make it known as a dos file. */

				InitIFFasDOS(Handle);

					/* Open the file for reading. */

				if(!OpenIFF(Handle,IFFF_READ))
				{
						/* Remember the properties to store. */

					if(!PropChunks(Handle,Properties,4))
					{
							/* Remember the chunks to stop at. */

						if(!StopChunks(Handle,Stops,2))
						{
							struct StoredProperty	*Prop;
							struct ContextNode	*Top;

								/* Start scanning... */

							while(Success && !ParseIFF(Handle,IFFPARSE_SCAN))
							{
									/* Pick up the topmost chunk. */

								if(Top = CurrentChunk(Handle))
								{
									struct AnimNode *Node;

										/* Allocate a new node. */

									if(Node = AllocVec(sizeof(struct AnimNode),MEMF_ANY | MEMF_CLEAR))
									{
											/* Add it to the list. */

										AddTail((struct List *)List,(struct Node *)Node);

											/* Add the bitmap header. */

										if(Prop = FindProp(Handle,ID_ILBM,ID_BMHD))
										{
											if(!GetBitMapHeader(Node,(BitMapHeader *)Prop -> sp_Data))
											{
												Success = FALSE;
												break;
											}
										}

											/* Add the animation header. */

										if(Prop = FindProp(Handle,ID_ILBM,ID_ANHD))
										{
											if(!GetAnimationHeader(Node,(AnimationHeader *)Prop -> sp_Data))
											{
												Success = FALSE;
												break;
											}
										}

											/* Add the display mode information. */

										if(Prop = FindProp(Handle,ID_ILBM,ID_CAMG))
										{
											Node -> ViewModes	= &Node -> Modes;
											Node -> Modes		= *(ULONG *)Prop -> sp_Data;
										}

											/* Add the colour map information. */

										if(Prop = FindProp(Handle,ID_ILBM,ID_CMAP))
										{
											if(!GetColourMap(Node,(UBYTE *)Prop -> sp_Data,Prop -> sp_Size))
											{
												Success = FALSE;
												break;
											}
										}

											/* Now we are about the read the BODY or the DLTA information,
											 * but not both.
											 */

										if(Node -> Body || (Top -> cn_ID == ID_BODY && !Node -> BitMapHeader) || (Top -> cn_ID == ID_DLTA && !Node -> AnimationHeader))
										{
											Success = FALSE;
											break;
										}

											/* Allocate BODY or DLTA data, the animation header will
											 * provide the necessary information.
											 */

										if(Node -> Body = AllocVec(Top -> cn_Size,MEMF_ANY))
										{
											if(ReadChunkBytes(Handle,Node -> Body,Top -> cn_Size) != Top -> cn_Size)
												Success = FALSE;
										}
										else
											Success = FALSE;
									}
									else
										Success = FALSE;
								}
							}
						}
					}

						/* Close the handle. */

					CloseIFF(Handle);
				}

					/* Close the file. */

				Close(Handle -> iff_Stream);
			}

				/* Free the handle data. */

			FreeIFF(Handle);
		}

			/* Delete the animation list if anything went wrong. */

		if(!Success)
		{
			DeleteAnim(List);

			List = NULL;
		}
	}

	return(List);
}

	/* ValidateAnim(struct MinList *List,struct Screen *Screen):
	 *
	 *	Validate the loaded animation and see if it fits both
	 *	the screen and the machine.
	 */

LONG
ValidateAnim(struct MinList *List,struct Screen *Screen)
{
	struct AnimNode	*Node,
			*Next;
	BitMapHeader	*Header;
	LONG		 Frames = 0;

		/* Is there any data? */

	Node = (struct AnimNode *)List -> mlh_Head;

	if(Node -> Node . mln_Succ)
	{
			/* We _need_ a bitmap header. */

		if(!Node -> BitMapHeader)
			return(ANIMERR_WRONG_SEQUENCE);
		else
			Header = Node -> BitMapHeader;
	}
	else
		return(ANIMERR_NO_ANIM);

		/* Now scan the list... */

	while(Next = (struct AnimNode *)Node -> Node . mln_Succ)
	{
			/* Is the display mode available? */

		if(Node -> ViewModes)
		{
			if(ModeNotAvailable(*Node -> ViewModes))
				return(ANIMERR_WRONG_MODE);
		}

			/* Is the image too `deep'? */

		if(Node -> BitMapHeader)
		{
			if(Node -> BitMapHeader -> nPlanes > 5 && !Is39)
				return(ANIMERR_WRONG_MODE);
		}

			/* No delta or image data available? */

		if(!Node -> Body)
			return(ANIMERR_NO_BODY);

			/* Animation header available? */

		if(Node -> AnimationHeader)
		{
				/* Only type 5 animations are supported
				 * so far...
				 */

			if(Node -> AnimationHeader -> operation != 5)
				return(ANIMERR_WRONG_OPERATION);

				/* No masking, please. */

			if(Node -> AnimationHeader -> mask)
				return(ANIMERR_MASKING);

			Frames++;
		}

			/* Bitmap header available? */

		if(Node -> BitMapHeader)
		{
				/* Strange image information? */

			if(Node -> BitMapHeader -> nPlanes < 1 || Node -> BitMapHeader -> nPlanes > 8 || !Node -> BitMapHeader -> w || !Node -> BitMapHeader -> h)
				return(ANIMERR_IMAGE);

				/* No masking, please. */

			if(Node -> BitMapHeader -> masking)
				return(ANIMERR_MASKING);

				/* The image _has_ to be compressed! */

			if(Node -> BitMapHeader -> compression != 1)
				return(ANIMERR_COMPRESSION);

				/* Does it fit the screen? */

			if(Node -> BitMapHeader -> w != Screen -> Width || Node -> BitMapHeader -> h != Screen -> Height || Node -> BitMapHeader -> nPlanes != Screen -> RastPort . BitMap -> Depth)
				return(ANIMERR_WRONG_SIZE);

			Frames++;
		}

		Node = Next;
	}

		/* There should be at least two frames. */

	if(Frames < 2)
		return(ANIMERR_NO_ANIM);
	else
	{
		if(!Header)
			return(ANIMERR_NO_HEADER);
		else
			return(0);
	}
}

	/* PlayAnim(struct MinList *List,struct Screen *Screen,BYTE (*CheckAbort)(VOID)):
	 *
	 *	Replay an animation.
	 */

BYTE
PlayAnim(struct MinList *List,struct Screen *Screen,BYTE (*CheckAbort)(VOID))
{
	struct DBI	*Info;
	BYTE		 Result = FALSE;

		/* Allocate double-buffering information. */

	if(Info = AllocDBI(Screen))
	{
		UWORD *LineTable;

			/* Set up the line byte modulo table. */

		if(LineTable = (UWORD *)AllocVec(sizeof(UWORD) * Screen -> Height,MEMF_ANY))
		{
			struct AnimNode		*Node,
						*Next;
			struct BitMap		*BitMap;
			BYTE			 Done = FALSE;
			UWORD			 Count,
						 i;

				/* Get the bitmap to render into. */

			BitMap = GetDBI(Info);

				/* Set up the table information. */

			for(i = Count = 0 ; i < BitMap -> Rows ; i++)
			{
				LineTable[i] = Count;

				Count += BitMap -> BytesPerRow;
			}

				/* Run the loop... */

			do
			{
					/* Get the first list entry, this
					 * should be the first image.
					 */

				Node = (struct AnimNode *)List -> mlh_Head;

					/* Run down the list... */

				while(Next = (struct AnimNode *)Node -> Node . mln_Succ)
				{
						/* Get the bitmap to render into. */

					BitMap = GetDBI(Info);

						/* Any palette to set? */

					if(Node -> Palette)
						PaletteDBI(Info,Node -> Palette);

						/* Delta data to unpack? */

					if(Node -> AnimationHeader)
					{
							/* Any inter-frame delay? */

						if(Node -> AnimationHeader -> reltime)
						{
								/* Start the timer... */

							StartDBI(Info,Node -> AnimationHeader -> reltime);

								/* Unpack the delta data. */

							SetRIFF(BitMap,Node,LineTable);

								/* Wait for timer to elapse. */

							WaitDBI(Info);
						}
						else
							SetRIFF(BitMap,Node,LineTable);
					}
					else
						SetBODY(BitMap,Node);

						/* Swap in the bitmap just rendered. */

					SwapDBI(Info,TRUE);

						/* Check for abort action. */

					if((*CheckAbort)())
					{
						Done = TRUE;
						break;
					}

						/* Skip to the next frame. */

					Node = Next;
				}

					/* Check for abort action. */

				if((*CheckAbort)())
					Done = TRUE;
			}
			while(!Done);

				/* Free the line modulo table. */

			FreeVec(LineTable);

				/* We are successful so far. */

			Result = TRUE;
		}

			/* Free the double-buffering information. */

		FreeDBI(Info);
	}

	return(Result);
}
