/****************************************************************************
*
* MOVIE.C
*
* PROGRAMMER : Chris
*    VERSION : 00.04
*    CREATED : 13/10/95
*   MODIFIED : 07/03/97
*       TABS : 3
*
* DESCRIPTION
*		FMV playback routines for 15bit movies
*. StModeStream2. Eliminates frame skipping on A only
*
*****************************************************************************/

/*---------------------------------------------------------------------------
 * Import Headers
\*--------------------------------------------------------------------------*/

#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 "typedefs.h"
#include "types.h"
#include "malloc.h"
#include "gpu.h"
#include "pad.h"

/*---------------------------------------------------------------------------
 * Constants
\*--------------------------------------------------------------------------*/

//#define FMV24

#define FMV_WIDTH	288
#define FMV_HEIGHT	144

#define FMV_WIDTH2	320
#define FMV_HEIGHT2	240

#define SCR_WIDTH	320
#ifdef NTSC
#define SCR_HEIGHT	240
#else
#define SCR_HEIGHT	256
#endif

#define FORM1_SIZE	2048
#define RING_SIZE	64		// sizeof ring buffer (sectors)
#define MIN_SECTORS	16

#define SLICE_WIDTH	16

/*---------------------------------------------------------------------------
 * Type Definitions
\*--------------------------------------------------------------------------*/

typedef struct {
	ULONG	*vlcbuf[2];	// VLC buffers
	int		vlcid;		// active VLC buffer

	UWORD	*imgbuf[2];	// decode buffers
	int		imgid;		// active decode buffer

	RECT		rect[2];		// display buffer origin (upper left point) address
	int		rectid;		// active draw buffer

	DISPENV	disp[2];		// Double-buffered display environments
	DISPENV	*curdisp;		// current display environment

	RECT		slice;		// the region decoded by one DecDCTout() :

	int		isdone;		// the flag of decoding whole frame :
	int		LastFrame;	// physical end frame #

	DECDCTTAB	*DCTptr;
} DECENV;

/*---------------------------------------------------------------------------
 * Externals
\*--------------------------------------------------------------------------*/

extern volatile ControllerPacket GPad1,GPad2;
extern StCdIntrFlag;

/*---------------------------------------------------------------------------
 * Macros
\*--------------------------------------------------------------------------*/

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

#define POS_X		((SCR_WIDTH-FMV_WIDTH)/2)
#define POS_Y		((SCR_HEIGHT-FMV_HEIGHT)/2)

#define POS2_X		((SCR_WIDTH-FMV_WIDTH2)/2)
#define POS2_Y		((SCR_HEIGHT-FMV_HEIGHT2)/2)

#ifdef FMV24
#define DCT_MODE	3
#define SLICE_IMG_SIZE	(((16*SCR_HEIGHT)*3)/2)
#else
#define DCT_MODE	2
#define SLICE_IMG_SIZE	16*SCR_HEIGHT
#endif

#define VLC_BUF_SIZE	129*1024		// maximum buffer size = 128k + header

/* -------- D-Cache usage -------- */

#define DCACHE		0x1f800000
#define DCACHE_SIZE	0x300
#define DCACHE_TOP	(DCACHE + DCACHE_SIZE - 16)	// stupid nip fucks

#define DECENVORG (DECENV *)(DCACHE+DCACHE_SIZE);

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

#ifdef RELOC

int PlayFMV(char *name, int end);

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

#endif

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

static int StrWidth = 0;
static int StrHeight = 0;
static int EndFlag;
static RECT r;

static ULONG *RingBuf;
static ULONG *vlcbuf0;
static ULONG *vlcbuf1;
static UWORD *imgbuf0;
static UWORD *imgbuf1;

/*---------------------------------------------------------------------------
 * Local Functions
\*--------------------------------------------------------------------------*/

/****************************************************************************
* strCallback
*
* SYNOPSIS
*	void strCallback()
*
* PURPOSE
*	DecDCTout callback function
*
* INPUTS
*	None
*
* RETURNS
*	Nothing
*
*/
static void strCallback()
{
	int  id;
	register DECENV *de = DECENVORG;

#ifdef FMV24

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

#endif

	r = de->slice;

	id = de->imgid;
	de->imgid ^= 1;			// flip decoding buffer

	LoadImage(&de->slice, (ULONG *)de->imgbuf[id]);

	de->slice.x += de->slice.w;

	if (de->slice.x < de->rect[de->rectid].x + de->rect[de->rectid].w)
#ifdef FMV24
		DecDCTout((ULONG *)de->imgbuf[de->imgid], ((((de->slice.w*de->slice.h)*3)/2)/2));
#else
		DecDCTout((ULONG *)de->imgbuf[de->imgid], de->slice.w*de->slice.h/2);
#endif
	else
	{
		de->rectid ^= 1;		// flip
		de->slice.x = de->rect[de->rectid].x;
		de->slice.y = de->rect[de->rectid].y;
		de->isdone = 1;
	}
}
/****************************************************************************
* strSync
*
* SYNOPSIS
* 	void strSync(DECENV *dec)
*
* PURPOSE
* 	Waits for DecDCTout
*
* INPUTS
* 	dec	= ptr to a DECENV structure
*
* RETURNS
* 	Nothing
*
*/
static void strSync(DECENV *dec)
{
	register ULONG cnt = WAIT_TIME;

	while (dec->isdone == 0)
	{
		if (--cnt == 0)
		{
			dec->rectid ^= 1;
			dec->slice.x = dec->rect[dec->rectid].x;
			dec->slice.y = dec->rect[dec->rectid].y;
			dec->isdone = 1;
		}
	}
	dec->isdone = 0;
}

