#include <../source/TAL/objbase.h>
#include <../source/PAL/oshead.h>
#include "v_gr.h"

#error "complete me i'm good and usefull"

#define	UI_MESSAGE_MAX_CODES	2

typedef struct ui_message_s
{
	u32_t	event;
	u32_t	code[UI_MESSAGE_MAX_CODES];
	vec_t	posting_time; /* time since window creation */
} ui_message_t, *ui_message_p;

void_p		V_GLOBAL
archLoadWindowIcon(void_p id);

void_p		V_GLOBAL
archWarpPointer(s32_t screen_x,s32_t screen_y);

void_p		V_GLOBAL
archShowPointer(vboolean show);

arch_window_p	V_GLOBAL
archCreateWindow(arch_window_p parent,char *title,void_p icon,
	s32_t left,s32_t top,s32_t right,s32_t bottom,
	vboolean allow_border,vboolean allow_resize);

vboolean	V_GLOBAL
archWindowPending(arch_window_p aw);

vboolean	V_GLOBAL
archWindowNextMessage(arch_window_p aw);

vboolean	V_GLOBAL
archWindowResize(arch_window_p aw,s32_t width,s32_t height);

vboolean	V_GLOBAL
archWindowMove(arch_window_p aw,s32_t left,s32_t top);

/*
 * Platform - depenent window management.
 * If callback_chain != NULL, it will be called by window procedure
 * after all message translations.
 */
typedef struct win32_window_s
{
	void			(V_CALL *callback_chain)(void *ptr);
	void			*callback_chain_arg;

	char			class_name[64];
	WNDCLASS		wc;
	HWND			parent;			/* != NULL for child windows */
	char			title[128];		/* window title */
	HWND			wnd;			/* window handle */
	u32_t			width, height;
	BOOL			focused;		/* activity flag (focused) */
	HDC				dc;				/* window of bitmap DC */
	fifo_t			mqueue;			/* message queue */
	vboolean		mouse_captured;
	vboolean		fullscreen;		/* true if dimensions of backbuffers matches screen size */
	/* rest is HACKS ... */
	UINT			last_msg;			/* used to store last msg */
	s32_t			last_mx, last_my;	/* used to store last mousemove */
}	win32_window_t, *win32_window_p;

typedef struct vgr_output_s
{
	libv_object_t	base;
	/*
	 * Common part
	 */
	int				shift, pitch;
	void			*backbuffer;
	void			**vtab;
	vboolean		locked;			/* !=0 mean that backbuffer is in use */
	/*
	 * Win32 part
	 */
	win32_window_t oswnd;
	/*
	 * Parameters of backbuffer
	 */
	BITMAPINFOHEADER header;
}	vgr_output_t, *vgr_output_p;

static	vhandle_t gr_output = NULL;

/*
 * Platform - specific window management
 */
 
/*
 * Translates wParam and lParam into LibV key code.
 */
