/*
 *			GPAC - Multimedia Framework C SDK
 *
 *			Authors: Jean Le Feuvre
 *			Copyright (c) Telecom ParisTech 2000-2012
 *					All rights reserved
 *
 *  This file is part of GPAC / GAPI WinCE-iPaq video render module
 *
 *  GPAC is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU Lesser General Public License as published by
 *  the Free Software Foundation; either version 2, or (at your option)
 *  any later version.
 *
 *  GPAC is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; see the file COPYING.  If not, write to
 *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */


#include <windows.h>
#include <aygshell.h>
#include <wingdi.h>
#include <gx.h>

#include "gapi.h"

#ifdef GPAC_USE_GLES1X

#if (defined(WIN32) || defined(_WIN32_WCE)) && !defined(__GNUC__)

#if 0
# pragma message("Using OpenGL-ES Common Lite Profile")
# pragma comment(lib, "libGLES_CL")

#define GLES_NO_PBUFFER
#define GLES_NO_PIXMAP

#else
# pragma message("Using OpenGL-ES Common Profile")
# pragma comment(lib, "libGLES_CM")

//#define GLES_NO_PIXMAP

#endif

#pragma comment(lib, "gx.lib")
#endif

#endif

static Bool landscape = GF_FALSE;


#define PRINT(__str) OutputDebugString(_T(__str))

#define GAPICTX(dr)	GAPIPriv *gctx = (GAPIPriv *) dr->opaque;

static GF_Err GAPI_InitBackBuffer(GF_VideoOutput *dr, u32 VideoWidth, u32 VideoHeight);

static GF_VideoOutput *the_video_driver = NULL;

static void GAPI_GetCoordinates(DWORD lParam, GF_Event *evt)
{
	GAPIPriv *ctx = (GAPIPriv *)the_video_driver->opaque;
	evt->mouse.x = LOWORD(lParam);
	evt->mouse.y = HIWORD(lParam);

	if (ctx->scale_coords) {
		evt->mouse.x = evt->mouse.x * the_video_driver->max_screen_width / ctx->sys_w;
		evt->mouse.y = evt->mouse.y * the_video_driver->max_screen_height / ctx->sys_h;
	}


	if (ctx->fullscreen) {
		POINT pt;
		pt.x = evt->mouse.x;
		pt.y = evt->mouse.y;
		ClientToScreen(ctx->hWnd, &pt);
		if (landscape) {
			evt->mouse.x = ctx->fs_w - pt.y;
			evt->mouse.y = pt.x;
		} else {
			evt->mouse.x = pt.x;
			evt->mouse.y = pt.y;
		}
	}
}

