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

#ifdef	ARCH_WINDOWING_SUPPORTED

V_LOCAL char_p LIBV_HANDLE_PROP = "LIBV_HANDLE";
V_LOCAL char_p LIBV_ROOT_WINDOW = "libv_root_window";
V_LOCAL char_p LIBV_ROOT_WINDOW_CLASS = "libv_root_window_class";

V_LOCAL LRESULT CALLBACK
win32_root_window_proc(HWND wnd,UINT msg,WPARAM wparam,LPARAM lparam)
{
	PAINTSTRUCT ps;
	switch(msg)
	{
		case WM_PAINT:
			BeginPaint(wnd,&ps);
			EndPaint(wnd,&ps);
			return 0;
		default:
			break;
	}
	return DefWindowProc(wnd,msg,wparam,lparam);
}

arch_window_p	V_CALL
archCreateRootWindow(void)
{
	ARCH_CALLER_NAME(archCreateRootWindow)
	arch_window_p aw;
	aw = (arch_window_p)memAllocZ(sizeof(arch_window_t));
	if(!aw)
	{
		ARCH_ERROR("memAlloc() failed");
		return NULL;
	}
	INIT_COMMON_WINDOW_FIELDS(aw,NULL);
	fifoInit(&aw->raw_messages,sizeof(ui_message_t));
	strCopy(aw->title,LIBV_ROOT_WINDOW,MAX_WINDOW_TITLE);
	strCopy(aw->class_name,LIBV_ROOT_WINDOW_CLASS,MAX_CLASS_NAME);
	memZero(&aw->wc,sizeof(WNDCLASS));
	aw->wc.lpszClassName = aw->class_name;
	aw->wc.hInstance = GetModuleHandle(NULL);
	aw->wc.lpfnWndProc = win32_root_window_proc;
	aw->wc.hIcon = LoadIcon(NULL,IDI_WINLOGO);
	aw->wc.hCursor = LoadCursor(NULL, IDC_ARROW);
	aw->wc.hbrBackground = GetStockObject(BLACK_BRUSH);
	aw->wc.lpszMenuName = NULL;
	if(!RegisterClass(&aw->wc))
	{
		SYSCALL_FAILED("RegisterClass");
		archWinClose(aw);
		return NULL;
	}
	aw->width = GetSystemMetrics(SM_CXSCREEN);
	aw->height = GetSystemMetrics(SM_CYSCREEN);
	aw->creation_time = GetTickCount();
	aw->wnd = CreateWindow(aw->class_name,
		aw->title, WS_POPUP, 0, 0,
		aw->width, aw->height,
		NULL,NULL,aw->wc.hInstance,NULL);
	if(!aw->wnd)
	{
		SYSCALL_FAILED("CreateWindow");
		archWinClose(aw);
		return NULL;
	}
	SetProp(aw->wnd,LIBV_HANDLE_PROP,(HANDLE)aw);
	aw->dc = NULL;
	return aw;
}

arch_window_p
archGetRootWindow(void)
{
	ARCH_CALLER_NAME(archGetRootWindow)
	arch_window_p aw;
	HWND root_handle =
		FindWindowEx(NULL,NULL,LIBV_ROOT_WINDOW_CLASS,LIBV_ROOT_WINDOW);
	if(!root_handle) /* _no_ complains, this is valid for 1st call */
		return NULL;
 	aw = (arch_window_p)GetProp(root_handle,LIBV_HANDLE_PROP);
 	if(!VALID_ARCH_WINDOW(aw)) /* but this is invalid, though */
 	{
 		ARCH_WARNING("pseudo-root exists, but isn't linked to LibV");
 		return NULL;
 	}
 	return aw;
}

/* fixme: platform-independent */

void	V_CALL
archTryCloseRootWindow()
{
	TREE_ITERATOR;
	int child_count = 0;
	arch_window_p root_window = archGetRootWindow();
	if(!root_window)
		return;
	TREE_FOR_EACH_CHILD(root_window)
	{
		child_count ++;
	}
	if(!child_count)
		archWinClose(root_window);
}

/*
 * Translates wParam and lParam into LibV key code.
 */
