#include <../arch/arch.h>

#ifdef	ARCH_WINDOWING_SUPPORTED

V_LOCAL char_p LIBV_HANDLE_PROP = "LIBV_HANDLE";

/*
 * Translates wParam and lParam into LibV scan code.
 */
V_LOCAL	unsigned
win32_make_scan_code(WPARAM wParam,LPARAM lParam)
{
	u8_t scan = (u8_t)((lParam >> 16) & 0xFF);
	u8_t ext = (u8_t)((lParam & (1 << 24)) != 0);
	switch(wParam)
	{
		case VK_ESCAPE:	return sc_escape;

		case VK_F1: return sc_f1;
		case VK_F2: return sc_f2;
		case VK_F3: return sc_f3;
		case VK_F4: return sc_f4;
		case VK_F5: return sc_f5;
		case VK_F6: return sc_f6;
		case VK_F7: return sc_f7;
		case VK_F8: return sc_f8;
		case VK_F9: return sc_f9;
		case VK_F10: return sc_f10;
		case VK_F11: return sc_f11;
		case VK_F12: return sc_f12;
		case VK_SCROLL: return sc_scroll_lock;

		case 192: return sc_grave;
		case '1': return sc_1;
		case '2': return sc_2;
		case '3': return sc_3;
		case '4': return sc_4;
		case '5': return sc_5;
		case '6': return sc_6;
		case '7': return sc_7;
		case '8': return sc_8;
		case '9': return sc_9;
		case '0': return sc_0;
		case 189: return sc_minus;
		case 187: return sc_equal;
		case 220: return sc_backslash;
		case VK_BACK: return sc_backspace;

		case VK_TAB: return sc_tab;
		case 'Q': return sc_q;
		case 'W': return sc_w;
		case 'E': return sc_e;
		case 'R': return sc_r;
		case 'T': return sc_t;
		case 'Y': return sc_y;
		case 'U': return sc_u;
		case 'I': return sc_i;
		case 'O': return sc_o;
		case 'P': return sc_p;
		case 219: return sc_lbracket;
		case 221: return sc_rbracket;
		case VK_RETURN:	return ext ? sc_kp_enter : sc_enter;

		case VK_CAPITAL: return sc_caps_lock;
		case 'A': return sc_a;
		case 'S': return sc_s;
		case 'D': return sc_d;
		case 'F': return sc_f;
		case 'G': return sc_g;
		case 'H': return sc_h;
		case 'J': return sc_j;
		case 'K': return sc_k;
		case 'L': return sc_l;
		case 186: return sc_semicolon;
		case 222: return sc_apostrophe;

		case 16: return (scan == 42) ? sc_lshift : sc_rshift;
		case 'Z': return sc_z;
		case 'X': return sc_x;
		case 'C': return sc_c;
		case 'V': return sc_v;
		case 'B': return sc_b;
		case 'N': return sc_n;
		case 'M': return sc_m;
		case 188: return sc_comma;
		case 190: return sc_period;
		case 191: return sc_slash;

		case 17: return ext ? sc_rcontrol : sc_lcontrol;
		case VK_LWIN: return sc_lsuper;
		case 18: return ext ? sc_ralt : sc_lalt;
		case VK_SPACE: return sc_space;
		case VK_RWIN: return sc_rsuper;
		case VK_APPS: return sc_menu;
		case VK_NUMLOCK: return sc_num_lock;
		case VK_DIVIDE: return sc_kp_slash;
		case 106: return sc_kp_multiply;
		case 109: return sc_kp_subtract;
		case 107: return sc_kp_add;
		/* case VK_CLEAR: return sc_kp_begin; */
	}
	switch(scan)
	{
		case 71: return ext ? sc_home : sc_kp_home;
		case 72: return ext ? sc_up : sc_kp_up;
		case 73: return ext ? sc_prior : sc_kp_prior;
		case 75: return ext ? sc_left : sc_kp_left;
		case 76: return sc_kp_begin;
		case 77: return ext ? sc_right : sc_kp_right;
		case 79: return ext ? sc_end : sc_kp_end;
		case 80: return ext ? sc_down : sc_kp_down;
		case 81: return ext ? sc_next : sc_kp_next;
		case 82: return ext ? sc_insert : sc_kp_insert;
		case 83: return ext ? sc_delete : sc_kp_delete;
		case 69: return sc_pause;
		case 55: return sc_print;
	}
	return sc_unknown;
}