V_LOCAL	keycode_t
W_KeycodeForWL(u32_t wp,u32_t lp)
{
	keycode_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_grey_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_grey_slash; break;
		case 106:		key=key_grey_asterisk; break;
		case 109:		key=key_grey_minus; break;
		case 107:		key=key_grey_plus; break;
		default:
			switch(scan)
			{
				case 71:	key=(ext) ? key_home : key_numpad_7; break;
				case 72:	key=(ext) ? key_up : key_numpad_8; break;
				case 73:	key=(ext) ? key_pageup : key_numpad_9; break;
				case 75:	key=(ext) ? key_left : key_numpad_4; break;
				case 76:	key=key_numpad_5; break;
				case 77:	key=(ext) ? key_right : key_numpad_6; break;
				case 79:	key=(ext) ? key_end : key_numpad_1; break;
				case 80:	key=(ext) ? key_down : key_numpad_2; break;
				case 81:	key=(ext) ? key_pagedown : key_numpad_3; break;
				case 82:	key=(ext) ? key_insert : key_numpad_0; break;
				case 83:	key=(ext) ? key_delete : key_numpad_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
W_MakeInputState(void)
{
	u32_t lang_id;
	BYTE keys[256];
	input_state_t *is = inputState();
	lang_id = (UINT)GetKeyboardLayout(0) & 0xFF;
	if(lang_id == LANG_RUSSIAN)
    	is->locale = input_locale_russian;
	else
    	is->locale = input_locale_english;
	GetKeyboardState(keys);
	is->numlock=keys[VK_NUMLOCK] & 1 ? true : false;
	is->capslock=keys[VK_CAPITAL] & 1 ? true : false;
	is->scrolllock=keys[VK_SCROLL] & 1 ? true : false;
    is->control=keys[VK_CONTROL] & 0x80 ? true : false;
    is->shift=keys[VK_SHIFT] & 0x80 ? true : false;
    is->meta=keys[VK_MENU] & 0x80 ? true : false;
}

/*
 * Process message, updates message queue.
 * Returns ???
 */
V_LOCAL	vboolean
W_ProcessMessage(win32_windows_p oswnd,UINT msg,WPARAM wparam,LPARAM lparam)
{
	input_record_t ev;
	vboolean mouse_key_event = false;
	
	switch(msg)
	{
		case WM_SYSKEYDOWN:
		case WM_KEYDOWN:
#ifndef		ENABLE_AUTOREPEAT
			if(lparam & 0x40000000)
				return true;
#endif		/* ENABLE_AUTOREPEAT */
			ev.event=ev_key_down;
			ev.first_code=keycodeWin32Code(wparam,lparam);
            W_MakeInputState();
			ev.second_code=inputKeycodeToChar((keycode_t)ev.first_code);
			/* TODO: workaround Win32 bug with left & right control keys */
			break;
		case WM_KEYUP:
		case WM_SYSKEYUP:
			ev.event=ev_key_up;
			ev.first_code=keycodeWin32Code(wparam,lparam);
			ev.second_code=0;
			/* TODO: workaround Win32 bug with left & right control keys */
			break;
		case WM_MOUSEMOVE:
			ev.event=ev_mouse_move;
			ev.first_code=LOWORD(lparam);	/* TODO: SIGN_EXTEND(...) */
			ev.second_code=HIWORD(lparam);
			if(ev.first_code & 0x8000)
				ev.first_code|=0xFFFF0000;
			if(ev.second_code & 0x8000)
				ev.second_code|=0xFFFF0000;
        	if(oswnd->mouse_captured)
            {
            	s32_t cx = oswnd->width / 2;
            	s32_t cy = oswnd->height / 2;
				/* Avoid recursion */
				if(ev.first_code == cx && ev.second_code == cy)
					return true;
            	/* Store deltas */
				ev.first_code -= cx;
				ev.second_code -= cy;
				SetCursorPos(cx,cy);
            }
#ifndef		DISABLE_MOUSE_FILTERING
			if(oswnd->last_mx == ev.first_code &&
				oswnd->last_my == ev.second_code)
				return true;
			oswnd->last_mx = ev.first_code;
			oswnd->last_my = ev.second_code;
#endif		/* !DISABLE_MOUSE_FILTERING */
			break;
		case WM_LBUTTONDOWN:
			ev.event=ev_key_down;
			ev.first_code=key_left_mouse;
			ev.second_code=0;
			mouse_key_event=true;
			break;
		case WM_MBUTTONDOWN:
			ev.event=ev_key_down;
			ev.first_code=key_middle_mouse;
			ev.second_code=0;
			mouse_key_event=true;
			break;
		case WM_RBUTTONDOWN:
			ev.event=ev_key_down;
			ev.first_code=key_right_mouse;
			ev.second_code=0;
			mouse_key_event=true;
			break;
		case WM_LBUTTONUP:
			ev.event=ev_key_up;
			ev.first_code=key_left_mouse;
			ev.second_code=0;
			mouse_key_event=true;
			break;
		case WM_MBUTTONUP:
			ev.event=ev_key_up;
			ev.first_code=key_middle_mouse;
			ev.second_code=0;
			mouse_key_event=true;
			break;
		case WM_RBUTTONUP:
			ev.event=ev_key_up;
			ev.first_code=key_right_mouse;
			ev.second_code=0;
			mouse_key_event=true;
			break;
		default:
			return false;
	}
	fifoPush(&oswnd->mqueue,&ev);
	if(oswnd->parent)
		PostMessage(oswnd->parent,msg,wparam,lparam);
	/*
	 * Post mouse_move event after mouse press/release events.
	 */
	if(mouse_key_event)
	{
		ev.first_code=LOWORD(lparam);
		ev.second_code=HIWORD(lparam);
		if(ev.first_code & 0x8000)
			ev.first_code|=0xFFFF0000;
		if(ev.second_code & 0x8000)
			ev.second_code|=0xFFFF0000;
       	if(oswnd->mouse_captured)
        {
           	/* Store deltas */
			ev.first_code -= oswnd->width / 2;
			ev.second_code -= oswnd->height / 2;
        }
        else
        {
			if(ev.event==ev_key_down)
			{
				SetCapture(oswnd->wnd);
				SetFocus(oswnd->wnd);	/* Is this still needed ? */
			}
			else
				ReleaseCapture();
		}
		ev.event=ev_mouse_move;
		fifoPush(&oswnd->mqueue,&ev);
	}
	return true;
}

/*
 * Callback for all windows
 */
LRESULT CALLBACK
__win32_window_proc(HWND wnd,UINT msg,WPARAM wparam,LPARAM lparam)
{
	win32_window_p oswnd;
	input_record_t ev;
	PAINTSTRUCT ps;
	LRESULT result = 0;
	oswnd = (win32_window_p)GetWindowLong(wnd,GWL_USERDATA);
	/* Supress warning message */
	if(!oswnd)
		return DefWindowProc(wnd,msg,wparam,lparam);
	switch(msg)
	{
		case WM_ACTIVATE:
			oswnd->focused = LOWORD(wparam) != WA_INACTIVE;
			/* Extra stuff for fullscreen windows */
			if(oswnd->fullscreen)
			{
				/* TODO: implement video mode switching */
				if(oswnd->focused)
					SetWindowPos(oswnd->wnd,HWND_TOP,0,0,0,0,
						SWP_NOSIZE|SWP_NOMOVE);
				else
					SetWindowPos(oswnd->wnd,HWND_NOTOPMOST,0,0,0,0,
						SWP_NOSIZE|SWP_NOMOVE);
			}
			break;
		case WM_CLOSE:
			break;
		case WM_PAINT:
			BeginPaint(oswnd->wnd,&ps);
			EndPaint(oswnd->wnd,&ps);
			if(oswnd->last_msg != WM_PAINT)
			{
				ev.event = ev_paint;
				ev.first_code = 0;
				ev.second_code = 0;
				fifoPush(&oswnd->mqueue,&ev);
				if(oswnd->parent)
					PostMessage(oswnd->parent,msg,wparam,lparam);
			}
			break;
		case WM_MOVE:
		{
			oswnd->x=(u32_t)LOWORD(lparam);
			oswnd->y=(u32_t)HIWORD(lparam);
			ev.event=ev_move;
			ev.first_code=oswnd->x;
			ev.second_code=oswnd->y;
			fifoPush(&oswnd->mqueue,&ev);
			if(oswnd->parent)
				PostMessage(oswnd->parent,msg,wparam,lparam);
			break;
		}
		case WM_SIZE:
		{
			oswnd->width=(u32_t)LOWORD(lparam);
			oswnd->height=(u32_t)HIWORD(lparam);
			ev.event=ev_size;
			ev.first_code=oswnd->width;
			ev.second_code=oswnd->height;
			fifoPush(&oswnd->mqueue,&ev);
			if(oswnd->parent)
				PostMessage(oswnd->parent,msg,wparam,lparam);
			break;
		}
		case WM_PALETTECHANGED:
		{
			if(wnd!=(HWND)wparam)
			{
				SelectPalette(oswnd->dc,oswnd->pal,FALSE);
				RealizePalette(oswnd->dc);
			}
			break;
		}
		case WM_QUERYNEWPALETTE:
		{
			SelectPalette(oswnd->dc,oswnd->pal,FALSE);
			RealizePalette(oswnd->dc);
			if(oswnd->fullscreen)
				SetSystemPaletteUse(oswnd->dc,SYSPAL_NOSTATIC);
			result = TRUE;
			break;
		}
		default:
		{
			if(W_ProcessMessage(oswnd,msg,wparam,lparam))
				break;
			result = DefWindowProc(wnd,msg,wparam,lparam);
		}
	}
	oswnd->last_msg=msg;
	if(oswnd->callback_chain)
		oswnd->callback_chain(oswnd->callback_chain_arg);
	return result;
}

/*
 * Registers Win32 window class.
 */
V_LOCAL vboolean
W_RegisterWindowClass(win32_window_p oswnd)
{
	static u32_t context_count;
	sprintf(oswnd->class_name,"WC_CLASS_%0004X",context_count++);
	memZero(&oswnd->wc,sizeof(WNDCLASS));
	oswnd->wc.lpszClassName=oswnd->class_name;
	oswnd->wc.hInstance=GetModuleHandle(NULL);
	oswnd->wc.style = CS_CLASSDC | CS_NOCLOSE;
	oswnd->wc.lpfnWndProc=__win32_window_proc;
	oswnd->wc.hIcon = LoadIcon(NULL,IDI_WINLOGO);
	oswnd->wc.hCursor = LoadCursor(NULL, IDC_ARROW);
	oswnd->wc.hbrBackground = GetStockObject(HOLLOW_BRUSH);
	oswnd->wc.lpszMenuName=NULL;
	if(RegisterClass(&oswnd->wc))
		return true;
#ifdef	CHECKED_BUILD
	conlPrintf(LOG_ERROR,
		"W_RegisterWindowClass(): RegisterClass() failed (code %d).\n",
		GetLastError());
#endif	/* CHECKED_BUILD */
	return false;
}


TODO


/*
 *	Changes screen resolution, display depth and, under NT,
 *	display refresh rate.
 */
vboolean	V_LOCAL
W_SetVideoMode(gl_context_t *ctx,u32_t cur_sx,u32_t cur_sy,u32_t cur_bpp,u32_t cur_refresh)
{
	DEVMODE dm;
	LONG retval;
	if(ctx->vidmode_active)
		return true;
	if(ctx->width == cur_sx && ctx->height == cur_sy && ctx->bpp == cur_bpp)
		return true;
	memZero(&dm,sizeof(dm));
	dm.dmSize=sizeof(dm);
	dm.dmPelsWidth=ctx->width;
	dm.dmPelsHeight=ctx->height;
	dm.dmBitsPerPel=ctx->bpp;
	dm.dmFields=DM_PELSWIDTH | DM_PELSHEIGHT | DM_BITSPERPEL;
	if(ntGetVersion() > 0)
	{
		if(ctx->width <= cur_sx && ctx->height <= cur_sy && !ctx->refresh)
			ctx->refresh = cur_refresh;
		else
			ctx->refresh = 75;
		dm.dmDisplayFrequency=ctx->refresh;
		dm.dmFields |= DM_DISPLAYFREQUENCY;
	}
	retval=ChangeDisplaySettings(&dm,0);
	if(retval == DISP_CHANGE_SUCCESSFUL)
		ctx->vidmode_active = true;
#ifndef	CHECKED_BUILD
	return ctx->vidmode_active;
#else	/* CHECKED_BUILD */
	switch(retval)
	{
		case DISP_CHANGE_SUCCESSFUL:
			conlPuts(LOG_INFO,"success.\n");
			return true;
		case DISP_CHANGE_RESTART:
			conlPuts(LOG_INFO,"failed, restart required.\n");
			break;
		case DISP_CHANGE_BADMODE:
			conlPuts(LOG_INFO,"failed, bad mode.\n");
			break;
		case DISP_CHANGE_BADFLAGS:
			conlPuts(LOG_INFO,"failed, bad flags.\n");
			break;
		case DISP_CHANGE_FAILED:
			conlPuts(LOG_INFO,"failed, driver error.\n");
			break;
		default:
			conlPrintf(LOG_INFO,"failed, error code %d.\n",retval);
			break;
	}
	return false;
#endif	/* CHECKED_BUILD */
}

/*
 * Returns current display settings
 */
void	V_LOCAL
W_GetVideoMode(u32_t *sx,u32_t *sy,u32_t *sbpp,u32_t *refresh)
{
	HDC refdc = GetDC(NULL);
	*sbpp = GetDeviceCaps(refdc,BITSPIXEL);
	*refresh = GetDeviceCaps(refdc,VREFRESH);
	ReleaseDC(NULL,refdc);
	*sx = GetSystemMetrics(SM_CXSCREEN);
	*sy = GetSystemMetrics(SM_CYSCREEN);
}

/*
 *	Sets default display settings.
 */
void	V_LOCAL
W_SetDefaultMode(gl_context_t *ctx)
{
	/*	FIXME: we must switch from full-screen text mode */
	ChangeDisplaySettings(NULL,0);
	ctx->vidmode_active=false;
}

/*
 *	Creates OpenGL windowed context.
 *	Returns context on success, NULL on error.
 */
gl_context_p V_LOCAL
vglCreateWindowedContext(gl_context_t *ctx)
{
	u32_t sx, sy;
	if(!vglRegisterWindowClass(ctx))
	{
		vglDestroyWindow((vhandle_t)ctx);
		return NULL;
	}
	if(ctx->fullscreen)
	{
		u32_t sbpp, refresh;
		W_GetVideoMode(&sx,&sy,&sbpp,&refresh);
		ctx->wnd=CreateWindow(
			ctx->class_name,
			ctx->title,
			WS_POPUP,
			0,0,
			sx,sy,
			NULL,
			NULL,
			GetModuleHandle(NULL),
			NULL);
		if(!ctx->wnd)
		{
			/* Can't create window */
#ifdef		CHECKED_BUILD
			conlPrintf(LOG_ERROR,"vglCreateWindowedContext(): "
				"CreateWindow() failed (code %d).\n",GetLastError());
#endif		/* CHECKED_BUILD */
			memFreeObject(ctx);
			return NULL;
		}
		ShowWindow(ctx->wnd,SW_SHOWNORMAL);
		ctx->dc=GetDC(ctx->wnd);
		/*	under Chicago we can't change bpp; under Nashvile and
		 *	Memphis we can, but can't set refresh rate ...
		 */
		if(ctx->bpp==0 || ntGetVersion()==-1)
		{
#ifdef		CHECKED_BUILD
			conlPrintf(LOG_INFO,
				"vglCreateWindowedContext(): %susing desktop display "
				"depth %d.\n",
				ntGetVersion()==-1 ? "Windows 95/98 detected, " : "",
				sbpp);
#endif		/* CHECKED_BUILD */
			ctx->bpp=sbpp;
		}
		if(ntGetVersion()==-1)
		{
#ifdef		CHECKED_BUILD
			conlPrintf(LOG_INFO,
				"vglCreateWindowedContext(): can't set change refresh "
				"rate (%d Hz) under Windows 9x.\n",ctx->refresh);
#endif		/* CHECKED_BUILD */
			ctx->refresh=0;
		}
		SetWindowLong(ctx->wnd,GWL_USERDATA,(LONG)ctx);
		if(ctx->width<=0 || ctx->height<=0)
		{
#ifdef		CHECKED_BUILD
			conlPrintf(LOG_INFO,
				"vglCreateWindowedContext(): using desktop dimensions "
				"%d by %d.\n",sx,sy);
#endif		/* CHECKED_BUILD */
			ctx->width=sx;
			ctx->height=sy;
		}
		if(sbpp!=ctx->bpp || sx!=ctx->width || sy!=ctx->height)
		{
			/* Change screen resolution */
#ifdef		CHECKED_BUILD
			if((ctx->width>sx || ctx->height>sy) && ctx->refresh>=refresh)
				conlPrintf(LOG_WARNING,
					"vglCreateWindowedContext(): refresh rate %dHz may "
					"be to high (current is %dHz).",ctx->refresh,refresh);
			conlPrintf(LOG_INFO,
				"vglCreateWindowedContext(): "
				"calling CDS(%d,%d,%d,%d Hz) ... ",
				ctx->width,ctx->height,ctx->bpp,ctx->refresh);
#endif		/* CHECKED_BUILD */
			if(!W_SetVideoMode(ctx,sx,sy,sbpp,refresh))
			{
				/* Stay quiet: vidSetMode() already report error */
				DestroyWindow(ctx->wnd);
				memFreeObject(ctx);
				return NULL;
			}
			/* Resize window */
			SetWindowPos(ctx->wnd,HWND_NOTOPMOST,0,0,ctx->width,ctx->height,
				SWP_NOZORDER);
		}
	}
	else	/* top-level/child (not fullscreen) */
	{
		if(ctx->parent)
		{
			/* Create child window */
			ctx->wnd=CreateWindow(
				ctx->class_name,
				ctx->title,
				WS_CHILD /* | WS_CLIPCHILDREN | WS_CLIPSIBLINGS */,
				ctx->x,ctx->y,
				ctx->width,ctx->height,
				ctx->parent,
				NULL,
				GetModuleHandle(NULL),
				NULL);
		}
		else /* top-level window */
		{
			/* Create top-level window without sizing border */
			/* @&%# !!! damned user32.dll !!! */
			/*
			if(ctx->wnd && ctx->width<sx && ctx->height<sy)
			{
				RECT rc;
				u32_t rw, rh;
				GetClientRect(ctx->wnd,&rc);
				rw=2*ctx->width-rc.right;
				rh=2*ctx->height-rc.bottom;
				
					works only if started from cmd.exe !!! DAMN
				
				SetWindowPos(ctx->wnd,HWND_NOTOPMOST,0,0,rw,rh,SWP_NOMOVE);
			}
			*/
			/* Workaround: */
			ctx->wnd=CreateWindow(
				ctx->class_name,
				ctx->title,
				WS_POPUP | WS_CAPTION /* | WS_CLIPCHILDREN | WS_CLIPSIBLINGS */,
				ctx->x,ctx->y,
				ctx->width + 2 * GetSystemMetrics(SM_CXFIXEDFRAME),
				ctx->height + 2 * GetSystemMetrics(SM_CYFIXEDFRAME) +
					GetSystemMetrics(SM_CYCAPTION),
				NULL,
				NULL,
				GetModuleHandle(NULL),
				NULL);
		}
		if(!ctx->wnd)
		{
#ifdef	CHECKED_BUILD
			conlPrintf(LOG_INFO,
				"vglCreateWindowedContext(): "
				"CreateWindow() returns error '%d'.\n",
				GetLastError());
#endif	/* CHECKED_BUILD */
			memFreeObject(ctx);
			return NULL;
		}
		ctx->dc=GetDC(ctx->wnd);
		SetWindowLong(ctx->wnd,GWL_USERDATA,(LONG)ctx);
		ShowWindow(ctx->wnd,SW_SHOWNORMAL);
	}
	SetActiveWindow(ctx->wnd);
	/* clear window */
	PatBlt(ctx->dc,0,0,ctx->width,ctx->height,BLACKNESS);
	if(!vglInitWGL(ctx) || !vglMakeCurrent((vhandle_t)ctx))
	{
		vglDestroyWindow((vhandle_t)ctx);
		return NULL;
	}
	return ctx;
}

V_LOCAL u32_t V_CALL
DestroyWGLContext(vhandle_t handle)
{
	gl_context_t *ctx=(gl_context_t *)handle;
	if(--ctx->base.ref_count)
		return ctx->base.ref_count;
	vglShutdownWGL(ctx);
	fifoClear(&ctx->mqueue);
	if(ctx->dc)
		ReleaseDC(ctx->wnd,ctx->dc);
	if(ctx->wnd)
		DestroyWindow(ctx->wnd);
	if(!ctx->bmp)
		UnregisterClass(ctx->wc.lpszClassName,ctx->wc.hInstance);
	else
		DeleteObject(ctx->bmp);
	memFreeObject(ctx);
	return 0;
}

vhandle_t	V_GLOBAL
vglCreateWindow(char *title,u32_t x,u32_t y,u32_t width,u32_t height)
{
	gl_context_t *ctx;
	ctx=vglAllocContext(x,y,width,height,false,NULL,title,0,0,false);
	return (vhandle_t)vglCreateWindowedContext(ctx);
}

vhandle_t	V_GLOBAL
vglCreateFullscreenWindow(char *title,u32_t width,u32_t height,u32_t bpp,u32_t refresh)
{
	gl_context_t *ctx;
	/* TODO: debug it, it captures mouse in EITHER case ! */
	ctx=vglAllocContext(0,0,width,height,true,NULL,title,
		bpp,refresh,false);
	return (vhandle_t)vglCreateWindowedContext(ctx);
}

vhandle_t	V_GLOBAL
vglCreateChildWindow(char *title,u32_t x,u32_t y,u32_t width,u32_t height,void *parent)
{
	gl_context_t *ctx;
	ctx=vglAllocContext(x,y,width,height,false,parent,title,0,0,false);
	return (vhandle_t)vglCreateWindowedContext(ctx);
}

vhandle_t	V_GLOBAL
vglCreateMappedWindow(u32_t width,u32_t height)
{
	gl_context_t *ctx;
	ctx=vglAllocContext(0,0,width,height,false,NULL,NULL,0,0,true);
	/*
	 *	For bitmap - based contexts, we must only create
	 *	output bitmap.
	 */
	ctx->bmh.biSize=sizeof(BITMAPINFOHEADER);
	ctx->bmh.biWidth=ctx->width;
	ctx->bmh.biHeight=ctx->height;
	ctx->bmh.biPlanes=1;
	ctx->bmh.biBitCount=24;
	ctx->bmh.biCompression=BI_RGB;
	ctx->dc=CreateCompatibleDC(NULL);
	ctx->bmp=CreateDIBSection(ctx->dc,(LPBITMAPINFO)&ctx->bmh,
		DIB_RGB_COLORS,&ctx->bits,NULL,0);
	SelectObject(ctx->dc,ctx->bmp);
	if(!vglInitWGL(ctx) || !vglMakeCurrent((vhandle_t)ctx))
	{
		vglDestroyWindow(ctx);
		return NULL;
	}
	return (vhandle_t)ctx;
}

vboolean	V_GLOBAL
vglMakeCurrent(vhandle_t handle)
{
	gl_context_t *ctx,*oldctx;
	thread_data_t *td;
	td=GetThreadData();
	if(td->gl_context != NULL) /* Minimal check to avoid vglIsContext() */
	{
		oldctx=(gl_context_t*)td->gl_context;
		if(vglIsContext(oldctx,"vglMakeCurrent")!=NULL)
		{
			oldctx->base.ref_count--;
			W_InstallGrabs(oldctx,false);
			if(oldctx->vidmode_active)
				W_SetDefaultMode(oldctx);
		}
	}
	if(handle == NULL)
	{
		SetFocus(NULL);
		wglMakeCurrent(NULL,NULL);
		td->gl_context=NULL;
		return true;
	}
	ctx=vglIsContext(handle,"vglMakeCurrent");
#if	defined(CHECKED_BUILD) || defined(MALLOC_DEBUG)
	if(!ctx)
		return false;
#endif	/* CHECKED_BUILD */
	/*	SOMETHING IS WRONG HERE. I CANT FIX IT, SORRY ...
	 *		Under NT 4.0 wglMakeCurrent() crashes the program if
	 *	called twice from thread other then current (exception in
	 *	'NtWaitForMultipleObjects()'). Under Win98 and NT 5.0 - all Ok.
	 */
	/*
	 *	TODO: catch exception.
	 */
	if(wglMakeCurrent(ctx->dc,ctx->rc)==0)
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,
			"vglMakeCurrent(): wglMakeCurrent() failed (code %d).\n",
			GetLastError());
#endif	/* CHECKED_BUILD */
		return false;
	}
	td->gl_context=ctx;
	ctx->base.ref_count++;
	if(!ctx->was_selected)
	{
		int i;
		char *exts;
		ctx->was_selected=true;
		ctx->vendor=(char*)glGetString(GL_VENDOR);
		if(ctx->vendor)
			ctx->vendor=strDup(ctx->vendor);
		ctx->renderer=(char*)glGetString(GL_RENDERER);
		if(ctx->renderer)
			ctx->renderer=strDup(ctx->renderer);
		ctx->version=(char*)glGetString(GL_VERSION);
		if(ctx->version)
			ctx->version=strDup(ctx->version);
		ctx->extensions=NULL;
		ctx->num_extensions=0;
		exts=(char*)glGetString(GL_EXTENSIONS);
		if(exts)
		{
			vhandle_t ep=tokParseString(exts,NULL);
			while(tokNext(ep))
				ctx->num_extensions++;
			if(ctx->num_extensions)
			{
				ctx->extensions=(char**)
                	memAlloc(ctx->num_extensions*sizeof(char*));
				tokRestart(ep,NULL,NULL);
				for(i=0;tokNext(ep);i++)
					ctx->extensions[i]=strDup(tokLast(ep));
			}
			tokDestroy(ep);
		}
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_INFO,"vglMakeCurrent(): %s OpenGL on %s ver. %s\n",
			ctx->vendor,ctx->renderer,ctx->version);
#if		0
		conlPuts(LOG_INFO,"OpenGL extensions: \n");
		for(i=0;i<(int)ctx->num_extensions;i++)
			conlPrintf(LOG_INFO,"    %s\n",ctx->extensions[i]);
#endif	/* !0 */
#endif	/* CHECKED_BUILD */
	}
	if(ctx->fullscreen)
	{
		u32_t sx, sy, sbpp, refresh;
		W_GetVideoMode(&sx, &sy, &sbpp, &refresh);
		W_SetVideoMode(ctx,sx,sy,sbpp,refresh);
	}
	W_InstallGrabs(ctx,ctx->visual.flags.activate_mouse);
    return true;
}

