#include <stdlib.h>
#include <windows.h>
#include <commctrl.h>
#include <commdlg.h>
#include <aygshell.h>
#include <sipapi.h>
#include <afx.h>

#include "resource.h"

extern "C" {

#include "Version"
#include "defs.h"
#include "input.h"

int MAIN(int argc, char *argv[]);
void die(char *fmt, ...);
int rc_setvar(char *name, int c, char **v);
int rc_getint(char *name);
void vid_setscreenmode(int mode);
void vid_setframeskip(int skip);
void vid_suspend();
void vid_resume();
int joy_process(MSG *msg);

#define STR_LEN 100
#define DOTDIR "\\My Documents\\gnuboy"
#define MENU_HEIGHT 26

static int paused = 0;

static HINSTANCE inst = NULL;
static HWND wnd = NULL;
static HWND cbar = NULL;
static HACCEL accel = NULL;

/*
 * standard C functions missing in Win CE
 */

time_t time(time_t* t)
{
	CTime ctime = CTime::GetCurrentTime();
	return ctime.GetTime();
}

char *strdup(const char *src)
{
	return _strdup(src);
}

int strcasecmp(const char *a, const char *b)
{
	return _stricmp(a, b);
}

static int _vprintf(const char *fmt, va_list ap)
{
	char buf[STR_LEN];
	wchar_t buf2[STR_LEN];
	int len;
	
	len = vsprintf(buf, fmt, ap);
	mbstowcs(buf2, buf, len + 1);
	OutputDebugString(buf2);

	return len;
}

int printf(const char *fmt, ...)
{
	va_list ap;
	int len;
	
	va_start(ap, fmt);
	len = _vprintf(fmt, ap);
	va_end(ap);

	return len;
}

/*
 * emulator code
 */

static void checksaveslot(int slot)
{
	HMENU menu;

	menu = (HMENU) SendMessage(cbar, SHCMBM_GETSUBMENU, 0, IDM_FILE);
	CheckMenuRadioItem(menu, IDM_SAVE_SLOT_0, IDM_SAVE_SLOT_9,
					   IDM_SAVE_SLOT_0 + slot, MF_BYCOMMAND);
}

static void checkscreenmode(int mode)
{
	HMENU menu;

	menu = (HMENU) SendMessage(cbar, SHCMBM_GETSUBMENU, 0, IDM_TOOLS);
	CheckMenuRadioItem(menu, IDM_SCREEN_MODE_160X144,
					   IDM_SCREEN_MODE_240X216LA,
					   IDM_SCREEN_MODE_160X144 + mode, MF_BYCOMMAND);
}

static void checkframeskip(int frameskip)
{
	HMENU menu;

	menu = (HMENU) SendMessage(cbar, SHCMBM_GETSUBMENU, 0, IDM_TOOLS);
	CheckMenuRadioItem(menu, IDM_FRAME_RATE_60, IDM_FRAME_RATE_10,
					   IDM_FRAME_RATE_60 + frameskip, MF_BYCOMMAND);
}

void *sys_timer()
{
	SYSTEMTIME st;
	ULARGE_INTEGER *tm;

#if 0
	GetSystemTime(&st);
	tm = (ULARGE_INTEGER *) malloc(sizeof(ULARGE_INTEGER));
	SystemTimeToFileTime(&st, (FILETIME *) tm);
#else
	tm = NULL;
#endif

	return tm;
}

int sys_elapsed(void *prev)
{
	SYSTEMTIME st;
	ULARGE_INTEGER tm, *pr;
	int usecs;

#if 0
	GetSystemTime(&st);
	SystemTimeToFileTime(&st, (FILETIME *) &tm);
	pr = (ULARGE_INTEGER *) prev;
	usecs = (int) (tm.QuadPart - pr->QuadPart) / 10;
	*pr = tm;
#else
	usecs = 0;
#endif

	return usecs;
}

static void processmessage(MSG *msg)
{
	if (!TranslateAccelerator(msg->hwnd, accel, msg)) {
		TranslateMessage(msg);
		DispatchMessage(msg);
	}
}

static void pause(MSG *bdownmsg)
{
	RECT rt;
	MSG msg;
	int cmd;
	int key;
	int ret;
	int arg;
	event_t ev;

	vid_suspend();

	paused = 1;
	GetClientRect(wnd, &rt);
	InvalidateRect(wnd, &rt, 1);
	UpdateWindow(wnd);

	checksaveslot(rc_getint("saveslot"));
	checkscreenmode(rc_getint("screenmode"));
	checkframeskip(rc_getint("frameskip"));
	processmessage(bdownmsg);

	while (GetMessage(&msg, NULL, 0, 0)) {
		processmessage(&msg);

		if (msg.message == WM_COMMAND) {
			cmd = LOWORD(msg.wParam);
			key = 0;
			ret = 0;
			switch (cmd) {
			case IDM_SAVE_SLOT_0:
			case IDM_SAVE_SLOT_1:
			case IDM_SAVE_SLOT_2:
			case IDM_SAVE_SLOT_3:
			case IDM_SAVE_SLOT_4:
			case IDM_SAVE_SLOT_5:
			case IDM_SAVE_SLOT_6:
			case IDM_SAVE_SLOT_7:
			case IDM_SAVE_SLOT_8:
			case IDM_SAVE_SLOT_9:
				arg = cmd - IDM_SAVE_SLOT_0;
				key = '0' + arg;
				checksaveslot(arg);
				break;
			case IDM_SAVE:
				key = K_INS;
				ret = 1;
				break;
			case IDM_LOAD:
				key = K_DEL;
				ret = 1;
				break;
			case IDM_RESUME:
				ret = 1;
				break;
			case IDM_EXIT:
				key = K_ESC;
				ret = 1;
				break;
			case IDM_SCREEN_MODE_160X144:
			case IDM_SCREEN_MODE_160X144L:
			case IDM_SCREEN_MODE_240X216:
			case IDM_SCREEN_MODE_240X216L:
			case IDM_SCREEN_MODE_240X216A:
			case IDM_SCREEN_MODE_240X216LA:
				arg = cmd - IDM_SCREEN_MODE_160X144;
				checkscreenmode(arg);
				vid_setscreenmode(arg);
				break;
			case IDM_FRAME_RATE_60:
			case IDM_FRAME_RATE_30:
			case IDM_FRAME_RATE_20:
			case IDM_FRAME_RATE_15:
			case IDM_FRAME_RATE_12:
			case IDM_FRAME_RATE_10:
				arg = cmd - IDM_FRAME_RATE_60;
				checkframeskip(arg);
				vid_setframeskip(arg);
				break;
			}
			if (key) {
				/* this must be impolite... */
				ev.type = EV_PRESS;
				ev.code = key;
				ev_postevent(&ev);
				ev.type = EV_RELEASE;
				ev_postevent(&ev);
			}
			if (ret) {
				vid_resume();
				break;
			}
		}
	}

	paused = 0;
	GetClientRect(wnd, &rt);
	InvalidateRect(wnd, &rt, 1);
	UpdateWindow(wnd);
}

static void suspend(MSG *killfocusmsg)
{
	MSG msg;

	vid_suspend();
	processmessage(killfocusmsg);

	while (GetMessage(&msg, NULL, 0, 0)) {
		processmessage(&msg);

		if (msg.message == WM_SETFOCUS) {
			vid_resume();
			break;
		}
	}
}

void sys_sleep(int us)
{
	int ms;
	MSG msg;

	/* ignore sleep duration us */

	while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
		if (joy_process(&msg))
			continue;

		switch (msg.message) {
		case WM_PAINT:
			processmessage(&msg);
			break;
		case WM_LBUTTONDOWN:
			pause(&msg);
			break;
		case WM_KILLFOCUS:
			suspend(&msg);
			break;
		default:
			/* dispose message; is it OK? */
			break;
		}
	}
}