static void w32_translate_key(u32 wParam, u32 lParam, GF_EventKey *evt)
{
	evt->flags = 0;
	evt->hw_code = wParam;
	switch (wParam) {
	case VK_BACK:
		evt->key_code = GF_KEY_BACKSPACE;
		break;
	case VK_TAB:
		evt->key_code = GF_KEY_TAB;
		break;
	case VK_CLEAR:
		evt->key_code = GF_KEY_CLEAR;
		break;
	case VK_RETURN:
		evt->key_code = GF_KEY_ENTER;
		break;
	case VK_SHIFT:
		evt->key_code = GF_KEY_SHIFT;
		break;
	case VK_CONTROL:
		evt->key_code = GF_KEY_CONTROL;
		break;
	case VK_MENU:
		evt->key_code = GF_KEY_ALT;
		break;
	case VK_PAUSE:
		evt->key_code = GF_KEY_PAUSE;
		break;
	case VK_CAPITAL:
		evt->key_code = GF_KEY_CAPSLOCK;
		break;
	case VK_KANA:
		evt->key_code = GF_KEY_KANAMODE;
		break;
	case VK_JUNJA:
		evt->key_code = GF_KEY_JUNJAMODE;
		break;
	case VK_FINAL:
		evt->key_code = GF_KEY_FINALMODE;
		break;
	case VK_KANJI:
		evt->key_code = GF_KEY_KANJIMODE;
		break;
	case VK_ESCAPE:
		evt->key_code = GF_KEY_ESCAPE;
		break;
	case VK_CONVERT:
		evt->key_code = GF_KEY_CONVERT;
		break;
	case VK_SPACE:
		evt->key_code = GF_KEY_SPACE;
		break;
	case VK_PRIOR:
		evt->key_code = GF_KEY_PAGEUP;
		break;
	case VK_NEXT:
		evt->key_code = GF_KEY_PAGEDOWN;
		break;
	case VK_END:
		evt->key_code = GF_KEY_END;
		break;
	case VK_HOME:
		evt->key_code = GF_KEY_HOME;
		break;
	case VK_LEFT:
		evt->key_code = GF_KEY_LEFT;
		break;
	case VK_UP:
		evt->key_code = GF_KEY_UP;
		break;
	case VK_RIGHT:
		evt->key_code = GF_KEY_RIGHT;
		break;
	case VK_DOWN:
		evt->key_code = GF_KEY_DOWN;
		break;
	case VK_SELECT:
		evt->key_code = GF_KEY_SELECT;
		break;
	case VK_PRINT:
	case VK_SNAPSHOT:
		evt->key_code = GF_KEY_PRINTSCREEN;
		break;
	case VK_EXECUTE:
		evt->key_code = GF_KEY_EXECUTE;
		break;
	case VK_INSERT:
		evt->key_code = GF_KEY_INSERT;
		break;
	case VK_DELETE:
		evt->key_code = GF_KEY_DEL;
		break;
	case VK_HELP:
		evt->key_code = GF_KEY_HELP;
		break;

	/*	case VK_LWIN: return ;
		case VK_RWIN: return ;
		case VK_APPS: return ;
	*/
	case VK_NUMPAD0:
		evt->key_code = GF_KEY_0;
		evt->flags = GF_KEY_EXT_NUMPAD;
		break;
	case VK_NUMPAD1:
		evt->key_code = GF_KEY_1;
		evt->flags = GF_KEY_EXT_NUMPAD;
		break;
	case VK_NUMPAD2:
		evt->key_code = GF_KEY_2;
		evt->flags = GF_KEY_EXT_NUMPAD;
		break;
	case VK_NUMPAD3:
		evt->key_code = GF_KEY_3;
		evt->flags = GF_KEY_EXT_NUMPAD;
		break;
	case VK_NUMPAD4:
		evt->key_code = GF_KEY_4;
		evt->flags = GF_KEY_EXT_NUMPAD;
		break;
	case VK_NUMPAD5:
		evt->key_code = GF_KEY_5;
		evt->flags = GF_KEY_EXT_NUMPAD;
		break;
	case VK_NUMPAD6:
		evt->key_code = GF_KEY_6;
		evt->flags = GF_KEY_EXT_NUMPAD;
		break;
	case VK_NUMPAD7:
		evt->key_code = GF_KEY_7;
		evt->flags = GF_KEY_EXT_NUMPAD;
		break;
	case VK_NUMPAD8:
		evt->key_code = GF_KEY_8;
		evt->flags = GF_KEY_EXT_NUMPAD;
		break;
	case VK_NUMPAD9:
		evt->key_code = GF_KEY_9;
		evt->flags = GF_KEY_EXT_NUMPAD;
		break;
	case VK_MULTIPLY:
		evt->key_code = GF_KEY_STAR;
		evt->flags = GF_KEY_EXT_NUMPAD;
		break;
	case VK_ADD:
		evt->key_code = GF_KEY_PLUS;
		evt->flags = GF_KEY_EXT_NUMPAD;
		break;
	case VK_SEPARATOR:
		evt->key_code = GF_KEY_FULLSTOP;
		evt->flags = GF_KEY_EXT_NUMPAD;
		break;
	case VK_SUBTRACT:
		evt->key_code = GF_KEY_HYPHEN;
		evt->flags = GF_KEY_EXT_NUMPAD;
		break;
	case VK_DECIMAL:
		evt->key_code = GF_KEY_COMMA;
		evt->flags = GF_KEY_EXT_NUMPAD;
		break;
	case VK_DIVIDE:
		evt->key_code = GF_KEY_SLASH;
		evt->flags = GF_KEY_EXT_NUMPAD;
		break;
	case VK_F1:
		evt->key_code = GF_KEY_F1;
		break;
	case VK_F2:
		evt->key_code = GF_KEY_F2;
		break;
	case VK_F3:
		evt->key_code = GF_KEY_F3;
		break;
	case VK_F4:
		evt->key_code = GF_KEY_F4;
		break;
	case VK_F5:
		evt->key_code = GF_KEY_F5;
		break;
//	case VK_F6: evt->key_code = GF_KEY_F6; break;
//	case VK_F7: evt->key_code = GF_KEY_F7; break;
	case VK_F6:
		evt->key_code = GF_KEY_VOLUMEUP;
		break;
	case VK_F7:
		evt->key_code = GF_KEY_VOLUMEDOWN;
		break;
	case VK_F8:
		evt->key_code = GF_KEY_F8;
		break;

	case VK_F9:
		evt->key_code = GF_KEY_F9;
		break;
	case VK_F10:
		evt->key_code = GF_KEY_F10;
		break;
	case VK_F11:
		evt->key_code = GF_KEY_F11;
		break;
	case VK_F12:
		evt->key_code = GF_KEY_F12;
		break;
	case VK_F13:
		evt->key_code = GF_KEY_F13;
		break;
	case VK_F14:
		evt->key_code = GF_KEY_F14;
		break;
	case VK_F15:
		evt->key_code = GF_KEY_F15;
		break;
	case VK_F16:
		evt->key_code = GF_KEY_F16;
		break;
	case VK_F17:
		evt->key_code = GF_KEY_F17;
		break;
	case VK_F18:
		evt->key_code = GF_KEY_F18;
		break;
	case VK_F19:
		evt->key_code = GF_KEY_F19;
		break;
	case VK_F20:
		evt->key_code = GF_KEY_F20;
		break;
	case VK_F21:
		evt->key_code = GF_KEY_F21;
		break;
	case VK_F22:
		evt->key_code = GF_KEY_F22;
		break;
	case VK_F23:
		evt->key_code = GF_KEY_F23;
		break;
	case VK_F24:
		evt->key_code = GF_KEY_F24;
		break;

	case VK_NUMLOCK:
		evt->key_code = GF_KEY_NUMLOCK;
		break;
	case VK_SCROLL:
		evt->key_code = GF_KEY_SCROLL;
		break;

	/*
	 * VK_L* & VK_R* - left and right Alt, Ctrl and Shift virtual keys.
	 * Used only as parameters to GetAsyncKeyState() and GetKeyState().
	 * No other API or message will distinguish left and right keys in this way.
	 */
	case VK_LSHIFT:
		evt->key_code = GF_KEY_SHIFT;
		evt->flags = GF_KEY_EXT_LEFT;
		break;
	case VK_RSHIFT:
		evt->key_code = GF_KEY_SHIFT;
		evt->flags = GF_KEY_EXT_RIGHT;
		break;
	case VK_LCONTROL:
		evt->key_code = GF_KEY_CONTROL;
		evt->flags = GF_KEY_EXT_LEFT;
		break;
	case VK_RCONTROL:
		evt->key_code = GF_KEY_CONTROL;
		evt->flags = GF_KEY_EXT_RIGHT;
		break;
	case VK_LMENU:
		evt->key_code = GF_KEY_ALT;
		evt->flags = GF_KEY_EXT_LEFT;
		break;
	case VK_RMENU:
		evt->key_code = GF_KEY_ALT;
		evt->flags = GF_KEY_EXT_RIGHT;
		break;

#if(WINVER >= 0x0400)
	case VK_PROCESSKEY:
		evt->key_code = GF_KEY_PROCESS;
		break;
#endif /* WINVER >= 0x0400 */

	case VK_ATTN:
		evt->key_code = GF_KEY_ATTN;
		break;
	case VK_CRSEL:
		evt->key_code = GF_KEY_CRSEL;
		break;
	case VK_EXSEL:
		evt->key_code = GF_KEY_EXSEL;
		break;
	case VK_EREOF:
		evt->key_code = GF_KEY_ERASEEOF;
		break;
	case VK_PLAY:
		evt->key_code = GF_KEY_PLAY;
		break;
	case VK_ZOOM:
		evt->key_code = GF_KEY_ZOOM;
		break;
	//case VK_NONAME: evt->key_code = GF_KEY_NONAME; break;
	//case VK_PA1: evt->key_code = GF_KEY_PA1; break;
	case VK_OEM_CLEAR:
		evt->key_code = GF_KEY_CLEAR;
		break;

	/*thru VK_9 are the same as ASCII '0' thru '9' (0x30 - 0x39) */
	/* VK_A thru VK_Z are the same as ASCII 'A' thru 'Z' (0x41 - 0x5A) */
	default:
		if ((wParam>=0x30) && (wParam<=0x39))  evt->key_code = GF_KEY_0 + wParam-0x30;
		else if ((wParam>=0x41) && (wParam<=0x5A))  evt->key_code = GF_KEY_A + wParam-0x41;
		else {
			GAPIPriv *ctx = (GAPIPriv *)the_video_driver->opaque;
			short res = (LOWORD(wParam) != 0x5b) ? LOWORD(wParam) : wParam;

			if (res==ctx->keys.vkLeft) evt->key_code = landscape ? GF_KEY_UP : GF_KEY_LEFT;
			else if (res==ctx->keys.vkRight) evt->key_code = landscape ? GF_KEY_DOWN : GF_KEY_RIGHT;
			else if (res==ctx->keys.vkDown) evt->key_code = landscape ? GF_KEY_LEFT : GF_KEY_DOWN;
			else if (res==ctx->keys.vkUp) evt->key_code = landscape ? GF_KEY_RIGHT : GF_KEY_UP;
			else if (res==ctx->keys.vkStart) evt->key_code = GF_KEY_ENTER;
			else if (res==ctx->keys.vkA)
				evt->key_code = GF_KEY_MEDIAPREVIOUSTRACK;
			else if (res==ctx->keys.vkB)
				evt->key_code = GF_KEY_MEDIANEXTTRACK;
			else if (res==ctx->keys.vkC)
				evt->key_code = GF_KEY_SHIFT;
			else if (res==0xc1)
				evt->key_code = GF_KEY_ALT;
			else if (res==0xc2)
				evt->key_code = GF_KEY_CONTROL;
			else if (res==0xc5)
				evt->key_code = GF_KEY_VOLUMEDOWN;
			else {
				evt->key_code = GF_KEY_UNIDENTIFIED;
			}
		}
		break;
	}
}

//#define DIRECT_BITBLT