V_LOCAL void
win32_get_key_modifiers(arch_window_p aw,ui_event_p ev)
{
	BYTE *keys = aw->oem_kbstate;
	GetKeyboardState(keys);
	ev->state = ev->button = 0;
	if(keys[VK_NUMLOCK] & 1)
		ev->state |= UI_NUM_LOCK_MASK;
	if(keys[VK_CAPITAL] & 1)
		ev->state |= UI_CAPS_LOCK_MASK;
	if(keys[VK_SCROLL] & 1)
		ev->state |= UI_SCROLL_LOCK_MASK;
    if(keys[VK_LCONTROL] & 0x80 || keys[VK_RCONTROL] & 0x80)
		ev->state |= UI_CONTROL_MASK;
    if(keys[VK_LSHIFT] & 0x80 || keys[VK_RSHIFT] & 0x80)
		ev->state |= UI_SHIFT_MASK;
	if(keys[18] & 0x80)
		ev->state |= UI_ALT_MASK;
    if(keys[VK_LMENU] & 0x80 || keys[VK_RMENU] & 0x80)
		ev->state |= UI_SUPER_MASK;
    if(keys[VK_LBUTTON] & 0x80)
		ev->button |= UI_LBUTTON_MASK;
    if(keys[VK_MBUTTON] & 0x80)
		ev->button |= UI_MBUTTON_MASK;
    if(keys[VK_RBUTTON] & 0x80)
		ev->button |= UI_RBUTTON_MASK;
}

V_LOCAL void
win32_get_mouse_pos(HWND wnd,POINT *psc,ui_event_p ev)
{
	POINT pcc;
	pcc.x = ev->screen_x = psc->x;
	pcc.y = ev->screen_y = psc->y;
	if(ScreenToClient(wnd,&pcc))
	{
		ev->client_x = pcc.x;
		ev->client_y = pcc.y;
	}
	else
	{
		ev->client_x = psc->x;
		ev->client_y = psc->y;
	}
}

/*
 * Should be called _after_ win32_get_key_modifiers().
 */
V_LOCAL void
win32_get_char_codes(arch_window_p aw,WPARAM wParam,LPARAM lParam,ui_event_p ev)
{
	WORD chars[4];
	WCHAR uchars[4];
	UINT oem_scan = (lParam >> 16) & 0xFF;
	ev->key.acode = ev->key.ucode = 0;
	if(ToAscii(wParam,oem_scan,aw->oem_kbstate,chars,0) == 1)
		ev->key.acode = chars[0] & 0xFF;
	if(ToUnicode(wParam,oem_scan,aw->oem_kbstate,uchars,4,0) == 1)
		ev->key.ucode = uchars[0];
}

#define	SIGNED_LOWORD(src) \
	(((src) & 0x8000) ? LOWORD(src) | 0xFFFF0000 : LOWORD(src))

#define	SIGNED_HIWORD(src) \
	(((src) & 0x80000000) ? HIWORD(src) | 0xFFFF0000 : HIWORD(src))