/*
 * The only lack of the Win32 UI is an unablity to pass
 * message posting time.
 */
V_LOCAL LONG
W_DispatchMessage(MSG *msg)
{
	arch_window_p aw = GetWindowLong(msg,GWL_USERDATA);
	if(!aw)
	{
		return DispatchMessage(msg);
	}
	return aw->ProcEx(msg->hwnd,msg->message,msg->wParam,msg->lParam,msg->time);
}


u32_t V_LOCAL
vglPendingCTX(gl_context_t *ctx)
{
	MSG msg;
	while(PeekMessage(&msg,ctx->wnd,0,0,PM_REMOVE))
	{
		TranslateMessage(&msg); 
		/*
		 * Since we need posting time, DispatchMessage()
		 * from user32.dll can't be used.
		 */
		W_DispatchMessage(&msg);
	}
	return fifoCount(&ctx->mqueue);
}

/*
**	Extensions
*/
vboolean	V_GLOBAL
vglHasExtension(char *extension_name)
{
	int i,n;
	gl_context_t *ctx=vglSafeGetCurrentContext("vglHasExtension");
	if(!ctx)
		return false;
	n = ctx->num_extensions;
	for(i=0;i<n;i++)
		if(!strcmp(extension_name,ctx->extensions[i]))
			return true;
	return false;
}