LRESULT APIENTRY GAPI_WindowProc(HWND hWnd, UINT msg, UINT wParam, LONG lParam)
{
	GF_Event evt;
	switch (msg) {
	case WM_SIZE:
	{
		GAPIPriv *ctx = (GAPIPriv *)the_video_driver->opaque;
		evt.type = GF_EVENT_SIZE;
		evt.size.width = LOWORD(lParam);
		evt.size.height = HIWORD(lParam);
		the_video_driver->on_event(the_video_driver->evt_cbk_hdl, &evt);
	}
	break;
	case WM_CLOSE:
		memset(&evt, 0, sizeof(GF_Event));
		evt.type = GF_EVENT_QUIT;
		the_video_driver->on_event(the_video_driver->evt_cbk_hdl, &evt);
		return 1;
	case WM_DESTROY:
	{
		GAPIPriv *ctx = (GAPIPriv *)the_video_driver->opaque;
		if (ctx->owns_hwnd ) {
			PostQuitMessage (0);
		} else if (ctx->orig_wnd_proc) {
			/*restore window proc*/
			SetWindowLong(ctx->hWnd, GWL_WNDPROC, ctx->orig_wnd_proc);
			ctx->orig_wnd_proc = 0L;
		}
	}
	break;

	case WM_ERASEBKGND:
		evt.type = GF_EVENT_REFRESH;
		the_video_driver->on_event(the_video_driver->evt_cbk_hdl, &evt);
		break;
	case WM_PAINT:
#ifndef DIRECT_BITBLT
	{
		GAPIPriv *gctx = (GAPIPriv *)the_video_driver->opaque;
		if (gctx->gx_mode || !gctx->bitmap) break;
		HDC dc = GetDC(gctx->hWnd);
		BitBlt(dc, gctx->dst_blt.x, gctx->dst_blt.y, gctx->bb_width, gctx->bb_height, gctx->hdcBitmap, 0, 0, SRCCOPY);
		ReleaseDC(gctx->hWnd, dc);
	}
#endif
	break;

	case WM_MOUSEMOVE:
		GAPI_GetCoordinates(lParam, &evt);
		evt.type = GF_EVENT_MOUSEMOVE;
		the_video_driver->on_event(the_video_driver->evt_cbk_hdl, &evt);
		break;
	case WM_LBUTTONDOWN:
	case WM_LBUTTONDBLCLK:
		GAPI_GetCoordinates(lParam, &evt);
		evt.type = GF_EVENT_MOUSEDOWN;
		evt.mouse.button = GF_MOUSE_LEFT;
		the_video_driver->on_event(the_video_driver->evt_cbk_hdl, &evt);
		break;
	case WM_LBUTTONUP:
		GAPI_GetCoordinates(lParam, &evt);
		evt.type = GF_EVENT_MOUSEUP;
		evt.mouse.button = GF_MOUSE_LEFT;
		the_video_driver->on_event(the_video_driver->evt_cbk_hdl, &evt);
		break;

	/*FIXME - there's a bug on alt state (we miss one event)*/
	case WM_SYSKEYDOWN:
	case WM_SYSKEYUP:
	case WM_KEYDOWN:
	case WM_KEYUP:
		w32_translate_key(wParam, lParam, &evt.key);
		evt.type = ((msg==WM_SYSKEYDOWN) || (msg==WM_KEYDOWN))  ? GF_EVENT_KEYDOWN : GF_EVENT_KEYUP;
		the_video_driver->on_event(the_video_driver->evt_cbk_hdl, &evt);
		break;
	case WM_CHAR:
		evt.type = GF_EVENT_TEXTINPUT;
		evt.character.unicode_char = wParam;
		break;
	}
	return DefWindowProc (hWnd, msg, wParam, lParam);
}

void GAPI_WindowThread(void *par)
{
	MSG msg;
	WNDCLASS wc;
	GAPIPriv *ctx = (GAPIPriv *)the_video_driver->opaque;

	memset(&wc, 0, sizeof(WNDCLASS));
	wc.hInstance = GetModuleHandle(_T("gm_gapi.dll"));
	wc.lpfnWndProc = GAPI_WindowProc;
	wc.hCursor = LoadCursor (NULL, IDC_ARROW);
	wc.hbrBackground = (HBRUSH)GetStockObject (BLACK_BRUSH);
	wc.lpszClassName = _T("GPAC GAPI Output");
	RegisterClass (&wc);

	ctx->hWnd = CreateWindow(_T("GPAC GAPI Output"), NULL, WS_POPUP, 0, 0, 120, 100, NULL, NULL, wc.hInstance, NULL);
	if (ctx->hWnd == NULL) {
		ctx->ThreadID = 0;
		ExitThread(1);
	}
	ShowWindow(ctx->hWnd, SW_SHOWNORMAL);

	while (GetMessage (&(msg), NULL, 0, 0)) {
		TranslateMessage (&(msg));
		DispatchMessage (&(msg));
	}
	ctx->ThreadID = 0;
	ExitThread (0);
}


void GAPI_SetupWindow(GF_VideoOutput *dr)
{
#ifdef GPAC_USE_GLES1X
	GF_Err e;
#endif
	GAPIPriv *ctx = (GAPIPriv *)dr->opaque;
	if (the_video_driver) return;
	the_video_driver = dr;

	if (!ctx->hWnd) {
		ctx->hThread = CreateThread (NULL, 0, (LPTHREAD_START_ROUTINE) GAPI_WindowThread, (LPVOID) dr, 0, &(ctx->ThreadID) );
		while (!ctx->hWnd && ctx->hThread) gf_sleep(10);
		if (!ctx->hThread) return;
		ctx->owns_hwnd = GF_TRUE;
	} else {
		ctx->orig_wnd_proc = GetWindowLong(ctx->hWnd, GWL_WNDPROC);
		/*override window proc*/
		SetWindowLong(ctx->hWnd, GWL_WNDPROC, (DWORD) GAPI_WindowProc);
	}

	{
		HDC hdc;
		hdc = GetDC(ctx->hWnd);
		dr->dpi_x = GetDeviceCaps(hdc, LOGPIXELSX);
		dr->dpi_y = GetDeviceCaps(hdc, LOGPIXELSY);
		ReleaseDC(ctx->hWnd, hdc);
	}


#ifdef GPAC_USE_GLES1X
	ctx->use_pbuffer = GF_TRUE;
	dr->hw_caps |= GF_VIDEO_HW_OPENGL_OFFSCREEN_ALPHA;
	e = GAPI_SetupOGL_ES_Offscreen(dr, 20, 20);
	if (e!=GF_OK) {
		dr->hw_caps &= ~GF_VIDEO_HW_OPENGL_OFFSCREEN_ALPHA;
#ifndef GLES_NO_PIXMAP
		e = GAPI_SetupOGL_ES_Offscreen(dr, 20, 20);
#endif
	}
	if (!e) {
		dr->hw_caps |= GF_VIDEO_HW_OPENGL_OFFSCREEN;
		return;
	}
	ctx->use_pbuffer = GF_FALSE;

	WNDCLASS wc;
	HINSTANCE hInst;
	hInst = GetModuleHandle(_T("gm_gapi.dll") );
	memset(&wc, 0, sizeof(WNDCLASS));
	wc.hInstance = hInst;
	wc.lpfnWndProc = GAPI_WindowProc;
	wc.lpszClassName = _T("GPAC GAPI Offscreen");
	RegisterClass (&wc);

	ctx->gl_hwnd = CreateWindow(_T("GPAC GAPI Offscreen"), _T("GPAC GAPI Offscreen"), WS_POPUP, 0, 0, 120, 100, NULL, NULL, hInst, NULL);
	if (!ctx->gl_hwnd) return;
	ShowWindow(ctx->gl_hwnd, SW_HIDE);

	dr->hw_caps |= GF_VIDEO_HW_OPENGL_OFFSCREEN_ALPHA;
	e = GAPI_SetupOGL_ES_Offscreen(dr, 20, 20);

	if (e!=GF_OK) {
		dr->hw_caps &= ~GF_VIDEO_HW_OPENGL_OFFSCREEN_ALPHA;
		e = GAPI_SetupOGL_ES_Offscreen(dr, 20, 20);
	}
	if (e==GF_OK) dr->hw_caps |= GF_VIDEO_HW_OPENGL_OFFSCREEN;
#endif

}

void GAPI_ShutdownWindow(GF_VideoOutput *dr)
{
	GAPIPriv *ctx = (GAPIPriv *)dr->opaque;

	if (ctx->owns_hwnd) {
		PostMessage(ctx->hWnd, WM_DESTROY, 0, 0);
		while (ctx->ThreadID) gf_sleep(10);
		UnregisterClass(_T("GPAC GAPI Output"), GetModuleHandle(_T("gapi.dll")));
		CloseHandle(ctx->hThread);
		ctx->hThread = NULL;
	} else if (ctx->orig_wnd_proc) {
		/*restore window proc*/
		SetWindowLong(ctx->hWnd, GWL_WNDPROC, ctx->orig_wnd_proc);
		ctx->orig_wnd_proc = 0L;
	}
	ctx->hWnd = NULL;
#ifdef GPAC_USE_GLES1X
	PostMessage(ctx->gl_hwnd, WM_DESTROY, 0, 0);
	ctx->gl_hwnd = NULL;
	UnregisterClass(_T("GPAC GAPI Offscreen"), GetModuleHandle(_T("gm_gapi.dll") ));
#endif

	the_video_driver = NULL;
}


GF_Err GAPI_Clear(GF_VideoOutput *dr, u32 color)
{
	GAPICTX(dr);
	gctx->erase_dest = GF_TRUE;
	return GF_OK;
}


