#include <clip.h>

//#define REDUCE

/////////////////////////////////////////////////////////////////////////////
#define DIR	"C:\\Home\\Emulator\\watarasupervision\\roms\\"
/* 32K */
//#define FNAME	"Carrier (199x) (Travellmate).sv"
//#define FNAME	"Final Combat (199x) (Watara).sv"
//#define FNAME	"Penguin Hideout (1992) (Thin Chen Enterprise).sv"
//#define FNAME	"Pyramid (199x) (Watara).sv"
//#define FNAME	"Super Block (1992) (Bon Treasure).sv"
/* 64K */
//#define FNAME	"Alien (199x) (Supervision).sv"
//#define FNAME	"Balloon Fight (199x) (Watara).sv"
//#define FNAME	"Block Buster & Cross High (199x) (Supervision).sv"
//#define FNAME	"Block Buster (199x) (-).sv"
//#define FNAME	"Brain Power (199x) (Supervision).sv"
//#define FNAME	"Cave Wonder (1992) (Bon Treasure).sv"
//#define FNAME	"Challenger Tank (199x) (-).sv"
//#define FNAME	"Chimera (199x) (Supervision).sv"
//#define FNAME	"Crystball (199x) (Travellmate).sv"
//#define FNAME	"Delta Hero (1992) (Bon Treasure).sv"
//#define FNAME	"Eagle Plan (1992) (GTC Inc).sv"
//#define FNAME	"Earth Defender (1992) (Bon Treasure).sv"
//#define FNAME	"Fatal Craft (1992) (Bon Treasure).sv"
//#define FNAME	"Galactic Crusader (199x) (Watara).sv"
#define FNAME	"Galaxy Fighter (1992) (Thin Chen Enterprise).sv"
//#define FNAME	"Grand Prix (1992) (Bon Treasure).sv"
//#define FNAME	"Happy Pairs (1992) (Sachen).sv"
//#define FNAME	"Hash Block (1991) (GTC Inc).sv"
//#define FNAME	"Hash Blocks & Eagle Plan (1992) (GTC Inc).sv"
//#define FNAME	"Hero Kid (1992) (-).sv"
//#define FNAME	"Honey Bee (199x) (Watara).sv"
//#define FNAME	"Jacky Lucky (199x) (-).sv"
//#define FNAME	"Jaguar Bomber (1992) (Bon Treasure).sv"
//#define FNAME	"John Adventure (199x) (Watara).sv"
//#define FNAME	"Juggler (1992) (Bon Treasure).sv"
//#define FNAME	"Kabi Island (199x) (Watara).sv"
//#define FNAME	"Linear Racing (199x) (-).sv"
//#define FNAME	"Magincross (1992) (Thin Chen Enterprise).sv"
//#define FNAME	"Matta Blatta (199x) (Watara).sv"
//#define FNAME	"Olympic Trials (199x) (-).sv"
//#define FNAME	"P-52 Sea Battle (199x) (-).sv"
//#define FNAME	"PacBoy and Mouse (199x) (Watara).sv"
//#define FNAME	"Police Bust (199x) (Bon Treasure).sv"
//#define FNAME	"Soccer Champion (199x) (-).sv"
//#define FNAME	"SSSnake (199x) (Watara).sv"
//#define FNAME	"Super Kong (1992) (Thin Chen Enterprise).sv"
//#define FNAME	"Tasac 2010 (1992) (Thin Chen Enterprise).sv"
//#define FNAME	"Tennis Pro '92 (1992) (B.I.T.S).sv"
//#define FNAME	"Treasure Hunter (199x) (Watara).sv"
/////////////////////////////////////////////////////////////////////////////
#define APPNAME	"SVWIN"
#define WIDTH	160
#define HEIGHT	160
#define SCALE	1
unsigned char xbuff[WIDTH * HEIGHT];
unsigned char ram[ 0x8000];
unsigned char rom[0x10000];
const unsigned char* ROM_BANK; /* $8000-$BFFF */
int TIMER;
int INPUT;
/////////////////////////////////////////////////////////////////////////////
#define NWHDR		8	/* これ以上増やすとwaveOutWriteでエラーになるみたい */
HWAVEOUT g_wout;
WAVEFORMATEX g_fmt;
WAVEHDR g_whdr[NWHDR];
short g_wbuff[NWHDR][SVSOUNDBUFLEN];
CRITICAL_SECTION snd_cs;
void CALLBACK
waveOutProc(HWAVEOUT hwo, UINT uMsg, DWORD dwInstance, DWORD dwParam1, DWORD dwParam2)
{
	static int I;
	int retval;

	if(uMsg != WOM_DONE) return;

	EnterCriticalSection(&snd_cs);
	svsound_mix(g_wbuff[I]);
	LeaveCriticalSection(&snd_cs);

	retval = waveOutWrite(hwo, &g_whdr[I], sizeof g_whdr[I]);
	assert(retval == 0);

	I = (I + 1) % NWHDR;
}
void
InitSnd()
{
	int retval;
	int i;

	InitializeCriticalSection(&snd_cs);

	/* init stream */
	memset(&g_fmt, 0, sizeof g_fmt);
	g_fmt.wFormatTag = WAVE_FORMAT_PCM;
	g_fmt.nChannels = 1;
	g_fmt.nSamplesPerSec = 16000;
	g_fmt.nAvgBytesPerSec = 16000 * 2;
	g_fmt.nBlockAlign = 2;
	g_fmt.wBitsPerSample = 16;
	retval = waveOutOpen(&g_wout, WAVE_MAPPER, &g_fmt, (DWORD)waveOutProc, 0, CALLBACK_FUNCTION);
	assert(retval == 0);
	for(i = 0; i < NWHDR; i++) {
		memset(&g_whdr[i], 0, sizeof g_whdr[i]);
		g_whdr[i].lpData = (char*)g_wbuff[i];
		g_whdr[i].dwBufferLength = sizeof g_wbuff[i];
		retval = waveOutPrepareHeader(g_wout, &g_whdr[i], sizeof g_whdr[i]);
		assert(retval == 0);
	}

	/* start playing */
	for(i = 0; i < NWHDR; i++) {
		retval = waveOutWrite(g_wout, &g_whdr[i], sizeof g_whdr[i]);
		assert(retval == 0);
	}
}
/////////////////////////////////////////////////////////////////////////////
BITMAPINFO* pBMI;
HWND MainWnd;
LRESULT CALLBACK
WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
	switch(msg) {
	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;
	case WM_CHAR:
		if(wParam == 'q') {
			DestroyWindow(hWnd);
			return 0;
		}
		return 0;
	case WM_KEYDOWN:
		switch(wParam) {
		case VK_RIGHT : INPUT &= ~(1<<0); break;
		case VK_LEFT  : INPUT &= ~(1<<1); break;
		case VK_DOWN  : INPUT &= ~(1<<2); break;
		case VK_UP    : INPUT &= ~(1<<3); break;
		case 'Z'      : INPUT &= ~(1<<4); break;
		case 'X'      : INPUT &= ~(1<<5); break;
		case VK_BACK  : INPUT &= ~(1<<6); break;
		case VK_RETURN: INPUT &= ~(1<<7); break;
		}
		return 0;
	case WM_KEYUP:
		switch(wParam) {
		case VK_RIGHT : INPUT |= (1<<0); break;
		case VK_LEFT  : INPUT |= (1<<1); break;
		case VK_DOWN  : INPUT |= (1<<2); break;
		case VK_UP    : INPUT |= (1<<3); break;
		case 'Z'      : INPUT |= (1<<4); break;
		case 'X'      : INPUT |= (1<<5); break;
		case VK_BACK  : INPUT |= (1<<6); break;
		case VK_RETURN: INPUT |= (1<<7); break;
		}
		return 0;
	}
	return DefWindowProc(hWnd, msg, wParam, lParam);
}
int
DoMsg()
{
	MSG msg;
	while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
		if(msg.message == WM_QUIT) {
			return 0;
		}
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
	return 1;
}
void
InitUI()
{
	int i;
	BITMAPINFOHEADER* bi;
	RGBQUAD* rgb;
	HINSTANCE hInst;
	WNDCLASS wc;
	RECT rcWin;
	RECT rcCli;
	int ncW;
	int ncH;

	pBMI = (BITMAPINFO*)calloc(sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * 256, 1);
	bi = &pBMI->bmiHeader;
	rgb = &pBMI->bmiColors[0];
	bi->biSize = sizeof(BITMAPINFOHEADER);
#ifdef REDUCE
	bi->biWidth = 128;
	bi->biHeight = -88;
#else /*REDUCE*/
	bi->biWidth = WIDTH;
	bi->biHeight = -HEIGHT;
#endif /*REDUCE*/
	bi->biPlanes = 1;
	bi->biBitCount = 8;
	for(i = 0; i < 4; i++) {
		rgb[i].rgbRed   = 255 - (255 * i / 4);
		rgb[i].rgbGreen = 255 - (255 * i / 4);
		rgb[i].rgbBlue  = 255 - (255 * i / 4);
	}

	hInst = GetModuleHandle(NULL);
	//
	memset(&wc, 0, sizeof wc);
	wc.style = CS_HREDRAW | CS_VREDRAW;
	wc.lpfnWndProc = WndProc;
	wc.hInstance = hInst;
	wc.hCursor = LoadCursor(NULL, IDC_CROSS);
	wc.lpszClassName = APPNAME;
	RegisterClass(&wc);
	//
	MainWnd = CreateWindow(APPNAME, APPNAME,
		WS_OVERLAPPED,
		CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
		NULL, NULL, hInst, NULL);
	//
	GetWindowRect(MainWnd, &rcWin);
	GetClientRect(MainWnd, &rcCli);
	ncW = (rcWin.right - rcWin.left) - (rcCli.right - rcCli.left);
	ncH = (rcWin.bottom - rcWin.top) - (rcCli.bottom - rcCli.top);
	MoveWindow(MainWnd, 0, 0,
		ncW + bi->biWidth  * SCALE,
		ncH - bi->biHeight * SCALE, 0);
	//
	ShowWindow(MainWnd, SW_SHOW);
	UpdateWindow(MainWnd);
}
/////////////////////////////////////////////////////////////////////////////
void
InitROM()
{
	FILE* fp;
	int size;

	memset(ram, 0, sizeof ram);
	INPUT = 0xff;

	/* ROM size = 32K or 64K */
	fp = fopen(DIR FNAME, "rb");
	if(!fp) {
		DIE();
	}
	size = fread(&rom[0x0000], 1, 0x10000, fp);
	fclose(fp);

	/* 32Kならば、後半にもミラーします。 */
	if(size <= 0x8000) {
		memcpy(&rom[0x8000], &rom[0x0000], 0x8000);
	}

	/* 初期バンク設定。 */
	ROM_BANK = rom - 0x8000;
}

