#include <../arch/arch.h>
#include <X11/Xlib.h>
#include <X11/extensions/xf86vmode.h>

#define	ARCH_VERBOSE

typedef XEvent arch_raw_message_t, *arch_raw_message_p;

typedef struct arch_window_s
{
	ARCH_COMMON_WINDOW_FIELDS
	XSetWindowAttributes attr;
	XVisualInfo		*visinfo;
	Display			*dpy;
#ifdef	CONFIG_XF86_VIDMODE
	int				num_modelines;
	XF86VidModeModeInfo **modelines;
#endif	/* CONFIG_XF86_VIDMODE */
	Cursor			null_cursor;
	vboolean		need_free_null_cursor;
	Window 			root_window;
	int				scrnum;
	Window			win;			/* window handle */
}	arch_window_t, *arch_window_p;

arch_window_p temporary_hack = NULL; /* fixme */

arch_window_p	V_CALL
archCreateRootWindow(void)
{
	ARCH_CALLER_NAME(archCreateRootWindow)
	V_ASSERT(!temporary_hack); /* fixme */
	char *dpy_name = getenv("DISPLAY");
	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);
	aw->dpy = XOpenDisplay(dpy_name);
	if(!aw->dpy)
	{
		ARCH_SYSCALL_FAILED_1STR("XOpenDisplay",strSafe(dpy_name));
		CLEAR_COMMON_WINDOW_FIELDS(aw);
		memFree(aw);
		return NULL;
	}
	aw->scrnum = DefaultScreen(aw->dpy);
	aw->win /* fixme */ = aw->root_window = RootWindow(aw->dpy,aw->scrnum);
	aw->width = DisplayWidth(aw->dpy,aw->scrnum);
	aw->height = DisplayHeight(aw->dpy,aw->scrnum);
	aw->creation_time = archTickCount();
	strCopy(aw->title,LIBV_ROOT_WINDOW,MAX_WINDOW_TITLE);
#ifdef	CONFIG_XF86_VIDMODE
	aw->num_modelines = 0;
	aw->modelines = NULL;
#endif	/* CONFIG_XF86_VIDMODE */
	temporary_hack = aw; /* fixme */
	return aw;
}

arch_window_p
archGetRootWindow(void)
{
	ARCH_CALLER_NAME(archGetRootWindow)
 	if(temporary_hack && !VALID_ARCH_WINDOW(temporary_hack))
 	{
 		ARCH_WARNING("pseudo-root exists, but isn't linked to LibV");
 		return NULL;
 	}
 	return temporary_hack;
}

/* 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);
}

#ifdef	CONFIG_XF86_VIDMODE

V_LOCAL void
X_FreeModeInfo
{
	int i;
	if(aw->modelines)
	{
		for(i=0;i < aw->num_modelines;i++)
		{
			if(modes[i]->privsize)
#ifdef	__cplusplus
				XFree(modes[i]->c_private);
#else	/* !__cplusplus */
				XFree(modes[i]->private);
#endif	/* !__cplusplus */
		}
		XFree(aw->modelines);
		aw->modelines = NULL;
		aw->num_modelines = 0;
	}
}

#endif	/* CONFIG_XF86_VIDMODE */

/*
 * Original copyright (C) Id Software.
 */
V_LOCAL void
X_CreateNullCursor(arch_window_p aw)
{
	Pixmap cursormask;
    XGCValues xgc;
    GC gc;
    XColor dummycolour;
	if(aw->need_free_null_cursor)
	{
		XFreeCursor(aw->null_cursor);
		aw->need_free_null_cursor = false;
	}
    cursormask = XCreatePixmap(aw->dpy, aw->root, 1, 1, 1/*depth*/);
    xgc.function = GXclear;
    gc =  XCreateGC(aw->dpy, cursormask, GCFunction, &xgc);
    XFillRectangle(aw->dpy, cursormask, gc, 0, 0, 1, 1);
    dummycolour.pixel = 0;
    dummycolour.red = 0;
    dummycolour.flags = 04;
    aw->null_cursor = XCreatePixmapCursor(aw->dpy, cursormask, cursormask,
		&dummycolour,&dummycolour, 0,0);
    XFreePixmap(aw->dpy,cursormask);
    XFreeGC(aw->dpy,gc);
	aw->need_free_null_cursor = true;
}