V_LOCAL	s32_t
win32_vk2keycode(u32_t wp,u32_t lp)
{
	s32_t key;
	unsigned char scan = (unsigned char)((lp>>16) & 0xFF);
	unsigned char ext = (unsigned char)((lp & (1<<24)) != 0);
	switch(wp)
	{
		case VK_ESCAPE:	key = key_escape; break;
		case '1':		key = key_1; break;
		case '2':		key = key_2; break;
		case '3':		key = key_3; break;
		case '4':		key = key_4; break;
		case '5':		key = key_5; break;
		case '6':		key = key_6; break;
		case '7':		key = key_7; break;
		case '8':		key = key_8; break;
		case '9':		key = key_9; break;
		case '0':		key = key_0; break;
		case 189:		key = key_minus; break;
		case 187:		key = key_equal; break;
		case VK_BACK:	key = key_backspace; break;
		case VK_TAB:	key = key_tab; break;
		case 'Q':		key = key_Q; break;
		case 'W':		key = key_W; break;
		case 'E':		key = key_E; break;
		case 'R':		key = key_R; break;
		case 'T':		key = key_T; break;
		case 'Y':		key = key_Y; break;
		case 'U':		key = key_U; break;
		case 'I':		key = key_I; break;
		case 'O':		key = key_O; break;
		case 'P':		key = key_P; break;
		case 219:		key = key_left_bracket; break;
		case 221:		key = key_right_bracket; break;
		case VK_RETURN:	key = ext ?
			key_kp_enter : key_enter; break;
		case 17:		key = ext ? 
			key_right_control : key_left_control; break;
		case 18:		key = ext ? key_right_alt : key_left_alt; break;
		case 'A':		key = key_A; break;
		case 'S':		key = key_S; break;
		case 'D':		key = key_D; break;
		case 'F':		key = key_F; break;
		case 'G':		key = key_G; break;
		case 'H':		key = key_H; break;
		case 'J':		key = key_J; break;
		case 'K':		key = key_K; break;
		case 'L':		key = key_L; break;
		case 186:		key = key_colon; break;
		case 222:		key = key_apostrophe; break;
		case 192:		key = key_back_quote; break;
		case 16:		key = (scan == 42) ?
			key_left_shift : key_right_shift; break;
		case 220:		key = key_back_slash; break;
		case 'Z':		key = key_Z; break;
		case 'X':		key = key_X; break;
		case 'C':		key = key_C; break;
		case 'V':		key = key_V; break;
		case 'B':		key = key_B; break;
		case 'N':		key = key_N; break;
		case 'M':		key = key_M; break;
		case 188:		key = key_comma; break;
		case 190:		key = key_dot; break;
		case 191:		key = key_slash; break;
		case VK_F1:		key = key_f1; break;
		case VK_F2:		key = key_f2; break;
		case VK_F3:		key = key_f3; break;
		case VK_F4:		key = key_f4; break;
		case VK_F5:		key = key_f5; break;
		case VK_F6:		key = key_f6; break;
		case VK_F7:		key = key_f7; break;
		case VK_F8:		key = key_f8; break;
		case VK_F9:		key = key_f9; break;
		case VK_F10:	key = key_f10; break;
		case VK_F11:	key = key_f11; break;
		case VK_F12:	key = key_f12; break;
		case VK_NUMLOCK:	key = key_numlock; break;
		case VK_SCROLL:	key = key_scrolllock; break;
		case 32:		key = key_space; break;
		case 20:		key = key_capslock; break;
		case 91:		key = key_left_win; break;
		case 92:		key = key_right_win; break;
		case 93:		key = key_context; break;
		case 111:		key = key_kp_slash; break;
		case 106:		key = key_kp_asterisk; break;
		case 109:		key = key_kp_minus; break;
		case 107:		key = key_kp_plus; break;
		default:
			switch(scan)
			{
				case 71:	key = (ext) ? key_home : key_kp_7; break;
				case 72:	key = (ext) ? key_up : key_kp_8; break;
				case 73:	key = (ext) ? key_pageup : key_kp_9; break;
				case 75:	key = (ext) ? key_left : key_kp_4; break;
				case 76:	key = key_kp_5; break;
				case 77:	key = (ext) ? key_right : key_kp_6; break;
				case 79:	key = (ext) ? key_end : key_kp_1; break;
				case 80:	key = (ext) ? key_down : key_kp_2; break;
				case 81:	key = (ext) ? key_pagedown : key_kp_3; break;
				case 82:	key = (ext) ? key_insert : key_kp_0; break;
				case 83:	key = (ext) ? key_delete : key_kp_dot; break;
				case 69:	key = key_pause; break;
				case 55:	key = key_printscreen; break;
				default:	key = key_unknown; break;
			}
		break;
	}
	return key;
}

/*
 * Retrieves information about modifier keys.
 */