#ifdef	_MSC_VER
#pragma warning(disable:4054)	/* Cast from function to data pointer */
#endif	/* _MSC_VER */

vboolean	V_GLOBAL
vglQueryExtension(char *extension_name,void **entry)
{
	*entry=(void*)wglGetProcAddress(extension_name);
	return *entry ? true : false;
}

#ifdef	_MSC_VER
#pragma warning(default:4054)
#endif	/* _MSC_VER */

vboolean	V_GLOBAL
vglPending()
{
	gl_context_t *ctx=vglSafeGetCurrentContext("vglPending");
	if(ctx)
		return vglPendingCTX(ctx) ? true : false;
	else
		return false;
}

vboolean	V_GLOBAL
vglNextEvent(input_record_t *event_return)
{
	gl_context_t *ctx=vglSafeGetCurrentContext("vglNextEvent");
	if(!ctx)
		return false;
	if(!event_return)
	{
#ifdef	CHECKED_BUILD
		conlPuts(LOG_ERROR,"vglNextEvent(NULL): incorrect parameter.\n");
#endif	/* CHECKED_BUILD */
		return false;
	}
	while(!vglPendingCTX(ctx))
		WaitMessage();
    return fifoPop(&ctx->mqueue,event_return);
}

vboolean	V_GLOBAL
vglSetFocus()
{
	gl_context_t *ctx=vglSafeGetCurrentContext("vglSetFocus");
	if(!ctx)
		return false;
	SetFocus(ctx->wnd);
	return true;
}