static void createPixmap(GAPIPriv *ctx, u32 pix_type)
{
	const size_t    bmiSize = sizeof(BITMAPINFO) + 256U*sizeof(RGBQUAD);
	BITMAPINFO*     bmi;
	DWORD*          p;
	u32 bpel = 0;

	if (ctx->bmi) gf_free(ctx->bmi);

	bmi = (BITMAPINFO*)gf_malloc(bmiSize);
	memset(bmi, 0, bmiSize);

	bmi->bmiHeader.biSize           = sizeof(BITMAPINFOHEADER);
	bmi->bmiHeader.biWidth          = ctx->bb_width;
	bmi->bmiHeader.biHeight         = -1 *  (s32) ctx->bb_height;	/*top-down image*/
	bmi->bmiHeader.biPlanes         = (short)1;
	bmi->bmiHeader.biBitCount       = (unsigned int) ctx->bits_per_pixel;
	bmi->bmiHeader.biCompression    = BI_BITFIELDS;
	bmi->bmiHeader.biClrUsed        = 3;

	p = (DWORD*)bmi->bmiColors;
	switch (ctx->pixel_format) {
	case GF_PIXEL_RGB_555:
		p[0] = 0x00007c00;
		p[1] = 0x000003e0;
		p[2] = 0x0000001f;
		bpel = 16;
		break;
	case GF_PIXEL_RGB_565:
		p[0] = 0x0000f800;
		p[1] = 0x000007e0;
		p[2] = 0x0000001f;
		bpel = 16;
		break;
	case GF_PIXEL_RGB_24:
		p[0] = 0x00ff0000;
		p[1] = 0x0000ff00;
		p[2] = 0x000000ff;
		bpel = 24;
		break;
	}
	ctx->hdc = GetDC(NULL/*ctx->hWnd*/);

	if (pix_type==2) {
#ifdef GPAC_USE_GLES1X
		ctx->gl_bitmap = CreateDIBSection(ctx->hdc, bmi, DIB_RGB_COLORS, (void **) &ctx->gl_bits, NULL, 0);
#endif
	} else if (pix_type==1) {
		ctx->hdcBitmap = CreateCompatibleDC(ctx->hdc);
		ctx->bitmap = CreateDIBSection(ctx->hdc, bmi, DIB_RGB_COLORS, (void **) &ctx->bits, NULL, 0);
		ctx->old_bitmap = (HBITMAP) SelectObject(ctx->hdcBitmap, ctx->bitmap);
	} else {
		ctx->hdcBitmap = CreateCompatibleDC(ctx->hdc);
		ctx->bitmap = CreateDIBSection(ctx->hdc, bmi, DIB_RGB_COLORS, (void **) &ctx->backbuffer, NULL, 0);
		ctx->old_bitmap = (HBITMAP) SelectObject(ctx->hdcBitmap, ctx->bitmap);

		/*watchout - win32 always create DWORD align memory, so align our pitch*/
		while ((ctx->bb_pitch % 4) != 0) ctx->bb_pitch ++;
	}
	ReleaseDC(NULL/*ctx->hWnd*/, ctx->hdc);

	ctx->bmi = bmi;
//	gf_free(bmi);
}


#ifdef GPAC_USE_GLES1X

void GAPI_ReleaseOGL_ES(GAPIPriv *ctx, Bool offscreen_only)
{
	if (ctx->egldpy) {
		eglMakeCurrent(ctx->egldpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);

		if (ctx->eglctx) eglDestroyContext(ctx->egldpy, ctx->eglctx);
		ctx->eglctx = 0;
		if (ctx->surface) eglDestroySurface(ctx->egldpy, ctx->surface);
		ctx->surface = 0;
		if (ctx->egldpy) eglTerminate(ctx->egldpy);
		ctx->egldpy = 0;
	}
	if (ctx->gl_bitmap) DeleteObject(ctx->gl_bitmap);
	ctx->gl_bitmap = NULL;

	if (offscreen_only) return;

	if (ctx->bitmap) DeleteObject(ctx->bitmap);
	ctx->bitmap = NULL;
}

GF_Err GAPI_SetupOGL_ES(GF_VideoOutput *dr)
{
	EGLint n, maj, min;
	u32 i;
	GF_Event evt;
	GAPICTX(dr)
	static int atts[32];
	const char *opt;

	i=0;
	atts[i++] = EGL_RED_SIZE;
	atts[i++] = (gctx->pixel_format==GF_PIXEL_RGB_24) ? 8 : 5;
	atts[i++] = EGL_GREEN_SIZE;
	atts[i++] = (gctx->pixel_format==GF_PIXEL_RGB_24) ? 8 : (gctx->pixel_format==GF_PIXEL_RGB_565) ? 6 : 5;
	atts[i++] = EGL_BLUE_SIZE;
	atts[i++] = (gctx->pixel_format==GF_PIXEL_RGB_24) ? 8 : 5;
	opt = gf_modules_get_option((GF_BaseInterface *)dr, "Video", "GLNbBitsDepth");
	atts[i++] = EGL_DEPTH_SIZE;
	atts[i++] = opt ? atoi(opt) : 16;
	atts[i++] = EGL_SURFACE_TYPE;

#ifdef GLES_NO_PIXMAP
	atts[i++] = EGL_WINDOW_BIT;
#else
	atts[i++] = EGL_PIXMAP_BIT;
//	atts[i++] = gctx->fullscreen ? EGL_WINDOW_BIT : EGL_PIXMAP_BIT;
#endif
	atts[i++] = EGL_ALPHA_SIZE;
	atts[i++] = EGL_DONT_CARE;
	atts[i++] = EGL_STENCIL_SIZE;
	atts[i++] = EGL_DONT_CARE;
	atts[i++] = EGL_NONE;

	/*whenever window is resized we must reinit OGL-ES*/
	GAPI_ReleaseOGL_ES(gctx, GF_FALSE);

	if (!gctx->fullscreen) {
		RECT rc;
		::GetClientRect(gctx->hWnd, &rc);
		gctx->bb_width = rc.right-rc.left;
		gctx->bb_height = rc.bottom-rc.top;

#ifndef GLES_NO_PIXMAP
		createPixmap(gctx, 1);
#endif
	}

	gctx->egldpy = eglGetDisplay(EGL_DEFAULT_DISPLAY);
	if (!gctx->egldpy) {
		GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[GAPI] Cannot get OpenGL display\n"));
		return GF_IO_ERR;
	}
	if (!eglInitialize(gctx->egldpy, &maj, &min)) {
		gctx->egldpy = NULL;
		GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[GAPI] Cannot initialize OpenGL layer\n"));
		return GF_IO_ERR;
	}

	if (!eglChooseConfig(gctx->egldpy, atts, &gctx->eglconfig, 1, &n) || (eglGetError() != EGL_SUCCESS)) {
		GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[GAPI] Cannot choose OpenGL config\n"));
		return GF_IO_ERR;
	}

	if (gctx->fullscreen
#ifdef GLES_NO_PIXMAP
	        || 1
#endif
	   ) {
		gctx->surface = eglCreateWindowSurface(gctx->egldpy, gctx->eglconfig, gctx->hWnd, 0);
	} else {
		gctx->surface = eglCreatePixmapSurface(gctx->egldpy, gctx->eglconfig, gctx->bitmap, 0);
	}

	if (!gctx->surface) {
		GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[GAPI] Cannot create OpenGL surface - error %d\n", eglGetError()));
		return GF_IO_ERR;
	}
	gctx->eglctx = eglCreateContext(gctx->egldpy, gctx->eglconfig, NULL, NULL);
	if (!gctx->eglctx) {
		GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[GAPI] Cannot create OpenGL context\n"));
		eglDestroySurface(gctx->egldpy, gctx->surface);
		gctx->surface = 0L;
		return GF_IO_ERR;
	}
	if (!eglMakeCurrent(gctx->egldpy, gctx->surface, gctx->surface, gctx->eglctx)) {
		GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[GAPI] Cannot bind OpenGL context\n"));
		eglDestroyContext(gctx->egldpy, gctx->eglctx);
		gctx->eglctx = 0L;
		eglDestroySurface(gctx->egldpy, gctx->surface);
		gctx->surface = 0L;
		return GF_IO_ERR;
	}
	GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("[GAPI] OpenGL initialize - %d x %d \n", gctx->bb_width, gctx->bb_height));
	memset(&evt, 0, sizeof(GF_Event));
	evt.type = GF_EVENT_VIDEO_SETUP;
	evt.hw_reset = 1;
	dr->on_event(dr->evt_cbk_hdl, &evt);
	return GF_OK;
}



