#include "toaplan.h"
// Snow Bros. 2

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 DrvJoy3[8] = {0, 0, 0, 0, 0, 0, 0, 0};
static unsigned char DrvJoy4[8] = {0, 0, 0, 0, 0, 0, 0, 0};
static unsigned char DrvInput[8] = {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, *RamPal;

static int nColCount = 0x0800;

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

static struct BurnInputInfo snowbro2InputList[] = {
	{"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" },

	{"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" },
	
	{"P3 Start",	0, DrvJoy3 + 6,		"p3 start"  },

	{"P3 Up",	0, DrvJoy3 + 0, 	"p3 up"     },
	{"P3 Down",	0, DrvJoy3 + 1, 	"p3 down"   },
	{"P3 Left",	0, DrvJoy3 + 2, 	"p3 left"   },
	{"P3 Right",	0, DrvJoy3 + 3, 	"p3 right"  },
	{"P3 Button 1",	0, DrvJoy3 + 4,		"p3 fire 1" },
	{"P3 Button 2",	0, DrvJoy3 + 5,		"p3 fire 2" },
	
	{"P4 Start",	0, DrvJoy4 + 6,		"p4 start"  },

	{"P4 Up",	0, DrvJoy4 + 0, 	"p4 up"     },
	{"P4 Down",	0, DrvJoy4 + 1, 	"p4 down"   },
	{"P4 Left",	0, DrvJoy4 + 2, 	"p4 left"   },
	{"P4 Right",	0, DrvJoy4 + 3, 	"p4 right"  },
	{"P4 Button 1",	0, DrvJoy4 + 4,		"p4 fire 1" },
	{"P4 Button 2",	0, DrvJoy4 + 5,		"p4 fire 2" },

	{"Reset",	0, &DrvReset,		"reset"     },
	{"Service",	0, DrvButton + 0,	"service"   },
	{"Dip 1",	2, DrvInput + 3,	"dip"       },
	{"Dip 2",	2, DrvInput + 4,	"dip"       },
	{"Dip 3",	2, DrvInput + 5,	"dip"       },
};

STDINPUTINFO(snowbro2);

static struct BurnDIPInfo snowbro2DIPList[] = {
	// Defaults
	{0x20,	0xFF, 0xFF,	0x00, NULL},
	{0x21,	0xFF, 0xFF,	0x00, NULL},
	{0x22,	0xFF, 0xFF,	0x00, NULL},

	// DIP 1
	{0   , 0xFE, 0   , 2   , "Continue Mode"                },
	{0x20, 0x01, 0x01, 0x00, "Normal"                       },
	{0x20, 0x01, 0x01, 0x01, "Discount"                     },
	
	{0   , 0xFE, 0   , 2   , "Flip Screen"                  },
	{0x20, 0x01, 0x02, 0x00, "Off"                          },
	{0x20, 0x01, 0x02, 0x02, "On"                           },

	{0   , 0xFE, 0   , 2   , "Service Mode"                 },
	{0x20, 0x01, 0x04, 0x00, "Off"                          },
	{0x20, 0x01, 0x04, 0x04, "On"                           },
	
	{0   , 0xFE, 0   , 2   , "Demo Sounds"                  },
	{0x20, 0x01, 0x08, 0x08, "Off"                          },
	{0x20, 0x01, 0x08, 0x00, "On"                           },
	
	// Normal coin settings
	{0,		0xFE, 0,	4,	  "Coin A"},
	{0x14,	0x82, 0x30,	0x00, "1 coin 1 play"},
	{0x16,	0x00, 0x0F, 0x08, NULL},
	{0x14,	0x82, 0x30,	0x10, "1 coin 2 plays"},
	{0x16,	0x00, 0x0F, 0x08, NULL},
	{0x14,	0x82, 0x30,	0x20, "2 coins 1 play"},
	{0x16,	0x00, 0x0F, 0x08, NULL},
	{0x14,	0x82, 0x30,	0x30, "2 coins 3 plays"},
	{0x16,	0x00, 0x0F, 0x08, NULL},
	{0,		0xFE, 0,	4,	  "Coin B"},
	{0x14,	0x82, 0xC0,	0x00, "1 coin 1 play"},
	{0x16,	0x00, 0x0F, 0x08, NULL},
	{0x14,	0x82, 0xC0,	0x40, "1 coin 2 plays"},
	{0x16,	0x00, 0x0F, 0x08, NULL},
	{0x14,	0x82, 0xC0,	0x80, "2 coins 1 play"},
	{0x16,	0x00, 0x0F, 0x08, NULL},
	{0x14,	0x82, 0xC0,	0xC0, "2 coins 3 plays"},
	{0x16,	0x00, 0x0F, 0x08, NULL},
	
	// European coin settings
	{0,		0xFE, 0,	4,	  "Coin A"},
	{0x14,	0x02, 0x30,	0x00, "1 coin 1 play"},
	{0x16,	0x00, 0x0F, 0x08, NULL},
	{0x14,	0x02, 0x30,	0x10, "2 coins 1 play"},
	{0x16,	0x00, 0x0F, 0x08, NULL},
	{0x14,	0x02, 0x30,	0x20, "3 coins 1 play"},
	{0x16,	0x00, 0x0F, 0x08, NULL},
	{0x14,	0x02, 0x30,	0x30, "3 coins 1 play"},
	{0x16,	0x00, 0x0F, 0x08, NULL},
	{0,		0xFE, 0,	4,	  "Coin B"},
	{0x14,	0x02, 0xC0,	0x00, "1 coin 2 plays"},
	{0x16,	0x00, 0x0F, 0x08, NULL},
	{0x14,	0x02, 0xC0,	0x40, "1 coin 3 plays"},
	{0x16,	0x00, 0x0F, 0x08, NULL},
	{0x14,	0x02, 0xC0,	0x80, "1 coin 4 play"},
	{0x16,	0x00, 0x0F, 0x08, NULL},
	{0x14,	0x02, 0xC0,	0xC0, "1 coin 6 plays"},
	{0x16,	0x00, 0x0F, 0x08, NULL},
	
	// DIP 2
	{0   , 0xFE, 0   , 4   , "Difficulty"                   },
	{0x21, 0x01, 0x03, 0x01, "Easy"                         },
	{0x21, 0x01, 0x03, 0x00, "Normal"                       },
	{0x21, 0x01, 0x03, 0x02, "Hard"                         },
	{0x21, 0x01, 0x03, 0x03, "Very Hard"                    },

	{0   , 0xFE, 0   , 4   , "Bonus Life"                   },
	{0x21, 0x01, 0x0C, 0x04, "100000 / 500000"              },
	{0x21, 0x01, 0x0C, 0x00, "100000 only"                  },
	{0x21, 0x01, 0x0C, 0x08, "200000 only"                  },
	{0x21, 0x01, 0x0C, 0x0C, "None"                         },
	
	{0   , 0xFE, 0   , 4   , "Lives"                        },
	{0x21, 0x01, 0x30, 0x30, "1"                            },
	{0x21, 0x01, 0x30, 0x20, "2"                            },
	{0x21, 0x01, 0x30, 0x00, "3"                            },
	{0x21, 0x01, 0x30, 0x10, "4"                            },

	{0   , 0xFE, 0   , 2   , "Game Type"                    },
	{0x21, 0x01, 0x40, 0x00, "Normal"                       },
	{0x21, 0x01, 0x40, 0x40, "No Death & Stop Mode"         },
	
	{0   , 0xFE, 0   , 2   , "Max Players"                  },
	{0x21, 0x01, 0x80, 0x80, "2"                            },
	{0x21, 0x01, 0x80, 0x00, "4"                            },
	
	// Dip 3
	{0   , 0xFE, 0   , 7   , "Territory"                    },
	{0x22, 0x01, 0x1C, 0x08, "Europe"                       },
	{0x22, 0x01, 0x1C, 0x10, "Hong Kong"                    },
	{0x22, 0x01, 0x1C, 0x00, "Japan"                        },
	{0x22, 0x01, 0x1C, 0x0c, "Korea"                        },
	{0x22, 0x01, 0x1C, 0x18, "South East Asia"              },
	{0x22, 0x01, 0x1C, 0x14, "Taiwan"                       },
	{0x22, 0x01, 0x1C, 0x04, "USA"                          },

	{0   , 0xFE, 0   , 2   , "Show All Rights Reserved"     },
	{0x22, 0x01, 0x20, 0x00, "No"                           },
	{0x22, 0x01, 0x20, 0x20, "Yes"                          },
};

STDDIPINFO(snowbro2);

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

	return 0;
}

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

		case 0x30000D:
			return ToaVBlankRegister();

		case 0x70000D:								// Player 1 inputs
			return DrvInput[0];
		case 0x700011:								// Player 2 inputs
			return DrvInput[1];
		case 0x700015:								// Player 3 inputs
			return DrvInput[6];
		case 0x700019:								// Player 4 inputs
			return DrvInput[7];
		case 0x70001D:								// Other inputs
			return DrvInput[2];
		case 0x700005:								// Dipswitch A
			return DrvInput[3];
		case 0x700009:								// Dipswitch B
			return DrvInput[4];
		case 0x700000:								// Dipswitch C - Territory
			return DrvInput[5];

		case 0x600001:
			return OKIM6295ReadStatus(0);
		case 0x500003:
			return YM2151ReadStatus(0);

		default: {
//			printf("Attempt to read byte value of location %x\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:
			return ToaVBlankRegister();

		case 0x70000C:								// Player 1 inputs
			return DrvInput[0];
		case 0x700010:								// Player 2 inputs
			return DrvInput[1];
		case 0x700014:								// Player 3 inputs
			return DrvInput[6];
		case 0x700018:								// Player 4 inputs
			return DrvInput[7];
		case 0x70001C:								// Other inputs
			return DrvInput[2];
		case 0x700004:								// Dipswitch A
			return DrvInput[3];
		case 0x700008:								// Dipswitch B
			return DrvInput[4];

		case 0x600000:
			return OKIM6295ReadStatus(0);
		case 0x500002:
			return YM2151ReadStatus(0);

		default: {
// 			printf("Attempt to read word value of location %x\n", sekAddress);
		}
	}	
	return 0;
}

