/*-----------------09-03-97 12:16pm-----------------
ultimate movie player (hassle free)
--------------------------------------------------*/
#include <sys/types.h>
#include <stdio.h>
#include <libetc.h>
#include <libpad.h>
#include <libgte.h>
#include <libgpu.h>
#include <libcd.h>
#include <libsn.h>
#include <libpress.h>
#include <kernel.h>
#include <libsnd.h>
#include "typedefs.h"
#include "types.h"
#include "malloc.h"
#include "gpu.h"
#include "pad.h"

/* ---------------------------------------------------------------------------
 * - CONSTANTS
 * ---------------------------------------------------------------------------
 */

// Streaming modes.
#define STR_MODE24				1
#define STR_MODE16				0


// Border modes.
#define STR_BORDERS_OFF			0
#define STR_BORDERS_ON			1


// PlayStream return values.
#define	PLAYSTR_ERROR	-1
#define	PLAYSTR_END	0
#define	PLAYSTR_USER	1
#define	PLAYSTR_START	2

/* ---------------------------------------------------------------------------
 * - DATA TYPE AND STRUCTURE DECLARATIONS
 * ---------------------------------------------------------------------------
 */

typedef struct {
		char		*strName;					// Stream file name.
		short		mode;						// 24-Bit or 16-Bit streaming.
		short		drawBorders;				// != 0 if borders on.
		u_short		scrWidth;					// Screen res width.
		u_short		x;							// X,Y position.
		u_short		y;
		u_short		width;						// Stream width and height.
		u_short		height;
		u_long		endFrame;					// Last frame No.
		u_long		vlcBufSize;					// Size of each VLC buffer (including header).
		u_short		volume;						// Left and Right ADPCM volume.
} StrInfo;

/* ---------------------------------------------------------------------------
 * - PUBLIC FUNCTION PROTOTYPES
 * ---------------------------------------------------------------------------
 */

short	PlayStream(StrInfo *str);
void	del_play_movie(char *filename);
extern volatile ControllerPacket GPad1,GPad2;

#define SCREEN_X 0
#define FRAME_X 640			   		// width = 320
#ifdef NTSC
#define FRAME_Y 240			   		// height = 256
#define SCREEN_Y 0			   		// i used 28 for shellshock... but who cares...
#else
#define SCREEN_Y 18			   		// i used 28 for shellshock... but who cares...
#define FRAME_Y 256			   		// height = 256
#endif

/* ------------------------------------------------------------------------ */



extern	int		G_MODE;


int		G_width;
int		G_height;

static StrInfo sfaStr01 = {
	NULL,			// filename
	STR_MODE24,		// mode (16bit or 24bit)
	STR_BORDERS_ON,	// cls on/off
	320,				// screen X resolution (256,320,384,512 or 640)
	0, 0,			// x/y offset
	0, 0,			// w/h
	5240,			// number of frames
	0,				// size of each vlc buffer (inc header)
	127				// volume
};


/* ---------------------------------------------------------------------------
 * - CONSTANTS
 * ---------------------------------------------------------------------------
 */




// Maximum number of retries.
#define MAX_RETRY				5

// Form1 sector size in bytes.
#define FORM1_SIZE				2048

// Ring buffer size in sectors.
#define RING_SIZE				64

// Maximum possible VLC buffer size (bytes) = 128K + (Header) = 129K.
#define MAX_VLCBUF_SIZE			132096

// Slice width in pixels.
#define SLICE_WIDTH				16

// Words/Pixel for 24-bit and 16-bit modes.
#define PPW24					3 / 2
#define PPW16					1

// DCT modes for 24-bit and 16-bit (STP = 1) streaming.
#define DCT_MODE24				3
#define DCT_MODE16				2

#define bound(val, n)			((((val) - 1) / (n) + 1) * (n))
#define bound16PPW(val, ppw)	(bound((val), 16 * ppw))
#define bound16(val)			(bound((val), 16))

// Speed of volume fade.
#define FADE_VOL_SPD			8

// RunLevel header size in bytes.
#define RL_HEADER_SIZE			1024