/****************************************************************************
 *	
 ****************************************************************************/

int
reg_read(int addr)
{
	switch(addr) {
	/*========== IRQ ==========*/
	case 0x2024:
		/* Timer IRQ要因をクリアします。 */
		ram[0x2027] &= ~(1<<0);
		return 0; /* 読み出した値は無意味です */
	case 0x2025:
		/* Sound DMA IRQ要因をクリアします。 */
		ram[0x2027] &= ~(1<<1);
		return 0; /* 読み出した値は無意味です */
	}
	return ram[addr];
}

void
reg_write(int addr, int data)
{
	ram[addr] = data;

	switch(addr) {
	/*========== Video ==========*/
	case 0x2000:
	case 0x2001:
	case 0x2002:
	case 0x2003:
		svvideo_write(addr, data);
		return;
	/*========== Sound ==========*/
	case 0x2010: /* Tone 0 */
	case 0x2011:
	case 0x2012:
	case 0x2013:
	case 0x2014: /* Tone 1 */
	case 0x2015:
	case 0x2016:
	case 0x2017:
	case 0x2018: /* DMA */
	case 0x2019:
	case 0x201a:
	case 0x201b:
	case 0x201c:
	case 0x2028: /* Noise */
	case 0x2029:
	case 0x202a:
		svsound_write(addr, data);
		return;
	/*========== I/O ==========*/
	case 0x2022:
		ram[0x2021] = data; /* $2022(W) -> $2021(R) */
		return;
	/*========== Timer ==========*/
	case 0x2023:
		if(!data) {
			data = 0x100; /* (忘れないで!!) */
		}
		if(ram[0x2026] & (1<<4)) {
			TIMER = data << 15; /* x16384 clock */
		} else {
			TIMER = data << 8;  /* x  256 clock */
		}
		return;
	/*========== Bank,etc ==========*/
	case 0x2026:
		/* [0x2026] = bbbtldtn
		 *            |||||||+-- NMI enable
		 *            ||||||+--- Timer IRQ enable
		 *   +-++-----+++||+---- Sound DMA IRQ enable
		 *   | ||        |+----- LCD on (※未対応)
		 *   | ||        +------ Timer mode
		 *   | ||
		 *   b bb000000 00000000 ... ROM bank address
		 */
		//ROM_BANK = &rom[(data & 0xe0) << 9] - 0x8000/*ROM window base address*/;
		//↓ハードウェア仕様上は8bank=128KBまでのカートリッジを扱えますが、
		//↓実際には2bank=32KBと4bank=64KBのカートリッジしか存在しません。
		//↓簡単のために、[0x2026]の最上位ビットは0固定と考えて処理します。
		ROM_BANK = &rom[(data & 0x60) << 9] - 0x8000/*ROM window base address*/;
		return;
	}
}

