#include <clip.h>

#define ROM(fname)	"C:\\Home\\Emulator\\colecovision\\roms\\" fname
//#define BIOS		ROM("ColecoVision BIOS (1982).col")
#define BIOS		ROM("ColecoVision BIOS (1982) [h1] (no title delay).col")
//#define GAME		ROM("Mr. Do's Castle (1983) (Parker Bros).col")
#define GAME		ROM("Donkey Kong (1982).col")
//#define GAME		ROM("Alcazar - The Forgotten Fortress (1985) (Activision).col")
//#define GAME		ROM("Boulder Dash (1984) (Micro Fun).col")

/////////////////////////////////////////////////////////////////////////////
#define APPNAME	"COLWIN"
#define WIDTH	256
#define HEIGHT	192
#define SCALE	2
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_KEYDOWN:
		switch(wParam) {
		case VK_F9:
			DestroyWindow(hWnd);
			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
Init()
{
	int i;
	BITMAPINFOHEADER* bi;
	RGBQUAD* rgb;
	HINSTANCE hInst;
	WNDCLASS wc;
	RECT rc;

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

	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);
	//
	SetRect(&rc, 0, 0, WIDTH * SCALE, HEIGHT * SCALE);
	AdjustWindowRect(&rc, WS_OVERLAPPED, 0);
	//
	MainWnd = CreateWindow(APPNAME, APPNAME,
		WS_OVERLAPPED,
		0, 0, rc.right - rc.left, rc.bottom - rc.top,
		NULL, NULL, hInst, NULL);
	ShowWindow(MainWnd, SW_SHOW);
	UpdateWindow(MainWnd);
}
/////////////////////////////////////////////////////////////////////////////

unsigned char xbuff[WIDTH * HEIGHT];

typedef struct _DRIVER {
	Z80 cpu;
	unsigned char mem[0x10000];
	//
	TMS9918 vdp;
} DRIVER;
DRIVER _driver;
DRIVER* driver = &_driver;

int joymode;

unsigned char
z80_in(Z80* z80, unsigned short addr)
{
	int data;

	addr &= 0xff;
	switch(addr & 0xe0) {
	//case 0x40: /* プリンタステータス (ADAM専用) */
	case 0xa0: /* TMS9918A */
		return tms9918_read(&driver->vdp, addr & 1);
	case 0xe0: /* キーパッド、ジョイスティック (0xe0=Player1, 0xe1=Player2) */
		if(addr & 1) break;

#define JOYPAD_UP	(~(1<<0))
#define JOYPAD_RI	(~(1<<1))
#define JOYPAD_DN	(~(1<<2))
#define JOYPAD_LF	(~(1<<3))
#define JOYPAD_FIRE2	(~(1<<6))
#define KEYPAD_0	0xfa
#define KEYPAD_1	0xfd
#define KEYPAD_2	0xf7
#define KEYPAD_3	0xfc
#define KEYPAD_4	0xf2
#define KEYPAD_5	0xf3
#define KEYPAD_6	0xfe
#define KEYPAD_7	0xf5
#define KEYPAD_8	0xf1
#define KEYPAD_9	0xfb
//#define KEYPAD_STER	0xf6
//#define KEYPAD_POUND	0xf9
//↓ColEm(The Portable ColecoVision Emulator)やVColeco(The Virtual ColecoVision)は、
//↓'*'と'#'が逆のような気がします。MEKA(Multi machine emulator)は正しいみたいです。
#define KEYPAD_STER	0xf9
#define KEYPAD_POUND	0xf6
#define JOYPAD_FIRE1	(~(1<<6))

		data = 0xff;
		if(joymode) { /* ジョイスティック */
			/* bit0: Up    (0=On,1=Off)
			 * bit1: Right (0=On,1=Off)
			 * bit2: Down  (0=On,1=Off)
			 * bit3: Left  (0=On,1=Off)
			 * bit6: Fire2 (0=On,1=Off)
			 */
			if(GetAsyncKeyState(VK_UP   ) & 0x8000) data &= JOYPAD_UP;
			if(GetAsyncKeyState(VK_RIGHT) & 0x8000) data &= JOYPAD_RI;
			if(GetAsyncKeyState(VK_DOWN ) & 0x8000) data &= JOYPAD_DN;
			if(GetAsyncKeyState(VK_LEFT ) & 0x8000) data &= JOYPAD_LF;
			if(GetAsyncKeyState('X'     ) & 0x8000) data &= JOYPAD_FIRE2;

		} else { /* キーパッド */
			/* bit3-0: 15=Off
			 * +-----+  0='0'  6='6'
			 * |1 2 3|  1='1'  7='7'
			 * |4 5 6|  2='2'  8='8'
			 * |7 8 9|  3='3'  9='9'
			 * |* 0 #|  4='4' 10='*'
			 * +-----+  5='5' 11='#'
			 */
			if(GetAsyncKeyState('0') & 0x8000) data = KEYPAD_0;
			if(GetAsyncKeyState('1') & 0x8000) data = KEYPAD_1;
			if(GetAsyncKeyState('2') & 0x8000) data = KEYPAD_2;
			if(GetAsyncKeyState('3') & 0x8000) data = KEYPAD_3;
			if(GetAsyncKeyState('4') & 0x8000) data = KEYPAD_4;
			if(GetAsyncKeyState('5') & 0x8000) data = KEYPAD_5;
			if(GetAsyncKeyState('6') & 0x8000) data = KEYPAD_6;
			if(GetAsyncKeyState('7') & 0x8000) data = KEYPAD_7;
			if(GetAsyncKeyState('8') & 0x8000) data = KEYPAD_8;
			if(GetAsyncKeyState('9') & 0x8000) data = KEYPAD_9;
			if(GetAsyncKeyState('A') & 0x8000) data = KEYPAD_STER; /*'*'*/
			if(GetAsyncKeyState('S') & 0x8000) data = KEYPAD_POUND; /*'#'*/
			if(GetAsyncKeyState('Z') & 0x8000) data &= JOYPAD_FIRE1;
		}
		return data;
	}

	return 0xff;
}

void
z80_out(Z80* z80, unsigned short addr, unsigned char data)
{
	addr &= 0xff;
	switch(addr & 0xe0) {
	//case 0x40: /* プリンタ出力 (ADAM専用) */
	case 0x80: /* キーパッド読み込み選択 */
		joymode = 0;
		break;
	case 0xa0: /* TMS9918A (0xa0=VRAM, 0xa1=Register) */
		tms9918_write(&driver->vdp, addr & 1, data);
		break;
	case 0xc0: /* ジョイスティック読み込み選択 */
		joymode = 1;
		break;
	//case 0xe0: /* SN76489 */
	}
}

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

	Init();

	memset(driver->mem, -1, 0x10000);

	fp = fopen(BIOS, "rb");
	if(!fp) DIE();
	fread(&driver->mem[0x0000], 1, 0x2000, fp); /* just 8KB */
	fclose(fp);

	fname = argv[1] ? argv[1] : GAME;
	fp = fopen(fname, "rb");
	fread(&driver->mem[0x8000], 1, 0x8000, fp); /* max 32KB */
	fclose(fp);

	tms9918_reset(&driver->vdp);
	z80_reset(&driver->cpu, NULL, NULL, z80_in, z80_out);

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

		z80_run(&driver->cpu, NTSC_COLOR_SUBCARRIER / 60);
		if(tms9918_update(&driver->vdp, xbuff) & 0x80) {
			z80_nmi(&driver->cpu);
		}
#ifdef TMS9918_GRAYSCALE
		tms9918_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);
}

