#include <clip.h>

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

#define CLOCK	(NTSC_COLOR_SUBCARRIER / 2)

#define DIR	"C:\\Home\\Emulator\\nes\\roms\\"
//#define DIR	"C:\\Documents and Settings\\Administrator\\デスクトップ\\"
//#define FNAME	"Donkey Kong (JU).nes"
//#define FNAME	"Mario Bros (JU).nes"
//#define FNAME	"Popeye (JU).nes"
//#define FNAME	"Balloon Fight (JU).nes"
//#define FNAME	"Challenger (J).nes"		//スクロールダメ
//#define FNAME	"Pac-Man (J).nes"		//全然ダメ(CPU?)
//#define FNAME	"Mappy (J).nes"			//全然ダメ(CPU?)
//#define FNAME	"Bomber Man (J).nes"		//爆発時にハングアップ(CPU?)
//#define FNAME	"Super Mario Bros (J).nes"	//スクロールダメ、背景色おかしい
//#define FNAME	"Galaxian (J).nes"		//スクロールダメ
//#define FNAME	"Ice Climber (JE).nes"
//#define FNAME	"Clu Clu Land (JU).nes"
//#define FNAME	"Tower_of_Druaga,_The_(J).nes"
//#define FNAME	"1942 (JU).nes"			// とても遅い...
#define FNAME	"Xevious (J).nes"
//#define FNAME	"Exed Exes (J).nes"
//#define FNAME	"Yie Ar Kung-Fu (J).nes"
//#define FNAME	"Gradius_(J).nes"

/////////////////////////////////////////////////////////////////////////////
#define APPNAME	"FC4WIN"
#define WIDTH	128
#define HEIGHT	120
#define SCALE	4
M6502 cpu;
unsigned char mem[0x10000];
unsigned char xbuff[WIDTH * HEIGHT];
/////////////////////////////////////////////////////////////////////////////

#define NWHDR		8	/* これ以上増やすとwaveOutWriteでエラーになるみたい */
HWAVEOUT g_wout;
WAVEFORMATEX g_fmt;
WAVEHDR g_whdr[NWHDR];
short g_wbuff[NWHDR][APUBUFLEN];

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

	apu_reset(mem);

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

/////////////////////////////////////////////////////////////////////////////

/*  0:A    1:B    2:SEL  3:ST   4:UP  5:DN  6:LF  7:RI
 *  8:?    9:?   10:?   11:?   12:?  13:?  14:?  15:?
 * 16:Sig 17:Sig 18:Sig 19:Sig 20:0  21:0  22:0  23:0
 */
int input = 1 << 16;
int input_shift;

/////////////////////////////////////////////////////////////////////////////
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 'X':	input |= 1 << 0; return 0;
		case 'Z':	input |= 1 << 1; return 0;
		case VK_BACK:	input |= 1 << 2; return 0;
		case VK_RETURN:	input |= 1 << 3; return 0;
		case VK_UP:	input |= 1 << 4; return 0;
		case VK_DOWN:	input |= 1 << 5; return 0;
		case VK_LEFT:	input |= 1 << 6; return 0;
		case VK_RIGHT:	input |= 1 << 7; return 0;
		}
		return 0;
	case WM_KEYUP:
		switch(wParam) {
		case 'X':	input &= ~(1 << 0); return 0;
		case 'Z':	input &= ~(1 << 1); return 0;
		case VK_BACK:	input &= ~(1 << 2); return 0;
		case VK_RETURN:	input &= ~(1 << 3); return 0;
		case VK_UP:	input &= ~(1 << 4); return 0;
		case VK_DOWN:	input &= ~(1 << 5); return 0;
		case VK_LEFT:	input &= ~(1 << 6); return 0;
		case VK_RIGHT:	input &= ~(1 << 7); return 0;
		}
		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 PPU_GRAYSCALE
	bi->biWidth = 128;
	bi->biHeight = -88;
#else /*PPU_GRAYSCALE*/
	bi->biWidth = WIDTH;
	bi->biHeight = -HEIGHT;