void sys_checkdir(char *path)
{
	wchar_t path2[MAX_PATH];
	DWORD attr;
	char *p;
	
	mbstowcs(path2, path, strlen(path) + 1);
	attr = GetFileAttributes(path2);
    if (attr == 0xFFFFFFFF) {
		p = strrchr(path, '\\');
		if (!p) die("descended to root trying to create dirs\n");
		*p = 0;
		sys_checkdir(path);
		*p = '\\';
		if (!CreateDirectory(path2, NULL))
			die("cannot create %s: %lu\n", path, GetLastError());
	}
}

void sys_initpath(char *exe)
{
	char *var[1];
	char *rcpath;
	char *exepath;
	char *end;
	int len;

	exepath = (char *) malloc(sizeof(char) * strlen(exe));
	if ((end = strrchr(exe, '\\')) == NULL)
		rcpath = DOTDIR;
	else {
		len = end - exe;
		strncpy(exepath, exe, len);
		exepath[len] = '\0';
		rcpath = (char *)
			malloc(sizeof(char) * (strlen(DOTDIR) + len + 2));
		sprintf(rcpath, "%s;" DOTDIR, exepath);
	}
	var[0] = rcpath;
	rc_setvar("rcpath", 1, var);

	var[0] = DOTDIR;
	rc_setvar("savedir", 1, var);
}