V_LOCAL	void
win32_get_locale(ui_message_p msg)
{
	u32_t lang_id;
	lang_id = (UINT)GetKeyboardLayout(0) & 0xFF;
	switch(lang_id)
	{
		case LANG_RUSSIAN:
    		MSG_KEY_LOCALE(msg) = MSG_LOCALE_RU;
    		break;
    	default:
    		MSG_KEY_LOCALE(msg) = MSG_LOCALE_EN;
    		break;
    }
}

V_LOCAL void
win32_get_key_modifiers(ui_message_p msg)
{
	u8_t keys[256];
	memZero(keys,256);
	GetKeyboardState(keys);
	if(keys[VK_NUMLOCK] & 1)
		MSG_KEY_FLAGS(msg) |= MSG_KEY_NUM;
	else
		MSG_KEY_FLAGS(msg) &= ~MSG_KEY_NUM;
	if(keys[VK_CAPITAL] & 1)
		MSG_KEY_FLAGS(msg) |= MSG_KEY_CAPS;
	else
		MSG_KEY_FLAGS(msg) &= ~MSG_KEY_CAPS;
	if(keys[VK_SCROLL] & 1)
		MSG_KEY_FLAGS(msg) |= MSG_KEY_SCROLL;
	else
		MSG_KEY_FLAGS(msg) &= ~MSG_KEY_SCROLL;
    if(keys[VK_LCONTROL] & 0x80)
		MSG_KEY_FLAGS(msg) |= MSG_KEY_LCONTROL;
	else
		MSG_KEY_FLAGS(msg) &= ~MSG_KEY_LCONTROL;
    if(keys[VK_LSHIFT] & 0x80)
		MSG_KEY_FLAGS(msg) |= MSG_KEY_LSHIFT;
	else
		MSG_KEY_FLAGS(msg) &= ~MSG_KEY_LSHIFT;
    if(keys[VK_LMENU] & 0x80)
		MSG_KEY_FLAGS(msg) |= MSG_KEY_LMETA;
	else
		MSG_KEY_FLAGS(msg) &= ~MSG_KEY_LMETA;
    if(keys[VK_RCONTROL] & 0x80)
		MSG_KEY_FLAGS(msg) |= MSG_KEY_RCONTROL;
	else
		MSG_KEY_FLAGS(msg) &= ~MSG_KEY_RCONTROL;
    if(keys[VK_RSHIFT] & 0x80)
		MSG_KEY_FLAGS(msg) |= MSG_KEY_RSHIFT;
	else
		MSG_KEY_FLAGS(msg) &= ~MSG_KEY_RSHIFT;
    if(keys[VK_RMENU] & 0x80)
		MSG_KEY_FLAGS(msg) |= MSG_KEY_RMETA;
	else
		MSG_KEY_FLAGS(msg) &= ~MSG_KEY_RMETA;
}

/* ----------------------------------------------------------------------------- */

V_LOCAL vboolean
default_proc(arch_window_p aw,ui_message_p msg)
{
	switch(msg->event)
	{
		case ui_event_activate:
			aw->active = MSG_WIN_ACTIVE(msg);
			/* Extra stuff for fullscreen windows */
			if(aw->active)
			{
				if(aw->vidmode_on_activate != -1)
				{
					archWinSetVideoMode(aw,aw->vidmode_on_activate,NULL);
					aw->vidmode_on_activate = -1;
				}
			}
			else
			{
				if(aw->current_vidmode != -1)
				{
					aw->vidmode_on_activate = aw->current_vidmode;
					archWinSetVideoMode(aw,-1,NULL);
				}
			}
			break;
		case ui_event_close:
			if(aw->current_vidmode != -1)
				archWinSetVideoMode(aw,-1,NULL);
			break;
		case ui_event_move:
			aw->left = MSG_WIN_LEFT(msg);
			aw->top = MSG_WIN_TOP(msg);
			break;
		case ui_event_size:
			aw->width = MSG_WIN_WIDTH(msg);
			aw->height = MSG_WIN_HEIGHT(msg);
			break;
#ifndef	CONFIG_ENABLE_AUTOREPEAT
		case ui_event_key_down:
			if(MSG_KEY_FLAGS(msg) & MSG_KEY_WAS_DOWN)
				return true;
#endif	/* !CONFIG_ENABLE_AUTOREPEAT */
		case ui_event_mouse_move:
        	if(aw->auto_warp_pointer)
            {
            	s32_t cx = aw->width / 2;
            	s32_t cy = aw->height / 2;
				/* Avoid recursion */
				if(MSG_MOUSE_X(msg) == cx && MSG_MOUSE_Y(msg) == cy)
					return true;
				/* Store deltas */
				MSG_MOUSE_X(msg) -= cx;
				MSG_MOUSE_Y(msg) -= cy;
				archWinWarpPointer(aw,cx,cy);
            }
#ifdef	DISCARD_DUPLICATE_MOUSEMOVE
			if(aw->last_mx == MSG_MOUSE_X(msg) &&
				aw->last_my == MSG_MOUSE_Y(msg))
				return true;
			aw->last_mx = MSG_MOUSE_X(msg);
			aw->last_my = MSG_MOUSE_Y(msg);
#endif	/* DISCARD_DUPLICATE_MOUSEMOVE */
			break;
		default:
			break;
	}
	return false;
}