/****************************************************************************
* strNext
*
* SYNOPSIS
* 	ULONG *strNext(DECENV *dec)
*
* PURPOSE
* 	Reads a frame of data
*
* INPUTS
* 	dec	= ptr to a DECENV structure
*
* RETURNS
* 	Address in frame buffer of data
*
*/
static ULONG *strNext(DECENV *dec)
{
	ULONG	*addr;
	StHEADER *sector;
	register int cnt = WAIT_TIME;

	while (StGetNext((ULONG **)&addr,(ULONG **)&sector))
	{
		if (--cnt == 0)
			return NULL;
	}

	if (sector->frameCount >= dec->LastFrame)
	 	EndFlag = 1;

/* -------- clear screen if frame dimensions have changed */

	if ((StrWidth != sector->width) || (StrHeight != sector->height))
	{
		static RECT r = { 0, 0, SCR_WIDTH, SCR_HEIGHT*2 };

		ClearImage(&r, 0, 0, 0);

		StrWidth  = sector->width;
		StrHeight = sector->height;
	}

#ifdef FMV24
	dec->rect[0].w = dec->rect[1].w = (StrWidth*3)/2;
#else
	dec->rect[0].w = dec->rect[1].w = StrWidth;
#endif
	dec->rect[0].h = dec->rect[1].h = StrHeight;
	dec->slice.h   = StrHeight;

	return(addr);
}
/****************************************************************************
* strNextVlc
*
* SYNOPSIS
* 	void strNextVlc(DECENV *dec)
*
* PURPOSE
* 	Decodes a frame of data
*
* INPUTS
* 	dec	= ptr to a DECENV structure
*
* RETURNS
* 	Nothing
*
*/
static void strNextVlc(DECENV *dec)
{
	register ULONG *next;
	register int cnt = WAIT_TIME;

	while ((next = strNext(dec)) == NULL)
	{
		if (--cnt == 0)
			return;
	}

	dec->vlcid ^= 1;		// flip vlc buffer

	DecDCTvlc2(next, dec->vlcbuf[dec->vlcid], (UWORD *)dec->DCTptr);
	StFreeRing(next);
}
/****************************************************************************
* strKickCD
*
* SYNOPSIS
* 	void strKickCD(CdlLOC *loc)
*
* PURPOSE
* 	Starts streaming from a given position
*
* INPUTS
* 	loc	=  ptr to CdlLOC structure containing seek position
*
* RETURNS
* 	Nothing
*
*/
static void strKickCD(CdlLOC *loc)
{
	static UBYTE param = CdlModeSpeed;

loop:
	while (CdControl(CdlSetloc, (UBYTE *)loc, 0) == 0);

	while (CdControl(CdlSetmode, &param, 0) == 0);
	VSync(3);

	if (CdRead2(CdlModeStream2|CdlModeSpeed|CdlModeRT) == 0)
		goto loop;
}
/****************************************************************************
* strInit
*
* SYNOPSIS
* 	void strInit(CdlLOC *loc)
*
* PURPOSE
* 	Initialises the streaming environment and starts the CD
*
* INPUTS
* 	loc	=  ptr to CdlLOC structure containing seek position
*
* RETURNS
* 	Nothing
*
*/
static void strInit(CdlLOC *loc)
{
	DecDCTReset(0);
	DecDCTvlcSize(0);
	DecDCToutCallback(strCallback);

	StSetRing(RingBuf, RING_SIZE);

#ifdef FMV24
	StSetStream(1, 1, 0xffffffff, 0, 0);
#else
	StSetStream(0, 1, 0xffffffff, 0, 0);
#endif

	strKickCD(loc);
}