void sys_sanitize(char *s)
{
}

#if 0
void die(char *fmt, ...)
{
	va_list ap;

	va_start(ap, fmt);
	_vprintf(fmt, ap);
	va_end(ap);

	exit(1);
}
#endif

/*
 * initialization
 */

static LRESULT CALLBACK aboutbox(HWND dlg, UINT message,
								 WPARAM wpar, LPARAM lpar)
{
	SHINITDLGINFO info;
	char version[STR_LEN];
	wchar_t version2[STR_LEN];

	switch (message) {
	case WM_INITDIALOG:
		sprintf(version, "Version %s", VERSION);
		mbstowcs(version2, version, strlen(version) + 1);
		SetWindowText(GetDlgItem(dlg, IDC_VERSION), version2);
		info.dwMask = SHIDIM_FLAGS;
		info.dwFlags = SHIDIF_DONEBUTTON | SHIDIF_SIPDOWN |
			SHIDIF_SIZEDLGFULLSCREEN;
		info.hDlg = dlg;
		SHInitDialog(&info);
		return 1; 
	case WM_COMMAND:
		if (LOWORD(wpar) == IDOK) {
			EndDialog(dlg, LOWORD(wpar));
			return 1;
		}
		break;
	}

    return 0;
}

static HWND createcommandbar(HWND wnd)
{
	SHMENUBARINFO info;

	memset(&info, 0, sizeof(SHMENUBARINFO));
	info.cbSize = sizeof(SHMENUBARINFO);
	info.hwndParent = wnd;
	info.dwFlags = SHCMBF_HIDESIPBUTTON;
	info.nToolBarId = IDM_MENU;
	info.hInstRes = inst;
	info.nBmpId = 0;
	info.cBmpImages = 0;

	if (!SHCreateMenuBar(&info)) 
		return NULL;

	return info.hwndMB;
}

static LRESULT CALLBACK windowproc(HWND wnd, UINT message,
								   WPARAM wpar, LPARAM lpar)
{
	HDC hdc;
	PAINTSTRUCT ps;
	RECT rect;
	SHACTIVATEINFO actinfo;

	switch (message) {
	case WM_COMMAND:
		switch (LOWORD(wpar)) {	
		case IDM_ABOUT:
			DialogBox(inst, (LPCTSTR) IDD_ABOUTBOX, wnd, (DLGPROC) aboutbox);
			break;
		case IDOK:
			SendMessage(wnd, WM_ACTIVATE, MAKEWPARAM(WA_INACTIVE, 0),
						(LPARAM) wnd);
			SendMessage(wnd, WM_CLOSE, 0, 0);
			break;
		default:
			return DefWindowProc(wnd, message, wpar, lpar);
		}
		break;
	case WM_CREATE:
		cbar = createcommandbar(wnd);
		break;
	case WM_PAINT: 
		hdc = BeginPaint(wnd, &ps);
		if (paused) {
			wchar_t str[STR_LEN];
			LoadString(inst, IDS_PAUSE, str, STR_LEN);
			SetBkColor(hdc, RGB(0, 0, 0));
			SetTextColor(hdc, RGB(255, 255, 255));
			GetClientRect(wnd, &rect);
			DrawText(hdc, str, _tcslen(str), &rect, 
					 DT_SINGLELINE | DT_VCENTER | DT_CENTER);
		}
		EndPaint(wnd, &ps);
		break; 
	case WM_DESTROY:
		CommandBar_Destroy(cbar);
		PostQuitMessage(0);
		break;
	case WM_SETTINGCHANGE:
		SHHandleWMSettingChange(wnd, wpar, lpar, &actinfo);
		break;
	default:
		return DefWindowProc(wnd, message, wpar, lpar);
   }

   return 0;
}