V_LOCAL void
archWinQueryVideoModes(arch_window_p aw)
{
	ARCH_CALLER_NAME(archWinQueryVideoModes)
#ifdef	CONFIG_XF86_VIDMODE
	u32_t result;
	int i, op, event, error, mn, mj;
	vidmode_p ptr;
	if(aw->vidmodes)
	{
		memFree(aw->vidmodes);
		aw->vidmodes = NULL;
	}
	aw->num_vidmodes = 0;
	if(!XQueryExtension(ctx->dpy,
		"XFree86-VidModeExtension",&op,&event, &error) ||
		!XF86VidModeQueryVersion(aw->dpy,&mj,&mn))
	{
		ARCH_ERROR("XFree86-VidModeExtension not supported by X server");
		return;
	}
	if(XQueryExtension(ctx->dpy,"XINERAMA",&op,&event, &error))
	{
		ARCH_ERROR("found XINERAMA; disabling mode changes.");
		return;
	}
	X_FreeModeInfo(aw);
	if(!XF86VidModeGetAllModeLines(dpy,DefaultScreen(dpy),
		&aw->num_modelines,&aw->modelines)
		|| aw->num_modelines <= 0 || !aw->modelines)
	{
		ARCH_ERROR("XF86VidModeGetAllModeLines() failed.");
		return;
	}
	aw->vidmodes = (vidmode_p)memAllocZ(num_modes * sizeof(vidmode_t));
	ptr = aw->vidmodes;
	for(i=0;i < aw->num_modelines;i++)
	{
		ptr->width = aw->modelines[i]->hdisplay;
		ptr->height = aw->modelines[i]->vdisplay;
		ptr->bpp = DefaultDepth(aw->dpy,DefaultScreen(aw->dpy));
		ptr->refresh = aw->modelines[i]->dotclock * 1000 / 
			(aw->modelines[i]->htotal * aw->modelines[i]->vtotal);
		ptr->modeline = i;
		if(modes[i]->flags & 0x20) /* doublescan */
			ptr->refresh /= 2;
		ptr++;
	}
#else	/* !CONFIG_XF86_VIDMODE */
	ARCH_ERROR("requires CONFIG_XF86_VIDMODE");
	return;
#endif	/* !CONFIG_XF86_VIDMODE */
}

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

vboolean V_CALL
archWinSetVideoMode(arch_window_p aw,int new_mode)
{
	ARCH_CALLER_NAME(archWinSetVideoMode)
	if(new_mode == -1)
	{
		/* restore default mode */
		if(aw->current_vidmode != -1)
		{
			XF86VidModeSwitchToMode(aw->dpy,aw->scrnum,aw->modelines[0]);
			aw->current_vidmode = -1;
		}
		result = true;
	}
	else
	{
		vidmode_p mode = archWinGetVideoMode(aw,new_mode);
		if(!mode)
			return false;
		if(XF86VidModeSwitchToMode(aw->dpy,aw->scrnum,
			aw->modelines[mode->modeline]))
		{
			aw->current_vidmode = new_mode;
			/*
			SetWindowPos(aw->wnd,HWND_TOP,0,0,0,0,SWP_NOSIZE|SWP_NOMOVE);
			*/
			result = true;
		}
		else
		{
			ARCH_ERROR("XF86VidModeSwitchToMode() failed.");
			return false;
		}
	}
	if(palette_opt)
	{
		/* TODO */


		/* quake */
		XColor colors[256];
		for(i=0 ; i<256 ; i++)
		{
			colors[i].pixel = i;
			colors[i].flags = DoRed | DoGreen | DoBlue;
			colors[i].red = palette_opt[i*3] * 257;
			colors[i].green = palette_opt[i*3+1] * 257;
			colors[i].blue = palette_opt[i*3+2] * 257;
		}
		XStoreColors(aw->dpy,aw->cmap,colors,256);
		int attribmask = CWEventMask  | CWColormap | CWBorderPixel;
		XSetWindowAttributes attribs;
		Colormap tmpcmap;
		tmpcmap = XCreateColormap(x_disp, XRootWindow(x_disp,
		x_visinfo->screen), x_vis, AllocNone);
		attribs.colormap = tmpcmap;
		x_win = XCreateWindow(x_disp,XRootWindow(x_disp, x_visinfo->screen),
			0, 0,	// x, y
			vid.width, vid.height,
			0, // borderwidth
			x_visinfo->depth,
			InputOutput,
			x_vis,
			attribmask,
			&attribs );
		XStoreName( x_disp,x_win,"xquake");


		if (x_visinfo->class != TrueColor)
			XFreeColormap(x_disp, tmpcmap);
	}

	if (x_visinfo->depth == 8)
	{
		// create and upload the palette
		if (x_visinfo->class == PseudoColor)
		{
			
		x_cmap = XCreateColormap(x_disp, x_win, x_vis, AllocAll);			
		VID_SetPalette(palette);
				
		if(aw->palette)
		{
			...
			aw->palette = NULL;
		}
		aw->palette = XCreateColormap(...);
		XSetWindowColormap(aw->dpy,aw->win,aw->palette);
	}
	return result;
}


	return false;
}