/****************************************************************************
* strSetDecEnv
*
* SYNOPSIS
*  	void strSetDecEnv(DECENV *dec, int x0, int y0, int x1, int y1)
*
* PURPOSE
*  	Initialises a decompression environment structure
*
* INPUTS
*  	dec		= ptr to a DECENV structure
*  	x0,y0	= top left offset into VRAM for screen #0
*  	x1,y1	= top left offset into VRAM for screen #1
*
* RETURNS
*	Nothing
*
*/
static void strSetDecEnv(DECENV *d, int x0, int y0, int x1, int y1)
{
	d->vlcbuf[0] = vlcbuf0;
	d->vlcbuf[1] = vlcbuf1;
	d->vlcid     = 0;

	d->imgbuf[0] = imgbuf0;
	d->imgbuf[1] = imgbuf1;
	d->imgid     = 0;

	d->rect[0].x = x0;
	d->rect[0].y = y0;
	d->rect[1].x = x1;
	d->rect[1].y = y1;
	d->rectid    = 0;

	d->slice.x = x0;
	d->slice.y = y0;
#ifdef FMV24
	d->slice.w = 24;
#else
	d->slice.w = 16;
#endif

	d->isdone    = 0;

	SetDefDispEnv(&d->disp[0], 0,          0, SCR_WIDTH, SCR_HEIGHT);
	SetDefDispEnv(&d->disp[1], 0, SCR_HEIGHT, SCR_WIDTH, SCR_HEIGHT);

#ifdef PAL
	d->disp[0].screen.y = d->disp[1].screen.y = 18+8;
	d->disp[0].screen.h = d->disp[1].screen.h = 240;
#endif

#ifdef FMV24
	d->disp[0].isrgb24 = d->disp[1].isrgb24 = 1;
#endif

	d->curdisp = &d->disp[0];
}

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

/****************************************************************************
* PlayFMV
*
* SYNOPSIS
*	LONG PlayFMV(char *name, ULONG end)
*
* PURPOSE
* 	Surprisingly, plays some FMV
*
* INPUTS
* 	name		= ptr to fmv filename
* 	end		= # frames
*
* RETURNS
*   !0		= user aborted
*
*/
int PlayFMV(char *name, int end)
{
	int logo=0;
	int pad;
	CdlFILE file;
	register DECENV *de = DECENVORG;

/* -------- locate file */

	if (CdSearchFile(&file, name) == 0)
		return -1;

	if (strcmp(name, "\\FMV\\LOGO.FMV;1") == 0)
		logo = 1;


	GPU_ClearVRAM();

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

	de->DCTptr = (DECDCTTAB *)game_malloc(sizeof(DECDCTTAB), 0);
	DecDCTvlcBuild((UWORD *)de->DCTptr);

	RingBuf = (ULONG *)game_malloc(RING_SIZE * 2048, 0);
	vlcbuf0 = (ULONG *)game_malloc(VLC_BUF_SIZE, 0);
	vlcbuf1 = (ULONG *)game_malloc(VLC_BUF_SIZE, 0);
#ifdef FMV24
	imgbuf0 = (UWORD *)game_malloc(((16*SCR_HEIGHT) * sizeof(BYTE)*3), 0);
	imgbuf1 = (UWORD *)game_malloc(((16*SCR_HEIGHT) * sizeof(BYTE)*3), 0);
#else
	imgbuf0 = (UWORD *)game_malloc(SLICE_IMG_SIZE * sizeof(UWORD), 0);
	imgbuf1 = (UWORD *)game_malloc(SLICE_IMG_SIZE * sizeof(UWORD), 0);
#endif

/* -------- initialise stream */

	if (logo)
	{
		strSetDecEnv(de, POS2_X, POS2_Y, POS2_X, POS2_Y+SCR_HEIGHT);
		strSetDecEnv(de, POS2_X, POS2_Y, POS2_X, POS2_Y+SCR_HEIGHT);
	}
	else
	{
		strSetDecEnv(de, POS_X, POS_Y, POS_X, POS_Y+SCR_HEIGHT);
		strSetDecEnv(de, POS_X, POS_Y, POS_X, POS_Y+SCR_HEIGHT);
	}
	de->LastFrame = end;

	strInit(&file.pos);
	strNextVlc(de);

	EndFlag = 0;

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

	do {
		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(de->vlcbuf[de->vlcid], DCT_MODE);
		DecDCTout((ULONG *)de->imgbuf[de->imgid], de->slice.w*de->slice.h/2);
		strNextVlc(de);
		strSync(de);

		VSync(0);

		PutDispEnv(de->curdisp);
		de->curdisp = (de->curdisp == &de->disp[0]) ? &de->disp[1] : &de->disp[0];
		SetDispMask(1);

	} while ((EndFlag == 0) && ((pad & (PAD_START|PAD_RD)) == 0));

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


	SetDispMask(0);
	GPU_ClearVRAM();

	DecDCToutCallback(0);
	StUnSetRing();
	CdControlB(CdlPause,0,0);

	game_free(SLICE_IMG_SIZE * sizeof(UWORD), 0);
	game_free(SLICE_IMG_SIZE * sizeof(UWORD), 0);
	game_free(VLC_BUF_SIZE, 0);
	game_free(VLC_BUF_SIZE, 0);
	game_free(RING_SIZE * 2048, 0);

	game_free(sizeof(DECDCTTAB), 0);

	return !EndFlag;
}