V_LOCAL void
translate_messages(arch_window_p aw)
{
	vboolean got_resize = false, got_move = false, got_redraw = false;
	ui_message_t msg, last_resize, last_move;
	vec_t redraw_time = 0;
	s32_t left = MAXLONG, top = MAXLONG, right = 0, bottom = 0;
	while(fifoPop(&aw->raw_messages,&msg))
	{
		switch(msg.event)
		{
			case ui_event_size:
				got_resize = true;
				memCopy(&last_resize,&msg,sizeof(ui_message_t));
				break;
			case ui_event_move:
				got_move = true;
				memCopy(&last_move,&msg,sizeof(ui_message_t));
				break;
			case ui_event_redraw:
				if(left > MSG_DRAW_LEFT(&msg))
					left = MSG_DRAW_LEFT(&msg);
				if(top > MSG_DRAW_TOP(&msg))
					top = MSG_DRAW_TOP(&msg);
				if(right < MSG_DRAW_RIGHT(&msg))
					right = MSG_DRAW_RIGHT(&msg);
				if(bottom < MSG_DRAW_BOTTOM(&msg))
					bottom = MSG_DRAW_BOTTOM(&msg);
				got_redraw = true;
				redraw_time = msg.time;
				break;
			default:
				fifoPush(&aw->messages,&msg);
		}
	}
	if(got_move)
		fifoPush(&aw->messages,&last_move);
	if(got_resize)
		fifoPush(&aw->messages,&last_resize);
	if(got_redraw && left < right && top < bottom)
	{
		msg.event = ui_event_redraw;
		msg.time = redraw_time;
		MSG_DRAW_LEFT(&msg) = left;
		MSG_DRAW_TOP(&msg) = top;
		MSG_DRAW_RIGHT(&msg) = right;
		MSG_DRAW_BOTTOM(&msg) = bottom;
		fifoPush(&aw->messages,&msg);
	}
}

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

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

/*
 * Returns:
 *  true - message processed;
 *	false - requested call to the next handler.
 */