GF_Err GAPI_SetupOGL_ES_Offscreen(GF_VideoOutput *dr, u32 width, u32 height)
{
	int atts[15];
	const char *opt;
	EGLint n, maj, min;

	GAPICTX(dr)

	GAPI_ReleaseOGL_ES(gctx, GF_TRUE);

	if (!gctx->use_pbuffer) {
		SetWindowPos(gctx->gl_hwnd, NULL, 0, 0, width, height, SWP_NOZORDER | SWP_NOMOVE);
		createPixmap(gctx, 2);
	}

	gctx->egldpy = eglGetDisplay(/*gctx->dpy*/EGL_DEFAULT_DISPLAY);
	if (!eglInitialize(gctx->egldpy, &maj, &min)) {
		gctx->egldpy = NULL;
		return GF_IO_ERR;
	}
	atts[0] = EGL_RED_SIZE;
	atts[1] = 8;
	atts[2] = EGL_GREEN_SIZE;
	atts[3] = 8;
	atts[4] = EGL_BLUE_SIZE;
	atts[5] = 8;
	atts[6] = EGL_ALPHA_SIZE;
	atts[7] = (dr->hw_caps & GF_VIDEO_HW_OPENGL_OFFSCREEN_ALPHA) ? 8 : EGL_DONT_CARE;
	opt = gf_modules_get_option((GF_BaseInterface *)dr, "Video", "GLNbBitsDepth");
	atts[8] = EGL_DEPTH_SIZE;
	atts[9] = opt ? atoi(opt) : 16;

	atts[10] = EGL_STENCIL_SIZE;
	atts[11] = EGL_DONT_CARE;
	atts[12] = EGL_SURFACE_TYPE;
	atts[13] = gctx->use_pbuffer ? EGL_PBUFFER_BIT : EGL_PIXMAP_BIT;
	atts[14] = EGL_NONE;

	eglGetConfigs(gctx->egldpy, NULL, 0, &n);
	if (!eglChooseConfig(gctx->egldpy, atts, &gctx->eglconfig, 1, &n)) {
		return GF_IO_ERR;
	}

	if (!gctx->use_pbuffer) {
		gctx->surface = eglCreatePixmapSurface(gctx->egldpy, gctx->eglconfig, gctx->gl_bitmap, 0);
	} else {
		atts[0] = EGL_WIDTH;
		atts[1] = width;
		atts[2] = EGL_HEIGHT;
		atts[3] = height;
		atts[4] = EGL_NONE;

		gctx->surface = eglCreatePbufferSurface(gctx->egldpy, gctx->eglconfig, atts);
	}

	if (!gctx->surface) {
		return GF_IO_ERR;
	}
	gctx->eglctx = eglCreateContext(gctx->egldpy, gctx->eglconfig, NULL, NULL);
	if (!gctx->eglctx) {
		eglDestroySurface(gctx->egldpy, gctx->surface);
		gctx->surface = 0L;
		return GF_IO_ERR;
	}
	if (!eglMakeCurrent(gctx->egldpy, gctx->surface, gctx->surface, gctx->eglctx)) {
		eglDestroyContext(gctx->egldpy, gctx->eglctx);
		gctx->eglctx = 0L;
		eglDestroySurface(gctx->egldpy, gctx->surface);
		gctx->surface = 0L;
		return GF_IO_ERR;
	}
	return GF_OK;
}

#endif


void GAPI_ReleaseObjects(GAPIPriv *ctx)
{
	ctx->raw_ptr = NULL;

#ifdef GPAC_USE_GLES1X
	if (ctx->output_3d_type) GAPI_ReleaseOGL_ES(ctx, GF_FALSE);
	else
#endif
		if (ctx->bitmap) DeleteObject(ctx->bitmap);
		else if (ctx->backbuffer) gf_free(ctx->backbuffer);
	ctx->backbuffer = NULL;
	ctx->bitmap = NULL;

	if (ctx->hdcBitmap) {
		if (ctx->old_bitmap) SelectObject(ctx->hdcBitmap, ctx->old_bitmap);
		ctx->old_bitmap = NULL;
		DeleteDC(ctx->hdcBitmap);
		ctx->hdcBitmap = NULL;
	}
	if (ctx->hdc) ReleaseDC(NULL/*ctx->hWnd*/, ctx->hdc);
	ctx->hdc = NULL;
}

GF_Err GAPI_Setup(GF_VideoOutput *dr, void *os_handle, void *os_display, u32 noover)
{
	struct GXDisplayProperties gx = GXGetDisplayProperties();
	RECT rc;
	GAPICTX(dr);
	gctx->hWnd = (HWND) os_handle;

	/*get keys in both 2D and 3D modes*/
	gctx->keys = GXGetDefaultKeys(GX_NORMALKEYS);

#if 0
	/*FIXME - not supported in rasterizer*/
	if (gx.ffFormat & kfDirect444) {
		gctx->pixel_format = GF_PIXEL_RGB_444;
		gctx->BPP = 2;
		gctx->bitsPP = 12;
	}
	else
#endif
		if (gx.ffFormat & kfDirect555) {
			gctx->pixel_format = GF_PIXEL_RGB_555;
			gctx->BPP = 2;
			gctx->bits_per_pixel = 15;
		}
		else if (gx.ffFormat & kfDirect565) {
			gctx->pixel_format = GF_PIXEL_RGB_565;
			gctx->BPP = 2;
			gctx->bits_per_pixel = 16;
		}
		else if (gx.ffFormat & kfDirect888) {
			gctx->pixel_format = GF_PIXEL_RGB_24;
			gctx->BPP = 3;
			gctx->bits_per_pixel = 24;
		} else {
			return GF_NOT_SUPPORTED;
		}

	GAPI_SetupWindow(dr);
	if (!gctx->hWnd) return GF_IO_ERR;

	/*setup GX*/
	if (!GXOpenDisplay(gctx->hWnd, 0L)) {
		MessageBox(NULL, _T("Cannot open display"), _T("GAPI Error"), MB_OK);
		return GF_IO_ERR;
	}
	GetClientRect(gctx->hWnd, &rc);
	gctx->backup_w = rc.right - rc.left;
	gctx->backup_h = rc.bottom - rc.top;

	return GAPI_InitBackBuffer(dr, gctx->backup_w, gctx->backup_h);
}

static void GAPI_Shutdown(GF_VideoOutput *dr)
{
	GAPICTX(dr);

	gf_mx_p(gctx->mx);
	GAPI_ReleaseObjects(gctx);

	GXCloseDisplay();
	GAPI_ShutdownWindow(dr);
	gf_mx_v(gctx->mx);
}

static GF_Err GAPI_SetFullScreen(GF_VideoOutput *dr, Bool bOn, u32 *outWidth, u32 *outHeight)
{
	Bool is_wide_scene = (bOn==2) ? GF_TRUE : GF_FALSE;
	GF_Err e;
	GAPICTX(dr);

	if (!gctx) return GF_BAD_PARAM;
	if (is_wide_scene) bOn = GF_TRUE;
	if (bOn == gctx->fullscreen) return GF_OK;

#ifdef GPAC_USE_GLES1X
	if (gctx->output_3d_type==1) {
		gctx->fullscreen = bOn;
		return GAPI_SetupOGL_ES(dr);
	}
#endif

	gf_mx_p(gctx->mx);
	GAPI_ReleaseObjects(gctx);
	GXCloseDisplay();
	e = GF_OK;
	if (bOn) {
		if (!GXOpenDisplay(GetParent(gctx->hWnd), 0L/*GX_FULLSCREEN*/)) {
			GXOpenDisplay(gctx->hWnd, 0L);
			gctx->fullscreen = GF_FALSE;
			e = GF_IO_ERR;
		} else {
			gctx->fullscreen = GF_TRUE;
		}
	} else {
		GXOpenDisplay(gctx->hWnd, 0L);
		gctx->fullscreen = GF_FALSE;
	}

	landscape = GF_FALSE;
	if (!e) {
		if (gctx->fullscreen) {
			gctx->backup_w = *outWidth;
			gctx->backup_h = *outHeight;

			if (is_wide_scene && (gctx->screen_w < gctx->screen_h)) landscape = GF_TRUE;
			else if (!is_wide_scene && (gctx->screen_w > gctx->screen_h)) landscape = GF_TRUE;
			else landscape = GF_FALSE;

			if (landscape) {
				gctx->fs_w = gctx->screen_h;
				gctx->fs_h = gctx->screen_w;
			} else {
				gctx->fs_w = gctx->screen_w;
				gctx->fs_h = gctx->screen_h;
			}

			*outWidth = gctx->fs_w;
			*outHeight = gctx->fs_h;
		} else {
			*outWidth = gctx->backup_w;
			*outHeight = gctx->backup_h;
		}
	}
	gf_mx_v(gctx->mx);

	return e;
}

