/*
    SDL - Simple DirectMedia Layer
    Copyright (C) 1997-2006 Sam Lantinga

    This library 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.1 of the License, or (at your option) any later version.

    This library 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; if not, write to the Free Software
    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

    Sam Lantinga
    slouken@libsdl.org
*/
#include "SDL_config.h"

/* Pocket PC GAPI SDL video driver implementation;
Implemented by Dmitry Yakimov - support@activekitten.com
Inspired by http://arisme.free.fr/ports/SDL.php
*/

// TODO: copy surface on window when lost focus
// TODO: test buttons rotation
// TODO: test on be300 and HPC ( check WinDib fullscreen keys catching )
// TODO: test on smartphones
// TODO: windib on SH3 PPC2000 landscape test
// TODO: optimize 8bpp landscape mode

// there is some problems in runnings apps from a device landscape mode
// due to WinCE bugs. Some works and some - does not.
// TODO: finish Axim Dell X30 and user landscape mode, device landscape mode
// TODO: finish Axim Dell X30 user portrait, device landscape stylus conversion
// TODO: fix running GAPI apps from landscape mode - 
//       wince goes to portrait mode, but does not update video memory


#include "SDL.h"
#include "SDL_error.h"
#include "SDL_video.h"
#include "SDL_mouse.h"
#include "../SDL_sysvideo.h"
#include "../SDL_pixels_c.h"
#include "../../events/SDL_events_c.h"
#include "../wincommon/SDL_syswm_c.h"
#include "../wincommon/SDL_sysmouse_c.h"
#include "../windib/SDL_dibevents_c.h" 

#include "SDL_gapivideo.h"

#define GAPIVID_DRIVER_NAME "gapi"

#if defined(DEBUG) || defined (_DEBUG) || defined(NDEBUG)
#define REPORT_VIDEO_INFO 1
#else
#define REPORT_VIDEO_INFO 0
#endif

// for testing with GapiEmu
#define USE_GAPI_EMU 0
#define EMULATE_AXIM_X30 0
#define WITHOUT_GAPI 0

#if USE_GAPI_EMU && !REPORT_VIDEO_INFO
#pragma message("Warning: Using GapiEmu in release build. I assume you'd like to set USE_GAPI_EMU to zero.")
#endif

// defined and used in SDL_sysevents.c
extern HINSTANCE aygshell;
extern void SDL_UnregisterApp();
extern int DIB_AddMode(_THIS, int bpp, int w, int h);

/* Initialization/Query functions */
static int GAPI_VideoInit(_THIS, SDL_PixelFormat *vformat);
static SDL_Rect **GAPI_ListModes(_THIS, SDL_PixelFormat *format, Uint32 flags);
static SDL_Surface *GAPI_SetVideoMode(_THIS, SDL_Surface *current, int width, int height, int bpp, Uint32 flags);
static int GAPI_SetColors(_THIS, int firstcolor, int ncolors, SDL_Color *colors);
static void GAPI_VideoQuit(_THIS);

/* Hardware surface functions */
static int GAPI_AllocHWSurface(_THIS, SDL_Surface *surface);
static int GAPI_LockHWSurface(_THIS, SDL_Surface *surface);
static void GAPI_UnlockHWSurface(_THIS, SDL_Surface *surface);
static void GAPI_FreeHWSurface(_THIS, SDL_Surface *surface);

/* Windows message handling functions, will not be processed */
static void GAPI_RealizePalette(_THIS);
static void GAPI_PaletteChanged(_THIS, HWND window);
static void GAPI_WinPAINT(_THIS, HDC hdc); 

/* etc. */
static void GAPI_UpdateRects(_THIS, int numrects, SDL_Rect *rects);