V_LOCAL void
win32_window_proc(arch_window_p aw,
	UINT msg,WPARAM wParam,LPARAM lParam,DWORD time,POINT *psc)
{
	POINT def_psc;
	RECT rc;
	PAINTSTRUCT ps;
	ui_event_t dst;
	vboolean zap_message = false;
	if(!time)
		time = GetTickCount();
	if(!psc)
	{
		psc = &def_psc;
		GetCursorPos(psc);
	}
	dst.time = ui_make_message_time(&aw->start_time,time);
	dst.type = ui_no_event;
	switch(msg)
	{
		case WM_SHOWWINDOW:
			if(wParam)
			{
				aw->visible = true;
				dst.type = ui_window_map_event;
			}
			else
			{
				aw->visible = false;
				dst.type = ui_window_unmap_event;
			}
			win32_get_mouse_pos(aw->wnd,psc,&dst);
			win32_get_key_modifiers(aw,&dst);
			ui_make_default_key_event(&dst.key);
			ui_set_configure_from_aw(aw,&dst.configure);
			break;

		case WM_SYSCOMMAND:
			switch(wParam)
			{
				case SC_MINIMIZE:
					if(!aw->visible)
						zap_message = true;
					else
					{
						aw->visible = false;
						dst.type = ui_window_unmap_event;
					}
					break;

				case SC_MAXIMIZE:
				case SC_RESTORE:
					if(aw->visible)
						zap_message = true;
					else
					{
						aw->visible = true;
						dst.type = ui_window_map_event;
					}
					break;

				default:
					zap_message = true;
			}
			if(!zap_message)
			{
				win32_get_mouse_pos(aw->wnd,psc,&dst);
				win32_get_key_modifiers(aw,&dst);
				ui_make_default_key_event(&dst.key);
				ui_set_configure_from_aw(aw,&dst.configure);
			}
			break;

		case WM_SETFOCUS:
		case WM_KILLFOCUS:
			if(msg == WM_SETFOCUS)
			{
				aw->focused = true;
				dst.type = ui_window_focus_in_event;
			}
			else
			{
				aw->focused = false;
				dst.type = ui_window_focus_out_event;
			}
			win32_get_mouse_pos(aw->wnd,psc,&dst);
			win32_get_key_modifiers(aw,&dst);
			ui_make_default_key_event(&dst.key);
			ui_set_configure_from_aw(aw,&dst.configure);
			break;

		case WM_QUIT:
			dst.type = ui_wm_event;
			win32_get_mouse_pos(aw->wnd,psc,&dst);
			win32_get_key_modifiers(aw,&dst);
			ui_make_default_key_event(&dst.key);
			ui_set_configure_from_aw(aw,&dst.configure);
			dst.configure.wm_event = ui_wm_close;
			break;

		case WM_PAINT:
			memZero(&rc, sizeof(RECT));
			if(!GetUpdateRect(aw->wnd,&rc,FALSE))
				return;
			BeginPaint(aw->wnd,&ps);
			EndPaint(aw->wnd,&ps);
			dst.type = ui_window_expose_event;
			win32_get_mouse_pos(aw->wnd,psc,&dst);
			win32_get_key_modifiers(aw,&dst);
			ui_make_default_key_event(&dst.key);
			ui_set_configure_from_aw(aw,&dst.configure);
			dst.configure.exposed_left = rc.left;
			dst.configure.exposed_top = rc.top;
			dst.configure.exposed_right = rc.right;
			dst.configure.exposed_bottom = rc.bottom;
			break;

		case WM_MOVE:
			if(GetUpdateRect(aw->wnd,&rc,FALSE))
				PatBlt(aw->dc,rc.left,rc.top,
					rc.right - rc.left,rc.bottom-rc.top,BLACKNESS);
			dst.type = ui_window_move_event;
			/* win32_get_window_geometry */
			aw->x = (int)SIGNED_LOWORD(lParam);
			aw->y = (int)SIGNED_HIWORD(lParam);
			if(aw->x == -32000)
				aw->x = 0;
			if(aw->y == -32000)
				aw->y = 0;
			win32_get_mouse_pos(aw->wnd,psc,&dst);
			win32_get_key_modifiers(aw,&dst);
			ui_make_default_key_event(&dst.key);
			ui_set_configure_from_aw(aw,&dst.configure);
			break;

		case WM_SIZE:
			if(GetUpdateRect(aw->wnd,&rc,FALSE))
				PatBlt(aw->dc,rc.left,rc.top,
					rc.right - rc.left,rc.bottom-rc.top,BLACKNESS);
			dst.type = ui_window_size_event;
			aw->width = (unsigned)LOWORD(lParam);
			aw->height = (unsigned)HIWORD(lParam);
			win32_get_mouse_pos(aw->wnd,psc,&dst);
			win32_get_key_modifiers(aw,&dst);
			ui_make_default_key_event(&dst.key);
			ui_set_configure_from_aw(aw,&dst.configure);
			break;

		case WM_KEYDOWN:
		case WM_SYSKEYDOWN:
		case WM_KEYUP:
		case WM_SYSKEYUP:
			if(msg == WM_KEYDOWN || msg == WM_SYSKEYDOWN)
				dst.key.press = true;
			else
				dst.key.press = false;
			if(aw->auto_repeat_disabled && dst.key.press &&
				(lParam & (1 << 30)))
			{
				zap_message = true;
				break;
			}
			dst.type = ui_key_event;
			win32_get_mouse_pos(aw->wnd,psc,&dst);
			win32_get_key_modifiers(aw,&dst);
			dst.key.scan = win32_make_scan_code(wParam,lParam);
			win32_get_char_codes(aw,wParam,lParam,&dst);
			ui_set_configure_from_aw(aw,&dst.configure);
			if(dst.key.scan != sc_unknown)
				aw->keyboard_state[dst.key.scan] = (u8_t)(dst.key.press ? 1 : 0);
			break;

		case WM_MOUSEMOVE:
			dst.type = ui_pointer_motion_event;
			ui_make_default_key_event(&dst.key);
			dst.client_x = SIGNED_LOWORD(lParam);
			dst.client_y = SIGNED_HIWORD(lParam);
			dst.screen_x = psc->x;
			dst.screen_y = psc->y;
			win32_get_key_modifiers(aw,&dst);
			ui_set_configure_from_aw(aw,&dst.configure);
			break;

		case WM_LBUTTONDOWN:
		case WM_MBUTTONDOWN:
		case WM_RBUTTONDOWN:
		case WM_LBUTTONUP:
		case WM_MBUTTONUP:
		case WM_RBUTTONUP:
		case 522: /* WM_MOUSEWHEEL */
			dst.type = ui_key_event;
			switch(msg)
			{
				case WM_LBUTTONUP:
				case WM_MBUTTONUP:
				case WM_RBUTTONUP:
					dst.key.press = false;
					if(msg == aw->auto_capture_button)
					{
						archWinGrabMouse(aw, false);
						aw->auto_capture_button = 0;
						aw->zap_next_mousemove = true;
					}
					break;
				case WM_LBUTTONDOWN:
					dst.key.press = true;
					if(!aw->auto_capture_button)
						aw->auto_capture_button = WM_LBUTTONUP;
					break;
				case WM_MBUTTONDOWN:
					dst.key.press = true;
					if(!aw->auto_capture_button)
						aw->auto_capture_button = WM_MBUTTONUP;
					break;
				case WM_RBUTTONDOWN:
					dst.key.press = true;
					if(!aw->auto_capture_button)
						aw->auto_capture_button = WM_RBUTTONUP;
					break;
				default:
					dst.key.press = true;
			}
			if(aw->auto_capture_button)
				archWinGrabMouse(aw, true);
			win32_get_mouse_pos(aw->wnd,psc,&dst);
			win32_get_key_modifiers(aw,&dst);
			ui_set_configure_from_aw(aw,&dst.configure);
			dst.key.acode = dst.key.ucode = 0; 
			if(msg == 522)
			{
				aw->wheel_delta += SIGNED_HIWORD(wParam);
				if(abs(aw->wheel_delta) < 120)
				{
					zap_message = true;
					break;
				}
				if(aw->wheel_delta > 0)
				{
					dst.key.scan = sc_wheelup;
					aw->keyboard_state[sc_wheeldown] = 0;
					aw->keyboard_state[sc_wheelup] = 1;
				}
				else
				{
					dst.key.scan = sc_wheeldown;
					aw->keyboard_state[sc_wheeldown] = 1;
					aw->keyboard_state[sc_wheelup] = 0;
				}
				aw->wheel_delta = 0;
			}
			else
			{
				if(msg == WM_LBUTTONDOWN || msg == WM_LBUTTONUP)
					dst.key.scan = sc_lbutton;
				else if(msg == WM_RBUTTONDOWN || msg == WM_RBUTTONUP)
					dst.key.scan = sc_rbutton;
				else
					dst.key.scan = sc_mbutton;
				aw->keyboard_state[dst.key.scan] = (u8_t)(dst.key.press ? 1 : 0);
			}
			break;

		default:
			zap_message = true;
			break;
	}
	if(!zap_message && ui_default_wnd_proc(aw,&dst))
		DEQ_PUSH(event,aw->msg_que,&dst);
	return;
}

