#include "toaplan.h"
// Fix Eight

static unsigned char DrvButton[8] = {0, 0, 0, 0, 0, 0, 0, 0};
static unsigned char DrvJoy1[8] = {0, 0, 0, 0, 0, 0, 0, 0};
static unsigned char DrvJoy2[8] = {0, 0, 0, 0, 0, 0, 0, 0};
static unsigned char DrvInput[6] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00};

static unsigned char *Mem = NULL, *MemEnd = NULL;
static unsigned char *RamStart, *RamEnd;
static unsigned char *Rom01;
static unsigned char *Ram01, *Ram02, *Ram03, *RamPal;

static int nColCount = 0x0800;

static unsigned char DrvReset = 0;
static unsigned char bDrawScreen;
static bool bVBlank;

static int nBlah = 0;
static int nZX80Status = 0;

static struct BurnInputInfo DrvInputList[] = {
	{"P1 Coin",		0, DrvButton + 3,	"p1 coin"},
	{"P1 Start",	0, DrvButton + 5,	"p1 start"},

	{"P1 Up",		0, DrvJoy1 + 0, 	"p1 up"},
	{"P1 Down",		0, DrvJoy1 + 1, 	"p1 down"},
	{"P1 Left",		0, DrvJoy1 + 2, 	"p1 left"},
	{"P1 Right",	0, DrvJoy1 + 3, 	"p1 right"},
	{"P1 Button 1",	0, DrvJoy1 + 4,		"p1 fire 1"},
	{"P1 Button 2",	0, DrvJoy1 + 5,		"p1 fire 2"},
	{"P1 Button 3",	0, DrvJoy1 + 6,		"p1 fire 3"},

	{"P2 Coin",		0, DrvButton + 4,	"p2 coin"},
	{"P2 Start",	0, DrvButton + 6,	"p2 start"},

	{"P2 Up",		0, DrvJoy2 + 0, 	"p2 up"},
	{"P2 Down",		0, DrvJoy2 + 1, 	"p2 down"},
	{"P2 Left",		0, DrvJoy2 + 2, 	"p2 left"},
	{"P2 Right",	0, DrvJoy2 + 3, 	"p2 right"},
	{"P2 Button 1",	0, DrvJoy2 + 4,		"p2 fire 1"},
	{"P2 Button 2",	0, DrvJoy2 + 5,		"p2 fire 2"},
	{"P2 Button 3",	0, DrvJoy2 + 6,		"p2 fire 3"},

	{"Reset",		0, &DrvReset,		"reset"},
	{"Diagnostics",	0, DrvButton + 0,	"diag"},
	{"Dip A",		2, DrvInput + 3,	"dip"},
	{"Dip B",		2, DrvInput + 4,	"dip"},
	{"Dip C",		2, DrvInput + 5,	"dip"},
};

// Return 0 if a input number is defined, and (if pii != NULL) information about each input
static int DrvInputInfo(struct BurnInputInfo *pii, unsigned int i)
{
	if (i >= sizeof(DrvInputList) / sizeof(DrvInputList[0])) {
		return 1;
	}
	if (pii != NULL) {
		*pii=DrvInputList[i];
	}
	return 0;
}

static int __fastcall DrvResetCallback()
{
	// Reset instruction on 68000

	nZX80Status = 0;
	nBlah = 0;

	return 0;
}

static unsigned int ZX80Status()
{
	if (nZX80Status == 0) {
		nZX80Status = 0xFF;
	}
	nZX80Status ^= 0x55;
	
	printf("Attempt to read Zx80 status (%02X).\n", nZX80Status);
	return nZX80Status;
}

static unsigned char __fastcall Drv1ReadByte(unsigned int sekAddress)
{
	switch (sekAddress) {

		case 0x200001:								// Player 1 inputs
			printf("-");
			return DrvInput[0];
		case 0x200005:								// Player 2 inputs
			printf("-");
			return DrvInput[1];
		case 0x200011:								// Other inputs
			printf("-");
			return DrvInput[2];
		
		case 0x30000D:
			return ToaVBlankRegister();

		case 0x28F001:			
//			if (nBlah > 1) *((unsigned short*)(Rom01 + 0x469E)) = 0x4e71;
			if (nBlah > 1) return ZX80Status();
		case 0x28F005:								// Dipswitch A
			if (nBlah > 1) printf("*");
			if (nBlah > 1) return DrvInput[3];
		case 0x28F007:								// Dipswitch B
			if (nBlah > 1) printf("*");
			if (nBlah > 1) return DrvInput[4];
		case 0x28F009:								// Dipswitch C - Territory
			if (nBlah > 1) printf("*");
			if (nBlah > 1) return DrvInput[5];

		default: {			
			if ((sekAddress & 0x00FF0000) == 0x00600000) {
//				printf("Attempt to read %06X (byte).\n", sekAddress);
				return ExtraTROM[(sekAddress & 0xFFFF) >> 1];
			}

			if ((sekAddress & 0x00FFF000) == 0x0028F000) {
//				printf("Attempt to read %06X (byte).\n", sekAddress);
				return Ram03[(sekAddress & 0x0FFF) ^ 1];
			}

//			printf("Attempt to read %06X (byte).\n", sekAddress);

		}
	}
		
	return 0;
}

