
#include <sys\types.h>
#include <libgte.h>
#include <libgpu.h>
#include <libetc.h>
#include <libapi.h>
#include <stdio.h>
#include "typedefs.h"
#include "memory.h"
#include "gpu.h"
#include "psxinput.h"
#include "profile.h"
#include "..\game\input.h"
#include "..\game\text.h"

extern char exit_message[];

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

#define PRINT_POLY_COUNT
#define MAX_VSYNC_ROUTINES 4

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

extern int InfoFlag;

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

DB_STRUCT db;
volatile uint32 GnFrameCounter=0;
volatile uint32 GnLastFrameCount=0;

uint32 GadwOrderingTables[OTSIZE*2];
uint32 GadwPolygonBuffers[PBSIZE*2];

PSXTEXTSTRUCT* psxtextinfo=NULL;		// Texture information..
PSXSPRITESTRUCT* psxspriteinfo=NULL;	// Sprite information..
MMTEXTURE *RoomTextInfo;

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

static int LnFlipFrame;
#ifdef PROFILE_ME
static bool LtProfilerBarEnabled;
#endif

int LnVSyncRoutines;
static PFNVSYNCROUTINE LpfnOldVSyncCallback;
static PFNVSYNCROUTINE LapfnVSyncRoutine[MAX_VSYNC_ROUTINES];

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

static void GPU_VSyncRoutine()
{
	GnFrameCounter++;
	GnLastFrameCount++;
}

static int GPU_DrawSync()
{
	return DrawSync(1);
}

static int GPU_FrameSync()
{
	return GnLastFrameCount<LnFlipFrame;
}

static void VSYNC_Handler()
{
	int nRoutine;

	for (nRoutine=LnVSyncRoutines-1;nRoutine>=0;--nRoutine)
		LapfnVSyncRoutine[nRoutine]();
}

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

void GPU_Init()
{
	static uint32 dwSTP[3]={0x02000000,0xe6000001,0x00000000};

#ifndef NTSC
	SetVideoMode(MODE_PAL);
#endif

	ResetGraph(0);
	SetGraphDebug(0);
	GPU_ClearVRAM();
	S_MemSet(&db, 0, sizeof db);
	SetDefDrawEnv(db.draw+0, 0,   0, MY_PSX_W, MY_PSX_H);
	SetDefDispEnv(db.disp+0, 0, 256, MY_PSX_W, MY_PSX_H);
	SetDefDrawEnv(db.draw+1, 0, 256, MY_PSX_W, MY_PSX_H);
	SetDefDispEnv(db.disp+1, 0,   0, MY_PSX_W, MY_PSX_H);
	GPU_SetHorizontalResolution(MY_PSX_W);
	GPU_SetScreenPosition(MY_PSX_X_OFF, MY_PSX_Y_OFF);

#ifndef NTSC
	setWH(&(db.disp[0].screen),0,PAL_MAGIC);
	setWH(&(db.disp[1].screen),0,PAL_MAGIC);
#endif

	GPU_EnableDither(TRUE);
	GPU_EnableBGClear(TRUE);
	GPU_SetBGColour(0, 0, 0);
	GPU_FlipToBuffer(0);
	SetDispMask(1);
	VSYNC_Add(GPU_VSyncRoutine);

#ifdef PROFILE_ME
	GPU_EnableProfilerBar(TRUE);
#endif

	DrawPrim(dwSTP);
}

void GPU_Cleanup()
{
	VSync(0);
	SetDispMask(0);
	GPU_ClearVRAM();
}

void GPU_ClearVRAM()
{
	RECT rc;

	DrawSync(0);
	VSync(0);

	setRECT(&rc, 0, 0, 512,256);
	ClearImage(&rc, 0, 0, 0);

	rc.y = 256;
	ClearImage(&rc, 0, 0, 0);

	rc.x = 512;
	ClearImage(&rc, 0, 0, 0);

	rc.y = 0;
	ClearImage(&rc, 0, 0, 0);

	DrawSync(0);
	VSync(0);
}

void GPU_UseOrderingTables(uint32* pBuffers,int nOTSize)
{
	db.nOTSize=nOTSize;
	db.order_table[0] = (uint32 *)((u_long)pBuffers & 0xffffff);
	db.order_table[1] = (uint32 *)((u_long)(pBuffers+nOTSize) & 0xffffff);
}

void GPU_UsePolygonBuffers(uint32* pBuffers,int nPBSize)
{
	db.nPBSize=nPBSize;
	db.poly_buffer[0] = (uint32 *)((u_long)pBuffers & 0xffffff);
	db.poly_buffer[1] = (uint32 *)((u_long)(pBuffers+nPBSize) & 0xffffff);
}

void GPU_BeginScene(void)
{
	db.ot = db.order_table[db.current_buffer];

	ClearOTagR(db.ot, db.nOTSize);

	db.polyptr = db.curpolybuf = (char*)db.poly_buffer[db.current_buffer];
	db.polybuf_limit = db.curpolybuf+(POLYPTR_CLAMP*4);
	SetDispMask(1);
}

