// Burn - Drivers module
#include "version.h"
#include "burnint.h"
#include "driverlist.h"

// This is our dprintf() filler function, if the application is not printing debug messages
extern "C" int __cdecl BurnDprintfFiller(char *szFormat,...) { (void)szFormat; return 0;}
extern "C" int (__cdecl *dprintf) (char *szFormat,...);
int (__cdecl *dprintf) (char *szFormat,...) = BurnDprintfFiller;

int nBurnVer = BURN_VERSION;		// Version number of the library

unsigned int nBurnDrvCount = 0;		// Count of game drivers
unsigned int nBurnDrvSelect = ~0U;	// Which game driver is selected

#ifdef _DEBUG
 clock_t starttime = 0;
#endif

unsigned int nCurrentFrame;			// Framecount for emulated game

unsigned int nFramesEmulated;		// Counters for FPS	display
unsigned int nFramesRendered;		//
int nBurnFPS;

// Burn Draw:
unsigned char *pBurnDraw = NULL;	// Pointer to correctly sized bitmap
int nBurnPitch = 0;					// Pitch between each line
int nBurnBpp;						// Bytes per pixel (2, 3, or 4)
int nBurnBitDepth;					// Bitdepth of pBurnDraw (15, 16, 24, or 32)

int nBurnSoundRate = 0;				// sample rate of sound or zero for no sound
int nBurnSoundLen = 0;				// length in samples per frame
short *pBurnSoundOut = NULL;		// pointer to output buffer
unsigned char nBurnLayer = 0xFF;	// Can be used externally to select which layers to show
unsigned char nSpriteEnable = 0xFF;	// Can be used externally to select which layers to show

int nMaxPlayers;

extern "C" int BurnLibInit()
{
	BurnLibExit();					// Make sure exitted
	nBurnDrvCount = sizeof(pDriver) / sizeof(pDriver[0]); // count available drivers
	return 0;
}

extern "C" int BurnLibExit()
{
	nBurnDrvCount=0;
	return 0;
}

static int BurnExtInputFiller(unsigned int i) { (void)i; return 0;}
extern "C" int (*BurnExtInput) (unsigned int i);  // App-defined func to get the state of each input
int (*BurnExtInput) (unsigned int i) = BurnExtInputFiller;

int BurnGetZipName(char** pszName, unsigned int i)
{
	static char szFilename[MAX_PATH];
	char* pszGameName = NULL;

	if (pszName == NULL) {
		return 1;
	}

	if (i == 0) {
		pszGameName = BurnDrvText(0);
	} else {
		int nOldBurnDrvSelect = nBurnDrvSelect;
		unsigned int j = 0;
		
		// Go through the list to seek out the parent
		while (j++ < i) {
			char* pszParent = BurnDrvText(6);
			pszGameName = NULL;

			if (pszParent == NULL) {							// No parent
				break;
			}
			
			for (nBurnDrvSelect = 0; nBurnDrvSelect < nBurnDrvCount; nBurnDrvSelect++) {
                if (strcmp(pszParent, BurnDrvText(0)) == 0) {	// Found parent
					pszGameName = BurnDrvText(0);
					break;
				}
			}
		}

		nBurnDrvSelect = nOldBurnDrvSelect;

		// If there are no more parents, try BIOS/board ROMs
		if (pszGameName == NULL) {
			if (BurnDrvText(7)) {								// There is a BIOS/board ROM
				pszGameName = BurnDrvText(7);
			} else {											// Return error
				*pszName = NULL;
				return 1;
			}
		}
	}
	
	strcpy(szFilename, pszGameName);
	strcat(szFilename, ".zip");

	*pszName = szFilename;

	return 0;
}

// Static functions which forward to each driver's data and functions

// Get the text fields for the driver
extern "C" char* BurnDrvText(unsigned int i)
{
	// Limit to the available fields
	if (i>=sizeof(pDriver[0]->szText) / sizeof(pDriver[0]->szText[0])) {
		return NULL;
	}
	
	return pDriver[nBurnDrvSelect]->szText[i];
}

// Get the zip names for the driver
extern "C" int BurnDrvGetZipName(char** pszName, unsigned int i)
{
	if (pDriver[nBurnDrvSelect]->GetZipName) {									// Forward to drivers function
		return pDriver[nBurnDrvSelect]->GetZipName(pszName, i);
	}

	return BurnGetZipName(pszName, i);											// Forward to general function
}

extern "C" int BurnDrvGetRomInfo(struct BurnRomInfo* pri, unsigned int i)		// Forward to drivers function
{
	return pDriver[nBurnDrvSelect]->GetRomInfo(pri, i);
}

extern "C" int BurnDrvGetRomName(char** pszName, unsigned int i, int nAka)		// Forward to drivers function
{
	return pDriver[nBurnDrvSelect]->GetRomName(pszName, i, nAka);
}

extern "C" int BurnDrvGetInputInfo(struct BurnInputInfo* pii, unsigned int i)	// Forward to drivers function
{
	return pDriver[nBurnDrvSelect]->GetInputInfo(pii, i);
}

extern "C" int BurnDrvGetDIPInfo(struct BurnDIPInfo* pdi, unsigned int i)
{
	if (pDriver[nBurnDrvSelect]->GetDIPInfo) {									// Forward to drivers function
		return pDriver[nBurnDrvSelect]->GetDIPInfo(pdi, i);
	}
	
	return 1;																	// Fail automatically
}