static void __fastcall Drv1WriteByte(unsigned int sekAddress, unsigned char byteValue)
{
	switch (sekAddress) {
		case 0x600001:
			OKIM6295Command(0, byteValue);
			break;

		case 0x500001:
			ToaYM2151SelectRegister(byteValue);
			break;
		case 0x500003:
			ToaYM2151WriteRegister(byteValue);
			break;

		default: {
//			printf("Attempt to write byte value %x to location %x\n", byteValue, sekAddress);
		}
	}
}

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 0x600000:
			OKIM6295Command(0, wordValue & 0xFF);
			break;
		case 0x700030: {
			int nBankOffset = (wordValue & 0x01) << 18;
			OKIM6295SampleInfo[0][0] = OKIM6295ROM + nBankOffset;
			OKIM6295SampleData[0][0] = OKIM6295ROM + nBankOffset;
			OKIM6295SampleInfo[0][1] = OKIM6295ROM + nBankOffset + 0x0100;
			OKIM6295SampleData[0][1] = OKIM6295ROM + nBankOffset + 0x10000;
			OKIM6295SampleInfo[0][2] = OKIM6295ROM + nBankOffset + 0x0200;
			OKIM6295SampleData[0][2] = OKIM6295ROM + nBankOffset + 0x20000;
			OKIM6295SampleInfo[0][3] = OKIM6295ROM + nBankOffset + 0x0300;
			OKIM6295SampleData[0][3] = OKIM6295ROM + nBankOffset + 0x30000;
			break;
		}
		
		case 0x500000:
			ToaYM2151SelectRegister(wordValue);
			break;
		case 0x500002:
			ToaYM2151WriteRegister(wordValue);
			break;

		default: {
//			printf("Attempt to write word value %x to location %x\n", wordValue, sekAddress);
		}
	}
}