/****************************************************************************
 *	
 ****************************************************************************/

int
w65c02_read(int addr)
{
	       if(addr < 0x2000) {	/* $0000-$1FFF: RAM */
		/* FALLTHRU */
	} else if(addr < 0x4000) {	/* $2000-$3FFF: Register */
		return reg_read(addr);
	} else if(addr < 0x8000) {	/* $4000-$5FFF: VRAM, ($6000-$7FFFはR/W不可ですが、簡単のためにVRAMに含めてしまいます) */
		/* FALLTHRU */
	} else if(addr < 0xC000) {	/* $8000-$BFFF: ROM bank */
		return ROM_BANK[addr];
	} else {			/* $C000-$FFFF: ROM $C000-$FFFF fix */
		return rom[addr];
	}
	return ram[addr];
}
void
w65c02_write(int addr, int data)
{
	       if(addr < 0x2000) {	/* $0000-$1FFF: RAM */
		/* FALLTHRU */
	} else if(addr < 0x4000) {	/* $2000-$3FFF: Register */
		reg_write(addr, data);
		return;
	} else if(addr < 0x8000) {	/* $4000-$5FFF: VRAM ($6000-$7FFFはR/W不可ですが、簡単のためにVRAMに含めてしまいます)
		/* FALLTHRU */
	} else {			/* $6000-$FFFF: ROM */
		/** no job **/
		return;
	}
	ram[addr] = data;
}
/////////////////////////////////////////////////////////////////////////////
int
main(int argc, char* argv[])
{
	int line;
	int irq;
	int t0;
	int t1;
	HDC hDC;
	RECT rc;

	InitUI();
	InitROM();
	InitSnd();

	svvideo_reset(ram);
	svsound_reset(ram, rom);
	w65c02_reset();

	t0 = GetTickCount();
	hDC = GetDC(MainWnd);
	GetClientRect(MainWnd, &rc);
	for(;;) {
		if(!DoMsg()) {
			break;
		}
		ram[0x2020] = INPUT;

		//-----------------------------------------------------------
		line = 160;
		do {
			w65c02_run(SUPERVISION_CLOCK/60/160);
			if(TIMER) {
				TIMER -= SUPERVISION_CLOCK/60/160;
				if(TIMER <= 0) {
					TIMER = 0;
					if(ram[0x2026] & (1<<1)) {	/* Timer IRQ enable? */
						ram[0x2027] |= (1<<0);	/* Timer IRQ */
					}
				}
			}
			/* IRQを判定します。
			 * マスク(0x2026)と要因(0x2027)は1bitズレていることに注意してください。
			 */
			irq = ram[0x2027/*D0=Timer,D1=AudioDMA*/] & 3;
			if(irq) { /* 高速化のため、まず大まかに判定 */
				irq &= ram[0x2026/*D1=Timer,D2=AudioDMA*/] >> 1;
				if(irq) {
					w65c02_irq();
				}
			}
		} while(--line);
		if(ram[0x2026] & 1) { /* NMI enable? */
			w65c02_nmi();
		}
		//-----------------------------------------------------------

		svvideo_update(xbuff);
#ifdef REDUCE
		svvideo_reduce(xbuff, xbuff);
#endif /*REDUCE*/
		StretchDIBits(hDC,
			rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top,
			0, 0, pBMI->bmiHeader.biWidth, -pBMI->bmiHeader.biHeight,
			xbuff,
			pBMI,
			DIB_RGB_COLORS,
			SRCCOPY);

		for(;;) {
			t1 = GetTickCount();
			if(t1 - t0 >= 1000 / 60) break;
			Sleep(0);
		}
		t0 += 1000 / 60;
	}
	ReleaseDC(MainWnd, hDC);
}

