#include <clip.h>

/////////////////////////////////////////////////////////////////////////////
#define DIR	"C:\\Home\\Emulator\\atari2600\\roms\\"
//#define FNAME	"Space Invaders (1978) (Atari) [!].a26"
//#define FNAME	"Pitfall! (1982) (Activision) [!].a26"
#define FNAME	"Spider Fighter (1983) (Activision) [!].a26"

/////////////////////////////////////////////////////////////////////////////
#define APPNAME	"VCSWIN"
#define WIDTH	160
#define HEIGHT	192
#define SCALE	1
M6502 m6502cpu;
unsigned char m6502mem[0x10000];
unsigned char xbuff[WIDTH * HEIGHT];
/////////////////////////////////////////////////////////////////////////////
#define NWHDR		8	/* これ以上増やすとwaveOutWriteでエラーになるみたい */
HWAVEOUT g_wout;
WAVEFORMATEX g_fmt;
WAVEHDR g_whdr[NWHDR];
short g_wbuff[NWHDR][TIABUFLEN];
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);
	tia_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);
	}
}
/////////////////////////////////////////////////////////////////////////////
unsigned NOW;
unsigned CYCLE;
/////////////////////////////////////////////////////////////////////////////
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:
		return 0;
	case WM_KEYUP:
		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 TIA_GRAYSCALE
	bi->biWidth = 128;
	bi->biHeight = -88;
#else /*TIA_GRAYSCALE*/
	bi->biWidth = WIDTH;
	bi->biHeight = -HEIGHT;
#endif /*TIA_GRAYSCALE*/
	bi->biPlanes = 1;
	bi->biBitCount = 8;
#ifdef TIA_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 /*TIA_GRAYSCALE*/
	for(i = 0; i < 16 * 8; i++) {
		rgb[i].rgbRed   = tia_color_table[i][0];
		rgb[i].rgbGreen = tia_color_table[i][1];
		rgb[i].rgbBlue  = tia_color_table[i][2];
	}
#endif /*TIA_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);
#ifdef TIA_GRAYSCALE
	MoveWindow(MainWnd, 0, 0,
		ncW + (int)(bi->biWidth  * SCALE * 1.0/*アスペクト比調整*/),
		ncH -       bi->biHeight * SCALE, 0);
#else /*TIA_GRAYSCALE*/
	MoveWindow(MainWnd, 0, 0,
		ncW + (int)(bi->biWidth  * SCALE * 1.5/*アスペクト比調整*/),
		ncH -       bi->biHeight * SCALE, 0);
#endif /*TIA_GRAYSCALE*/
	//
	ShowWindow(MainWnd, SW_SHOW);
	UpdateWindow(MainWnd);
}
/////////////////////////////////////////////////////////////////////////////
void
InitROM()
{
	FILE* fp;

	memset(m6502mem, 0, 0x10000);

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

	fseek(fp, 0, SEEK_END);
	if(ftell(fp) != 0x1000) {
		DIE();
	}
	rewind(fp);

	fread(&m6502mem[0xf000], 1, 0x1000, fp);

	fclose(fp);
}
/////////////////////////////////////////////////////////////////////////////
int
vcs_tia_read(int addr)
{
	NOW += CYCLE - m6502cpu.cycle;
	CYCLE = m6502cpu.cycle;

	return tia_read(NOW, addr);
}
void
vcs_tia_write(int addr, int data)
{
	NOW += CYCLE - m6502cpu.cycle;
	CYCLE = m6502cpu.cycle;

	m6502cpu.cycle -= tia_write(NOW, addr, data);
}
/////////////////////////////////////////////////////////////////////////////
int
vcs_pia_read(int addr)
{
	NOW += CYCLE - m6502cpu.cycle;
	CYCLE = m6502cpu.cycle;

	return pia_read(NOW, addr);
}
void
vcs_pia_write(int addr, int data)
{
	NOW += CYCLE - m6502cpu.cycle;
	CYCLE = m6502cpu.cycle;

	pia_write(NOW, addr, data);
}
/////////////////////////////////////////////////////////////////////////////
unsigned char
vcs_read(M6502* m6502cpu, unsigned short addr)
{
	       if(addr < 0x080) {	/* $0000-$007F: TIA */
		return vcs_tia_read(addr);
	} else if(addr < 0x280) {	/* $0080-$00FF: RAM(PIA内蔵), $0180-$01FF: RAMのミラー */
		/* FALLTHRU */
	} else if(addr < 0x300) {	/* $0200-$02FF: PIA */
		return vcs_pia_read(addr);
	} else {			/* $0300-$F000: TIA,RAM,PIAのミラー, $F000-$FFFF: ROM */
		/** no job **/
	}
	return m6502mem[addr];
}
void
vcs_write(M6502* m6502cpu, unsigned short addr, unsigned char data)
{
	       if(addr < 0x080) {	/* $0000-$007F: TIA */
		vcs_tia_write(addr, data);
		return;
	} else if(addr < 0x280) {	/* $0080-$00FF: RAM(PIA内蔵), $0180-$01FF: RAMのミラー */
		/* FALLTHRU */
	} else if(addr < 0x300) {	/* $0200-$02FF: PIA */
		vcs_pia_write(addr, data);
		return;
	} else {			/* $0300-$F000: TIA,RAM,PIAのミラー, $F000-$FFFF: ROM */
		/** no job **/
	}
	m6502mem[addr] = data;
}
/////////////////////////////////////////////////////////////////////////////
void
input_update()
{
	tia_set_i4_i5(
		(GetAsyncKeyState(VK_SPACE) & 0x8000) ? 0 : 1,
		1);

	pia.SWCHA = 0xff;
	if(GetAsyncKeyState(VK_RIGHT ) & 0x8000) pia.SWCHA &= ~(1<<7);
	if(GetAsyncKeyState(VK_LEFT  ) & 0x8000) pia.SWCHA &= ~(1<<6);
	if(GetAsyncKeyState(VK_DOWN  ) & 0x8000) pia.SWCHA &= ~(1<<5);
	if(GetAsyncKeyState(VK_UP    ) & 0x8000) pia.SWCHA &= ~(1<<4);
	pia.SWCHB = 0x0b;
	if(GetAsyncKeyState(VK_RETURN) & 0x8000) pia.SWCHB &= ~(1<<0);
}

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

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

	NOW = CYCLE = 0;

	tia_reset(NOW, xbuff);
	pia_reset(NOW);
	m6502_reset(&m6502cpu, vcs_read, vcs_write);

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

		input_update();

		CYCLE += VCS_CLOCK / 60;
		m6502_run(&m6502cpu, CYCLE);
		NOW += CYCLE - m6502cpu.cycle;
		CYCLE = m6502cpu.cycle;

#ifdef TIA_GRAYSCALE
		tia_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);
}