static int DrvExit()
{
	OKIM6295Exit(0);
	ToaYM2151Exit();

	ToaPalExit();

	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
	}
	
	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
	DrvInput[6] = 0x00;
	DrvInput[7] = 0x00;
	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;
		DrvInput[6] |= (DrvJoy3[i] & 1) << i;
		DrvInput[7] |= (DrvJoy4[i] & 1) << i;
	}
	ToaClearOpposites(&DrvInput[0]);
	ToaClearOpposites(&DrvInput[1]);
	ToaClearOpposites(&DrvInput[6]);
	ToaClearOpposites(&DrvInput[7]);

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

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

	int nSoundBufferPos = 0;

	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);
			}

			bVBlank = true;
			
			ToaBufferGP9001Sprites();
			
			SekInterrupt(4);
		}

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

		{
			// Render sound segment
			if (pBurnSoundOut) {
				int nSegmentLength = nBurnSoundLen / nInterleave;
				short* pSoundBuf = pBurnSoundOut + (nSoundBufferPos << 1);
				YM2151UpdateOne(0, pYM2151Buffer, nSegmentLength);
				for (int n = 0; n < nSegmentLength; n++) {
					pSoundBuf[(n << 1) + 0] = pYM2151Buffer[0][n] >> 1;
					pSoundBuf[(n << 1) + 1] = pYM2151Buffer[1][n] >> 1;
				}
				OKIM6295Render(0, pSoundBuf, nSegmentLength);
				nSoundBufferPos += nSegmentLength;
			}
		}
	}

	SekClose();

	{
		// Make sure the buffer is entirely filled.
		if (pBurnSoundOut) {
			int nSegmentLength = nBurnSoundLen - nSoundBufferPos;
			short* pSoundBuf = pBurnSoundOut + (nSoundBufferPos << 1);
			if (nSegmentLength) {
				YM2151UpdateOne(0, pYM2151Buffer, nSegmentLength);
				for (int n = 0; n < nSegmentLength; n++) {
					pSoundBuf[(n << 1) + 0] = pYM2151Buffer[0][n] >> 1;
					pSoundBuf[(n << 1) + 1] = pYM2151Buffer[1][n] >> 1;
				}
				OKIM6295Render(0, pSoundBuf, nSegmentLength);
			}
		}
	}

	if (pBurnDraw != NULL) {
		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
	OKIM6295ROM	= Next; Next += 0x080000;
	RamStart	= Next;
	Ram01		= Next; Next += 0x010000;		// CPU #0 work RAM
	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(unsigned int);
	MemEnd		= Next;

	return 0;
}

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

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

	// Load OKIM6295 ADPCM data
	BurnLoadRom(OKIM6295ROM, 5, 1);
	
	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

		OKIM6295Scan(0);
		ToaYM2151Scan(nAction);

		SCAN_VAR(DrvInput);
	}
	return 0;
}

