/******************************************************************************
*
*	Cycling.c 01/07/89 by:	Olaf Barthel
*				Brabeckstrasse 35
*				D-3000 Hannover 71
*
*				Federal Republic of Germany
*
*	Cycling 1.5 (C) Copyright 1989 by Olaf Barthel & ED Hannover
*
*	Not  even  a  single character of this wonderful piece of code
*	may  be  incorporated  into  any  other  program  without  the
*	author's  consent.   Yep,  still a splendid time is guaranteed
*	for all!
*
*******************************************************************************
*
*	Cycling  contains colour cycling support routines for LoadImage.c,
*	and is based on example source code written by Carolin Scheppner
*	(Display.c). This version uses interrupt code to handle the
*	cycling job.
*
******************************************************************************/

#include <graphics/gfxbase.h>
#include <exec/interrupts.h>
#include <graphics/view.h>
#include <exec/memory.h>
#include <stdio.h>

	/* The typical format of a DPaint colour cycling range. */

typedef struct
{
	WORD pad1;	/* Not used. */
	WORD rate;	/* Cycling speed. */
	WORD active;	/* Is it active? Which way is it cycling? */
	UBYTE low,high;	/* Start and stop colour. */
} CRange;

	/* LoadCycleRange needs these macros. */

#define CRNG_NORATE	36	/* Don't cycle this range. */
#define CRNG_ACTIVE	1 << 0	/* This range is active. */
#define CRNG_REVERSE	1 << 1	/* This range is cycling backwards. */

	/* The following values are to controll the interrupt code. */

#define CY_CYCL 0	/* Keep on cycling. */
#define CY_WAIT	1	/* Don't cycle, wait for startup message. */

	/* A sole forward declaration. */

extern BOOL FindChunk();

	/* CycleCode needs this data, InitCycleCode prepares it. */

struct ViewPort	*cy_VPort;		/* Where to cycle. */
struct Interrupt cy_VBlank;		/* The Cycling-interrupt. */
CRange		*cy_Range;		/* The cycling-ranges. */
LONG		 cy_RangeNum;		/* The number of ranges. */
UWORD		 cy_ColMap[32];		/* The colourmap. */
LONG		 cy_ColNum;		/* The number of colours. */
short		 cy_TicksPassed[16];	/* Event counter for ranges. */
UWORD		 cy_TempCol;		/* Temporary colour buffer. */
UWORD		 cy_UndoBuffer[32];
short		 cy_WeAreWaiting;	/* CycleCode is waiting. */
short		 cy_Command;		/* What is the CycleCode up to do? */
BOOL		 cy_CodeRun = FALSE;	/* Is the CycleCode already running? */

extern void VBlankHandler();

#asm
_VBlankHandler:	MOVEM.L	A2-A6/D2-D7,-(A7)	; Save some registers

		JSR	_geta4#			; Adjust data register
		JSR	_CycleCode		; Call 'C' routine

		CLR.L	D0			; D0 must be cleared
						; or strange things will
						; happen

		MOVEM.L	(A7)+,A2-A6/D2-D7	; Restore registers

		RTS				; ...and go on
#endasm

	/* CycleCode() :
	 *
	 *	CycleCode is the 'C' cycling routine. It runs as an
	 *	interrupt server to save task time.
	 */