/*
 * Translates wParam and lParam into LibV key code.
 */
V_LOCAL	s32_t
X2keycode()
{
	return key;
}

/*
 * Retrieves information about modifier keys.
 */

V_LOCAL	void
X_get_locale(ui_message_p msg)
{
	MSG_KEY_LOCALE(msg) = MSG_LOCALE_EN;
}

V_LOCAL void
win32_get_key_modifiers(ui_message_p msg)
{
	// XQueryKeymap
	/*
	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->vidmode_changed)
			{
				if(aw->active)
				{
					if(aw->need_restore_vidmode)
					{
						archSetVideoMode(&aw->last_vidmode,NULL);
						aw->need_restore_vidmode = false;
					}
					SetWindowPos(aw->wnd,HWND_TOP,0,0,0,0,
						SWP_NOSIZE|SWP_NOMOVE);
				}
				else
				{
					archSetVideoMode(&aw->first_vidmode,NULL);
					aw->need_restore_vidmode = true;
					SetWindowPos(aw->wnd,HWND_NOTOPMOST,0,0,0,0,
						SWP_NOSIZE|SWP_NOMOVE);
				}
			}
			break;
		case ui_event_close:
			if(aw->vidmode_changed)
			{
				archSetVideoMode(&aw->first_vidmode,NULL);
				aw->vidmode_changed = false;
				/*
				SetWindowPos(aw->wnd,HWND_NOTOPMOST,0,0,0,0,
					SWP_NOSIZE|SWP_NOMOVE);
				*/
			}	
			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 vboolean
X_window_proc(arch_window_p aw,...,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;
	memZero(&ui_msg,sizeof(ui_message_t));
	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;
			/*
			 * FIXME: we leave global input_state untouched (should get rid of it).
			 * Implement another inputKeycodeToChar() in incodes.c.
			 */
			MSG_KEY_CHAR(&ui_msg) = inputKeycodeToChar(MSG_KEY_CODE(&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 = true;
			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)GetWindowLong(wnd,GWL_USERDATA);
	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 */
		case WM_PALETTECHANGED:
			if(aw && aw->palette && aw->wnd != (HWND)wparam)
			{
				SelectPalette(aw->dc,aw->palette,FALSE);
				RealizePalette(aw->dc);
			}
			break;
		case WM_QUERYNEWPALETTE:
			if(aw && aw->palette)
			{
				SelectPalette(aw->dc,aw->palette,FALSE);
				RealizePalette(aw->dc);
				if(aw->vidmode_changed)
					SetSystemPaletteUse(aw->dc,SYSPAL_NOSTATIC);
				return TRUE;
			}
			return FALSE;
		/*
		 * PeekMessage() _never_ (???) retrieves these messages,
		 * so we 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);
}

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);
	}
}

vboolean	V_CALL
archWinPending(arch_window_p aw)
{
	MSG msg;
	while(PeekMessage(&msg,aw->wnd,0,0,PM_REMOVE))
	{
		/*
		 * We don't process WM_CHAR message, but this call is still
		 * needed for ALT+SPACE system menu activation.
		 */
		TranslateMessage(&msg);
		if(win32_window_proc(aw,msg.message,msg.wParam,msg.lParam,msg.time))
			DefWindowProc(msg.hwnd,msg.message,msg.wParam,msg.lParam);
		else
			DispatchMessage(&msg);
		translate_messages(aw);
	}
	return fifoCount(&aw->messages) ? true : false;
}