/*
 * Default callback. All system-generated messages and messages
 * unknown to win32_window_proc() have to be processed here.
 */
V_LOCAL LRESULT CALLBACK
win32_sys_window_proc(HWND wnd,UINT msg,WPARAM wParam,LPARAM lParam)
{
	arch_window_p aw = (arch_window_p)GetProp(wnd,LIBV_HANDLE_PROP);
	if(!aw || aw->wnd != wnd)
		aw = NULL;

	switch(msg)
	{
		case WM_CREATE:
			return 0;

		case WM_CLOSE:
			PostQuitMessage(0);
			return 0;

		case WM_ERASEBKGND:
			return 1;

		case WM_SHOWWINDOW:
		case WM_PAINT:
		case WM_MOVE:
		case WM_SIZE:
		case WM_QUIT:
		case WM_SETFOCUS:
		case WM_KILLFOCUS:
		case WM_SYSCOMMAND:
			if(aw)
				win32_window_proc(aw,msg,wParam,lParam,0,NULL);
			if(msg != WM_SYSCOMMAND)
				return 0;
			break;

		default:
			break;
	}

	return DefWindowProc(wnd,msg,wParam,lParam);
}

V_LOCAL void
win32_query_vid_modes(arch_window_p aw)
{
	DWORD num;
	DEVMODE dm;
	fifo_t modes;
	memZero(&dm, sizeof(DEVMODE));
	dm.dmSize = sizeof(dm);
	fifoInit(&modes, sizeof(DEVMODE));
	for(num = 0;EnumDisplaySettings(NULL, num, &dm);num++)
	{
		if(dm.dmBitsPerPel >= 15) /* Hi/TrueColor */
			fifoPush(&modes,&dm);
	}
	if(aw->vidmodes)
	{
		memFree(aw->vidmodes);
		aw->vidmodes = NULL;
	}
	aw->num_vidmodes = 0;
	if(num)
	{
		aw->vidmodes = (vidmode_p)memAllocZ(num * sizeof(vidmode_t));
		if(aw->vidmodes)
		{
			vidmode_p ptr = aw->vidmodes;
			while(fifoPop(&modes,&dm))
			{
				ptr->width = dm.dmPelsWidth;
				ptr->height = dm.dmPelsHeight;
				ptr->bpp = dm.dmBitsPerPel;
				ptr->refresh = dm.dmDisplayFrequency;
				if(ptr->refresh == 1)
					ptr->refresh = 0;
				ptr->modeline = aw->num_vidmodes;
				aw->num_vidmodes++;
				ptr++;
			}
		}
	}
	fifoClear(&modes);
}