static unsigned short __fastcall Drv1ReadWord(unsigned int sekAddress)
{
	switch (sekAddress) {

		case 0x300004:
			return ToaGP9001ReadRAM_Hi(0);
		case 0x300006:
			return ToaGP9001ReadRAM_Lo(0);

		case 0x30000C:
			printf("VBlank status read as WORD!\n");
			return 0;

		case 0x800000: {
			static int nStatus = 0x8000;

			nStatus ^= 0x0100;
			return nStatus;
		}

		case 0x200000:								// Player 1 inputs
			printf("-");
			return DrvInput[0];
		case 0x200004:								// Player 2 inputs
			printf("-");
			return DrvInput[1];
		case 0x200010:								// Other inputs
			printf("-");
			return DrvInput[2];
		
		
		case 0x28F000:
			if (nBlah > 1) return ZX80Status();
		case 0x28F004:								// Dipswitch A
			if (nBlah > 1) printf("*");
			if (nBlah > 1) return DrvInput[3];
		case 0x28F006:								// Dipswitch B
			if (nBlah > 1) printf("*");
			if (nBlah > 1) return DrvInput[4];
		case 0x28F008:								// Dipswitch C - Territory
			if (nBlah > 1) printf("*");
			if (nBlah > 1) return DrvInput[5];

		default: {
			if ((sekAddress & 0x00FF0000) == 0x00600000) {
//				printf("Attempt to read %06X (word).\n", sekAddress);			
				return ExtraTROM[(sekAddress & 0xFFFF) >> 1] | (ExtraTROM[0x8000 + ((sekAddress & 0xFFFF) >> 1)] << 8);
			}
		
			if ((sekAddress & 0x00FFF000) == 0x0028F000) {
//				printf("Attempt to read %06X (word).\n", sekAddress);			
				return *((unsigned short*)(Ram03 + (sekAddress & 0x0FFF)));
			}

//			printf("Attempt to read %06X (word).\n", sekAddress);			
		}
	}
	
	return 0;
}

static void __fastcall Drv1WriteByte(unsigned int sekAddress, unsigned char byteValue)
{
	switch (sekAddress) {
		case 0x700000:
//		case 0x700001:
			printf("nBlah++ (%02X)\n", byteValue);
			nBlah++;
			break;

		default: {
//			printf("Attempt to write %06X (byte) -> %04X.\n", sekAddress, byteValue);
	
			if ((sekAddress & 0x00FF0000) == 0x00600000) {
				ExtraTROM[(sekAddress & 0xFFFF) >> 1] = byteValue;
			}
		
			if ((sekAddress & 0x00FFF000 == 0x0028F000)) {
				Ram03[(sekAddress & 0x0FFF) ^ 1] = byteValue;
			}
		}
	}	
}

static void __fastcall Drv1WriteWord(unsigned int sekAddress, unsigned short wordValue)
{
	switch (sekAddress) {
		case 0x300000:								// Set GP9001 VRAM address-pointer
			ToaGP9001SetRAMPointer(wordValue);
			break;

		case 0x300004:
		case 0x300006:
			ToaGP9001WriteRAM(wordValue, 0);
			break;

		case 0x300008:
			ToaGP9001SelectRegister(wordValue);
			break;

		case 0x30000C:
			ToaGP9001WriteRegister(wordValue);
			break;

		case 0x700000:
			printf("nBlah++ (%04X)\n", wordValue);
			break;

		default: {
//			printf("Attempt to write %06X (word) -> %04X.\n", sekAddress, wordValue);

			if ((sekAddress & 0x00FF0000) == 0x00600000) {
				ExtraTROM[(sekAddress & 0xFFFF) >> 1] = wordValue & 0xFF;
				ExtraTROM[0x8000 + ((sekAddress & 0xFFFF) >> 1)] = wordValue << 8;
			}
		
			if ((sekAddress & 0x00FFF000) == 0x0028F000) {
				*((unsigned short*)(Ram03 + (sekAddress & 0x0FFF))) = wordValue;
			}
		}
	}
}