/* ---------------------------------------------------------------------------
 * - DATA TYPE AND STRUCTURE DECLARATIONS
 * ---------------------------------------------------------------------------
 */

typedef struct {
	ULONG vlcBufSize;
	ULONG imgBufSize;
	DECDCTTAB	*DCTptr;

	volatile short		is24Bit;						// = 1 when 24-bit streaming.
	volatile u_short	scrWidth;						// Screen width resolution.
	volatile u_short	x;								// Str X, Y.
	volatile u_short	y;
	volatile u_short	width;							// Str width and height.
	volatile u_short	height;
	volatile u_long		endFrame;                  		// End frame.
	volatile short		volume;							// ADPCM Volume.
	volatile u_long		frameCount;						// Frame count.
	RECT				rect[2];
	RECT				slice;							// Slice decoded by DecDCTout().
	volatile short		rewindSwitch;					// = 1 when end of stream.
	volatile short		frameDone;						// = 1 when decoding and drawing done.
	volatile short		rectId;
	volatile short		vlcId;							// VLC buffer id.
	volatile short		imageId;						// Image buffer id.
	u_long				*vlcBuffer[2];					// VLC run-level buffers.
	u_long				*imageBuffer[2];				// Strip image buffers.
	DISPENV				disp[2];						// Display envs.
} DecEnv;

/*---------------------------------------------------------------------------
 * Globals
\*--------------------------------------------------------------------------*/

#ifdef RELOC

int PlayFMV(char *name, int end);

void *func[] __attribute__((section(".header"))) = {
	&PlayFMV
};

#endif

/*---------------------------------------------------------------------------
 * Locals
\*--------------------------------------------------------------------------*/

static DecEnv dec;			// Streaming Env.
static SndVolume oldVolume;	// Old ADPCM volume.

/* ---------------------------------------------------------------------------
 * - PRIVATE FUNCTION PROTOTYPES
 * ---------------------------------------------------------------------------
 */

static void StrDecDCToutCB(void);
static void StrInit(StrInfo *str);
static void StrEnd(void);
static void StrClearVRAM(void);
static short StrKickCD(CdlLOC *loc);
static u_long *StrGetNextFrame(void);
static void StrVLCFrame(u_long *frame);
static void	StrDrawDecodeSync(void);
static short StrFadeOutVol(short *fadeVol);
static long GetDCToutSize(void);
static u_short ScalePPW(u_short n);
static u_short GetDCT_MODE(void);



/* ---------------------------------------------------------------------------
 * - FUNCTION DEFINITIONS
 * ---------------------------------------------------------------------------
 */
/* - Type:	PUBLIC
 * -
 * - Param:	str = (In) Stream info.
 * -		keyHandler = (In) Read controller function (Returns non-zero if exit).
 * -
 * - Ret:	PLAYSTR_ERROR = Error.
 * -		PLAYSTR_END = End of stream exit.
 * -		PLAYSTR_USER = User exit.
 * -
 * - Usage:	Play a stream.
 */