GF_Err GAPI_ClearFS(GAPIPriv *gctx, unsigned char *ptr)
{
	gf_mx_p(gctx->mx);
	memset(ptr, 0, sizeof(char) * gctx->screen_w*gctx->screen_h*gctx->BPP);
	gf_mx_v(gctx->mx);
	return GF_OK;
}


static GF_Err GAPI_FlipBackBuffer(GF_VideoOutput *dr)
{
	GF_VideoSurface src, dst;
	unsigned char *ptr;
	GAPICTX(dr);
	if (!gctx || !gctx->gx_mode) return GF_BAD_PARAM;

	/*get a pointer to video memory*/
	if (gctx->gx_mode==1) {
		ptr = (unsigned char *) GXBeginDraw();
	} else {
		ptr = (unsigned char *) gctx->raw_ptr;
	}
	if (!ptr) return GF_IO_ERR;

	src.video_buffer = gctx->backbuffer;
	src.width = gctx->bb_width;
	src.height = gctx->bb_height;
	src.pitch_x = gctx->BPP;
	src.pitch_y = gctx->y_pitch;
	src.pixel_format = gctx->pixel_format;
	src.is_hardware_memory = GF_FALSE;


	dst.width = gctx->dst_blt.w;
	dst.height = gctx->dst_blt.h;
	dst.pixel_format = gctx->pixel_format;
	dst.is_hardware_memory = GF_TRUE;
	dst.video_buffer = (char*)ptr;
	dst.pitch_x = gctx->x_pitch;
	dst.pitch_y = gctx->y_pitch;

	if (gctx->fullscreen) {
		if (gctx->erase_dest) {
			gctx->erase_dest = GF_FALSE;
			GAPI_ClearFS(gctx, ptr);
		}
	} else {
		gctx->dst_blt.x += gctx->off_x;
		gctx->dst_blt.y += gctx->off_y;
	}

	/*apply x/y offset*/
	if (!gctx->fullscreen)
		dst.video_buffer += gctx->dst_blt.x * gctx->x_pitch + gctx->y_pitch * gctx->dst_blt.y;

	if (gctx->contiguous_mem) {
		memcpy(dst.video_buffer, src.video_buffer, src.width*gctx->BPP*src.height );
	} else if (landscape) {
		u32 y, lsize = dst.height*gctx->x_pitch;
		for (y=0; y<dst.width; y++) {
			char *s = src.video_buffer + y*gctx->y_pitch;
			char *d = dst.video_buffer + y*gctx->y_pitch;
			memcpy(d, s, lsize);
		}
	} else {
		u32 y, lsize = dst.width*gctx->x_pitch;
		for (y=0; y<dst.height; y++) {
			char *s = src.video_buffer + y*gctx->y_pitch;
			char *d = dst.video_buffer + y*gctx->y_pitch;
			memcpy(d, s, lsize);
		}
	}

	if (gctx->gx_mode==1) GXEndDraw();
	return GF_OK;
}



static GF_Err GAPI_Flush(GF_VideoOutput *dr, GF_Window *dest)
{
	GF_Err e;
	GAPICTX(dr);

	if (!gctx) return GF_BAD_PARAM;

	gf_mx_p(gctx->mx);

#ifdef GPAC_USE_GLES1X
	if (gctx->output_3d_type==1) {
#ifndef GLES_NO_PIXMAP
		if (gctx->fullscreen && gctx->surface && gctx->egldpy) {
#endif
			if (gctx->erase_dest) {
				InvalidateRect(gctx->hWnd, NULL, TRUE);
				gctx->erase_dest = GF_FALSE;
			}
			eglSwapBuffers(gctx->egldpy, gctx->surface);
#ifndef GLES_NO_PIXMAP
		} else {
			InvalidateRect(gctx->hWnd, NULL, gctx->erase_dest);
			gctx->erase_dest = GF_FALSE;
		}
#endif
		gf_mx_v(gctx->mx);
		return GF_OK;
	}
#endif
	e = GF_OK;
	if (gctx->backbuffer) {
		if (dest) {
			gctx->dst_blt = *dest;
		} else {
			assert(0);
			gctx->dst_blt.x = gctx->dst_blt.y = 0;
			gctx->dst_blt.w = gctx->bb_width;
			gctx->dst_blt.h = gctx->bb_height;
		}
		if (gctx->gx_mode) {
			if (!gctx->fullscreen && gctx->erase_dest) {
				InvalidateRect(gctx->hWnd, NULL, TRUE);
				gctx->erase_dest = GF_FALSE;
			}
			e = GAPI_FlipBackBuffer(dr);
		} else {
#ifndef DIRECT_BITBLT
			InvalidateRect(gctx->hWnd, NULL, gctx->erase_dest);
#else
//			BitBlt(gctx->hdc, gctx->dst_blt.x, gctx->dst_blt.y, gctx->bb_width, gctx->bb_height, gctx->hdcBitmap, 0, 0, SRCCOPY);
			HDC dc = GetDC(NULL);
			BitBlt(dc, gctx->dst_blt.x, gctx->dst_blt.y, gctx->bb_width, gctx->bb_height, gctx->hdcBitmap, 0, 0, SRCCOPY);
			ReleaseDC(NULL, dc);
#endif
			gctx->erase_dest = GF_FALSE;
		}
	}
	gf_mx_v(gctx->mx);
	return e;
}

u32 get_sys_col(int idx)
{
	u32 res;
	DWORD val = GetSysColor(idx);
	res = (val)&0xFF;
	res<<=8;
	res |= (val>>8)&0xFF;
	res<<=8;
	res |= (val>>16)&0xFF;
	return res;
}