// Get the screen size
extern "C" int BurnDrvGetScreen(int* pnWidth, int* pnHeight)
{
	*pnWidth =pDriver[nBurnDrvSelect]->nWidth;
	*pnHeight=pDriver[nBurnDrvSelect]->nHeight;
	
	return 0;
}

// Get screen aspect ratio
extern "C" int BurnDrvGetAspect(int* pnXAspect, int* pnYAspect)
{
	*pnXAspect = pDriver[nBurnDrvSelect]->nXAspect;
	*pnYAspect = pDriver[nBurnDrvSelect]->nYAspect;
	
	return 0;
}

// Get the hardware code
extern "C" int BurnDrvGetHardwareCode()
{
	return pDriver[nBurnDrvSelect]->hardware;
}

// Get flags, including BDF_GAME_WORKING flag
extern "C" int BurnDrvGetFlags()
{
	return pDriver[nBurnDrvSelect]->flags;
}

// Return BDF_WORKING flag
extern "C" bool BurnDrvIsWorking()
{
	return pDriver[nBurnDrvSelect]->flags & BDF_GAME_WORKING;
}

// Init game emulation (loading any needed roms)
extern "C" int BurnDrvInit()
{
	int nReturnValue;
	if (nBurnDrvSelect >= nBurnDrvCount) {
		return 1;
	}

	CheatInit();
	
	nReturnValue = pDriver[nBurnDrvSelect]->Init();	// Forward to drivers function

	nMaxPlayers = pDriver[nBurnDrvSelect]->players;

	nBurnFPS = 6000;

#ifdef _DEBUG
	printf("*** Starting emulation of %s (%s).\n", BurnDrvText(0), BurnDrvText(1));
	if (!nReturnValue) {
		starttime = clock();
		nFramesEmulated = 0;
		nFramesRendered = 0;
		nCurrentFrame = 0;
	} else {
		starttime = 0;
	}
#endif

	return nReturnValue;
}

// Exit game emulation
extern "C" int BurnDrvExit()
{
#ifdef _DEBUG
	if (starttime) {
		clock_t endtime;
		time_t nElapsedSecs;

		endtime = clock();
		nElapsedSecs = (endtime - starttime);
		printf(" ** Emulation ended.\n");
		printf("    Time elapsed: %ld milliseconds.\n", nElapsedSecs);
		printf("    Rendered %d out of %d frames (%.2f%%).\n", nFramesRendered, nFramesEmulated, (float)nFramesRendered / nFramesEmulated * 100);
		printf("    Average frames per second: %.2f.\n", (float)nFramesRendered / nFramesEmulated * nBurnFPS / 100);
		printf("\n");
	}
#endif

	CheatExit();

	return pDriver[nBurnDrvSelect]->Exit();			// Forward to drivers function
}

// Do one frame of game emulation
extern "C" int BurnDrvFrame()
{
	CheatApply();									// Apply cheats (if any)
	return pDriver[nBurnDrvSelect]->Frame();		// Forward to drivers function
}

// Force redraw of the screen
extern "C" int BurnDrvRedraw()
{
	if (pDriver[nBurnDrvSelect]->Redraw) {
		return pDriver[nBurnDrvSelect]->Redraw();	// Forward to drivers function
	}
	
	return 1;										// No funtion provide, so simply return
}

// Refresh Palette
extern "C" int BurnRecalcPal()
{
	unsigned char* pr = pDriver[nBurnDrvSelect]->pRecalcPal;
	if (pr == NULL) return 1;
	*pr = 1;										// Signal for the driver to refresh it's palette
	
	return 0;
}

inline static int BurnClearSize(int w, int h)
{
	unsigned char *pl;
	int y;
	
	w *= nBurnBpp;
	
	// clear the screen to zero
	for (pl = pBurnDraw, y = 0; y < h; pl += nBurnPitch, y++) {
		memset(pl, 0x00, w);
	}
	
	return 0;
}

int BurnClearScreen()
{
	struct BurnDriver* pbd = pDriver[nBurnDrvSelect];
	
	if (pbd->flags & (BDF_ROTATE_GRAPHICS_CW | BDF_ROTATE_GRAPHICS_CCW)) {
		BurnClearSize(pbd->nHeight, pbd->nWidth);
	} else {
		BurnClearSize(pbd->nWidth, pbd->nHeight);
	}
	
	return 0;
}

// Byteswaps an area of memory
int BurnByteswap(unsigned char* pMem, int nLen)
{
	nLen >>= 1;
	for (int i = 0; i < nLen; i++, pMem += 2) {
		unsigned char t = pMem[0];
		pMem[0] = pMem[1];
		pMem[1] = t;
	}
	
	return 0;
}

static unsigned int BurnHighColFiller(int, int, int, int)
{
	return (unsigned int)(~0);
}

// Callback used to convert truecolor colors into output (highcol) format
extern "C" unsigned int (*BurnHighCol) (int r, int g, int b, int i);
unsigned int (*BurnHighCol) (int r, int g, int b, int i) = BurnHighColFiller;

// Area (Memory) scanning
int BurnAreaScan(int nAction, int* pnMin)			// &1=for reading &2=for writing &4=Volatile &8=Non-Volatile
{
	// Forward to drivers function
	int (*pas)(int nAction, int *pnMin);

	if ((pas = pDriver[nBurnDrvSelect]->AreaScan) != NULL) {
		return pas(nAction, pnMin);
	}
	
	return 1;
}

static int DefAcb (struct BurnArea* /* pba */)
{
	 return 1;
}

int (*BurnAcb) (struct BurnArea *pba) = DefAcb;		// Area callback