short PlayStream(StrInfo *str)
{
	int pad, quitpad;

	CdlFILE		fp;

	u_long		*frame;

	short		frameRetry = 0;
	short		strQuit = 0;
	short		fadeVol = 0;


	if (!CdSearchFile(&fp, str->strName))
	{
#ifdef DEBUG
		printf("ERROR: MOVIE -- Could not find stream file '%s'.\n", str->strName);
#endif
		return PLAYSTR_ERROR;
	}

	StrInit(str);

	if (!StrKickCD(&fp.pos))
	{
#ifdef DEBUG
		printf("ERROR: MOVIE -- Couldn't start the CD!!\n");
#endif
		StrEnd();
		return PLAYSTR_ERROR;
	}


	while (!(frame = StrGetNextFrame()))
	{
		if (++frameRetry == MAX_RETRY)
		{
#ifdef DEBUG
			printf("ERROR: MOVIE -- Couldn't get first frame!!\n");
#endif
			StrEnd();
			return PLAYSTR_ERROR;
		}
	}

	frameRetry = 0;
	StrVLCFrame(frame);

	while (!dec.rewindSwitch && !strQuit)
	{
		pad = 0;

		if (GoodData(&GPad1))
		{
			UBYTE type;

			type = PadInfoMode(0, InfoModeCurID, 0);

			if ((type == CTL_PAD) || (type == CTL_ANALOGC))
   				pad = (~GPad1.data.pad)&0xffff;
		}

		DecDCTin(dec.vlcBuffer[dec.vlcId], GetDCT_MODE());
		DecDCTout(dec.imageBuffer[dec.imageId], GetDCToutSize());

		if (!(frame = StrGetNextFrame()))
		{
			if (++frameRetry == MAX_RETRY)
				strQuit = PLAYSTR_ERROR;

		}
		else
			frameRetry = 0;

		StrVLCFrame(frame);

	// check for exit
		if (!fadeVol && (pad & (PAD_START|PAD_RD)))
		{
			fadeVol = dec.volume;
			quitpad = pad;
		}

	// Fade the volume down to zero. Then trigger exit.
		if (fadeVol)
			strQuit = StrFadeOutVol(&fadeVol);

		StrDrawDecodeSync();

		DrawSync(0);
		VSync(0);

		PutDispEnv(&dec.disp[dec.rectId]);
	}

	StrEnd();

	if (dec.rewindSwitch)								// Check for end of stream exit.
		strQuit =  PLAYSTR_END;

#ifdef DEBUG
	if (strQuit == PLAYSTR_ERROR)
		printf("ERROR: MOVIE -- Couldn't get frame from ring buffer!!\n");
#endif

	if ((strQuit == PLAYSTR_USER)
	&&  (quitpad & PAD_START))
		return PLAYSTR_START;

	else
		return strQuit;
}

/* ------------------------------------------------------------------------ */

static void StrDecDCToutCB(void) {


/* - Type:	CALLBACK
 * -
 * - Usage:	Receives decoded image data from the MDEC and loads it to VRAM.
 */


	// In 24bit mode call StCdInterrupt() manually.
	if (dec.is24Bit) {

		extern StCdIntrFlag;

		if(StCdIntrFlag) {
			StCdInterrupt();
			StCdIntrFlag = 0;
		}
	}

	LoadImage(&dec.slice, dec.imageBuffer[dec.imageId]);
	dec.imageId ^= 1;
	dec.slice.x += dec.slice.w;

	if (dec.slice.x < (dec.rect[dec.rectId].x + dec.rect[dec.rectId].w)) {

		DecDCTout(dec.imageBuffer[dec.imageId], GetDCToutSize());

	} else {

		dec.frameDone = 1;
	}
}

/* ------------------------------------------------------------------------ */