static GF_Err GAPI_ProcessEvent(GF_VideoOutput *dr, GF_Event *evt)
{
	GAPICTX(dr);
	if (!evt) return GF_OK;
	switch (evt->type) {
	case GF_EVENT_SHOWHIDE:
		if (gctx->hWnd) ShowWindow(gctx->hWnd, evt->show.show_type ? SW_SHOW : SW_HIDE);
		break;
	case GF_EVENT_SIZE:
		/*nothing to do since we don't own the window*/
		break;
	case GF_EVENT_VIDEO_SETUP:
		switch (evt->setup.opengl_mode) {
		case 0:
#ifdef GPAC_USE_GLES1X
			gctx->output_3d_type = 0;
#endif
			return GAPI_InitBackBuffer(dr, evt->setup.width, evt->setup.height);
#ifdef GPAC_USE_GLES1X
		case 1:
			gctx->output_3d_type = 1;
			return GAPI_SetupOGL_ES(the_video_driver);
		case 2:
			gctx->output_3d_type = 2;
			return GAPI_SetupOGL_ES_Offscreen(the_video_driver, evt->setup.width, evt->setup.height);
#else
		default:
			return GF_NOT_SUPPORTED;
#endif
		}
	case GF_EVENT_SYS_COLORS:
		evt->sys_cols.sys_colors[0] = get_sys_col(COLOR_ACTIVEBORDER);
		evt->sys_cols.sys_colors[1] = get_sys_col(COLOR_ACTIVECAPTION);
		evt->sys_cols.sys_colors[2] = get_sys_col(COLOR_APPWORKSPACE);
		evt->sys_cols.sys_colors[3] = get_sys_col(COLOR_BACKGROUND);
		evt->sys_cols.sys_colors[4] = get_sys_col(COLOR_BTNFACE);
		evt->sys_cols.sys_colors[5] = get_sys_col(COLOR_BTNHIGHLIGHT);
		evt->sys_cols.sys_colors[6] = get_sys_col(COLOR_BTNSHADOW);
		evt->sys_cols.sys_colors[7] = get_sys_col(COLOR_BTNTEXT);
		evt->sys_cols.sys_colors[8] = get_sys_col(COLOR_CAPTIONTEXT);
		evt->sys_cols.sys_colors[9] = get_sys_col(COLOR_GRAYTEXT);
		evt->sys_cols.sys_colors[10] = get_sys_col(COLOR_HIGHLIGHT);
		evt->sys_cols.sys_colors[11] = get_sys_col(COLOR_HIGHLIGHTTEXT);
		evt->sys_cols.sys_colors[12] = get_sys_col(COLOR_INACTIVEBORDER);
		evt->sys_cols.sys_colors[13] = get_sys_col(COLOR_INACTIVECAPTION);
		evt->sys_cols.sys_colors[14] = get_sys_col(COLOR_INACTIVECAPTIONTEXT);
		evt->sys_cols.sys_colors[15] = get_sys_col(COLOR_INFOBK);
		evt->sys_cols.sys_colors[16] = get_sys_col(COLOR_INFOTEXT);
		evt->sys_cols.sys_colors[17] = get_sys_col(COLOR_MENU);
		evt->sys_cols.sys_colors[18] = get_sys_col(COLOR_MENUTEXT);
		evt->sys_cols.sys_colors[19] = get_sys_col(COLOR_SCROLLBAR);
		evt->sys_cols.sys_colors[20] = get_sys_col(COLOR_3DDKSHADOW);
		evt->sys_cols.sys_colors[21] = get_sys_col(COLOR_3DFACE);
		evt->sys_cols.sys_colors[22] = get_sys_col(COLOR_3DHIGHLIGHT);
		evt->sys_cols.sys_colors[23] = get_sys_col(COLOR_3DLIGHT);
		evt->sys_cols.sys_colors[24] = get_sys_col(COLOR_3DSHADOW);
		evt->sys_cols.sys_colors[25] = get_sys_col(COLOR_WINDOW);
		evt->sys_cols.sys_colors[26] = get_sys_col(COLOR_WINDOWFRAME);
		evt->sys_cols.sys_colors[27] = get_sys_col(COLOR_WINDOWTEXT);
		return GF_OK;
	}
	return GF_OK;
}

#if 1



#define ESC_QUERYESCSUPPORT 8
#define GETGXINFO			0x00020000

typedef struct GXDeviceInfo
{
	long Version;           //00
	void * pvFrameBuffer;   //04
	unsigned long cbStride; //08
	unsigned long cxWidth;  //0c
	unsigned long cyHeight; //10
	unsigned long cBPP;     //14
	unsigned long ffFormat; //18
	char Unused[0x84-7*4];
} GXDeviceInfo;


static Bool check_resolution_switch(GF_VideoOutput *dr, u32 width, u32 height)
{
	GAPICTX(dr);

	gctx->sys_w = GetSystemMetrics(SM_CXSCREEN);
	gctx->sys_h = GetSystemMetrics(SM_CYSCREEN);
	gctx->scale_coords = GF_FALSE;
	if (gctx->sys_w != width) gctx->scale_coords = GF_TRUE;
	else if (gctx->sys_h != height) gctx->scale_coords = GF_TRUE;

	if (gctx->scale_coords) {
		gctx->off_x = gctx->off_x * width / gctx->sys_w;
		gctx->off_y = gctx->off_y * height / gctx->sys_h;
	}

	HDC hdc = GetDC(gctx->hWnd);
	dr->dpi_x = (u32) (width * 25.4 / GetDeviceCaps(hdc, HORZSIZE) );
	dr->dpi_y = (u32) (height * 25.4 / GetDeviceCaps(hdc, VERTSIZE) );
	ReleaseDC(gctx->hWnd, hdc);

	if ((gctx->screen_w==width) && (gctx->screen_h==height)) return GF_FALSE;

	GF_Event evt;
	dr->max_screen_width = gctx->screen_w = width;
	dr->max_screen_height = gctx->screen_h = height;
	dr->max_screen_bpp = 8;//we don't filter for bpp less than 8

	evt.type = GF_EVENT_RESOLUTION;
	evt.size.width = dr->max_screen_width;
	evt.size.height = dr->max_screen_height;
	dr->on_event(the_video_driver->evt_cbk_hdl, &evt);

	return GF_TRUE;
}

#ifndef GETRAWFRAMEBUFFER
#define GETRAWFRAMEBUFFER   0x00020001
typedef struct _RawFrameBufferInfo
{
	WORD wFormat;
	WORD wBPP;
	VOID *pFramePointer;
	int	cxStride;
	int	cyStride;
	int cxPixels;
	int cyPixels;
} RawFrameBufferInfo;

#define FORMAT_565 1
#define FORMAT_555 2
#define FORMAT_OTHER 3
#endif

static GF_Err gapi_get_raw_fb(GF_VideoOutput *dr)
{
	long tmp;
	RawFrameBufferInfo Info;
	GAPICTX(dr);
	HDC DC = GetDC(NULL);
	memset(&Info,0,sizeof(RawFrameBufferInfo));

	ExtEscape(DC, GETRAWFRAMEBUFFER, 0, NULL, sizeof(RawFrameBufferInfo), (char*)&Info);
	if (!Info.pFramePointer /* && QueryPlatform(PLATFORM_VER) >= 421*/ )
	{
		//try gxinfo
		DWORD Code = GETGXINFO;
		if (ExtEscape(DC, ESC_QUERYESCSUPPORT, sizeof(DWORD), (char*)&Code, 0, NULL) > 0)
		{
			DWORD DCWidth = GetDeviceCaps(DC,HORZRES);
			DWORD DCHeight = GetDeviceCaps(DC,VERTRES);
			GXDeviceInfo GXInfo;
			memset(&GXInfo,0,sizeof(GXInfo));
			GXInfo.Version = 100;
			ExtEscape(DC, GETGXINFO, 0, NULL, sizeof(GXInfo), (char*)&GXInfo);

			// avoid VGA devices (or QVGA smartphones emulating 176x220)
			if (GXInfo.cbStride>0 && !(GXInfo.ffFormat & kfLandscape) &&
			        ((DCWidth == GXInfo.cxWidth && DCHeight == GXInfo.cyHeight) ||
			         (DCWidth == GXInfo.cyHeight && DCHeight == GXInfo.cxWidth)))
			{
				Bool Detect = GF_FALSE;
				int* p = (int*)GXInfo.pvFrameBuffer;
				COLORREF Old = GetPixel(DC,0,0);
				*p ^= -1;
				Detect = (Bool) ( GetPixel(DC,0,0) != Old );
				*p ^= -1;

				if (Detect)
				{
					Info.pFramePointer = GXInfo.pvFrameBuffer;
					Info.cxPixels = GXInfo.cxWidth;
					Info.cyPixels = GXInfo.cyHeight;
					Info.cxStride = GXInfo.cBPP/8;
					Info.cyStride = GXInfo.cbStride;
					Info.wBPP = (WORD)GXInfo.cBPP;
					Info.wFormat = (WORD)GXInfo.ffFormat;
				}
			}
		}
	}

	ReleaseDC(NULL,DC);

	if (!Info.pFramePointer) return GF_NOT_SUPPORTED;

	gctx->x_pitch = Info.cxStride;
	gctx->y_pitch = Info.cyStride;

	if (abs(Info.cyStride) < abs(Info.cxStride))
	{
		if (abs(Info.cxStride)*8 < Info.cyPixels*Info.wBPP &&
		        abs(Info.cxStride)*8 >= Info.cxPixels*Info.wBPP) { //swapped resolution
			tmp = Info.cxPixels;
			Info.cxPixels = Info.cyPixels;
			Info.cyPixels = tmp;
		}
	}
	else
	{
		if (abs(Info.cyStride)*8 < Info.cxPixels*Info.wBPP &&
		        abs(Info.cyStride)*8 >= Info.cyPixels*Info.wBPP) {//swapped resolution
			tmp = Info.cxPixels;
			Info.cxPixels = Info.cyPixels;
			Info.cyPixels = tmp;
		}
	}

	gctx->raw_ptr = (unsigned char *)Info.pFramePointer;

	if (Info.cxStride<0)
		gctx->raw_ptr += (Info.cxStride * (Info.cxPixels-1));
	if (Info.cyStride<0)
		gctx->raw_ptr += (Info.cyStride * (Info.cyPixels-1));

	if (check_resolution_switch(dr, Info.cxPixels, Info.cyPixels))
		return GF_EOS;

	return GF_OK;
}