V_LOCAL LONG
win32_change_video_mode(vidmode_p new_mode)
{
	ARCH_CALLER_NAME(win32_change_video_mode)
	LONG result;
	if(new_mode)
	{
		DEVMODE dm;
		HDC refdc = GetDC(NULL);
		u32_t old_bpp = GetDeviceCaps(refdc,BITSPIXEL);
		u32_t old_refresh = GetDeviceCaps(refdc,VREFRESH);
		u32_t old_width = GetSystemMetrics(SM_CXSCREEN);
		u32_t old_height = GetSystemMetrics(SM_CYSCREEN);
		ReleaseDC(NULL,refdc);
		memZero(&dm, sizeof(DEVMODE));
		dm.dmSize = sizeof(dm);
		if(!new_mode->width)
		{
			ARCH_INFO("Using current desktop width");
			dm.dmPelsWidth = old_width;
		}
		else
			dm.dmPelsWidth = new_mode->width;
		if(!new_mode->height)
		{
			ARCH_INFO("Using current desktop height");
			dm.dmPelsHeight = old_height;
		}
		else
			dm.dmPelsHeight = new_mode->height;
		if(!new_mode->bpp)
		{
			ARCH_INFO("Using current desktop display depth");
			dm.dmBitsPerPel = old_bpp;
		}
		else
			dm.dmBitsPerPel = new_mode->bpp;
		dm.dmFields = DM_PELSWIDTH | DM_PELSHEIGHT | DM_BITSPERPEL;
		if(ntGetVersion() > 0)
		{
			if(!new_mode->refresh)
			{
				if(dm.dmPelsWidth > old_width || dm.dmPelsHeight > old_height)
				{
					ARCH_WARNING("No refresh rate for hi-res mode; using 75 Hz");
					/* suitable both for CRT and (good) LCD monitors */
					dm.dmDisplayFrequency = 75;
				}
				else
				{
					ARCH_INFO("Using current refresh rate");
					dm.dmDisplayFrequency = old_refresh;
				}
			}
			else
				dm.dmDisplayFrequency = new_mode->refresh;
			dm.dmFields |= DM_DISPLAYFREQUENCY;
		}
		else
		{
			if(new_mode->refresh)
				ARCH_WARNING("Unable to change refresh rate under Windows 9x");
		}
		if(new_mode->width != old_width ||
				new_mode->height != old_height ||
				new_mode->bpp != old_bpp ||
				new_mode->refresh != old_refresh)
			result = ChangeDisplaySettings(&dm,0);
		else
			result = DISP_CHANGE_SUCCESSFUL;
	}
	else
		result = ChangeDisplaySettings(NULL,0);
	if(result != DISP_CHANGE_SUCCESSFUL)
	{
		ARCH_ERROR("unable to change video mode");
		return false;
	}
	return true;
}