void
CycleCode()
{
	register short i,j;	/* Loop counter. */

		/* Are we to wait? */

	if(cy_Command == CY_WAIT)
	{
			/* Restore the original palette. */

		if(!cy_WeAreWaiting)
			LoadRGB4(cy_VPort,cy_UndoBuffer,cy_ColNum);

		cy_WeAreWaiting = TRUE;

			/* Don't continue to cycle. */

		return;
	}

		/* We aren't waiting any more. */

	if(cy_WeAreWaiting)
	{
			/* Re-initialize the palette. */

		for(i = 0 ; i < cy_ColNum ; i++)
			cy_ColMap[i] = cy_UndoBuffer[i];

			/* Reset event counters. */

		for(i = 0 ; i < cy_RangeNum ; i++)
			cy_TicksPassed[i] = 0;

		cy_WeAreWaiting = FALSE;
	}

		/* Now handle the cycle ranges. */

	for(i = 0 ; i < cy_RangeNum ; i++)
	{
			/* Increment event counter. */

		cy_TicksPassed[i]++;

			/* Is this one up to cycle next? */

		if(cy_TicksPassed[i] == cy_Range[i] . rate)
		{
				/* Reset event counter for this range. */

			cy_TicksPassed[i] = 0;

				/* Is this range active? */

			if(!(cy_Range[i] . active & CRNG_ACTIVE))
				continue;

				/* Cycling backwards? */

			if(cy_Range[i] . active & CRNG_REVERSE)
			{
					/* Move the colours. */

				cy_TempCol = cy_ColMap[cy_Range[i] . low];

				for(j = cy_Range[i] . low ; j < cy_Range[i] . high ; j++)
					cy_ColMap[j] = cy_ColMap[j + 1];

				cy_ColMap[cy_Range[i] . high] = cy_TempCol;
			}
			else
			{
					/* This one is cycling forwards. */

				cy_TempCol = cy_ColMap[cy_Range[i] . high];

				for(j = cy_Range[i] . high ; j > cy_Range[i] . low ; j--)
					cy_ColMap[j] = cy_ColMap[j - 1];

				cy_ColMap[cy_Range[i] . low] = cy_TempCol;
			}

				/* Okay, everything has been moved, now
				 * load the new palette.
				 */

			LoadRGB4(cy_VPort,cy_ColMap,cy_ColNum);
		}
	}
}

	/* InitCycleCode(ViewPort,ColMap,ColNum,Range,RangeNum) :
	 *
	 *	Initializes all data and starts up the interrupt
	 *	server.
	 */

BOOL
InitCycleCode(ViewPort,ColMap,ColNum,Range,RangeNum)
struct ViewPort *ViewPort;
UWORD *ColMap;
LONG ColNum;
CRange *Range;
LONG RangeNum;
{
	register short i;

		/* Check for valid parameters. */

	if(!ViewPort || !ColMap || !ColNum || !Range || !RangeNum)
		return(FALSE);

		/* Copy the data. */

	cy_VPort	= ViewPort;
	cy_ColNum	= ColNum;
	cy_Range	= Range;
	cy_RangeNum	= RangeNum;

	for(i = 0 ; i < ColNum ; i++)
		cy_ColMap[i] = ColMap[i];

		/* Clear event counters. */

	for(i = 0 ; i < cy_RangeNum ; i++)
		cy_TicksPassed[i] = 0;

		/* Copy the original palette to the undo buffer. */

	for(i = 0 ; i < cy_ColNum ; i++)
		cy_UndoBuffer[i] = cy_ColMap[i];

		/* Let the CycleCode wait. */

	cy_Command	= CY_WAIT;
	cy_WeAreWaiting	= TRUE;

		/* Ta Da! The CycleCode enters the stage!. */

	cy_VBlank . is_Data		= (APTR)NULL;
	cy_VBlank . is_Code		= VBlankHandler;
	cy_VBlank . is_Node . ln_Succ	= NULL;
	cy_VBlank . is_Node . ln_Pred	= NULL;
	cy_VBlank . is_Node . ln_Type	= NT_INTERRUPT;
	cy_VBlank . is_Node . ln_Pri	= 0;
	cy_VBlank . is_Node . ln_Name	= "Cycling Interrupt";

	AddIntServer(5,&cy_VBlank);

	cy_CodeRun = TRUE;

	return(TRUE);
}

	/* ClearCycleCode() :
	 *
	 *	Forces the CycleCode to hit and quit.
	 */

void
ClearCycleCode()
{
	register short i;

		/* Is the interrupt already running? */

	if(!cy_CodeRun)
		return;

		/* Tell it to wait. */

	cy_WeAreWaiting	= TRUE;
	cy_Command	= CY_WAIT;

		/* Remove the server code. */

	RemIntServer(5,&cy_VBlank);

		/* Rebuild palette. */

	for(i = 0 ; i < cy_ColNum ; i++)
		cy_ColMap[i] = cy_UndoBuffer[i];


		/* Wait for it to finish. */

	WaitTOF();
	WaitTOF();
	WaitTOF();

		/* Close the shop. */

	cy_CodeRun = FALSE;
}

	/* ToggleCycleCode() :
	 *
	 *	Toggles the activity of the cycling code.
	 */