#endif


static GF_Err GAPI_InitBackBuffer(GF_VideoOutput *dr, u32 VideoWidth, u32 VideoHeight)
{
	u32 gx_mode;
	GAPICTX(dr);

	if (!gctx || !VideoWidth || !VideoHeight) return GF_BAD_PARAM;

	gf_mx_p(gctx->mx);

	GAPI_ReleaseObjects(gctx);

	/*flip W&H in landscape mode*/
	if (landscape) {
		u32 t = VideoWidth;
		VideoWidth = VideoHeight;
		VideoHeight = t;
	}

	RECT rc;
	GetWindowRect(gctx->hWnd, &rc);
	gctx->off_x = rc.left;
	gctx->off_y = rc.top;
	gctx->erase_dest = GF_TRUE;

	const char *opt = gf_modules_get_option((GF_BaseInterface *)dr, "GAPI", "FBAccess");
	if (!opt || !strcmp(opt, "raw")) gx_mode = 2;
	else if (opt && !strcmp(opt, "gx")) gx_mode = 1;
	else gx_mode = 0;

	if ((gx_mode != gctx->gx_mode) || !gctx->screen_w) {
		struct GXDisplayProperties gx = GXGetDisplayProperties();

		gctx->x_pitch = gx.cbxPitch;
		gctx->y_pitch = gx.cbyPitch;

		gctx->gx_mode = gx_mode;
		if (gctx->gx_mode==2) {
			if (gapi_get_raw_fb(dr) == GF_EOS) {
				gf_mx_v(gctx->mx);
				return GF_OK;
			}
		} else if (check_resolution_switch(dr, gx.cxWidth, gx.cyHeight)) {
			gf_mx_v(gctx->mx);
			return GF_OK;
		}
	}
	if (gctx->gx_mode==2) {
		GF_Err e = gapi_get_raw_fb(dr);
		if (e) {
			gf_mx_v(gctx->mx);
			if (e==GF_EOS) return GF_OK;
			else return e;
		}
	}


	gctx->bb_size = VideoWidth * VideoHeight * gctx->BPP;
	gctx->bb_width = VideoWidth;
	gctx->bb_height = VideoHeight;
	gctx->bb_pitch = VideoWidth * gctx->BPP;


	if (gctx->gx_mode) {
		gctx->backbuffer = (char *) gf_malloc(sizeof(unsigned char) * gctx->bb_size);

		gctx->contiguous_mem = ((gctx->x_pitch==gctx->BPP) && (gctx->y_pitch==gctx->screen_w*gctx->BPP)) ? GF_TRUE : GF_FALSE;
	} else {
		createPixmap(gctx, 0);
	}

	gf_mx_v(gctx->mx);
	return GF_OK;
}


static void GAPI_AdjustLandscape(GAPIPriv *gctx, GF_VideoSurface *dst, s32 x_pitch, s32 y_pitch)
{
	if (y_pitch>0) {
#if 1
		dst->pitch_x = -y_pitch;
		/*start of frame-buffer is top-left corner*/
		if (x_pitch>0) {
			dst->video_buffer += dst->height * y_pitch;
			dst->pitch_y = x_pitch;
		}
		/*start of frame-buffer is top-right corner*/
		else {
			dst->video_buffer += dst->height * y_pitch + dst->width * x_pitch;
			dst->pitch_y = -x_pitch;
		}
#else
		dst->pitch_x = y_pitch;
		/*start of frame-buffer is top-left corner*/
		if (x_pitch>0) {
			dst->video_buffer += y_pitch - x_pitch;
			dst->pitch_y = -x_pitch;
		}
		/*start of frame-buffer is top-right corner*/
		else {
			dst->video_buffer += dst->height * y_pitch + dst->width * x_pitch;
			dst->pitch_y = -x_pitch;
		}
#endif
	} else {
		dst->pitch_x = y_pitch;
		/*start of frame-buffer is bottom-left corner*/
		if (x_pitch>0) {
			dst->pitch_y = x_pitch;
		}
		/*start of frame-buffer is bottom-right corner*/
		else {
			dst->video_buffer += dst->width * x_pitch;
			dst->pitch_y = x_pitch;
		}
	}
	u32 t = dst->width;
	dst->width = dst->height;
	dst->height = t;
}

static GF_Err GAPI_LockBackBuffer(GF_VideoOutput *dr, GF_VideoSurface *vi, Bool do_lock)
{
	GAPICTX(dr);

	if (do_lock) {
		if (!vi) return GF_BAD_PARAM;
		memset(vi, 0, sizeof(GF_VideoSurface));
		vi->width = gctx->bb_width;
		vi->height = gctx->bb_height;
		vi->video_buffer = gctx->backbuffer;
		vi->pixel_format = gctx->pixel_format;
		vi->is_hardware_memory = GF_FALSE;
		vi->pitch_x = gctx->x_pitch;
		vi->pitch_y = gctx->y_pitch;

		if (landscape)
			GAPI_AdjustLandscape(gctx, vi, gctx->x_pitch, gctx->y_pitch);
	}
	return GF_OK;
}


static void *NewGAPIVideoOutput()
{
	GAPIPriv *priv;
	GF_VideoOutput *driv = (GF_VideoOutput *) gf_malloc(sizeof(GF_VideoOutput));
	memset(driv, 0, sizeof(GF_VideoOutput));
	GF_REGISTER_MODULE_INTERFACE(driv, GF_VIDEO_OUTPUT_INTERFACE, "GAPI Video Output", "gpac distribution")

	priv = (GAPIPriv *) gf_malloc(sizeof(GAPIPriv));
	memset(priv, 0, sizeof(GAPIPriv));
	priv->mx = gf_mx_new("GAPI");
	driv->opaque = priv;

#ifdef GPAC_USE_GLES1X
	driv->hw_caps = GF_VIDEO_HW_OPENGL | GF_VIDEO_HW_OPENGL_OFFSCREEN | GF_VIDEO_HW_OPENGL_OFFSCREEN_ALPHA;
#endif
	/*rgb, yuv to do*/

	driv->Setup = GAPI_Setup;
	driv->Shutdown = GAPI_Shutdown;
	driv->Flush = GAPI_Flush;
	driv->ProcessEvent = GAPI_ProcessEvent;
	driv->Blit = NULL;
	driv->LockBackBuffer = GAPI_LockBackBuffer;
	driv->SetFullScreen = GAPI_SetFullScreen;
	return (void *)driv;
}

static void DeleteVideoOutput(void *ifce)
{
	GF_VideoOutput *driv = (GF_VideoOutput *) ifce;
	GAPICTX(driv);
	GAPI_Shutdown(driv);
	gf_mx_del(gctx->mx);
	gf_free(gctx);
	gf_free(driv);
}

#ifdef __cplusplus
extern "C" {
#endif


/*interface query*/
GPAC_MODULE_EXPORT
const u32 *QueryInterfaces()
{
	static u32 si [] = {
		GF_VIDEO_OUTPUT_INTERFACE,
		0
	};
	return si;
}

/*interface create*/
GPAC_MODULE_EXPORT
GF_BaseInterface *LoadInterface(u32 InterfaceType)
{
	if (InterfaceType == GF_VIDEO_OUTPUT_INTERFACE) return (GF_BaseInterface *) NewGAPIVideoOutput();
	return NULL;
}

/*interface destroy*/
GPAC_MODULE_EXPORT
void ShutdownInterface(GF_BaseInterface *ifce)
{
	GF_VideoOutput *dd = (GF_VideoOutput *)ifce;
	switch (dd->InterfaceType) {
	case GF_VIDEO_OUTPUT_INTERFACE:
		DeleteVideoOutput(dd);
		break;
	}
}

GPAC_MODULE_STATIC_DECLARATION( gapi )

#ifdef __cplusplus
}
#endif