V_LOCAL vboolean
win32_window_proc(arch_window_p aw,UINT msg,WPARAM wparam,LPARAM lparam,DWORD time)
{
	RECT rc;
	PAINTSTRUCT ps;
	ui_message_t ui_msg;
	arch_raw_message_t raw_msg;
	vboolean in_dispatch_message, need_post_mouse_move = false;
	MSG_SET_EMPTY(&ui_msg);
	if(!time)
	{
		in_dispatch_message = true;
		time = GetTickCount();
	}
	else
		in_dispatch_message = false;
	ui_msg.time = (vec_t)0.001 * (time - aw->creation_time);
	ui_msg.event = ui_event_unknown;
	switch(msg)
	{
		case WM_ACTIVATE:
			aw->active = LOWORD(wparam) != WA_INACTIVE;
			ui_msg.event = ui_event_activate;
			ui_msg.code[0] = aw->active ? 1 : 0;
			break;
		case WM_CLOSE:
			ui_msg.event = ui_event_close;
			break;
		case WM_PAINT:
			memZero(&rc,sizeof(RECT));
			ui_msg.event = ui_event_redraw;
			if(!GetUpdateRect(aw->wnd,&rc,FALSE))
				return true;
			BeginPaint(aw->wnd,&ps);
			if(aw->clears)
				PatBlt(aw->dc,rc.left,rc.top,
					rc.right - rc.left,rc.bottom-rc.top,BLACKNESS);
			EndPaint(aw->wnd,&ps);
			MSG_DRAW_LEFT(&ui_msg) = rc.left;
			MSG_DRAW_TOP(&ui_msg) = rc.top;
			MSG_DRAW_RIGHT(&ui_msg) = rc.right;
			MSG_DRAW_BOTTOM(&ui_msg) = 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);
			}
			ui_msg.event = ui_event_move;
			ui_msg.code[0] = (int)LOWORD(lparam);
			ui_msg.code[1] = (int)HIWORD(lparam);
			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);
			}
			ui_msg.event = ui_event_size;
			ui_msg.code[0] = (int)LOWORD(lparam);
			ui_msg.code[1] = (int)HIWORD(lparam);
			break;
		case WM_KEYDOWN:
		case WM_SYSKEYDOWN:
			ui_msg.event = ui_event_key_down;
			MSG_KEY_CODE(&ui_msg) = win32_vk2keycode(wparam,lparam);
			win32_get_key_modifiers(&ui_msg);
			win32_get_locale(&ui_msg);
			if(lparam & (1 << 30))
				MSG_KEY_FLAGS(&ui_msg) |= MSG_KEY_WAS_DOWN;
			/* TODO: rename, get rid of global input state */
			MSG_KEY_CHAR(&ui_msg) = inputKeycodeToChar2(&ui_msg);
			break;
		case WM_KEYUP:
		case WM_SYSKEYUP:
			ui_msg.event = ui_event_key_up;
			MSG_KEY_CODE(&ui_msg) = win32_vk2keycode(wparam,lparam);
			win32_get_key_modifiers(&ui_msg);
			win32_get_locale(&ui_msg);
			MSG_KEY_CHAR(&ui_msg) = 0;
			break;
		case WM_MOUSEMOVE:
			ui_msg.event = ui_event_mouse_move;
			MSG_MOUSE_X(&ui_msg) = SIGNED_LOWORD(lparam);
			MSG_MOUSE_Y(&ui_msg) = SIGNED_HIWORD(lparam);
			win32_get_key_modifiers(&ui_msg);
			win32_get_locale(&ui_msg);
			break;
		case WM_LBUTTONDOWN:
			ui_msg.event = ui_event_key_down;
			MSG_KEY_CODE(&ui_msg) = key_left_mouse;
			win32_get_key_modifiers(&ui_msg);
			win32_get_locale(&ui_msg);
			need_post_mouse_move = true;
			break;
		case WM_MBUTTONDOWN:
			ui_msg.event = ui_event_key_down;
			MSG_KEY_CODE(&ui_msg) = key_middle_mouse;
			win32_get_key_modifiers(&ui_msg);
			win32_get_locale(&ui_msg);
			need_post_mouse_move = true;
			break;
		case WM_RBUTTONDOWN:
			ui_msg.event = ui_event_key_down;
			MSG_KEY_CODE(&ui_msg) = key_right_mouse;
			win32_get_key_modifiers(&ui_msg);
			win32_get_locale(&ui_msg);
			need_post_mouse_move = true;
			break;
		case WM_LBUTTONUP:
			ui_msg.event = ui_event_key_up;
			MSG_KEY_CODE(&ui_msg) = key_left_mouse;
			win32_get_key_modifiers(&ui_msg);
			win32_get_locale(&ui_msg);
			need_post_mouse_move = false; /* hack */
			break;
		case WM_MBUTTONUP:
			ui_msg.event = ui_event_key_up;
			MSG_KEY_CODE(&ui_msg) = key_middle_mouse;
			win32_get_key_modifiers(&ui_msg);
			win32_get_locale(&ui_msg);
			need_post_mouse_move = true;
			break;
		case WM_RBUTTONUP:
			ui_msg.event = ui_event_key_up;
			MSG_KEY_CODE(&ui_msg) = key_right_mouse;
			win32_get_key_modifiers(&ui_msg);
			win32_get_locale(&ui_msg);
			need_post_mouse_move = true;
			break;
		default:
			break;
	}
	raw_msg.message = msg;
	raw_msg.wparam = wparam;
	raw_msg.lparam = lparam;
	if(aw->before_process && aw->before_process(aw,&raw_msg))
		return true;
	if(default_proc(aw,&ui_msg))
		return true;
	if(aw->after_process && aw->after_process(aw,&ui_msg))
		return true;
	if(ui_msg.event != ui_event_unknown)
	{
		fifoPush(&aw->raw_messages,&ui_msg);
		if(need_post_mouse_move)
		{
			POINT pt;
			ui_msg.event = ui_event_mouse_move;
			GetCursorPos(&pt);
			ScreenToClient(aw->wnd,&pt);
			MSG_MOUSE_X(&ui_msg) = pt.x;
			MSG_MOUSE_Y(&ui_msg) = pt.y;
			win32_get_key_modifiers(&ui_msg);
			win32_get_locale(&ui_msg);
			/* we leave ui_msg.time unmodified */
			fifoPush(&aw->raw_messages,&ui_msg);
		}
		if(!in_dispatch_message)
			return true;
	}
	return false;
}