vboolean	V_GLOBAL
vglResizeWindow(u32_t width,u32_t height)
{
	gl_context_t *ctx=vglSafeGetCurrentContext("vglResizeWindow");
	if(!ctx)
		return false;
	return SetWindowPos(ctx->wnd,HWND_NOTOPMOST,
    	0,0,width,height,SWP_NOMOVE|SWP_NOZORDER) ? true : false;
}

vboolean	V_GLOBAL
vglShowWindow(void)
{
	gl_context_t *ctx=vglSafeGetCurrentContext("vglShowWindow");
	if(!ctx)
		return false;
	if(ShowWindow(ctx->wnd,SW_SHOW))
		if(SetActiveWindow(ctx->wnd))
			return true;
	return false;
}

vboolean	V_GLOBAL
vglHideWindow(void)
{
	gl_context_t *ctx=vglSafeGetCurrentContext("vglHideWindow");
	if(!ctx)
		return false;
	return ShowWindow(ctx->wnd,SW_HIDE) ? true : false;
}

vboolean	V_GLOBAL
vglMoveWindow(u32_t left,u32_t top)
{
	gl_context_t *ctx=vglSafeGetCurrentContext("vglMoveWindow");
	if(!ctx)
		return false;
	return SetWindowPos(ctx->wnd,HWND_NOTOPMOST,
    	left,top,ctx->width,ctx->height,
		SWP_NOSIZE|SWP_NOZORDER) ? true : false;
}