void GPU_EndScene()
{
#ifdef PRINT_POLY_COUNT
	int nPolys;
	static int nWorstPolys=0;

	nPolys = ((int)db.polyptr-(int)db.curpolybuf) / (13*4);

	if (nPolys > nWorstPolys)
		nWorstPolys=nPolys;

	sprintf(exit_message,"Polys: %d %d %d",nPolys,nWorstPolys,PBSIZE/13);

//#ifdef GAMEDEBUG
//	if (InfoFlag)
		PrintDbug(1, 1, exit_message);
//#endif
#endif

#ifdef OT_DEBUG
	PsxPrint("Max OT: %d\n",maxot);
//	PsxPrint("SUB 1:%d 2:%d 3:%d\n",num0,num1,num2);
//	PsxPrint("OBJ 1:%d 2:%d 3:%d\n",onum0,onum1,onum2);
	num0=num1=num2=0;
	onum0=onum1=onum2=0;
	if (input&IN_E)
		maxot=0;
#endif
}


#ifdef GAMEDEBUG
static void DoVRAMScroller(void)
{
	static int x=0,y=0;

//	db.disp[0].disp.x=db.disp[1].disp.x=640;
//	db.disp[0].disp.y=db.disp[1].disp.y=256;
//	return;

	if (input&IN_E)					// Allow Scrolling Around of VideoRam
	{
		if (input&IN_FORWARD)
			y-=8;
		if (input&IN_BACK)
			y+=8;
		if (input&IN_LEFT)
			x-=8;
		if (input&IN_RIGHT)
			x+=8;

		x&=1023;
		y&=511;

		db.disp[0].disp.x=db.disp[1].disp.x=x;
		db.disp[0].disp.y=db.disp[1].disp.y=y;
	}
	else
	{
		db.disp[0].disp.x=db.disp[1].disp.x=0;
		db.disp[0].disp.y=256;
		db.disp[1].disp.y=0;
	}
}

static void DisplayFrameInfo(void)
	{
#if 0
	static int nWorstFrames=0;
	int	nFrames;

	nFrames=GnLastFrameCount;
	if (nFrames>nWorstFrames)
		nWorstFrames=nFrames;
	if (input&IN_E)
		nWorstFrames=0;
	PsxPrint("%s\n",exit_message);
	PsxPrint("Frames:%d %d\n",nFrames,nWorstFrames);
#endif
	}
#endif

void GPU_ResetLastFrameCount( void)
	{
	GnLastFrameCount=0;
	}


uint32 GPU_GetFrameCount(void)
	{
	return(GnFrameCounter);
	}


int GPU_FlipNoIdle( void)
{
#ifdef PROFILE_ME
	ProfileReadCount();

	if (LtProfilerBarEnabled)
		ProfileAddOT(db.ot);
#endif

	DrawSync(0);

#ifdef PROFILE_ME
	if (LtProfilerBarEnabled)
		ProfileAddDrawOT(db.ot);
#endif

	LnFlipFrame=GnLastFrameCount;
	if (LnFlipFrame<2)
	{
		while (LnFlipFrame<2)
		{                                  			// If Running In A Frame
			VSync(0);                       			// Wait another VBlank
			LnFlipFrame++;
		}
	}
	else
	{
		VSync(0);                       			// Wait another VBlank
		LnFlipFrame++;
	}

	GnLastFrameCount=0;
	PutDispEnv(db.disp+db.current_buffer);
	DrawOTagEnv(db.ot+(db.nOTSize-1),db.draw+db.current_buffer);
#ifdef PROFILE_ME
	ProfileStartCount();
#endif
	db.current_buffer^=1;
	return LnFlipFrame;
}

int GPU_Flip(PFNIDLEROUTINE pfnIdleRoutine)
{
#ifdef GAMEDEBUG
	DoVRAMScroller();
#endif

#ifdef PROFILE_ME
	ProfileReadCount();

	if (LtProfilerBarEnabled)
		ProfileAddOT(db.ot);
#endif

	if (GPU_DrawSync())
		while ((*pfnIdleRoutine)(GPU_DrawSync))
			;

#ifdef PROFILE_ME
	if (LtProfilerBarEnabled)
		ProfileAddDrawOT(db.ot);
#endif

	LnFlipFrame = GnLastFrameCount+1;

	if (LnFlipFrame < 2)
		LnFlipFrame = 2;

	if (GPU_FrameSync())
		while ((*pfnIdleRoutine)(GPU_FrameSync))
			;

#ifdef GAMEDEBUG
	DisplayFrameInfo();
#endif
	GnLastFrameCount=0;
	PutDispEnv(db.disp+db.current_buffer);
	DrawOTagEnv(db.ot+(db.nOTSize-1),db.draw+db.current_buffer);
#ifdef PROFILE_ME
	ProfileStartCount();
#endif
	db.current_buffer ^= 1;

	return LnFlipFrame;
}