/*
 * Default callback. All system-generated messages and messages
 * unknown to win32_window_proc() have to be processed here.
 */
V_LOCAL LRESULT CALLBACK
win32_default_window_proc(HWND wnd,UINT msg,WPARAM wparam,LPARAM lparam)
{
	arch_window_p aw = (arch_window_p)GetProp(wnd,LIBV_HANDLE_PROP);
	/* fixme: GetMessageTime() */
	if(!VALID_ARCH_WINDOW(aw) || aw->wnd != wnd)
		aw = NULL;
	switch(msg)
	{
		case WM_CREATE:
			return 0;
		case WM_PAINT:
			if(aw && win32_window_proc(aw,msg,wparam,lparam,0))
				return 0;
			break;
		case WM_ERASEBKGND:
			return 1; /* no further erasing required */
		/*
		 * These are nonqueued messages, so we have to transfer control by hand.
		 */
		case WM_ACTIVATE:
			if(aw && win32_window_proc(aw,msg,wparam,lparam,0))
				return 0;
			break;
		case WM_MOVE:
			if(aw && win32_window_proc(aw,msg,wparam,lparam,0))
				return 0;
			break;
		case WM_SIZE:
			if(aw && win32_window_proc(aw,msg,wparam,lparam,0))
				return 0;
			break;
		case WM_CLOSE:
			if(aw && win32_window_proc(aw,msg,wparam,lparam,0))
				return 0;
			break;
		default:
			break;
	}
	return DefWindowProc(wnd,msg,wparam,lparam);
}

#if 0

/*
 * This code (almost) works, also doesn't expected to; the MSDN states:
 * 	"GetMessage and PeekMessage perform some internal filtering
 * that may cause the message to be processed internally. For this reason,
 * the return value from GetQueueStatus should be considered only a hint",
 * i.e., even
 *	if(HIWORD(GetQueueStatus(QS_ALLEVENTS)))
 *		we_still_may_stuck_in_GetMessage().
 * But yet it works (except for bizzare disappearance of ui_event_close).
 */
vboolean	V_CALL
archWinPending_NoRootNeeded(arch_window_p aw)
{
	MSG msg;
	arch_window_p what_we_got;
	while(HIWORD(GetQueueStatus(QS_ALLEVENTS)))
	{
		GetMessage(&msg,NULL,0,0);
		what_we_got = (arch_window_p)GetProp(msg.hwnd,LIBV_HANDLE_PROP);
		if(!VALID_ARCH_WINDOW(what_we_got))
			what_we_got = NULL;
		/*
		 * We don't process WM_CHAR message, but this call is still
		 * needed for ALT+SPACE system menu activation and similar actions.
		 */
		TranslateMessage(&msg);
		if(!what_we_got && aw->wnd != msg.hwnd)
		{
			/* This window not ours; let Win32 dispatch the control */
			DispatchMessage(&msg);
		}
		else
		{
			if(!what_we_got) /* if(aw->wnd == msg.hwnd) */
				what_we_got = aw;
			if(!win32_window_proc(what_we_got,msg.message,msg.wParam,msg.lParam,msg.time))
				DispatchMessage(&msg);
		}
		if(what_we_got != aw)
			translate_messages(what_we_got);
	}
	translate_messages(aw);
	return fifoCount(&aw->messages) ? true : false; /* fixme: replacement needed */
}

#endif /* 0 */

/*
 * But the right way is based on the entire tree traversal.
 */

V_LOCAL void
arch_pending_for_this_window(arch_window_p aw)
{
	MSG msg;
	while(PeekMessage(&msg,aw->wnd,0,0,PM_REMOVE))
	{
		TranslateMessage(&msg);
		win32_window_proc(aw,msg.message,msg.wParam,msg.lParam,msg.time);
		/* fixme: duplication for Alt+F4 to work */
		DispatchMessage(&msg);
	}
	translate_messages(aw);
}

/* fixme: code is platform-independent */

void	V_CALL
arch_pending_for_subtree(arch_window_p aw)
{
	TREE_ITERATOR;
	TREE_FOR_EACH_CHILD_BACKWARD(aw)
	{
		arch_pending_for_subtree(TREE_NODE(arch_window_t));
	}
	arch_pending_for_this_window(aw);
}

/* fixme: code is platform-independent */