void 	V_GLOBAL
vglCapturePointer(vboolean capture)
{
	gl_context_t *ctx;
	ctx=vglSafeGetCurrentContext("vglResizeWindow");
	if(!ctx)
		return;
	if(capture)
	{
		ctx->visual.flags.activate_mouse=true;
		W_InstallGrabs(ctx,true);
	}
	else
	{
		ctx->visual.flags.activate_mouse=false;
		W_InstallGrabs(ctx,false);
	}
}


























V_LOCAL u32_t V_CALL	DestroyWGRContext(vhandle_t handle);

V_LOCAL	gl_context_p
vglAllocContext(u32_t x,u32_t y,u32_t width,u32_t height,
	vboolean fullscreen,void *parent,char *title,
	u32_t bpp,u32_t refresh,vboolean bitmap_based)
{
	gl_context_t *ctx;
    ALLOC_OBJECT(ctx,gl_context_t,0,GL_MAGIC);
    ctx->base.destroy=DestroyWGLContext;
	ctx->x=x;
	ctx->y=y;
	ctx->width=width;
	ctx->height=height;
	ctx->fullscreen=fullscreen;
	ctx->parent=parent;
	if(title)
		strCopy(ctx->title,title,128);
	ctx->bpp=bpp;
	ctx->refresh=refresh;
	ctx->bitmap_based=bitmap_based;
	fifoInit(&ctx->mqueue,sizeof(input_record_t));
	ctx->font=NULL;
	return ctx;
}