static void StrInit(StrInfo *str) {


/* - Type:	PRIVATE
 * -
 * - Param:	str = (In) = Stream Info.
 * -
 * - Usage: Init internal vars, the MDEC and allocate the buffers.
 */
	short	x, y, w, h;
	short	scrWidth;
	u_long	*ringBuf;

	x = str->x;
	y = str->y;
	w = str->width;
	h = str->height;
	scrWidth = str->scrWidth;

	dec.is24Bit = str->mode;
	dec.scrWidth = scrWidth;
	dec.x = x;
	dec.y = y;
	dec.width = w;
	dec.height = h;
	dec.endFrame = str->endFrame;
	dec.volume = str->volume;
	dec.frameCount = 0;
	dec.rewindSwitch = 0;
	dec.frameDone = 0;
	dec.rectId = 0;
	dec.vlcId = 0;
	dec.imageId = 0;


	// Init the display environments.
	SetDefDispEnv(&dec.disp[0], 0,   FRAME_Y, scrWidth, FRAME_Y);
	SetDefDispEnv(&dec.disp[1], 0,   0,       scrWidth, FRAME_Y);
	setRECT(&dec.disp[0].screen, SCREEN_X, SCREEN_Y, 0, FRAME_Y);
	setRECT(&dec.disp[1].screen, SCREEN_X, SCREEN_Y, 0, FRAME_Y);
	dec.disp[0].isrgb24 = dec.disp[1].isrgb24 = (str->mode == STR_MODE24);


	if (str->drawBorders || dec.is24Bit)			// Clear VRAM.
		StrClearVRAM();

	// Set correct display mode before we begin drawing.
	VSync(0);
	PutDispEnv(&dec.disp[dec.rectId]);


	setRECT(&dec.rect[0], ScalePPW(x), y, ScalePPW(w), h);
	setRECT(&dec.rect[1], ScalePPW(x), y + FRAME_Y, ScalePPW(w), h);
	dec.slice = dec.rect[dec.rectId];
	dec.slice.w = ScalePPW(SLICE_WIDTH);

/* -------- allocate VLC buffers */

	if (str->vlcBufSize)
		dec.vlcBufSize = str->vlcBufSize;
	else
		dec.vlcBufSize = MAX_VLCBUF_SIZE;

	dec.vlcBuffer[0] = (u_long *)game_malloc(dec.vlcBufSize, 0);
	dec.vlcBuffer[1] = (u_long *)game_malloc(dec.vlcBufSize, 0);

/* -------- allocate image buffers */

	if (dec.is24Bit)
		dec.imgBufSize = SLICE_WIDTH * 3 * h;
	else
		dec.imgBufSize = SLICE_WIDTH * 2 * h;

	dec.imageBuffer[0] = (u_long *)game_malloc(dec.imgBufSize, 0);
	dec.imageBuffer[1] = (u_long *)game_malloc(dec.imgBufSize, 0);

/* -------- initialise MDEC */

	dec.DCTptr = (DECDCTTAB *)game_malloc(sizeof(DECDCTTAB), 0);
	DecDCTvlcBuild((UWORD *)dec.DCTptr);

	DecDCTReset(0);
	DecDCTvlcSize(0);
	DecDCToutCallback((void (*)()) StrDecDCToutCB);
	ringBuf = (u_long *)game_malloc(RING_SIZE * FORM1_SIZE, 0);
	StSetRing(ringBuf, RING_SIZE);

	StSetStream(dec.is24Bit, 0, 0xffffffff, 0, 0);

	// Store old ADPCM volume and set new volume.
	SsGetSerialVol(SS_SERIAL_A, &oldVolume);
	SsSetSerialVol(SS_SERIAL_A, dec.volume, dec.volume);
}

/* ------------------------------------------------------------------------ */

static void StrEnd(void) {


/* - Type:	PRIVATE
 * -
 * - Usage: Clean up.
 */
	StUnSetRing();
	CdControlB(CdlPause, 0, 0);
	DecDCToutCallback(0);

	if (dec.is24Bit)			// Clear VRAM to remove any 24bit graphics.
		StrClearVRAM();

	// Restore previous ADPCM volume.
	SsSetSerialVol(SS_SERIAL_A, oldVolume.left, oldVolume.right);

	game_free(RING_SIZE * FORM1_SIZE, 0);
	game_free(sizeof(DECDCTTAB), 0);
	game_free(dec.imgBufSize, 0);
	game_free(dec.imgBufSize, 0);
	game_free(dec.vlcBufSize, 0);
	game_free(dec.vlcBufSize, 0);

#ifdef DEBUG
	printf("FrameCount %d\n", dec.frameCount);
#endif
}

/* ------------------------------------------------------------------------ */

static void StrClearVRAM(void) {


/* - Type:	PRIVATE
 * -
 * - Usage: Useful before and after streaming in 24bit mode	to ensure
 * - 		24bit data is not displayed in 16bit mode (and vice versa).
 */


	RECT	clrRect;


	setRECT(&clrRect, 0, 0, ScalePPW(dec.scrWidth), FRAME_Y);
	ClearImage(&clrRect, 0, 0, 0);
	clrRect.y = FRAME_Y;
	ClearImage(&clrRect, 0, 0, 0);
}

/* ------------------------------------------------------------------------ */