static HMODULE g_hGapiLib = 0;
#define LINK(type,name,import) \
	if( g_hGapiLib ) \
		name = (PFN##type)GetProcAddress( g_hGapiLib, _T(import) ); 

static char g_bRawBufferAvailable = 0;

/* GAPI driver bootstrap functions */

/* hi res definitions */
typedef struct _RawFrameBufferInfo
{
   WORD wFormat;
   WORD wBPP;
   VOID *pFramePointer;
   int  cxStride;
   int  cyStride;
   int  cxPixels;
   int  cyPixels;
} RawFrameBufferInfo; 

static struct _RawFrameBufferInfo g_RawFrameBufferInfo = {0};

#define GETRAWFRAMEBUFFER   0x00020001

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

/* Dell Axim x30 hangs when we use GAPI from landscape,
   so lets avoid using GxOpenDisplay there via GETGXINFO trick 
   It seems that GAPI subsystem use the same ExtEscape code.
*/
#define GETGXINFO 0x00020000

typedef struct GXDeviceInfo
{
long Version; //00 (should filled with 100 before calling ExtEscape)
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 int GAPI_Available(void)
{
	// try to use VGA display, even on emulator
	HDC hdc = GetDC(NULL); 
	int result = ExtEscape(hdc, GETRAWFRAMEBUFFER, 0, NULL, sizeof(RawFrameBufferInfo), (char *)&g_RawFrameBufferInfo);
	ReleaseDC(NULL, hdc);
	g_bRawBufferAvailable = result > 0;

#if WITHOUT_GAPI
	return g_bRawBufferAvailable;
#endif

#if USE_GAPI_EMU
	g_hGapiLib = LoadLibrary(_T("GAPI_Emu.dll"));
	if( !g_hGapiLib )
	{
		SDL_SetError("Gapi Emu not found!");
	}
	return g_hGapiLib != 0;
#endif

	// try to find gx.dll
	g_hGapiLib = LoadLibrary(_T("\\Windows\\gx.dll"));
	if( !g_hGapiLib )
	{
		g_hGapiLib = LoadLibrary(_T("gx.dll"));
		if( !g_hGapiLib ) return g_bRawBufferAvailable;
	}

	return(1);
}

static int cmpmodes(const void *va, const void *vb)
{
    SDL_Rect *a = *(SDL_Rect **)va;
    SDL_Rect *b = *(SDL_Rect **)vb;
    if ( a->w == b->w )
        return b->h - a->h;
    else
        return b->w - a->w;
}

static int GAPI_AddMode(_THIS, int bpp, int w, int h)
{
	SDL_Rect *mode;
	int i, index;
	int next_mode;

	/* Check to see if we already have this mode */
	if ( bpp < 8 ) {  /* Not supported */
		return(0);
	}
	index = ((bpp+7)/8)-1;
	for ( i=0; i<gapi->SDL_nummodes[index]; ++i ) {
		mode = gapi->SDL_modelist[index][i];
		if ( (mode->w == w) && (mode->h == h) ) {
			return(0);
		}
	}

	/* Set up the new video mode rectangle */
	mode = (SDL_Rect *)SDL_malloc(sizeof *mode);
	if ( mode == NULL ) {
		SDL_OutOfMemory();
		return(-1);
	}
	mode->x = 0;
	mode->y = 0;
	mode->w = w;
	mode->h = h;

	/* Allocate the new list of modes, and fill in the new mode */
	next_mode = gapi->SDL_nummodes[index];
	gapi->SDL_modelist[index] = (SDL_Rect **)
	       SDL_realloc(gapi->SDL_modelist[index], (1+next_mode+1)*sizeof(SDL_Rect *));
	if ( gapi->SDL_modelist[index] == NULL ) {
		SDL_OutOfMemory();
		gapi->SDL_nummodes[index] = 0;
		SDL_free(mode);
		return(-1);
	}
	gapi->SDL_modelist[index][next_mode] = mode;
	gapi->SDL_modelist[index][next_mode+1] = NULL;
	gapi->SDL_nummodes[index]++;

	return(0);
}

static void GAPI_DeleteDevice(SDL_VideoDevice *device)
{
	if( g_hGapiLib )
	{
		FreeLibrary(g_hGapiLib);
		g_hGapiLib = 0;
	}
	SDL_free(device->hidden);
	SDL_free(device);
}

static SDL_VideoDevice *GAPI_CreateDevice(int devindex)
{
	SDL_VideoDevice *device;

	if( !g_hGapiLib && !g_bRawBufferAvailable)
	{
		if( !GAPI_Available() )
		{
			SDL_SetError("GAPI dll is not found and VGA mode is not available!");
			return 0;
		}
	}

	/* Initialize all variables that we clean on shutdown */
	device = (SDL_VideoDevice *)SDL_malloc(sizeof(SDL_VideoDevice));
	if ( device ) {
		SDL_memset(device, 0, (sizeof *device));
		device->hidden = (struct SDL_PrivateVideoData *)
				SDL_malloc((sizeof *device->hidden));
	}
	if ( (device == NULL) || (device->hidden == NULL) ) {
		SDL_OutOfMemory();
		if ( device ) {
			SDL_free(device);
		}
		return(0);
	}
	SDL_memset(device->hidden, 0, (sizeof *device->hidden));

	/* Set the function pointers */
	device->VideoInit = GAPI_VideoInit;
	device->ListModes = GAPI_ListModes;
	device->SetVideoMode = GAPI_SetVideoMode;
	device->UpdateMouse = WIN_UpdateMouse; 
	device->CreateYUVOverlay = NULL;
	device->SetColors = GAPI_SetColors;
	device->UpdateRects = GAPI_UpdateRects;
	device->VideoQuit = GAPI_VideoQuit;
	device->AllocHWSurface = GAPI_AllocHWSurface;
	device->CheckHWBlit = NULL;
	device->FillHWRect = NULL;
	device->SetHWColorKey = NULL;
	device->SetHWAlpha = NULL;
	device->LockHWSurface = GAPI_LockHWSurface;
	device->UnlockHWSurface = GAPI_UnlockHWSurface;
	device->FlipHWSurface = NULL;
	device->FreeHWSurface = GAPI_FreeHWSurface;
	device->SetCaption = WIN_SetWMCaption;
	device->SetIcon = WIN_SetWMIcon;
	device->IconifyWindow = WIN_IconifyWindow;
	device->GrabInput = WIN_GrabInput;
	device->GetWMInfo = WIN_GetWMInfo;
	device->FreeWMCursor = WIN_FreeWMCursor;
	device->CreateWMCursor = WIN_CreateWMCursor; 
	device->ShowWMCursor = WIN_ShowWMCursor;	
	device->WarpWMCursor = WIN_WarpWMCursor; 
    device->CheckMouseMode = WIN_CheckMouseMode;
	device->InitOSKeymap = DIB_InitOSKeymap;
	device->PumpEvents = DIB_PumpEvents;

	/* Set up the windows message handling functions */
	WIN_RealizePalette = GAPI_RealizePalette;
	WIN_PaletteChanged = GAPI_PaletteChanged;
	WIN_WinPAINT = GAPI_WinPAINT;
	HandleMessage = DIB_HandleMessage; 

	device->free = GAPI_DeleteDevice;

	/* Load gapi library */
#define gx device->hidden->gxFunc

    LINK( GXOpenDisplay, gx.GXOpenDisplay,         "?GXOpenDisplay@@YAHPAUHWND__@@K@Z" )
    LINK( GXCloseDisplay, gx.GXCloseDisplay,        "?GXCloseDisplay@@YAHXZ" )
    LINK( GXBeginDraw, gx.GXBeginDraw,           "?GXBeginDraw@@YAPAXXZ" )
    LINK( GXEndDraw, gx.GXEndDraw,             "?GXEndDraw@@YAHXZ" )
    LINK( GXOpenInput, gx.GXOpenInput,           "?GXOpenInput@@YAHXZ" )
    LINK( GXCloseInput, gx.GXCloseInput,          "?GXCloseInput@@YAHXZ" )
    LINK( GXGetDisplayProperties, gx.GXGetDisplayProperties,"?GXGetDisplayProperties@@YA?AUGXDisplayProperties@@XZ" )
    LINK( GXGetDefaultKeys, gx.GXGetDefaultKeys,      "?GXGetDefaultKeys@@YA?AUGXKeyList@@H@Z" )
    LINK( GXSuspend, gx.GXSuspend,             "?GXSuspend@@YAHXZ" )
    LINK( GXResume, gx.GXResume,              "?GXResume@@YAHXZ" )
    LINK( GXSetViewport, gx.GXSetViewport,         "?GXSetViewport@@YAHKKKK@Z" )
    LINK( GXIsDisplayDRAMBuffer, gx.GXIsDisplayDRAMBuffer, "?GXIsDisplayDRAMBuffer@@YAHXZ" )

	/* wrong gapi.dll */
	if( !gx.GXOpenDisplay )
	{
		if( g_hGapiLib ) 
		{
			FreeLibrary(g_hGapiLib);
			g_hGapiLib = 0;
		}
	}
	
	if( !gx.GXOpenDisplay && !g_bRawBufferAvailable)
	{
		SDL_SetError("Error: damaged or unknown gapi.dll!\n");
		GAPI_DeleteDevice(device);
		return 0;
	}

	return device;
}

VideoBootStrap GAPI_bootstrap = {
	GAPIVID_DRIVER_NAME, "WinCE GAPI video driver",
	GAPI_Available, GAPI_CreateDevice
};

static void FillStructs(_THIS, BOOL useVga)
{
#ifdef _ARM_
	WCHAR oemstr[100];
#endif
	/* fill a device properties */

	if( !useVga )
	{
		this->hidden->gxProperties = this->hidden->gxFunc.GXGetDisplayProperties();
		this->hidden->needUpdate = 1;
		this->hidden->hiresFix = 0;
		this->hidden->useVga = 0;
		this->hidden->useGXOpenDisplay = 1;

#ifdef _ARM_
		/* check some devices and extract addition info */
		SystemParametersInfo( SPI_GETOEMINFO, sizeof( oemstr ), oemstr, 0 );

		// buggy iPaq38xx
		if ((oemstr[12] == 'H') && (oemstr[13] == '3') && (oemstr[14] == '8') && (this->hidden->gxProperties.cbxPitch > 0)) 
		{
			this->hidden->videoMem = (PIXEL*)0xac0755a0;
			this->hidden->gxProperties.cbxPitch = -640;
			this->hidden->gxProperties.cbyPitch = 2;
			this->hidden->needUpdate = 0;
		}
#if (EMULATE_AXIM_X30 == 0)
		// buggy Dell Axim X30
		if( _tcsncmp(oemstr, L"Dell Axim X30", 13) == 0 )
#endif
		{
			GXDeviceInfo gxInfo = {0};
			HDC hdc = GetDC(NULL);
			int result;

			gxInfo.Version = 100;
			result = ExtEscape(hdc, GETGXINFO, 0, NULL, sizeof(gxInfo), (char *)&gxInfo);
			if( result > 0 )
			{
				this->hidden->useGXOpenDisplay = 0;
				this->hidden->videoMem = gxInfo.pvFrameBuffer;
				this->hidden->needUpdate = 0;
				this->hidden->gxProperties.cbxPitch = 2;
				this->hidden->gxProperties.cbyPitch = 480;
				this->hidden->gxProperties.cxWidth = gxInfo.cxWidth;
				this->hidden->gxProperties.cyHeight = gxInfo.cyHeight;
				this->hidden->gxProperties.ffFormat = gxInfo.ffFormat;
			}
		}
#endif
	} else
	{
	    this->hidden->needUpdate = 0;		
		this->hidden->hiresFix = 0;
		this->hidden->gxProperties.cBPP = g_RawFrameBufferInfo.wBPP;
		this->hidden->gxProperties.cbxPitch = g_RawFrameBufferInfo.cxStride;
		this->hidden->gxProperties.cbyPitch = g_RawFrameBufferInfo.cyStride;
		this->hidden->gxProperties.cxWidth = g_RawFrameBufferInfo.cxPixels;
		this->hidden->gxProperties.cyHeight = g_RawFrameBufferInfo.cyPixels;
		this->hidden->videoMem = g_RawFrameBufferInfo.pFramePointer;
		this->hidden->useVga = 1;

		switch( g_RawFrameBufferInfo.wFormat )
		{
		case FORMAT_565:
			this->hidden->gxProperties.ffFormat = kfDirect565;
			break;
		case FORMAT_555:
			this->hidden->gxProperties.ffFormat = kfDirect555;
			break;
		default:
			/* unknown pixel format, try define by BPP! */
			switch( g_RawFrameBufferInfo.wBPP )
			{
			case 4:
			case 8:
				this->hidden->gxProperties.ffFormat = kfDirect;
			case 16:
				this->hidden->gxProperties.ffFormat = kfDirect565;
			default:
				this->hidden->gxProperties.ffFormat = kfDirect;
				break;
			}
		}
	}

	if( this->hidden->gxProperties.cBPP != 16 )
	{
		this->hidden->gapiOrientation = SDL_ORIENTATION_UP;
	} else
	if( (this->hidden->gxProperties.cbxPitch > 0) && (this->hidden->gxProperties.cbyPitch > 0 ))
	{
		this->hidden->gapiOrientation = SDL_ORIENTATION_UP;
	} else
	if( (this->hidden->gxProperties.cbxPitch > 0) && (this->hidden->gxProperties.cbyPitch < 0 ))
	{
		this->hidden->gapiOrientation = SDL_ORIENTATION_RIGHT; // ipaq 3660
	} else
	if( (this->hidden->gxProperties.cbxPitch < 0) && (this->hidden->gxProperties.cbyPitch > 0 ))
	{
		this->hidden->gapiOrientation = SDL_ORIENTATION_LEFT; // ipaq 3800
	}
}

static void GAPI_CreatePalette(int ncolors, SDL_Color *colors)
{
  // Setup a custom color palette
   BYTE buffer[ sizeof(LOGPALETTE) + 255 * sizeof(PALETTEENTRY) ];
   int i;
   LOGPALETTE*   pLogical = (LOGPALETTE*)buffer;
   PALETTEENTRY* entries  = pLogical->palPalEntry;
   HPALETTE hPalette;
   HDC hdc;

   for (i = 0; i < ncolors; ++i)
   {
      // Find intensity by replicating the bit patterns over a byte
      entries[i].peRed   = colors[i].r;
      entries[i].peGreen = colors[i].g;
      entries[i].peBlue  = colors[i].b;
      entries[i].peFlags = 0;
   }

   // Create the GDI palette object
   pLogical->palVersion    = 0x0300;
   pLogical->palNumEntries = ncolors;

   hPalette = CreatePalette( pLogical );
   ASSERT(hPalette);
	

   // Realize the palette
   hdc = GetDC(0);

   SelectPalette( hdc, hPalette, FALSE );
   RealizePalette( hdc );

   ReleaseDC( 0, hdc );
   DeleteObject( hPalette );
}

int GAPI_VideoInit(_THIS, SDL_PixelFormat *vformat)
{
	int i,bpp;

	/* Create the window */
	if ( DIB_CreateWindow(this) < 0 ) {
		return(-1);
	}

	if( g_hGapiLib )
	{
		FillStructs(this, 0);

		// SDL does not supports 2/4bpp mode, so use 16 bpp
		bpp = gapi->gxProperties.cBPP < 8 ? 16 : gapi->gxProperties.cBPP;
		
		/* set up normal and landscape mode */
		GAPI_AddMode(this, bpp, gapi->gxProperties.cyHeight, gapi->gxProperties.cxWidth);	
		GAPI_AddMode(this, bpp, gapi->gxProperties.cxWidth, gapi->gxProperties.cyHeight);	
	}

	/* add hi-res mode */
	if( g_bRawBufferAvailable && 
		!((gapi->gxProperties.cxWidth == (unsigned)g_RawFrameBufferInfo.cxPixels) && (gapi->gxProperties.cyHeight == (unsigned)g_RawFrameBufferInfo.cyPixels)))
	{
		FillStructs(this, 1);

		// SDL does not supports 2/4bpp mode, so use 16 bpp
		bpp = gapi->gxProperties.cBPP < 8 ? 16 : gapi->gxProperties.cBPP;

		/* set up normal and landscape mode */
		GAPI_AddMode(this, bpp, gapi->gxProperties.cyHeight, gapi->gxProperties.cxWidth);	
		GAPI_AddMode(this, bpp, gapi->gxProperties.cxWidth, gapi->gxProperties.cyHeight);	
	}

	/* Sort the mode lists */
	for ( i=0; i<NUM_MODELISTS; ++i ) {
		if ( gapi->SDL_nummodes[i] > 0 ) {
			SDL_qsort(gapi->SDL_modelist[i], gapi->SDL_nummodes[i], sizeof *gapi->SDL_modelist[i], cmpmodes);
		}
	}

	vformat->BitsPerPixel = this->hidden->gxProperties.cBPP < 8 ? 16 : (unsigned char)this->hidden->gxProperties.cBPP;

	// Get color mask
	if (this->hidden->gxProperties.ffFormat & kfDirect565) {
		vformat->BitsPerPixel = 16;
		vformat->Rmask = 0x0000f800;
		vformat->Gmask = 0x000007e0;
		vformat->Bmask = 0x0000001f;
		this->hidden->videoMode = GAPI_DIRECT_565;
	}
	else
	if (this->hidden->gxProperties.ffFormat & kfDirect555) {
		vformat->BitsPerPixel = 16;
		vformat->Rmask = 0x00007c00;
		vformat->Gmask = 0x000003e0;
		vformat->Bmask = 0x0000001f;
		this->hidden->videoMode = GAPI_DIRECT_555;
	}
	else
	if ((this->hidden->gxProperties.ffFormat & kfDirect) && (this->hidden->gxProperties.cBPP < 8)) {
		// We'll perform the conversion
		vformat->BitsPerPixel = 16;
		vformat->Rmask = 0x0000f800; // 16 bit 565
		vformat->Gmask = 0x000007e0;
		vformat->Bmask = 0x0000001f;
		if (this->hidden->gxProperties.ffFormat & kfDirectInverted)
			this->hidden->invert = (1 << this->hidden->gxProperties.cBPP) - 1;
		this->hidden->colorscale = this->hidden->gxProperties.cBPP < 8 ? 8 - this->hidden->gxProperties.cBPP : 0;
		this->hidden->videoMode = GAPI_MONO;
	}
	else
	if (this->hidden->gxProperties.ffFormat & kfPalette) {
		this->hidden->videoMode = GAPI_PALETTE;
	} 

	/* We're done! */
	return(0);
}

SDL_Rect **GAPI_ListModes(_THIS, SDL_PixelFormat *format, Uint32 flags)
{
	return(this->hidden->SDL_modelist[((format->BitsPerPixel+7)/8)-1]);
//  	 return (SDL_Rect **) -1;
}

SDL_Surface *GAPI_SetVideoMode(_THIS, SDL_Surface *current,
				int width, int height, int bpp, Uint32 flags)
{
	SDL_Surface *video; 
	Uint32 Rmask, Gmask, Bmask; 
	DWORD style; 
	SDL_Rect allScreen;

	if( bpp < 4 )
	{
		SDL_SetError("1 bpp and 2 bpp modes is not implemented yet!");
		return 0;
	}

	/* Recalculate bitmasks if necessary */
	if (bpp == current->format->BitsPerPixel) {
		video = current;
	}
	else {
		switch(bpp) {
			case 8:
				Rmask = 0;
				Gmask = 0;
				Bmask = 0;
				break;
			case 15:				
			case 16:
				/* Default is 565 unless the display is specifically 555 */
				if (this->hidden->gxProperties.ffFormat & kfDirect555) {
					Rmask = 0x00007c00;
					Gmask = 0x000003e0;
					Bmask = 0x0000001f;
				}
				else {
					Rmask = 0x0000f800;
					Gmask = 0x000007e0;
					Bmask = 0x0000001f;
				}
				break;
			case 24:
			case 32:
				Rmask = 0x00ff0000;
				Gmask = 0x0000ff00;
				Bmask = 0x000000ff;
				break;
			default:
				SDL_SetError("Unsupported Bits Per Pixel format requested");
				return NULL;
		}
		video = SDL_CreateRGBSurface(SDL_SWSURFACE,
					0, 0, bpp, Rmask, Gmask, Bmask, 0);
		if ( video == NULL ) {
			SDL_OutOfMemory();
			return(NULL);
		}
	}

	gapi->userOrientation = SDL_ORIENTATION_UP;
	video->flags = SDL_FULLSCREEN;	/* Clear flags, GAPI supports fullscreen only */

	/* GAPI or VGA? */
	if( g_hGapiLib )
	{
		FillStructs(this, 0);
		if( (((unsigned)width != gapi->gxProperties.cxWidth) || ((unsigned)height != gapi->gxProperties.cyHeight))
			&& (((unsigned)width != gapi->gxProperties.cyHeight) || ((unsigned)height != gapi->gxProperties.cxWidth)))
			FillStructs(this, 1); // gapi is found but we use VGA resolution			
	} else
		FillStructs(this, 1);

	if ( !this->hidden->needUpdate && !this->hidden->videoMem) {
		SDL_SetError("Couldn't get address of video memory, may be unsupported device or bug");
		return(NULL);
	}

	/* detect user landscape mode */
	if( (width > height) && (GetSystemMetrics(SM_CXSCREEN) < GetSystemMetrics(SM_CYSCREEN))) 
		gapi->userOrientation = SDL_ORIENTATION_RIGHT;

	/* shall we apply hires fix? for example when we do not use hires resource */
	gapi->hiresFix = 0;
	if( gapi->userOrientation == SDL_ORIENTATION_RIGHT )
	{
		if( (width > GetSystemMetrics(SM_CYSCREEN)) || (height > GetSystemMetrics(SM_CXSCREEN)))
			gapi->hiresFix = 1;
	} else
		if( (width > GetSystemMetrics(SM_CXSCREEN)) || (height > GetSystemMetrics(SM_CYSCREEN)))
			if( !((width == GetSystemMetrics(SM_CYSCREEN)) && (height == GetSystemMetrics(SM_CXSCREEN)))) // user portrait, device landscape
				gapi->hiresFix = 1;

	switch( gapi->userOrientation )
	{
	case SDL_ORIENTATION_UP:
		gapi->startOffset = 0;
		gapi->dstLineStep = gapi->gxProperties.cbyPitch;
		gapi->dstPixelStep = gapi->gxProperties.cbxPitch;
		break;
	case SDL_ORIENTATION_RIGHT:
		switch( gapi->gapiOrientation )
		{
		case SDL_ORIENTATION_UP:
		case SDL_ORIENTATION_RIGHT:
		case SDL_ORIENTATION_LEFT:
			if( (this->hidden->videoMode == GAPI_MONO) )
				gapi->startOffset = -gapi->gxProperties.cbxPitch + 1; // monochrome mode
			else
				gapi->startOffset = gapi->gxProperties.cbyPitch * (gapi->gxProperties.cyHeight - 1);
				
			gapi->dstLineStep = gapi->gxProperties.cbxPitch;
			gapi->dstPixelStep = -gapi->gxProperties.cbyPitch;
			break;
		}
	}

	video->w = this->hidden->w = width;
	video->h = this->hidden->h = height;
	video->pitch = SDL_CalculatePitch(video); 

	/* Small fix for WinCE/Win32 - when activating window
	   SDL_VideoSurface is equal to zero, so activating code
	   is not called properly for fullscreen windows because
	   macros WINDIB_FULLSCREEN uses SDL_VideoSurface
	*/
	SDL_VideoSurface = video;

	/* GAPI is always fullscreen, title bar is useless */
	style = 0;

	if (!SDL_windowid)
		SetWindowLongPtr(SDL_Window, GWL_STYLE, style);

	/* Allocate bitmap */
	if(gapiBuffer) 
	{
		SDL_free(gapiBuffer);
		gapiBuffer = NULL;
	}
	gapiBuffer = SDL_malloc(video->h * video->pitch);
	video->pixels = gapiBuffer; 

	if ( ! this->hidden->buffer ) {
		SDL_SetError("Couldn't allocate buffer for requested mode");
		return(NULL);
	}

	SDL_memset(gapiBuffer, 255, video->h * video->pitch);
	MoveWindow(SDL_Window, 0, 0, GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN), FALSE);
	ShowWindow(SDL_Window, SW_SHOW);
	SetForegroundWindow(SDL_Window);

	/* Open GAPI display */
	if( !gapi->useVga && this->hidden->useGXOpenDisplay )
		if( !gapi->gxFunc.GXOpenDisplay(SDL_Window, GX_FULLSCREEN) )
		{
			SDL_SetError("Couldn't initialize GAPI");
			return(NULL);
		}

#if REPORT_VIDEO_INFO
	printf("Video properties:\n");
	printf("display bpp: %d\n", gapi->gxProperties.cBPP);
	printf("display width: %d\n", gapi->gxProperties.cxWidth);
	printf("display height: %d\n", gapi->gxProperties.cyHeight);
	printf("x pitch: %d\n", gapi->gxProperties.cbxPitch);
	printf("y pitch: %d\n", gapi->gxProperties.cbyPitch);
	printf("gapi flags: 0x%x\n", gapi->gxProperties.ffFormat);

	if( !gapi->useVga && this->hidden->useGXOpenDisplay && gapi->needUpdate)
	{
		gapi->videoMem = gapi->gxFunc.GXBeginDraw(); 
		gapi->gxFunc.GXEndDraw();
	}

	printf("video memory: 0x%x\n", gapi->videoMem);
	printf("need update: %d\n", gapi->needUpdate);
	printf("hi-res fix: %d\n", gapi->hiresFix);
	printf("VGA is available on the device: %d\n", g_bRawBufferAvailable);
	printf("use raw framebuffer: %d\n", gapi->useVga);
	printf("video surface bpp: %d\n", video->format->BitsPerPixel);
	printf("video surface width: %d\n", video->w);
	printf("video surface height: %d\n", video->h);
#endif


	/* Blank screen */
	allScreen.x = allScreen.y = 0;
	allScreen.w = video->w - 1;
	allScreen.h = video->h - 1;
	GAPI_UpdateRects(this, 1, &allScreen);

	/* We're done */
	return(video);
}

/* We don't actually allow hardware surfaces other than the main one */
static int GAPI_AllocHWSurface(_THIS, SDL_Surface *surface)
{
	return(-1);
}
static void GAPI_FreeHWSurface(_THIS, SDL_Surface *surface)
{
	return;
}

/* We need to wait for vertical retrace on page flipped displays */
static int GAPI_LockHWSurface(_THIS, SDL_Surface *surface)
{
	return(0);
}

static void GAPI_UnlockHWSurface(_THIS, SDL_Surface *surface)
{
	return;
}

static int updateLine8to8(_THIS, unsigned char *srcPointer, unsigned char *destPointer, int width, int height, int lines)
{
	if( gapi->dstPixelStep == 1) /* optimized blitting on most devices */
	{
		SDL_memcpy(destPointer, srcPointer, width);
		return 1;
	} else
	{
		// TODO: read 4 pixels, write DWORD
		int step = gapi->dstPixelStep;
		while(width--)
		{
			*destPointer = *srcPointer++;
			destPointer += step;
		}
	}
	return 1;
}

/* Video memory is very slow so lets optimize as much as possible */
static int updateLine16to16(_THIS, PIXEL *srcPointer, PIXEL *destPointer, int width, int height, int lines)
{
	PIXEL *line1, *line2;
	int step = gapi->dstPixelStep / 2;

	if( step == 1 ) /* optimized blitting on most devices */
	{
		SDL_memcpy(destPointer, srcPointer, width * sizeof(PIXEL));
		return 1;
	}
	else
	{
		if( (gapi->gapiOrientation != SDL_ORIENTATION_UP) &&
			(gapi->userOrientation == SDL_ORIENTATION_UP )) // iPaq 3660/3800 and user orientation up
		{	
			// to prevent data misalignment copy only one line
			if( ((((unsigned)destPointer & 3) != 0) && (gapi->gapiOrientation == SDL_ORIENTATION_LEFT)) 
				|| ((((unsigned)destPointer & 3) == 0) && (gapi->gapiOrientation != SDL_ORIENTATION_LEFT))
				|| (lines == 1) ) 
			{
				while(width--)
				{
					*destPointer = *srcPointer++;
					destPointer += step;
				}
				return 1;
			}

			/* read two lines at the same time, write DWORD */
			line1 = srcPointer;
			line2 = srcPointer + SDL_VideoSurface->pitch / 2;

			if( gapi->gapiOrientation == SDL_ORIENTATION_LEFT )
				while(width--) // iPaq 3800
				{
					*(DWORD*)destPointer =(*line2++ << 16) | *line1++;
					destPointer += step;
				}
			else
			{
				destPointer += gapi->gxProperties.cbyPitch / 2;

				while(width--) // iPaq 3660
				{
					*(DWORD*)destPointer =(*line1++ << 16) | *line2++;
					destPointer += step;
				}
			}
			return 2;
		} else
		{
			// iPaq 3800 and user orientation landscape
			if( gapi->gapiOrientation == SDL_ORIENTATION_LEFT )
			{
				int w1;

				// to prevent data misalignment copy only one pixel
				if( (((unsigned)destPointer & 3) == 0) && (width > 0)) 
				{
					*destPointer-- = *srcPointer++;
					width--;
				}

				destPointer--;

				w1 = width / 2;

				while(w1--)
				{
					DWORD p = *(DWORD*)srcPointer;
					*((DWORD*)destPointer) = (p << 16) | (p >> 16);
					destPointer -= 2;
					srcPointer += 2;
				}

				if( width & 1 ) // copy the last pixel
				{
					destPointer++;
					*destPointer = *srcPointer;
				}

				return 1;
			}

			// modern iPaqs and user orientation landscape
			// read two pixels, write DWORD

			line1 = srcPointer;
			line2 = srcPointer + SDL_VideoSurface->pitch / 2;

			if( (((unsigned)destPointer & 3) != 0) || (lines == 1) ) 
			{
				while(width--)
				{
					*destPointer = *srcPointer++;
					destPointer += step;
				}
				return 1;
			}
			
			while(width--)
			{
				*(DWORD*)destPointer =(*line2++ << 16) | *line1++;
				destPointer -= gapi->gxProperties.cbyPitch / 2;
			}
			return 2;
		}
	}
}

// Color component masks for 565
#define REDMASK (31<<11)
#define GREENMASK (63<<5)
#define BLUEMASK (31)


static int updateLine16to4(_THIS, PIXEL *srcPointer, unsigned char *destPointer, int width, int height, int lines, int yNibble, int xNibble)
{
	PIXEL *line1, *line2;
	int step = gapi->dstPixelStep;

	if( gapi->userOrientation == SDL_ORIENTATION_UP )
	{
		if( yNibble ) // copy bottom half of a line
		{
			while(width--)
			{
				PIXEL c1 = *srcPointer++;
				c1 = ((c1 & REDMASK) >> 11) + ((c1 & GREENMASK) >> 5) + (c1 & BLUEMASK);			
				*destPointer = (*destPointer & 0x0F) | ((~(c1 >> 3) << 4));
				destPointer += step;
			}
			return 1;
		}

		// either 1 pixel picture or tail, anyway this is the last line
		if( lines == 1 )
		{
			while(width--)
			{
				PIXEL c1 = *srcPointer++;
				c1 = ((c1 & REDMASK) >> 11) + ((c1 & GREENMASK) >> 5) + (c1 & BLUEMASK);			
				*destPointer = (*destPointer & 0xF0) | ((~(c1 >> 3) & 0xF));
				destPointer += step;
			}
			return 1;
		}

		line1 = srcPointer;
		line2 = srcPointer + SDL_VideoSurface->pitch / 2;

		while(width--)
		{
			PIXEL c1 = *line1++;
			PIXEL c2 = *line2++;
			c1 = ((c1 & REDMASK) >> 11) + ((c1 & GREENMASK) >> 5) + (c1 & BLUEMASK);
			c2 = ((c2 & REDMASK) >> 11) + ((c2 & GREENMASK) >> 5) + (c2 & BLUEMASK);
			*destPointer = ~((c1 >> 3) + ((c2 >> 3) << 4));
			destPointer += step;
		}
		return 2;
	} else
	{
		int w1;
		w1 = width / 2;

		if( xNibble )
		{
			// copy one pixel
			PIXEL c1 = *srcPointer++;
			c1 = ((c1 & REDMASK) >> 11) + ((c1 & GREENMASK) >> 5) + (c1 & BLUEMASK);			
			*destPointer = (*destPointer & 0xF0) | ((~(c1 >> 3) & 0xF));
			destPointer++;
		}

		while(w1--)
		{
			PIXEL c1 = *srcPointer;
			PIXEL c2 = *(srcPointer + 1);
			c1 = ((c1 & REDMASK) >> 11) + ((c1 & GREENMASK) >> 5) + (c1 & BLUEMASK);
			c2 = ((c2 & REDMASK) >> 11) + ((c2 & GREENMASK) >> 5) + (c2 & BLUEMASK);
			*destPointer++ = ~((c2 >> 3) + ((c1 >> 3) << 4));
			srcPointer += 2;
		}

		// copy tail
		if( (width & 1) && !xNibble )
		{
			PIXEL c1 = *srcPointer;
			c1 = ((c1 & REDMASK) >> 11) + ((c1 & GREENMASK) >> 5) + (c1 & BLUEMASK);			
			*destPointer = (*destPointer & 0x0F) | ((~(c1 >> 3) << 4));
		}

		return 1;
	}
}

static void GAPI_UpdateRectsMono(_THIS, int numrects, SDL_Rect *rects)
{
	int i, height;
	int linesProcessed;
	int xNibble, yNibble;

	for (i=0; i<numrects; i++)
	{
		unsigned char *destPointer;
		unsigned char *srcPointer;

		if( gapi->userOrientation == SDL_ORIENTATION_UP )
			destPointer = (unsigned char*) gapi->videoMem + gapi->startOffset - rects[i].y * gapi->gxProperties.cBPP / 8 + rects[i].x * gapi->dstPixelStep;
		else
			destPointer = (unsigned char*) gapi->videoMem + gapi->startOffset + rects[i].x * gapi->gxProperties.cBPP / 8 + rects[i].y * gapi->dstLineStep;

		srcPointer = ((unsigned char*) SDL_VideoSurface->pixels) + rects[i].y * SDL_VideoSurface->pitch + rects[i].x * 2;
		yNibble = rects[i].y & 1; // TODO: only for 4 bpp
		xNibble = rects[i].x & 1;
		height = rects[i].h;
		while (height > 0)
		{
			switch(gapi->gxProperties.cBPP)
			{
			case 2: // TODO
			case 4:
					linesProcessed = updateLine16to4(this, (PIXEL*) srcPointer, destPointer, rects[i].w, rects[i].h, height, yNibble, xNibble);
					yNibble = 0;
			}
			height -= linesProcessed;
			if( gapi->userOrientation == SDL_ORIENTATION_UP )
				destPointer--; // always fill 1 byte
			else destPointer += gapi->dstLineStep;
			srcPointer += SDL_VideoSurface->pitch * linesProcessed; // pitch in bytes
		}
	}
}

static void GAPI_UpdateRectsColor(_THIS, int numrects, SDL_Rect *rects)
{
	int i, height;
	int bytesPerPixel = (gapi->gxProperties.cBPP + 1) / 8;
	int linesProcessed;
	for (i=0; i<numrects; i++) {
		unsigned char *destPointer = (unsigned char*) gapi->videoMem + gapi->startOffset + rects[i].y * gapi->dstLineStep + rects[i].x * gapi->dstPixelStep;
		unsigned char *srcPointer = ((unsigned char*) SDL_VideoSurface->pixels) + rects[i].y * SDL_VideoSurface->pitch + rects[i].x * bytesPerPixel;
		height = rects[i].h;

//		fprintf(stderr, "Starting rect %dx%d, dst=0x%x, w = %d, h = %d\n", rects[i].w, rects[i].h,destPointer,rects[i].w,rects[i].h);
//		fflush(stderr);
		linesProcessed = height;

		while (height > 0) {
			switch(bytesPerPixel)
			{
			case 1:
				linesProcessed = updateLine8to8(this, srcPointer, (unsigned char *) destPointer, rects[i].w, rects[i].h, height);
				break;
			case 2:
#pragma warning(disable: 4133)
				linesProcessed = updateLine16to16(this, (PIXEL*) srcPointer, destPointer, rects[i].w, rects[i].h, height);
				break;
			}
			height -= linesProcessed;
			destPointer += gapi->dstLineStep * linesProcessed;
			srcPointer += SDL_VideoSurface->pitch * linesProcessed; // pitch in bytes
		}
//		fprintf(stderr, "End of rect\n");
//		fflush(stderr);
	}
}


static void GAPI_UpdateRects(_THIS, int numrects, SDL_Rect *rects)
{
	if( gapi->needUpdate )
		gapi->videoMem = gapi->gxFunc.GXBeginDraw(); 

	if( gapi->gxProperties.cBPP < 8 )
		GAPI_UpdateRectsMono(this, numrects, rects);
	else
		GAPI_UpdateRectsColor(this, numrects, rects);

	if( gapi->needUpdate )
		gapi->gxFunc.GXEndDraw();
}

static void FlushMessageQueue()
{
	MSG  msg;
	while ( PeekMessage(&msg, NULL, 0, 0, PM_REMOVE) ) {
		if ( msg.message == WM_QUIT ) break;
		TranslateMessage( &msg );
		DispatchMessage( &msg );
	}
}


/* Note:  If we are terminated, this could be called in the middle of
   another SDL video routine -- notably UpdateRects.
*/
void GAPI_VideoQuit(_THIS)
{
	int i, j;
	/* Destroy the window and everything associated with it */
	if ( SDL_Window ) 
	{
	    if ((g_hGapiLib != 0) && this && this->hidden && this->hidden->gxFunc.GXCloseDisplay && !this->hidden->useVga)
			this->hidden->gxFunc.GXCloseDisplay(); 

		if (this->screen->pixels != NULL)
		{
			SDL_free(this->screen->pixels);
			this->screen->pixels = NULL;
		}
		if ( screen_icn ) {
			DestroyIcon(screen_icn);
			screen_icn = NULL;
		}

		DIB_DestroyWindow(this);
		SDL_UnregisterApp();
		FlushMessageQueue();

		SDL_Window = NULL;
#if defined(_WIN32_WCE)

// Unload wince aygshell library to prevent leak
		if( aygshell ) 
		{
			FreeLibrary(aygshell);
			aygshell = NULL;
		}
#endif

	/* Free video mode lists */
	for ( i=0; i<NUM_MODELISTS; ++i ) {
		if ( gapi->SDL_modelist[i] != NULL ) {
			for ( j=0; gapi->SDL_modelist[i][j]; ++j )
				SDL_free(gapi->SDL_modelist[i][j]);
			SDL_free(gapi->SDL_modelist[i]);
			gapi->SDL_modelist[i] = NULL;
		}
	}

	}

}

static void GAPI_RealizePalette(_THIS)
{
	OutputDebugString(TEXT("GAPI_RealizePalette NOT IMPLEMENTED !\r\n"));
}

static void GAPI_PaletteChanged(_THIS, HWND window)
{
	OutputDebugString(TEXT("GAPI_PaletteChanged NOT IMPLEMENTED !\r\n"));
}

/* Exported for the windows message loop only */
static void GAPI_WinPAINT(_THIS, HDC hdc)
{
	OutputDebugString(TEXT("GAPI_WinPAINT NOT IMPLEMENTED !\r\n"));
}

int GAPI_SetColors(_THIS, int firstcolor, int ncolors, SDL_Color *colors) 
{
	GAPI_CreatePalette(ncolors, colors);
	return 1;
}