static int DrvExit()
{
	ToaPalExit();

	ToaExtraTextExit();
	ToaExitGP9001();
	SekExit();				// Deallocate 68000s

	// Deallocate all used memory
	free(Mem);
	Mem = NULL;
	
	return 0;
}

static int DrvDoReset()
{
	SekOpen(0);
	SekReset();
	SekClose();

	return 0;
}

static int DrvDraw()
{
	ToaClearScreen();
	
	if (bDrawScreen) {
		ToaGetBitmap();		
		ToaRenderGP9001();					// Render GP9001 graphics
		ToaExtraTextLayer();				// Render extra text layer
	}
	
	ToaPalUpdate();							// Update the palette

	return 0;
}

inline static int CheckSleep(int)
{
	return 0;
}

static int DrvFrame()
{
	int nCyclesVBlank;
	int nInterleave = 4;

	if (DrvReset) {														// Reset machine
		DrvDoReset();
	}

	// Compile digital inputs
	DrvInput[0] = 0x00;													// Buttons
	DrvInput[1] = 0x00;													// Player 1
	DrvInput[2] = 0x00;													// Player 2
	for (int i = 0; i < 8; i++) {
		DrvInput[0] |= (DrvJoy1[i] & 1) << i;
		DrvInput[1] |= (DrvJoy2[i] & 1) << i;
		DrvInput[2] |= (DrvButton[i] & 1) << i;
	}
	ToaClearOpposites(&DrvInput[0]);
	ToaClearOpposites(&DrvInput[1]);

	nCyclesTotal[0] = TOA_68K_SPEED / 60;
	nCyclesDone[0] = 0;

	nCyclesVBlank = nCyclesTotal[0] - ((nCyclesTotal[0] * 0x10) / 0x0106);
	bVBlank = false;

	SekOpen(0);

	for (int i = 0; i < nInterleave; i++) {
    	int nCurrentCPU;
		int nNext;

		// Run 68000

		nCurrentCPU = 0;
		nNext = (i + 1) * nCyclesTotal[nCurrentCPU] / nInterleave;

		// Trigger VBlank interrupt
		if (!bVBlank && nNext > nCyclesVBlank) {
			if (nCyclesDone[nCurrentCPU] < nCyclesVBlank) {
				nCyclesSegment = nCyclesVBlank - nCyclesDone[nCurrentCPU];
				nCyclesDone[nCurrentCPU] += SekRun(nCyclesSegment);
			}

			ToaBufferGP9001Sprites();

			bVBlank = true;
			SekInterrupt(4);
		}

		nCyclesSegment = nNext - nCyclesDone[nCurrentCPU];
		if (bVBlank || (!CheckSleep(nCurrentCPU))) {					// See if this CPU is busywaiting  		
			nCyclesDone[nCurrentCPU] += SekRun(nCyclesSegment);
		} else {
			nCyclesDone[nCurrentCPU] += nCyclesSegment;
		}

//		printf("PC: %08X.\n", SekPc(-1));
//		printf("Thing: %08X.\n", *((unsigned int*)(Ram01 + 0x0334)));

	}

	SekClose();

	if (pBurnDraw) {
		DrvDraw();														// Draw screen if needed
	}

	return 0;
}

// This routine is called first to determine how much memory is needed (MemEnd-(unsigned char *)0),
// and then afterwards to set up all the pointers
static int MemIndex()
{
	unsigned char *Next; Next = Mem;
	Rom01		= Next; Next += 0x080000;		//
	GP9001ROM[0]= Next; Next += nGP9001ROMSize[0];	// GP9001 tile data
	RamStart	= Next;
	Ram01		= Next; Next += 0x004000;		// CPU #0 work RAM
	Ram02		= Next; Next += 0x00F000;		// Shared RAM
	Ram03		= Next; Next += 0x001000;		//
	ExtraTROM	= Next; Next += 0x010000;		// Extra tile layer
	ExtraTRAM	= Next; Next += 0x002000;		// Extra tile layer
	ExtraTOffset= Next; Next += 0x001000;		//
	ExtraTScroll= Next; Next += 0x001000;		//
	RamPal		= Next; Next += 0x001000;		// palette
	GP9001RAM[0]= Next; Next += 0x004000;
	GP9001Reg[0]= (unsigned short*)Next; Next += 0x0100 * sizeof(short);
	RamEnd		= Next;
	ToaPalette	= (unsigned int *)Next; Next += nColCount * sizeof(int);
	MemEnd		= Next;

	return 0;
}