#endif /*PPU_GRAYSCALE*/
	bi->biPlanes = 1;
	bi->biBitCount = 8;
#ifdef PPU_GRAYSCALE
	for(i = 0; i < 16; i++) {
		rgb[i].rgbRed   = 255 - (255 * i / 15);
		rgb[i].rgbGreen = 255 - (255 * i / 15);
		rgb[i].rgbBlue  = 255 - (255 * i / 15);
	}
#else /*PPU_GRAYSCALE*/
	for(i = 0; i < 64; i++) {
		rgb[i].rgbRed   = ppu_color_table[i][0];
		rgb[i].rgbGreen = ppu_color_table[i][1];
		rgb[i].rgbBlue  = ppu_color_table[i][2];
	}
#endif /*PPU_GRAYSCALE*/

	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;
	INESHEADER hdr;

	memset(mem, 0, 0x10000);

	fp = fopen(DIR FNAME, "rb");
	if(!fp) {
		DIE();
	}

	fread(&hdr, sizeof hdr, 1, fp);
	if(memcmp(hdr.nes, "NES", 3) != 0) {
		DIE();
	}
	ppu_reset(hdr.rom_control1 & 1);

	fread(&mem[0x8000], 1, 0x4000 * hdr.prgrom_page_count, fp);
	if(hdr.prgrom_page_count == 1) {
		memcpy(&mem[0xc000], &mem[0x8000], 0x4000);
	}

	fread(ppu_vram, 1, 0x2000, fp);
}
/////////////////////////////////////////////////////////////////////////////
unsigned char
mem_read(M6502* cpu, unsigned short addr)
{
	int data;
	if(addr >= 0x2000 && addr < 0x8000) {
		if(addr >= 0x2000 && addr <= 0x2007) {
			return ppu_read(addr);
		}
		if(addr >= 0x4000 && addr <= 0x4017) {
			switch(addr & 0x1f) {
			case 0x15: /* $4015: APU */
				EnterCriticalSection(&snd_cs);
				data = apu_read();
				LeaveCriticalSection(&snd_cs);
				return data;
			case 0x16: /* $4016: Input */
				input_shift >>= 1;
				return input_shift & 1;
			}
			/* FALLTHRU */
		}
	}
	return mem[addr];
}

void
mem_write(M6502* cpu, unsigned short addr, unsigned char data)
{
	if(addr >= 0x2000) {
		if(addr >= 0x2000 && addr <= 0x2007) {
			ppu_write(addr, data);
			return;
		}
		if(addr >= 0x4000 && addr <= 0x4017) {
			switch(addr & 0x1f) {
			case 0x14: /* $4014: Sprite DMA */
				memcpy(ppu_sprram, &mem[data << 8], 0x100);
				return;
			case 0x16: /* $4016: Input Strobe */
				input_shift = input << 1;
				return;
			default:   /* $40xx: APU */
				EnterCriticalSection(&snd_cs);
				apu_write(addr, data);
				LeaveCriticalSection(&snd_cs);
				break;
			}
		}
	}
	mem[addr] = data;
}

int
main(int argc, char* argv[])
{
	int t0;
	int t1;
	int cycle;
	RECT rc;
	HDC hDC;

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

	m6502_reset(&cpu, mem_read, mem_write);

	t0 = GetTickCount();
	hDC = GetDC(MainWnd);
	GetClientRect(MainWnd, &rc);
	for(;;) {
		if(!DoMsg()) {
			break;
		}

		do {
			cycle = ppu4_update(xbuff);
			if(ppu.flags & PPU_FLAGS_INTERRUPT) {
				ppu.flags &= ~PPU_FLAGS_INTERRUPT;
				m6502_nmi(&cpu);
			}
			m6502_run(&cpu, cycle / 2);
		} while(ppu.scan_end);
		if(apu.irq) { /* 参照だけなので排他制御不要 */
			m6502_int(&cpu);
		}

#ifdef PPU_GRAYSCALE
		ppu4_reduce(xbuff, xbuff);
#endif
		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);
}