static short StrKickCD(CdlLOC *loc) {


/* - Type:	PRIVATE
 * -
 * - Param:	loc = (In) location.
 * -
 * - Ret:	0 on error, 1 otherwise.
 * -
 * - Usage: Start the CD.
 */


	short	seekRetry = 0,
			readRetry = 0;


	do {

		while (!CdControl(CdlSeekL, (u_char *)loc, 0)) {

			if (++seekRetry == MAX_RETRY)
				return 0;
		}


		seekRetry = 0;

		if (readRetry++ == MAX_RETRY)
			return 0;


	} while (!CdRead2(CdlModeStream|CdlModeSpeed|CdlModeRT));


	return 1;
}

/* ------------------------------------------------------------------------ */

static u_long *StrGetNextFrame(void) {


/* - Type:	PRIVATE
 * -
 * - Ret:	Address of next frame or NULL on error.
 * -
 * - Usage:	Gets next frame from the ring buffer. Also checks for the
 * -		end of the stream. If the end has been reached rewindSwitch
 * -		is set. If frame is not obtained from the ring timeout and
 * -		return NULL.
 */


	long		timer = WAIT_TIME;
	u_long		*addr;
	StHEADER	*sector;


    // Get the next frame from the ring buffer.

	timer/=32;
	while (StGetNext(&addr, (u_long**) &sector)) {

			if (--timer == 0)
				return NULL;
	}


	dec.frameCount++;				// Update internal frame count.





	if (dec.frameCount == 1)
	{
		G_width=sector->width;
		G_height=sector->height;
	}

	// End of stream checks.
	if (sector->frameCount > dec.endFrame) {

#ifdef DEBUG
		printf("ERROR: MOVIE -- Found sector frame count over > end frame (%d > %d)\n",
				sector->frameCount, dec.endFrame);
#endif
		dec.rewindSwitch = 1;
		return addr;
	}


	if (sector->frameCount < dec.frameCount) {

#ifdef DEBUG
		printf("ERROR: MOVIE -- Found sector frame count < internal frame count. (%d < %d)\n",
			 sector->frameCount, dec.endFrame);
#endif
		dec.rewindSwitch = 1;
		return addr;
	}


	if (sector->width != dec.width || sector->height != dec.height) {

#ifdef DEBUG
		printf("ERROR: MOVIE -- Found bad sized frame (%d x %d) is not (%d x %d).\n",
				sector->width, sector->height, dec.width, dec.height);
#endif
		dec.rewindSwitch = 1;
		return addr;
	}


    return addr;
}

/* ------------------------------------------------------------------------ */

static void StrVLCFrame(u_long *frame) {


/* - Type:	PRIVATE
 * -
 * - Param:	frame = (In) Address of frame BS.
 * -
 * - Usage:	VLC decodes the frame's BS. Unlock frame from the ring buffer
 * -		once decoded.
 */


	// If no frame to decode return.
	if (frame == NULL) {
#ifdef DEBUG
		printf("ERROR: MOVIE -- No frame to VLC decode!!\n");
#endif
		return;
	}






	dec.vlcId ^= 1;
	DecDCTvlc(frame, dec.vlcBuffer[dec.vlcId]);
	StFreeRing(frame);
}

/* ------------------------------------------------------------------------ */

static void	StrDrawDecodeSync(void) {


/* - Type:	PRIVATE
 * -
 * - Usage:	Wait for current frame's decoding and drawing to be finished.
 */


	long	timer = WAIT_TIME;


	while (!dec.frameDone) {

		if (--timer == 0) {
#ifdef DEBUG
			printf("ERROR: MOVIE -- MDEC decode timeout!!\n");
#endif
			break;
		}
	}


	dec.frameDone = 0;

	dec.rectId ^= 1;
	dec.slice.x = dec.rect[dec.rectId].x;
	dec.slice.y = dec.rect[dec.rectId].y;
}

/* ------------------------------------------------------------------------ */

static short StrFadeOutVol(short *fadeVol) {


/* - Type:	PRIVATE
 * -
 * - Param:	fadeVol = (In/Out) Current volume.
 * -
 * - Ret:	1 = Volume completely faded (therefore finish playing the stream).
 * - 		0 = Otherwise.
 * -
 * -
 * - Usage: Fade out the volume.
 */


	if ((*fadeVol -= FADE_VOL_SPD) < 0)
		*fadeVol = 0;


	SsSetSerialVol(SS_SERIAL_A, *fadeVol, *fadeVol);


	if (!*fadeVol)
		return PLAYSTR_USER;			// Flag user exit.
	else
		return 0;
}