static int LoadRoms()
{
	// Load 68000 ROM
	BurnLoadRom(Rom01, 0, 1);

	// Load GP9001 tile data
	ToaLoadGP9001Tiles(GP9001ROM[0], 1, 2, nGP9001ROMSize[0]);

	return 0;
}

// Scan ram
static int DrvScan(int nAction,int *pnMin)
{
	struct BurnArea ba;

	if (nAction & 4) {					// Scan volatile ram
		if (pnMin != NULL) {			// Return minimum compatible version
			*pnMin = 0x020902;
		}

		memset(&ba, 0, sizeof(ba));
    	ba.Data		= RamStart;
		ba.nLen		= RamEnd-RamStart;
		ba.szName	= "All Ram";
		BurnAcb(&ba);

		SekScan(nAction & 3);			// scan 68000 states

		SCAN_VAR(DrvInput);
	}
	return 0;
}

static int DrvInit()
{
	int nLen;

	bRotatedScreen = true;
	nGP9001ROMSize[0] = 0x400000;

	// Find out how much memory is needed
	Mem = NULL;
	MemIndex();
	nLen = MemEnd - (unsigned char *)0;
	if ((Mem = (unsigned char *)malloc(nLen)) == NULL) {
		return 1;
	}
	memset(Mem, 0, nLen);										// blank all memory
	MemIndex();													// Index the allocated memory

	// Load the roms into memory
	if (LoadRoms()) {
		return 1;
	}

	{
		SekInit(1);												// Allocate 68000
		SekExt[0].ResetCallback = DrvResetCallback;				// Get cpu 1 reset requests

		// Map 68000 memory:
	    SekOpen(0);
		SekMemory(Rom01,		0x000000, 0x07FFFF, SM_ROM);	// CPU 0 ROM
		SekMemory(Ram01,		0x100000, 0x103FFF, SM_RAM);
		SekMemory(Ram02,		0x280000, 0x28EFFF, SM_RAM);
//		SekMemory(Ram03 + 0x0400,0x28F400, 0x28FFFF, SM_RAM);		
		SekMemory(RamPal,		0x400000, 0x400FFF, SM_RAM);	// Palette RAM
		SekMemory(ExtraTRAM,	0x500000, 0x501FFF, SM_RAM);
		SekMemory(ExtraTScroll,	0x502000, 0x502FFF, SM_RAM);	// 0x502000 - Scroll; 0x502200 - RAM
		SekMemory(ExtraTOffset,	0x503000, 0x503FFF, SM_RAM);	// 0x203000 - Offset; 0x503200 - RAM

		SekExt[0].ReadWord	= Drv1ReadWord;
		SekExt[0].ReadByte	= Drv1ReadByte;
		SekExt[0].WriteWord	= Drv1WriteWord;
		SekExt[0].WriteByte	= Drv1WriteByte;

		SekClose();
	}

	nLayer0XOffset = -0x01D6;
	nLayer1XOffset = -0x01D8;
	nLayer2XOffset = -0x01DA;

	nSpriteYOffset = 0x0001;
	ToaInitGP9001();

	ToaExtraTextInit();

	nToaPalLen = nColCount;
	ToaPalSrc = RamPal;
	ToaPalInit();

	bDrawScreen = true;

	DrvDoReset(); // Reset machine
	return 0;
}

// Rom information
static struct BurnRomInfo drvRomDesc[] = {
	{"tp026_1.bin", 0x080000, 0xF7B1746A, 0x10}, //  0 CPU #0 code

	{"tp026_3.bin", 0x200000, 0xB760CB53,    1}, //  1 GP9001 Tile data
	{"tp026_4.bin", 0x200000, 0xE5578D98,    1}, //  2

//	{"tp026.mcu",	0x008000, 0x00000000, 0x10}, //  3
	{"tp026_2.bin", 0x040000, 0x85063F1F,    2}, //  4 OKIM6295 ADPCM data
};

STD_ROM_PICK(drv);
STD_ROM_FN(drv);


struct BurnDriverD BurnDrvFixEight = {
	{"fixeight", "Fix Eight", "Incomplete, no sound.", "Toaplan", "Toaplan", "1993", NULL, NULL},
	1, 2, HARDWARE_TOAPLAN_68K_Zx80,
	NULL, drvRomInfo, drvRomName, DrvInputInfo, NULL,
	DrvInit, DrvExit, DrvFrame, DrvDraw, DrvScan, &ToaRecalcPalette,
	240, 320, 3, 4
};