static int DrvInit()
{
	int nLen;

	bRotatedScreen = false;
	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, 0x10FFFF, SM_RAM);
		SekMemory(RamPal,		0x400000, 0x400FFF, SM_RAM);	// Palette 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 = 0x0011;
	ToaInitGP9001();

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

	ToaYM2151Init(27000000 / 8);
	OKIM6295Init(0, 27000000 / 10 / 132, 50.0);

	bDrawScreen = true;

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

// Rom information
static struct BurnRomInfo snowbro2RomDesc[] = {
	{"pro-4"      , 0x080000, 0x4c7ee341, 0x10}, //  0 CPU #0 code

	{"rom2-l"     , 0x100000, 0xe9d366a9,    1}, //  1 GP9001 Tile data
	{"rom2-h"     , 0x080000, 0x9aab7a62,    1}, //  2
	{"rom3-l"     , 0x100000, 0xeb06e332,    1}, //  3
	{"rom3-h"     , 0x080000, 0xdf4a952a,    1}, //  4

	{"rom4"       , 0x080000, 0x638f341e,    2}, //  5 OKIM6295 ADPCM data
};

STD_ROM_PICK(snowbro2);
STD_ROM_FN(snowbro2);


struct BurnDriver BurnDrvSnowbro2 = {
	{"snowbro2", "Snow Bros. 2 - With New Elves", NULL, "[Toaplan] Hanafram", "Toaplan", "1994", NULL, NULL},
	1, 2, HARDWARE_TOAPLAN_68K_ONLY,
	NULL, snowbro2RomInfo, snowbro2RomName, snowbro2InputInfo, snowbro2DIPInfo,
	DrvInit, DrvExit, DrvFrame, DrvDraw, DrvScan, &ToaRecalcPalette,
	320, 240, 4, 3
};