/* ------------------------------------------------------------------------ */

static long GetDCToutSize(void) {


/* - Type:	PRIVATE
 * -
 * - Ret:	DecDCTout size in longs for current playback mode.
 */


	u_short	h = dec.height;


	if (dec.is24Bit)
		return (((SLICE_WIDTH * PPW24) * bound16(h)) >> 1);
	else
		return (((SLICE_WIDTH * PPW16) * bound16(h)) >> 1);
}

/* ------------------------------------------------------------------------ */

static u_short ScalePPW(u_short n) {


/* - Type:	PRIVATE
 * -
 * - Param:	n = (In) A number.
 * -
 * - Ret:	n scaled by the correct PPW (words/pixel). This depends
 * -		on the current playback mode.
 */


	if (dec.is24Bit)
		return (n * PPW24);
	else
		return (n * PPW16);
}

/* ------------------------------------------------------------------------ */

static u_short GetDCT_MODE(void) {


/* - Type:	PRIVATE
 * -
 * - Ret:	The current DCT mode.
 */


	return ((dec.is24Bit) ? DCT_MODE24 : DCT_MODE16);
}

/* ------------------------------------------------------------------------ */


/* ------------------------------------------------------------------------ */



// massive frig function to setup str info fields..

int		dels_init_movie_size(StrInfo *str)
{
	CdlFILE		fp;
	u_long		*frame;
	short		frameRetry = 0;


	if (!CdSearchFile(&fp, str->strName))
	{
		#ifdef DEBUG
		printf("ERROR: MOVIE -- Could not find stream file '%s'.\n", str->strName);
		#endif
		return PLAYSTR_ERROR;
	}

	StrInit(str);

	if (!StrKickCD(&fp.pos))
	{
		#ifdef DEBUG
		printf("ERROR: MOVIE -- Couldn't start the CD!!\n");
		#endif
		StrEnd();
		return PLAYSTR_ERROR;
	}


	while (!(frame = StrGetNextFrame()))		// If we can't get frame exit!
	{

		if (++frameRetry == MAX_RETRY)
		{
			#ifdef DEBUG
			printf("ERROR: MOVIE -- Couldn't get first frame!!\n");
			#endif
			StrEnd();
			return PLAYSTR_ERROR;
		}
	}

	StrEnd();

// setup a horizontal screen resolution (depending on FMV size)
//	if ( G_width<=256 )
//		str->scrWidth=256;
//	else if (G_width<=320)
		str->scrWidth=320;
//	else
//		str->scrWidth=640;

// setup width/height
	str->width=G_width;
	str->height=G_height;
// setup x/y (centre the slag)
	str->x = (str->scrWidth-str->width)/2;
	str->y = (FRAME_Y-str->height)/2;
	return PLAYSTR_ERROR;
}


/*---------------------------------------------------------------------------
 * Public Functions
\*--------------------------------------------------------------------------*/

#define MAX_VOL 127

/****************************************************************************
* PlayFMV
*
* SYNOPSIS
*	LONG PlayFMV(char *name, ULONG end)
*
* PURPOSE
* 	Plays some FMV
*
* INPUTS
* 	name		= ptr to fmv filename
* 	end		= # frames
*
* RETURNS
*   !0		= user aborted
*
*/
int PlayFMV(char *name, int end)
{
	int ret;
	StrInfo *str;

	GPU_ClearVRAM();
	SetDispMask(1);

	str = &sfaStr01;
	str->strName = name;
	str->mode = 1;
	str->drawBorders = STR_BORDERS_ON;
	str->volume = MAX_VOL;
	str->endFrame = end;
	dels_init_movie_size(str);
	ret = PlayStream(str);

	SetDispMask(0);
	VSync(0);

	GPU_ClearVRAM();

	return ret;
}