vboolean V_CALL
archWinSetVidMode(arch_window_p aw,int new_mode)
{
	ARCH_CALLER_NAME(archWinSetVidMode)
	vidmode_p mode;
	vboolean result;
	if(!aw->visible)
	{
		ARCH_ERROR("window invisible");
		return false;
	}
	if(!aw->focused)
	{
		ARCH_WARNING("vidmode change with non-focused window");
		archWinFocus(aw);
	}
	if(new_mode == -1)
	{
		/* restore default mode */
		if(aw->current_vidmode != -1)
		{
			if(aw->auto_capture_button)
				archWinGrabMouse(aw, false);
			win32_change_video_mode(NULL);
			if(aw->auto_capture_button)
			{
				archWinGrabMouse(aw, true);
				aw->zap_next_mousemove = true;
			}
			SetWindowPos(aw->wnd,HWND_NOTOPMOST,0,0,0,0,
				SWP_NOSIZE|SWP_NOMOVE);
			aw->current_vidmode = -1;
			aw->root_width = GetSystemMetrics(SM_CXSCREEN);
			aw->root_height = GetSystemMetrics(SM_CYSCREEN);
		}
		return true;
	}
	mode = aw->vidmodes + new_mode;
	if(aw->auto_capture_button)
		archWinGrabMouse(aw, false);
	if(win32_change_video_mode(mode))
	{
		aw->current_vidmode = new_mode;
		SetWindowPos(aw->wnd,HWND_TOP,0,0,0,0,SWP_NOSIZE|SWP_NOMOVE);
		aw->root_width = mode->width;
		aw->root_height = mode->height;
		result = true;
	}
	else
		result = false;
	if(aw->auto_capture_button)
	{
		archWinGrabMouse(aw, true);
		aw->zap_next_mousemove = true;
	}
	return result;
}

V_LOCAL vboolean
win32_register_window_class(arch_window_p aw)
{
	ARCH_CALLER_NAME(win32_register_window_class)
	strCopy(aw->class_name,"LIBV_CLS_",MAX_CLASS_NAME);
	archUniqueString(aw->class_name + 9,MAX_CLASS_NAME - 9);
	memZero(&aw->wc,sizeof(WNDCLASS));
	aw->wc.lpszClassName = aw->class_name;
	aw->wc.hInstance = GetModuleHandle(NULL);
	aw->wc.style = CS_CLASSDC;
	aw->wc.lpfnWndProc = win32_sys_window_proc;
	aw->wc.hIcon = LoadIcon(NULL,IDI_WINLOGO);
	aw->wc.hCursor = LoadCursor(NULL, IDC_ARROW);
	aw->wc.hbrBackground = GetStockObject(HOLLOW_BRUSH);
	aw->wc.lpszMenuName = NULL;
	if(RegisterClass(&aw->wc))
		return true;
	SYSCALL_FAILED("RegisterClass");
	return false;
}

arch_window_p V_CALL
archWinOpenNP(void_p osd_parent,void_p visual,
	char *title,u32_t left,u32_t top,u32_t width,u32_t height,
	vboolean caption,vboolean allow_resize)
{
	ARCH_CALLER_NAME(archWinOpenNP)
	RECT rc;
	arch_window_p aw;
	u32_t dw, dh;
	DWORD style = WS_CLIPCHILDREN | WS_CLIPSIBLINGS;
	if(allow_resize)
	{
		if(!caption)
		{
			ARCH_ERROR("can't create resizeable window without caption");
			return NULL;
		}
		style |= WS_OVERLAPPEDWINDOW;
	}
	else
	{
		style |= WS_POPUP;
		if(caption)
			style |= WS_CAPTION;
	}
	if(caption)
	{
		dw = 2 * GetSystemMetrics(SM_CXFIXEDFRAME);
		dh = 2 * GetSystemMetrics(SM_CYFIXEDFRAME) +
			GetSystemMetrics(SM_CYCAPTION);
	}
	else
		dw = dh = 0;
	if(visual)
	{
		ARCH_WARNING("win32: visinfo ignored");
	}
	ALLOCZ_VAR(aw,arch_window_t);
	if(!aw)
	{
		ARCH_ERROR("out of memory");
		return NULL;
	}
	INIT_COMMON_WINDOW_FIELDS(aw);
	if(!win32_register_window_class(aw))
	{
		archWinClose(aw);
		return NULL;
	}
	if(title)
		strCopy(aw->title,title,MAX_WINDOW_TITLE);
	aw->start_time = GetTickCount();
	aw->x = left;
	aw->y = top;
	aw->root_width = GetSystemMetrics(SM_CXSCREEN);
	aw->root_height = GetSystemMetrics(SM_CYSCREEN);
	if (width == 0)
		width = aw->root_width - left;
	if (height == 0)
		height = aw->root_height - top;
	aw->width = width;
	aw->height = height;
	aw->wnd = CreateWindow(aw->class_name,
		aw->title, style, left, top,
		width + dw,
		height + dh,
		osd_parent,
		NULL,
		GetModuleHandle(NULL),NULL);
	if(!aw->wnd)
	{
		SYSCALL_FAILED("CreateWindow");
		archWinClose(aw);
		return NULL;
	}
	SetProp(aw->wnd,LIBV_HANDLE_PROP,(HANDLE)aw);
	GetClientRect(aw->wnd,&rc);
	aw->delta_w = aw->width - rc.right;
	aw->delta_h = aw->height - rc.bottom;
	archWinSize(aw,aw->width,aw->height);
	aw->dc = GetDC(aw->wnd);
	aw->wheel_delta = 0;
	win32_query_vid_modes(aw);
	memZero(aw->oem_kbstate,256);
	aw->zap_next_mousemove = false;
	return aw;
}