vboolean	V_CALL
archWinNextMessage(arch_window_p aw,ui_message_p message_ret)
{
	while(!archWinPending(aw))
		WaitMessage();
	return fifoPop(&aw->messages,message_ret);
}

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

V_LOCAL vboolean
win32_register_window_class(arch_window_p aw,void_p icon)
{
	ARCH_CALLER_NAME(win32_register_window_class)
	return false;
}

void_p	V_CALL
archLoadWindowIcon(char_p icon_id)
{
	return (void_p)LoadIconImage(icon_id);
}

arch_window_p V_CALL
archWinOpen(arch_window_p parent,char *title,void_p icon,
	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;
	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)memAlloc(sizeof(arch_window_t));
	if(!aw)
	{
		ARCH_ERROR("memAlloc() failed");
		return NULL;
	}
	INIT_COMMON_WINDOW_FIELDS(aw);
	fifoInit(&aw->raw_messages,sizeof(ui_message_t));
	if(!win32_register_window_class(aw,icon))
	{
		CLEAR_COMMON_WINDOW_FIELDS(aw);
		memFree(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,
		parent ? (HMENU)(1+vpsCount(&parent->children)) : NULL,
		GetModuleHandle(NULL),NULL);
	if(!aw->wnd)
	{
		SYSCALL_FAILED("CreateWindow");
		CLEAR_COMMON_WINDOW_FIELDS(aw);
		memFree(aw);
		return NULL;
	}
	SetWindowLong(aw->wnd,GWL_USERDATA,(LONG)aw);
	GetClientRect(aw->wnd,&rc);
	aw->delta_w = width - rc.right;
	aw->delta_h = height - rc.bottom;
	archWinResize(aw,width,height);
	if(parent)
	{
		vpsPush(&parent->children,aw);
		aw->child_id = vpsCount(&parent->children);
		aw->parent = parent;
	}
	aw->dc = GetDC(aw->wnd);
	aw->palette = NULL;
	return aw;
}

vboolean V_CALL
archWinVideoMode(arch_window_p aw,vidmode_p new_mode,colorquad_p palette)
{
	vboolean result = true;
	vidmode_p old_mode;
	if(!new_mode)
	{
		if(aw->vidmode_changed)
		{
			archSetVideoMode(&aw->first_vidmode,NULL);
			aw->vidmode_changed = false;
		}
		result = true;
	}
	else
	{
		if(aw->vidmode_changed)
			old_mode = NULL;
		else
			old_mode = &aw->first_vidmode;
		if(archSetVideoMode(new_mode,old_mode))
		{
			aw->vidmode_changed = true;
			result = true;
		}
		else
			result = false;
	}
	if(palette)
	{
		LOGPALETTE *lp;
		unsigned int i, n, bpp = GetDeviceCaps(aw->dc,BITSPIXEL);
		n = 1 << bpp;
		lp = (PLOGPALETTE)memAlloc(sizeof(LOGPALETTE) + n * sizeof(PALETTEENTRY));
		lp->palVersion=0x300;
		lp->palNumEntries = (unsigned short)n;
		for(i=0;i < n;i++)
		{
			lp->palPalEntry[i].peRed = palette[i].r;
			lp->palPalEntry[i].peGreen = palette[i].g;
			lp->palPalEntry[i].peBlue = palette[i].b;
			lp->palPalEntry[i].peFlags = 0;
		}
		if(aw->vidmode_changed)
			SetSystemPaletteUse(aw->dc,SYSPAL_NOSTATIC);
		if(aw->palette)
		{
			SelectObject(aw->dc,GetStockObject(DEFAULT_PALETTE));
			DeleteObject(aw->palette);
			aw->palette = NULL;
		}
		aw->palette = CreatePalette(lp);
		memFree(lp);
		SelectPalette(aw->dc,aw->palette,FALSE);
		RealizePalette(aw->dc);
	}
	return result;
}

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
archWinWarpPointer(arch_window_p aw,s32_t x,s32_t y)
{
	XWarpPointer(aw->dpy,None,ar->win, 0, 0, 0, 0, x, y);
}