vboolean	V_CALL
archWinPending(arch_window_p aw)
{
	ARCH_CALLER_NAME(archWinPending)
	/*
	 * We haven't to stuck with unmovable/unsizeable windows, so
	 * use the opportunity to process messages for _all_ our windows.
	 */
	arch_window_p root = archGetRootWindow();
	if(!root)
	{
		/* this is strange, but at least, process for current tree */
		ARCH_WARNING("unable to get root window");
		root = aw;
		while(root->parent)
			root = root->parent;
	}
	arch_pending_for_subtree(root);
	return fifoCount(&aw->messages) ? true : false;
}

/* fixme: code is platform-independent */

vboolean	V_CALL
archWinNextMessage(arch_window_p aw,ui_message_p message_ret)
{
	while(!archWinPending(aw))
		WaitMessage();
	/* fixme: replace this with deque */
	return fifoPop(&aw->messages,message_ret);
}

/* fixme: implement archWinPeekMessage(); move it to ../common/wnl_util.c */

/* ====================================================================================== */

V_LOCAL vboolean
win32_register_window_class(arch_window_p aw,void_p icon)
{
	ARCH_CALLER_NAME(win32_register_window_class)
	strCopy(aw->class_name,"WC_CLASS_",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_default_window_proc;
	aw->wc.hIcon = (icon) ? (HICON)icon : 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;
}

V_LOCAL void_p
archLoadWindowIcon(char_p icon_id)
{
	/*
	 * FIXME: should explicitly load icon from file
	 */
	return (void_p)LoadIcon(GetModuleHandle(NULL),icon_id);
}

arch_window_p V_CALL
archWinOpen(arch_window_p parent,char *title,char *icon_id,
	s32_t left,s32_t top,s32_t width,s32_t height,
	vboolean caption,vboolean allow_resize)
{
	ARCH_CALLER_NAME(archWinOpen)
	RECT rc;
	arch_window_p aw, pseudo_parent;
	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;
	}
	aw = (arch_window_p)memAllocZ(sizeof(arch_window_t));
	if(!aw)
	{
		ARCH_ERROR("memAlloc() failed");
		return NULL;
	}
	if(parent)
		pseudo_parent = parent;
	else
	{
		pseudo_parent = archGetRootWindow();
		if(!pseudo_parent)
			pseudo_parent = archCreateRootWindow();
		if(!pseudo_parent)
		{
			ARCH_ERROR("unable to create pseudo-root window");
			memFree(aw);
			return NULL;
		}
	}
	INIT_COMMON_WINDOW_FIELDS(aw,pseudo_parent);
	fifoInit(&aw->raw_messages,sizeof(ui_message_t));
	if(!win32_register_window_class(aw,
		icon_id ? archLoadWindowIcon(icon_id) : NULL))
	{
		archWinClose(aw);
		return NULL;
	}
	if(title)
		strCopy(aw->title,title,MAX_WINDOW_TITLE);
	aw->creation_time = GetTickCount();
	aw->wnd = CreateWindow(aw->class_name,
		aw->title, style, left, top,
		width, height,
		parent ? parent->wnd : NULL,
		// BUG parent ? (HMENU)(1+vpsCount(&parent->children)) : NULL,
		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 = width - rc.right;
	aw->delta_h = height - rc.bottom;
	archWinResize(aw,width,height);
	aw->dc = GetDC(aw->wnd);
	aw->palette = NULL;
	return aw;
}

V_LOCAL void
archWinQueryVideoModes(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,
	u32_t old_width,u32_t old_height,u32_t old_bpp,u32_t old_refresh)
{
	ARCH_CALLER_NAME(win32_change_video_mode)
	if(new_mode)
	{
		DEVMODE dm;
		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");
		}
		return ChangeDisplaySettings(&dm,0);
	}
	else
		return ChangeDisplaySettings(NULL,0);
}

V_LOCAL vboolean
archChangeVideoMode(vidmode_p mode)
{
	ARCH_CALLER_NAME(archChangeVideoMode)
	HDC refdc = GetDC(NULL);
	LONG retval;
	u32_t bpp = GetDeviceCaps(refdc,BITSPIXEL);
	u32_t refresh = GetDeviceCaps(refdc,VREFRESH);
	u32_t width = GetSystemMetrics(SM_CXSCREEN);
	u32_t height = GetSystemMetrics(SM_CYSCREEN);
	ReleaseDC(NULL,refdc);
	retval = win32_change_video_mode(mode,width,height,bpp,refresh);
	if(retval == -1)
	{
		/*
		 * Try to switch away from fullscreen text mode.
		 */
		arch_window_p dummy;
		dummy = archWinOpen(NULL,"dummy",NULL,0,0,320,200,false,false);
		if(dummy)
		{
			archWinShow(dummy,true);
			archWinFocus(dummy);
			timeDelay(500);
			retval = win32_change_video_mode(mode,width,height,bpp,refresh);
			archWinClose(dummy);
		}
	}
	if(retval != DISP_CHANGE_SUCCESSFUL)
	{
		ARCH_ERROR("Unable to change video mode");
		return false;
	}
	return true;
}