vboolean	V_CALL
archWinShow(arch_window_p aw,vboolean show)
{
	if(show)
	{
		ShowWindow(aw->wnd,SW_SHOW);
		UpdateWindow(aw->wnd);
		ui_win_loose_control(aw, false);
		aw->visible = true;
	}
	else
	{
		ui_win_loose_control(aw, true);
		aw->visible = false;
		ShowWindow(aw->wnd,SW_HIDE);
	}
	return true;
}

void V_CALL
archWinFocus(arch_window_p aw)
{
	if(!aw->visible)
	{
		ShowWindow(aw->wnd,SW_SHOW);
		UpdateWindow(aw->wnd);
	}
	SetFocus(aw->wnd);
	SetActiveWindow(aw->wnd);
}

vboolean	V_CALL
archWinMove(arch_window_p aw,s32_t left,s32_t top)
{
	return SetWindowPos(aw->wnd,HWND_NOTOPMOST,left,top,0,0,
		SWP_NOSIZE | SWP_NOZORDER) ? true : false;
}

vboolean	V_CALL
archWinSize(arch_window_p aw,s32_t width,s32_t height)
{
	return SetWindowPos(aw->wnd,HWND_NOTOPMOST,
		0,0,width + aw->delta_w,height + aw->delta_h,
		SWP_NOMOVE | SWP_NOZORDER) ? true : false;
}

/*
 * Returns 'true' if cursor is visible at exit.
 */
vboolean V_CALL
archWinShowPointer(arch_window_p aw,vboolean show)
{
	vboolean result;
	if(show)
	{
		aw->pointer_hide_count--;
		if(!aw->pointer_hide_count)
			ShowCursor(TRUE);
	}
	else
	{
		if(!aw->pointer_hide_count)
			ShowCursor(FALSE);
		aw->pointer_hide_count++;
	}
	return result = (aw->pointer_hide_count > 0) ? false : true;
}

vboolean	V_CALL
archWinWarpPointer(arch_window_p aw,s32_t x,s32_t y)
{
	ARCH_CALLER_NAME(archWinWarpPointer)
	POINT pt;
	pt.x = x;
	pt.y = y;
	ClientToScreen(aw->wnd, &pt);
	if(pt.x < 0 || pt.y < 0 ||
			(u32_t)pt.x >= aw->root_width ||
			(u32_t)pt.y >= aw->root_height)
	{
		ARCH_ERROR("unable to warp pointer outside of root window");
		return false;
	}
	SetCursorPos(pt.x, pt.y);
	return true;
}

void V_CALL
archWinGrabMouse(arch_window_p aw,vboolean grab)
{
	ARCH_CALLER_NAME(archWinGrabMouse)
	if(!aw->visible)
	{
		ARCH_ERROR("window invisible");
		return;
	}
	if(grab)
	{
		if(!aw->ms_grab_count++)
			SetCapture(aw->wnd);
	}
	else
	{
		if(!--aw->ms_grab_count)
			ReleaseCapture();
	}
}

void V_CALL
archWinGrabKeyboard(arch_window_p aw,vboolean grab)
{
	ARCH_CALLER_NAME(archWinGrabKeyboard)
	if(!aw->visible)
	{
		ARCH_ERROR("window invisible");
		return;
	}
	if(grab)
	{
		if(!aw->kb_grab_count++)
		{
			ARCH_WARNING("grab_kb() not implemented under win32");
		}
	}
	else
	{
		if(!--aw->kb_grab_count)
		{
			ARCH_WARNING("ungrab_kb() not implemented under win32");
		}
	}
}