void	V_CALL
archWinCapturePointer(arch_window_p aw,vboolean capture)
{
	if(capture)
	{
		if(!aw->pointer_captured)
		{
			XGrabPointer(aw->dpy,aw->win,True,0,GrabModeAsync,
				GrabModeAsync,aw->win,None,CurrentTime);
			aw->pointer_captured = true;
		}
	}
	else
	{
		if(aw->pointer_captured)
		{
			XUngrabPointer(aw->dpy,CurrentTime);
			aw->pointer_captured = false;
		}
	}
}

void	V_CALL
archWinShowPointer(arch_window_p aw,vboolean show)
{
	if(show)
	{
		if(aw->_hidden)
		{
			XUndefineCursor(win->dpy,win->win);
			win->cursor_hidden = false;
		}
	}
	else
	{
		if(!win->cursor_hidden)
		{
			XDefineCursor(win->dpy,win->win,win->null_cursor);
			win->cursor_hidden = true;
		}
	}



void V_CALL
archCaptureKeyboard(arch_window_p win,vboolean capture)
{
	if(capture)
	{
		if(!win->keyboard_captured)
		{
			XGrabKeyboard(win->dpy,win->win,False,GrabModeAsync,
				GrabModeAsync,CurrentTime);
			win->keyboard_captured = true;
		}
	}
	else
	{
		if(win->keyboard_captured)
		{
			XUngrabKeyboard(win->dpy,CurrentTime);
			win->keyboard_captured = false;
		}
	}
}

void	V_CALL
archWinClose(arch_window_p aw)
{
	ui_message_t msg;
	arch_window_p child;
	if(!VALID_ARCH_WINDOW(aw))
		return;
	/*
	 * Destroy all child windows
	 */
	while(vpsPop(&aw->children,(void**)(&child)))
		if(VALID_ARCH_WINDOW(child))
			archWinClose(child);
	/*
	 * If window has a parent, remove it from parent's list
	 */
	if(aw->parent)
	{
		vsize_t i;
		vp_stack_p owner_list = &(aw->parent->children);
		for(i=0;i<vpsCount(owner_list);i++)
			if(vpsGet(owner_list,i) == aw)
				vpsSet(owner_list,i,NULL);
		/* archWinPostMessage(aw->parent,ui_event_child_closed,0,0,0,0); */
	}
	if(aw->vidmode_changed)
	{
		archSetVideoMode(&aw->first_vidmode,NULL);
		aw->vidmode_changed = false;
		SetWindowPos(aw->wnd,HWND_NOTOPMOST,0,0,0,0,SWP_NOSIZE|SWP_NOMOVE);
	}	
	/* Destroy palette */
	if(aw->palette)
	{
		SelectObject(aw->dc,GetStockObject(DEFAULT_PALETTE));
		DeleteObject(aw->palette);
	}
	ReleaseDC(aw->wnd,aw->dc);
	/* Destroy window */
	DestroyWindow(aw->wnd);
	/* Process all pending messages */
	while(archWinPending(aw))
		archWinNextMessage(aw,&msg);
	/* Unregister class */
	UnregisterClass(aw->class_name,GetModuleHandle(NULL));
	CLEAR_COMMON_WINDOW_FIELDS(aw);
	fifoClear(&aw->raw_messages);
	memFree(aw);
}