/*
 * FIXME: common for both platforms
 */
vidmode_p	V_CALL
archWinGetVideoMode(arch_window_p aw,int mode_num)
{
	if(!aw->vidmodes)
		archWinQueryVideoModes(aw);
	if(!aw->vidmodes || mode_num < 0 || mode_num >= (int)aw->num_vidmodes)
		return NULL;
	return &(aw->vidmodes[mode_num]);
}

vboolean V_CALL
archWinSetVideoMode(arch_window_p aw,int new_mode)
{
	vidmode_p mode;
	if(new_mode == -1)
	{
		/* restore default mode */
		if(aw->current_vidmode != -1)
		{
			archChangeVideoMode(NULL);
			SetWindowPos(aw->wnd,HWND_NOTOPMOST,0,0,0,0,
				SWP_NOSIZE|SWP_NOMOVE);
			aw->current_vidmode = -1;
		}
		return true;
	}
	mode = archWinGetVideoMode(aw,new_mode);
	if(!mode)
		return false;
	if(archChangeVideoMode(mode))
	{
		aw->current_vidmode = new_mode;
		SetWindowPos(aw->wnd,HWND_TOP,0,0,0,0,SWP_NOSIZE|SWP_NOMOVE);
		return true;
	}
	return false;
}

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

void V_CALL
archWinFocus(arch_window_p aw)
{
	/*
	 * We process WM_SETFOCUS and WM_KILLFOCUS in win32_window_proc
	 */
	SetFocus(aw->wnd);
	SetActiveWindow(aw->wnd);
}

vboolean	V_CALL
archWinResize(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;
}

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
archWinShowPointer(arch_window_p aw,vboolean show)
{
	if(show)
	{
		aw->pointer_hide_count++;
		return (ShowCursor(TRUE) >= 0) ? true : false;
	}
	aw->pointer_hide_count--;
	return (ShowCursor(FALSE) < 0) ? true : false;
}

vboolean	V_CALL
archWinWarpPointer(arch_window_p aw,s32_t x,s32_t y)
{
	POINT pt;
	pt.x = x;
	pt.y = y;
	ClientToScreen(aw->wnd,&pt);
	return SetCursorPos(pt.x,pt.y) ? true : false;
}

void	V_CALL
archWinCapturePointer(arch_window_p aw,vboolean capture)
{
	if(capture)
	{
		if(aw->pointer_captured)
			return;
		SetCapture(aw->wnd);
		aw->pointer_captured = true;
	}
	else
	{
		if(!aw->pointer_captured)
			return;
		ReleaseCapture();
		aw->pointer_captured = false;
	}
}

void	V_CALL
archWinClose(arch_window_p aw)
{
	TREE_ITERATOR;
	if(!VALID_ARCH_WINDOW(aw))
		return;
	/*
	 * Destroy all child windows
	 */
   	TREE_FOR_EACH_CHILD(aw)
   	{
    	archWinClose(TREE_NODE(arch_window_t));
    }
   	UNLINK_TREE_NODE(aw);
	/*
	 * Show mouse pointer
	 */
	while(aw->pointer_hide_count < 0)
		archWinShowPointer(aw,true);
	/*
	 * Restore original video mode
	 */
	if(aw->current_vidmode)
		archWinSetVideoMode(aw,-1,NULL);
	/* Destroy palette */
	if(aw->palette)
	{
		SelectObject(aw->dc,GetStockObject(DEFAULT_PALETTE));
		DeleteObject(aw->palette);
	}
	if(aw->wnd)
	{
		if(aw->dc)
			ReleaseDC(aw->wnd,aw->dc);
		/* Remove LibV handle */
		RemoveProp(aw->wnd,LIBV_HANDLE_PROP);
		/* Destroy window */
		DestroyWindow(aw->wnd);
	}
	/* Unregister class */
	UnregisterClass(aw->class_name,GetModuleHandle(NULL));
	CLEAR_COMMON_WINDOW_FIELDS_EXCEPT_LINK(aw);
	fifoClear(&aw->raw_messages);
	memFree(aw);
	/* try to close root window if no more childs left */
	archTryCloseRootWindow();
}

#endif	/* ARCH_WINDOWING_SUPPORTED */