void	V_CALL
archWinClose(arch_window_p aw)
{
	if(aw->pointer_hide_count > 0)
	{
		aw->pointer_hide_count = 1;
		archWinShowPointer(aw, true);
	}
	ui_win_loose_control(aw, true);
	if(aw->wnd)
	{
		if(aw->dc)
			ReleaseDC(aw->wnd,aw->dc);
		RemoveProp(aw->wnd,LIBV_HANDLE_PROP);
		DestroyWindow(aw->wnd);
	}
	if(aw->vidmodes)
		memFree(aw->vidmodes);
	UnregisterClass(aw->class_name,GetModuleHandle(NULL));
	CLEAR_COMMON_WINDOW_FIELDS(aw);
	memFree(aw);
}

struct rejection_arg_s
{
	vboolean met_size, met_move;
	ui_event_t last_expose;
	unsigned left, top, right, bottom;
};

V_LOCAL vboolean V_CALL
mvsz_rejection_proc(ui_event_p ev,void_p arg)
{
	struct rejection_arg_s *rj = (struct rejection_arg_s*)arg;
	/* assume scan from last (new) to first (old) */
	switch(ev->type)
	{
		case ui_window_move_event:
			if(rj->met_move)
				return true;
			rj->met_move = true;
			break;
		case ui_window_size_event:
			if(rj->met_size)
				return true;
			rj->met_size = true;
			break;
		default:
			break;
	}
	return false;
}

V_LOCAL vboolean V_CALL
expose_collapse_proc(ui_event_p ev,void_p arg)
{
	struct rejection_arg_s *rj = (struct rejection_arg_s*)arg;
	/* assume scan from first (old) to last (new) */
	if(ev->type == ui_window_expose_event)
	{
		memCopy(&rj->last_expose,ev,sizeof(ui_event_t));
		if(rj->left > ev->configure.exposed_left)
			rj->left = ev->configure.exposed_left;
		if(rj->top > ev->configure.exposed_top)
			rj->top = ev->configure.exposed_top;
		if(rj->right < ev->configure.exposed_right)
			rj->right = ev->configure.exposed_right;
		if(rj->bottom < ev->configure.exposed_bottom)
			rj->bottom = ev->configure.exposed_bottom;
		return true;
	}
	return false;
}

V_LOCAL event_deque_p
ui_collapse_configure_events(event_deque_p msg_que)
{
	struct rejection_arg_s rj;
	if(!msg_que)
		return NULL;
	memZero(&rj, sizeof(rj));
	rj.left = rj.top = MAXLONG;
	rj.met_size = rj.met_move = false;
	DEQ_SCAN_BACKWARD(event,msg_que,mvsz_rejection_proc,&rj);
	DEQ_SCAN_FORWARD(event,msg_que,expose_collapse_proc,&rj);
	if(rj.left != MAXLONG)
	{
		rj.last_expose.configure.exposed_left = rj.left;
		rj.last_expose.configure.exposed_top = rj.top;
		rj.last_expose.configure.exposed_right = rj.right;
		rj.last_expose.configure.exposed_bottom = rj.bottom;
		DEQ_PUSH(event,msg_que,&rj.last_expose);
	}
	return msg_que;
}

V_LOCAL void
win32_poll_events(arch_window_p aw)
{
	MSG msg;
	while(PeekMessage(&msg,aw->wnd,0,0,PM_REMOVE))
	{
		win32_window_proc(aw,msg.message,msg.wParam,msg.lParam,
			msg.time, &msg.pt);
		DispatchMessage(&msg);
	}
	aw->msg_que = ui_collapse_configure_events(aw->msg_que);
}

void V_CALL
archWinPoll(arch_window_p aw,vboolean block)
{
	if(!block)
	{
		win32_poll_events(aw);
		return;
	}
	for(;;)
	{
		win32_poll_events(aw);
		if(aw->msg_que)
			break;
		WaitMessage();
	}
}

/*
 * archWinWaitMessages()
 */
vboolean V_CALL
archWinWaitMessages(int timeout)
{
	/* ARCH_CALLER_NAME(archWinWaitMessages) */
	DWORD ms = (timeout == -1) ? INFINITE : (DWORD)timeout;
	if(MsgWaitForMultipleObjects(0,NULL,FALSE,ms,QS_ALLINPUT)
			== WAIT_OBJECT_0)
		return true;
	return false;
}

#endif	/* ARCH_WINDOWING_SUPPORTED */