void GPU_FlipNow(void)
	{
	GnLastFrameCount=0;
	PutDispEnv(db.disp+db.current_buffer);
	ResetGraph(1);
	DrawOTagEnv(db.ot+(db.nOTSize-1),db.draw+db.current_buffer);
	db.current_buffer^=1;
	}

void GPU_FlipToBuffer(int nBuffer)
	{
	DrawSync(0);
	VSync(0);
	PutDispEnv(db.disp+nBuffer);
	PutDrawEnv(db.draw+nBuffer);
	db.current_buffer=nBuffer^1;
	}

RECT* GPU_GetDispRect(int nWhich)
	{
	return &(db.disp[db.current_buffer^nWhich].disp);	//c.b.^1 is currently being displayed, c.b. is next, being drawn now (sorry)
	}

RECT* GPU_GetDrawRect(int nWhich)
	{
	return &(db.draw[db.current_buffer^nWhich].clip);	//c.b.^1 is currently drawing, c.b. is being calculated for eventual draw to current disp!
	}

void GPU_SetScreenPosition(sint16 nX,sint16 nY)
	{
	db.disp[0].screen.x=db.disp[1].screen.x=nX;
	db.disp[0].screen.y=db.disp[1].screen.y=nY;
	}

void GPU_GetScreenPosition(sint16* pnX,sint16* pnY)
	{
	*pnX=db.disp[0].screen.x;
	*pnY=db.disp[0].screen.y;
	}

bool GPU_EnableDither(bool tEnable)
	{
	bool tOld=db.draw[0].dtd;
	db.draw[0].dtd=db.draw[1].dtd=tEnable;
	return tOld;
	}

bool GPU_EnableBGClear(bool tEnable)
	{
	bool tOld=db.draw[0].isbg;
	db.draw[0].isbg=db.draw[1].isbg=tEnable;
	return tOld;
	}

uint32 GPU_SetBGColour(uint8 nR,uint8 nG,uint8 nB)
	{
	uint32 dwOld=(*((uint32*)&db.draw[0].isbg))>>8;
	setRGB0(db.draw+0,nR,nG,nB);
	setRGB0(db.draw+1,nR,nG,nB);
	return dwOld;
	}

#ifdef PROFILE_ME
bool GPU_EnableProfilerBar(bool tEnable)
	{
	bool tOld=LtProfilerBarEnabled;
	LtProfilerBarEnabled=tEnable;
	return tOld;
	}
#endif

int	GPU_SetHorizontalResolution(int nNewRes)
{
	int nOldRes=db.disp[0].disp.w;

	db.disp[0].disp.w=db.disp[1].disp.w=nNewRes;
	db.draw[0].clip.w=db.draw[1].clip.w=nNewRes;
	if (nOldRes!=nNewRes)
		{
		RECT rc;

		VSync(0);
		SetDispMask(0);
		setRECT(&rc,0,0,nNewRes,MY_PSX_H);
		ClearImage(&rc,0,0,0);
		rc.y=256;
		ClearImage(&rc,0,0,0);
		DrawSync(0);
		GPU_FlipToBuffer(0);
		SetDispMask(1);
		}
	return nOldRes;
	}

int GPU_GetHorizontalResolution(void)
	{
	return db.disp[0].disp.w;
	}


void VSYNC_Init()
{
	LnVSyncRoutines=0;
	LpfnOldVSyncCallback=(PFNVSYNCROUTINE)VSyncCallback(VSYNC_Handler);
}

void VSYNC_Cleanup()
{
	VSyncCallback(LpfnOldVSyncCallback);
}

void VSYNC_Add(PFNVSYNCROUTINE pfnRoutine)
{
	if (LnVSyncRoutines >= MAX_VSYNC_ROUTINES)
		return;

	EnterCriticalSection();
	LapfnVSyncRoutine[LnVSyncRoutines++]=pfnRoutine;
	ExitCriticalSection();
}

void VSYNC_Remove(PFNVSYNCROUTINE pfnRoutine)
{
	int nRoutine;

	EnterCriticalSection();

	for (nRoutine=LnVSyncRoutines-1;nRoutine>=0;--nRoutine)
	{
		if (LapfnVSyncRoutine[nRoutine]==pfnRoutine)
	 	{
			LapfnVSyncRoutine[nRoutine]=LapfnVSyncRoutine[--LnVSyncRoutines];
			break;
	 	}
	}

	ExitCriticalSection();
}


/*** stuff to get rid of prim.obj from libs ***/
u_short GetTPage(int tp,int abr,int x,int y)
	{
	return getTPage(tp,abr,x,y);
	}

u_short GetClut(int x,int y)
	{
	return getClut(x,y);
	}