void
ToggleCycleCode()
{
	static short Mode = FALSE;

	Mode ^= TRUE;

	if(Mode)
		cy_Command = CY_CYCL;
	else
		cy_Command = CY_WAIT;

	WaitTOF();
}

	/* IsCycling() :
	 *
	 *	Returns the present status of the cycling code.
	 */

short
IsCycling()
{
	if(cy_Command == CY_CYCL)
		return(TRUE);

	if(cy_Command == CY_WAIT)
		return(FALSE);
}

	/* LoadCycleRange(FileName,Range,MaxRange) :
	 *
	 *	Searches the IFF-ILBM-file for CRNG or CCRT
	 *	chunks und initializes the colour-ranges.
	 */

LONG
LoadCycleRange(FileName,Range,MaxRange)
char *FileName;
CRange *Range;
LONG MaxRange;
{
	FILE *CycFile;
	register short i;

		/* GraphiCraft private cycling chunk. */

	struct
	{
		WORD direction;
		UBYTE start;
		UBYTE end;
		LONG seconds;
		LONG microseconds;
		WORD pad;
	} CcrtChunk;

		/* Clear all ranges. */

	for(i = 0 ; i < MaxRange ; i++)
		Range[i] . active = 0;

		/* Open the file and try to locate the CRNG chunk. */

	if(!(CycFile = fopen(FileName,"r")))
		return(FALSE);

	if(FindChunk("CRNG",CycFile))
	{
			/* Read all ranges. */

		for(i = 0 ; i < MaxRange ; i++)
		{
			fread(&Range[i],sizeof(CRange),1,CycFile);

				/* Carefully determine the activity
				 * of the chunk.
				 */

			if(Range[i] . active == CRNG_NORATE || !Range[i] . rate || Range[i] . low == Range[i] . high)
				Range[i] . active = 0;

				/* Recalculate speed value. */

			if(Range[i] . rate > 0)
				Range[i] . rate = 16384 / Range[i] . rate;
			else
				Range[i] . rate = 0;

				/* Finished reading the chunks? */

			if(!FindChunk("CRNG",CycFile))
			{
				i++;
				break;
			}
		}
	}
	else
	{
			/* Didn't find a CRNG chunk, try to find a
			 * CCRT chunk.
			 */

		fseek(CycFile,0,0);

		if(FindChunk("CCRT",CycFile))
		{
			for(i = 0 ; i < MaxRange ; i++)
			{
				fread(&CcrtChunk,sizeof(CcrtChunk),1,CycFile);

					/* We have located a CCRT chunk, now
					 * make it a CRNG chunk.
					 */

				Range[i] . low	= CcrtChunk . start;
				Range[i] . high	= CcrtChunk . end;

				if(CcrtChunk . direction != 0)
					Range[i] . active = CRNG_ACTIVE;
				else
					Range[i] . active = 0;

				if(CcrtChunk . direction > 0)
					Range[i] . active |= CRNG_REVERSE;

					/* Recalculate speed (by
					 * Carolyn Scheppner).
					 */

				Range[i] . rate	= 16384 / (CcrtChunk . seconds * 60 + (CcrtChunk . microseconds + 8334) / 16667);

				if(!Range[i] . rate || Range[i] . low == Range[i] . high)
					Range[i] . active = 0;

				if(Range[i] . rate > 0)
					Range[i] . rate = 16384 / Range[i] . rate;
				else
					Range[i] . rate = 0;

				if(!FindChunk("CCRT",CycFile))
				{
					i++;
					break;
				}
			}
		}
		else
		{
				/* Seems that we failed. */

			fclose(CycFile);
			return(FALSE);
		}
	}

	fclose(CycFile);

	return(i);
}