static ATOM registerclass(HINSTANCE inst, LPTSTR szWindowClass)
{
	WNDCLASS wclass;

    wclass.style = CS_HREDRAW | CS_VREDRAW;
    wclass.lpfnWndProc = (WNDPROC) windowproc;
    wclass.cbClsExtra = 0;
    wclass.cbWndExtra = 0;
    wclass.hInstance = inst;
    wclass.hIcon = LoadIcon(inst, MAKEINTRESOURCE(IDI_GNUBOY));
    wclass.hCursor = 0;
    wclass.hbrBackground = (HBRUSH) GetStockObject(BLACK_BRUSH);
    wclass.lpszMenuName = 0;
    wclass.lpszClassName = szWindowClass;

	return RegisterClass(&wclass);
}

static int initinstance(HINSTANCE inst1, int show)
{
	wchar_t	title[STR_LEN];
	wchar_t	wclass[STR_LEN];
	RECT rect;

	inst = inst1;
	LoadString(inst, IDC_GNUBOY, wclass, STR_LEN);
	LoadString(inst, IDS_APP_TITLE, title, STR_LEN);

	if (wnd = FindWindow(wclass, title)) {
		SetForegroundWindow((HWND) (((DWORD) wnd) | 0x01));    
		return 0;
	} 

	registerclass(inst, wclass);
	if (!(wnd = CreateWindow(wclass, title, WS_VISIBLE,
							 CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
							 CW_USEDEFAULT, NULL, NULL, inst, NULL)))
		return 0;

	GetWindowRect(wnd, &rect);
	rect.bottom -= MENU_HEIGHT;
	if (cbar)
		MoveWindow(wnd, rect.left, rect.top, rect.right, rect.bottom, 0);

	ShowWindow(wnd, show);
	UpdateWindow(wnd);

	return 1;
}

static char *getromname()
{
	OPENFILENAME openname;
	wchar_t name2[MAX_PATH];
	char *name;
	int len;
	
	memset(&openname, 0, sizeof(openname));
	openname.lStructSize	= sizeof(openname);
	openname.hwndOwner = wnd;
	openname.lpstrTitle = TEXT("gnuboy CE");
	openname.lpstrFilter =
		TEXT("GB Roms (*.gb;*.gbc)\0*.gb;*.gbc\0All Files (*.*)\0*.*\0");	
	name2[0] = '\0';
	openname.lpstrFile = name2;
	openname.nMaxFile = MAX_PATH;	
	openname.Flags = OFN_EXPLORER;

	if (!GetOpenFileName(&openname))
		return NULL;

	len = wcslen(openname.lpstrFile);
	name = (char *) malloc(sizeof(char) * (len + 1));
	wcstombs(name, openname.lpstrFile, len + 1);

	return name;
}

int WINAPI WinMain(HINSTANCE inst, HINSTANCE previnst,
				   LPTSTR cmdline, int show)
{
	MSG msg;
	int argc;
	char **argv;

	if (!initinstance(inst, show))
		return 0;
	accel = LoadAccelerators(inst, (LPCTSTR)IDC_GNUBOY);

	argc = 2;
	argv = (char **) malloc(sizeof(char *) * 2);
	argv[0] = "gnuboy.exe";
	if ((argv[1] = getromname()) == NULL)
		return 0;

	SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL);

	MAIN(argc, argv);

	return 0;
}

}