LRESULT CALLBACK
dib_window_proc(HWND wnd,UINT msg,WPARAM wparam,LPARAM lparam)
{
	PAINTSTRUCT ps;
	switch(msg)
	{
		case WM_ACTIVATE:
		{
			sset.wndactive=LOWORD(wparam) != WA_INACTIVE;
			break;
		}
		case WM_CLOSE:
		{
			return 0;
		}
		case WM_PAINT:
		{
			if(!sset.locked)
			{
				EnterCriticalSection(&sset.blit_progress);
				BeginPaint(wnd,&ps);
				SetDIBitsToDevice(ps.hdc,0,0,sset.header.biWidth,
					sset.header.biHeight,0,0,0,sset.header.biHeight,sset.backbuffer,
					(LPBITMAPINFO)&sset.header,DIB_RGB_COLORS);
				if(!sset.wndactive)
					Sleep(10);
				EndPaint(wnd,&ps);
				LeaveCriticalSection(&sset.blit_progress);
			}
			return 0;
			/* If window is not active, reduce CPU overhead */
			if(!sset.wndactive)
				Sleep(33);	/* Maximum framerate limited to 30 FPS */
		}
	}
	return DefWindowProc(wnd,msg,wparam,lparam);
}

DWORD WINAPI RenderingThread(LPVOID param)
{
	/*
	 *	1. Initialization.
	 */
	RECT rc;
	int rw,rh,i;
	MSG msg;
	int width, height;
	width = LOWORD(param);
	height = HIWORD(param);
	memset(&sset,0,sizeof(screen_settings_t));
	sset.rendering_thread_id=GetCurrentThreadId();
	/*
	 *	BUGBUG: only true-color backbuffers currently supported
	 */
	#define bpp 32
	if(bpp!=32)
	{
		/*
		printf("graphInitWinDIB(%d,%d,%d): wrong bpp value '%d'.\n",
			width,height,bpp,bpp);
		*/
		memset(&sset,0,sizeof(screen_settings_t));
		return 0;
	}
	strcpy(sset.cls,"TESTgClass");
	sset.inst=GetModuleHandle(NULL);
	sset.width=width;
	sset.height=height;
	sset.fullscreen=(width==GetSystemMetrics(SM_CXSCREEN))&&
		(height==GetSystemMetrics(SM_CYSCREEN));
	if(bpp==8)
	{
		sset.shift=0;
		sset.pitch=width;
	}
	else if (bpp==32)
	{
		sset.shift=2;
		sset.pitch=width<<2;
	}
	InitializeCriticalSection(&sset.blit_progress);
	sset.wc.lpszClassName=sset.cls;
	sset.wc.hInstance=sset.inst;
	sset.wc.style = 0;
	sset.wc.lpfnWndProc=dib_window_proc;
	sset.wc.cbClsExtra = 0;
	sset.wc.cbWndExtra = 0;
	sset.wc.hIcon = LoadIcon(NULL,IDI_WINLOGO);
	sset.wc.hCursor = LoadCursor(NULL, IDC_ARROW);
	sset.wc.hbrBackground = GetStockObject(HOLLOW_BRUSH);
	sset.wc.lpszMenuName=NULL;
	sset.wndclass=RegisterClass(&sset.wc);
	strcpy(sset.title,"Initialization of DIB engine ...");
	if(!sset.wndclass) 
	{
		/*
		printf("graphInitWinDIB(): can't RegisterClass() '%s'\n",
			sset.wc.lpszClassName);
		*/
		DeleteCriticalSection(&sset.blit_progress);
		memset(&sset,0,sizeof(screen_settings_t));
		return 0;
	}
	if(sset.fullscreen)
	{
		sset.wnd=CreateWindow(sset.cls,sset.title,WS_POPUP,
			0,0,sset.width,sset.height,NULL,NULL,sset.inst,NULL);
	}
	else
	{
		sset.wnd=CreateWindow(sset.cls,sset.title,WS_POPUP|WS_CAPTION,
			0,0,sset.width,sset.height,NULL,NULL,sset.inst,NULL);
	}
	if(!sset.wnd)
	{
		/*
		puts("graphInitWinDIB(): can't CreateWindow().\n");
		*/
		UnregisterClass(sset.wc.lpszClassName,sset.inst);
		DeleteCriticalSection(&sset.blit_progress);
		memset(&sset,0,sizeof(screen_settings_t));
		return 0;
	}
	ShowWindow(sset.wnd,SW_SHOWNORMAL);
	if(!sset.fullscreen)
	{
		GetClientRect(sset.wnd,&rc);
		rw=2*sset.width-rc.right;
		rh=2*sset.height-rc.bottom;
		SetWindowPos(sset.wnd,HWND_NOTOPMOST,0,0,rw,rh,SWP_NOZORDER|SWP_NOMOVE);
	}
	/* setup backbuffer */
	sset.header.biSize=sizeof(sset.header);
	sset.header.biWidth=width;
	sset.header.biHeight=height;
	sset.header.biPlanes=1;
	sset.header.biBitCount=32;
	sset.backbuffer=(void*)malloc(sset.pitch*sset.height);
	sset.vtab=(void**)malloc(sset.height*sizeof(void*));
	if(!sset.backbuffer || !sset.vtab)
	{
		if(sset.backbuffer)
			free(sset.backbuffer);
		if(sset.vtab)
			free(sset.backbuffer);
		DestroyWindow(sset.wnd);
		UnregisterClass(sset.wc.lpszClassName,sset.inst);
		DeleteCriticalSection(&sset.blit_progress);
		memset(&sset,0,sizeof(screen_settings_t));
		return 0;
	}
	memset(sset.backbuffer,255,sset.pitch*sset.height);
	sset.vtab[0]=sset.backbuffer;
	for(i=1;i<sset.height;i++)
		sset.vtab[i]=(unsigned char*)sset.vtab[i-1]+sset.pitch;
	sset.magic=SCREEN_MAGIC;
	/*
	 *	2. Message loop.
	 */
	while(GetMessage(&msg,NULL,0,0))
	{
		PostThreadMessage(parent_thread_id,msg.message,msg.wParam,msg.lParam);
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
	/*
	 *	2. Collecting garbage.
	 */
	if(sset.backbuffer)
		free(sset.backbuffer);
	if(sset.vtab)
		free(sset.vtab);
	DestroyWindow(sset.wnd);
	UnregisterClass(sset.wc.lpszClassName,sset.inst);
	DeleteCriticalSection(&sset.blit_progress);
	memset(&sset,0,sizeof(screen_settings_t));
	return 1;
	/* BUGBUG */ 
	#undef bpp
}

/*
**	graphInitWinDIB()
*/
int
graphInitWinDIB(int width,int height)
{
	DWORD thr;
	LPVOID param;
	if(rendering_thread)
		return 0;
	parent_thread_id=GetCurrentThreadId();
	param=(LPVOID)MAKELPARAM(width,height);
	rendering_thread=CreateThread(NULL,0,RenderingThread,param,0,&thr);
	/*
	 *	Wait some time: we assume that thread initialization takes not more
	 *	then 250 milliseconds.
	 */
	if(WaitForSingleObject(rendering_thread,250)!=WAIT_TIMEOUT)
	{
		/*
		 *	Thread failed to initialize.
		 */
		/*
		printf("graphInitWinDIB(): rendering thread failed to initialize.")
		*/
		return 0;
	}
	return 1;
}	

/*
**	graphSetWindowTitle()
*/
void
graphSetWindowTitle(char *title)
{
	strncpy(sset.title,title,255);
	SendMessage(sset.wnd,WM_SETTEXT,0,(LPARAM)sset.title);
}

/*
**	graphShutdownWinDIB()
*/
void
graphShutdownWinDIB(void)
{
	if(rendering_thread && rendering_thread!=INVALID_HANDLE_VALUE &&
		sset.magic==SCREEN_MAGIC)
	{
		/*
		 *	Break RenderingThread's message loop
		 */
		PostThreadMessage(sset.rendering_thread_id,WM_QUIT,0,0);
		/*
		 *	Wait for garbage collection completion.
		 */
		WaitForSingleObject(rendering_thread,INFINITE);
		rendering_thread=NULL;
	}
}

/*
**	graphLockBackbuffer()
*/
void
**graphLockBackbuffer(void)
{
	InterlockedIncrement(&sset.locked);
	return sset.vtab;
}

/*
**	graphUnlockBackbuffer()
*/
void
graphUnlockBackbuffer(void)
{
	InterlockedDecrement(&sset.locked);
}

/*
**	graphSwapBuffers()
*/
void
graphSwapBuffers(void)
{
	HDC dc;
	if(!sset.locked)
	{
		EnterCriticalSection(&sset.blit_progress);
		dc=GetDC(sset.wnd);
		SetDIBitsToDevice(dc,0,0,sset.header.biWidth,
			sset.header.biHeight,0,0,0,sset.header.biHeight,sset.backbuffer,
			(LPBITMAPINFO)&sset.header,DIB_RGB_COLORS);
		if(!sset.wndactive)
			Sleep(10);
		ReleaseDC(sset.wnd,dc);
		LeaveCriticalSection(&sset.blit_progress);
	}
}

/*
**	graphClear()
*/
void
graphClear(unsigned int color)	/* FOR 32BPP RGB backbuffers only */
{
#if _MSC_VER	/* we have DOS - style inline assembly */
	__asm
	{
		push	ebx
		push	edi
		
		mov		eax, sset.header.biWidth
		mov		ebx, sset.header.biHeight
		imul	ebx
		mov		edi, sset.backbuffer
		mov		ecx, eax
		mov		eax, color

		rep  stosd
		
		pop  edi
		pop  ebx
	}
#else
	unsigned int *bb;
	int i, size;
	size=sset.width*sset.height;
	bb=(unsigned int *)sset.backbuffer;
	for(i=0;i<size;i++)
		bb[i]=color;
#endif
}

/*
**	graphSetPixel32()
*/
void
graphSetPixel32(int x,int y,unsigned int color)
{
	*((unsigned int*)sset.vtab[y]+x)=color;
}
