/* ScummVM - Graphic Adventure Engine
 *
 * ScummVM is the legal property of its developers, whose names
 * are too numerous to list here. Please refer to the COPYRIGHT
 * file distributed with this source distribution.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.

 * This program 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 General Public License for more details.

 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * $URL: https://scummvm.svn.sourceforge.net/svnroot/scummvm/scummvm/tags/release-1-1-1/backends/platform/wince/wince-sdl.cpp $
 * $Id: wince-sdl.cpp 45975 2009-11-18 15:22:15Z fingolfin $
 *
 */

#include "backends/platform/wince/wince-sdl.h"

#include "common/config-manager.h"
#include "common/debug.h"
#include "common/events.h"
#include "common/util.h"
#include "common/timer.h"

#include "engines/engine.h"

#include "base/main.h"
#include "base/plugins.h"

#include "sound/mixer_intern.h"
#include "sound/fmopl.h"

#include "backends/timer/default/default-timer.h"

#include "gui/Actions.h"
#include "gui/KeysDialog.h"
#include "gui/message.h"

#include "backends/platform/wince/resource.h"
#include "backends/platform/wince/CEActionsPocket.h"
#include "backends/platform/wince/CEActionsSmartphone.h"
#include "backends/platform/wince/CEgui/ItemAction.h"

#include "graphics/scaler/downscaler.h"

#include "backends/platform/wince/CEException.h"
#include "backends/platform/wince/CEScaler.h"

#ifdef USE_VORBIS
#ifndef USE_TREMOR
#include <vorbis/vorbisfile.h>
#else
#include <tremor/ivorbisfile.h>
#endif
#endif

#ifdef DYNAMIC_MODULES
#include "backends/plugins/win32/win32-provider.h"
#endif

#ifdef __GNUC__
extern "C" _CRTIMP FILE* __cdecl   _wfreopen (const wchar_t*, const wchar_t*, FILE*);
#endif

#define SAMPLES_PER_SEC_OLD 11025
#define SAMPLES_PER_SEC_NEW 22050

using namespace CEGUI;

// ********************************************************************************************

// Internal GUI names

#define NAME_MAIN_PANEL			"MainPanel"
#define NAME_PANEL_KEYBOARD		"Keyboard"
#define NAME_ITEM_OPTIONS		"Options"
#define NAME_ITEM_SKIP			"Skip"
#define NAME_ITEM_SOUND			"Sound"
#define NAME_ITEM_ORIENTATION	"Orientation"
#define NAME_ITEM_BINDKEYS		"Bindkeys"

// stdin/err redirection
#define STDOUT_FNAME "\\scummvm_stdout.txt"
#define STDERR_FNAME "\\scummvm_stderr.txt"
static FILE *stdout_file = NULL, *stderr_file = NULL;
static char stdout_fname[MAX_PATH], stderr_fname[MAX_PATH];

// Static member inits
typedef void (*SoundProc)(void *param, byte *buf, int len);
bool OSystem_WINCE3::_soundMaster = true;

bool _isSmartphone = false;
bool _hasSmartphoneResolution = false;

// Graphics mode consts

// Low end devices 240x320

static const OSystem::GraphicsMode s_supportedGraphicsModesLow[] = {
	{"1x", "Normal (no scaling)", GFX_NORMAL},
	{0, 0, 0}
};

// High end device 480x640

static const OSystem::GraphicsMode s_supportedGraphicsModesHigh[] = {
	{"1x", "Normal (no scaling)", GFX_NORMAL},
	{"2x", "2x", GFX_DOUBLESIZE},
#ifndef _MSC_VER // EVC breaks template functions, and I'm tired of fixing them :)
	{"2xsai", "2xSAI", GFX_2XSAI},
	{"super2xsai", "Super2xSAI", GFX_SUPER2XSAI},
	{"supereagle", "SuperEagle", GFX_SUPEREAGLE},
#endif
	{"advmame2x", "AdvMAME2x", GFX_ADVMAME2X},
#ifndef _MSC_VER
	{"hq2x", "HQ2x", GFX_HQ2X},
	{"tv2x", "TV2x", GFX_TV2X},
#endif
	{"dotmatrix", "DotMatrix", GFX_DOTMATRIX},
	{0, 0, 0}
};

#define DEFAULT_CONFIG_FILE "scummvm.ini"

// ********************************************************************************************

bool isSmartphone() {
	//return _isSmartphone;
	return _hasSmartphoneResolution;
}

const TCHAR *ASCIItoUnicode(const char *str) {
	static TCHAR ustr[MAX_PATH];	// size good enough

	MultiByteToWideChar(CP_ACP, 0, str, strlen(str) + 1, ustr, sizeof(ustr) / sizeof(TCHAR));
	return ustr;
}

// MAIN
#ifndef __GNUC__
int handleException(EXCEPTION_POINTERS *exceptionPointers) {
	CEException::writeException(TEXT("\\scummvmCrash"), exceptionPointers);
	drawError("Unrecoverable exception occurred - see crash dump in latest \\scummvmCrash file");
	fclose(stdout_file);
	fclose(stderr_file);
	CEDevice::end();
	SDL_Quit();
	exit(0);
	return EXCEPTION_EXECUTE_HANDLER;
}
#endif

OSystem *OSystem_WINCE3_create() {
	return new OSystem_WINCE3();
}

extern "C" char *getcwd(char *buf, int size);
int SDL_main(int argc, char **argv) {
	FILE *newfp = NULL;
#ifdef __GNUC__
	// Due to incomplete crt0.o implementation, we go through the constructor function
	// list provided by the linker and init all of them
	// thanks to joostp and DJWillis
	extern void (*__CTOR_LIST__)();
	void (**constructor)() = &__CTOR_LIST__;
	constructor++;	// First item in list of constructors has special meaning (platform dependent), ignore it.
	while (*constructor) {
		(*constructor)();
		constructor++;
	}
#endif

	CEDevice::init();

	/* Redirect standard input and standard output */
	strcpy(stdout_fname, getcwd(NULL, MAX_PATH));
	strcpy(stderr_fname, getcwd(NULL, MAX_PATH));
	strcat(stdout_fname, STDOUT_FNAME);
	strcat(stderr_fname, STDERR_FNAME);
#ifndef __GNUC__
	stdout_file = fopen(stdout_fname, "w");
	stderr_file = fopen(stderr_fname, "w");
#else
	stdout_file = newfp = _wfreopen(ASCIItoUnicode(stdout_fname), TEXT("w"), stdout);
	if (newfp == NULL) {
#if !defined(stdout)
		stdout = fopen(stdout_fname, "w");
		stdout_file = stdout;
#else
		newfp = fopen(stdout_fname, "w");
		if (newfp) {
			//*stdout = *newfp;
			stdout_file = stdout;
		}
#endif
	}
	stderr_file = newfp = _wfreopen(ASCIItoUnicode(stderr_fname), TEXT("w"), stderr);
	if (newfp == NULL) {
#if !defined(stderr)
		stderr = fopen(stderr_fname, "w");
		stderr_file = stderr;
#else
		newfp = fopen(stderr_fname, "w");
		if (newfp) {
			//*stderr = *newfp;
			stderr_file = stderr;
		}
#endif
	}
#endif

#ifdef DYNAMIC_MODULES
	PluginManager::instance().addPluginProvider(new Win32PluginProvider());
#endif


	int res = 0;
#if !defined(DEBUG) && !defined(__GNUC__)
	__try {
#endif
		g_system = OSystem_WINCE3_create();
		assert(g_system);

		// Invoke the actual ScummVM main entry point:
		res = scummvm_main(argc, argv);
		g_system->quit();	// TODO: Consider removing / replacing this!
#if !defined(DEBUG) && !defined(__GNUC__)
	}
	__except (handleException(GetExceptionInformation())) {
	}
#endif

	return res;
}

#ifdef DYNAMIC_MODULES

/* This is the OS startup code in the case of a plugin-enabled build.
 * It contains copied and slightly modified parts of SDL's win32/ce startup functions.
 * We copy these here because the calling stub already has a WinMain procedure
 * which overrides SDL's one and hence we essentially re-implement the startup procedure.
 * Note also that this has to be here and not in the stub because SDL is statically
 * linked in the scummvm.dll archive.
 * Take a look at the comments in stub.cpp as well.
 */

int console_main(int argc, char *argv[]) {
	int n;
	char *bufp, *appname;

	appname = argv[0];
	if ( (bufp=strrchr(argv[0], '\\')) != NULL )
		appname = bufp + 1;
	else if ( (bufp=strrchr(argv[0], '/')) != NULL )
		appname = bufp + 1;

	if ( (bufp=strrchr(appname, '.')) == NULL )
		n = strlen(appname);
	else
		n = (bufp-appname);

	bufp = (char *) alloca(n + 1);
	strncpy(bufp, appname, n);
	bufp[n] = '\0';
	appname = bufp;

	if ( SDL_Init(SDL_INIT_NOPARACHUTE) < 0 ) {
		error("WinMain() error: %d", SDL_GetError());
		return(FALSE);
	}

	SDL_SetModuleHandle(GetModuleHandle(NULL));

	// Run the application main() code
	SDL_main(argc, argv);

	return(0);
}

static int ParseCommandLine(char *cmdline, char **argv) {
	char *bufp;
	int argc;

	argc = 0;
	for (bufp = cmdline; *bufp;) {
		// Skip leading whitespace
		while (isspace(*bufp))
			++bufp;

		// Skip over argument
		if (*bufp == '"') {
			++bufp;
			if (*bufp) {
				if (argv)
					argv[argc] = bufp;
				++argc;
			}
			// Skip over word
			while (*bufp && (*bufp != '"'))
				++bufp;
		} else {
			if (*bufp) {
				if (argv)
					argv[argc] = bufp;
				++argc;
			}
			// Skip over word
			while (*bufp && ! isspace(*bufp))
				++bufp;
		}
		if (*bufp) {
			if (argv)
				*bufp = '\0';
			++bufp;
		}
	}
	if (argv)
		argv[argc] = NULL;

	return(argc);
}

int dynamic_modules_main(HINSTANCE hInst, HINSTANCE hPrev, LPWSTR szCmdLine, int sw) {
	HINSTANCE handle;
	char **argv;
	int argc;
	char *cmdline;
	wchar_t *bufp;
	int nLen;

	if (wcsncmp(szCmdLine, TEXT("\\"), 1)) {
		nLen = wcslen(szCmdLine)+128+1;
		bufp = (wchar_t *) alloca(nLen*2);
		wcscpy (bufp, TEXT("\""));
		GetModuleFileName(NULL, bufp+1, 128-3);
		wcscpy (bufp+wcslen(bufp), TEXT("\" "));
		wcsncpy(bufp+wcslen(bufp), szCmdLine,nLen-wcslen(bufp));
	} else
		bufp = szCmdLine;

	nLen = wcslen(bufp)+1;
	cmdline = (char *) alloca(nLen);
	WideCharToMultiByte(CP_ACP, 0, bufp, -1, cmdline, nLen, NULL, NULL);

	// Parse command line into argv and argc
	argc = ParseCommandLine(cmdline, NULL);
	argv = (char **) alloca((argc+1)*(sizeof *argv));
	ParseCommandLine(cmdline, argv);

	// fix gdb-emulator combo
	while (argc > 1 && !strstr(argv[0], ".exe")) {
		OutputDebugString(TEXT("SDL: gdb argv[0] fixup\n"));
		*(argv[1]-1) = ' ';
		int i;
		for (i=1; i<argc; i++)
			argv[i] = argv[i+1];
		argc--;
	}

	// Run the main program (after a little SDL initialization)
	return(console_main(argc, argv));

}
#endif

// ********************************************************************************************


// ********************************************************************************************

void pumpMessages() {
	MSG msg;
	while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
}

void drawError(char *error) {
	TCHAR errorUnicode[200];
	MultiByteToWideChar(CP_ACP, 0, error, strlen(error) + 1, errorUnicode, sizeof(errorUnicode));
	pumpMessages();
	MessageBox(GetActiveWindow(), errorUnicode, TEXT("ScummVM error"), MB_OK | MB_ICONERROR);
	pumpMessages();
}

// ********************************************************************************************
static DefaultTimerManager *_int_timer = NULL;
static Uint32 timer_handler_wrapper(Uint32 interval) {
	_int_timer->handler();
	return interval;
}

void OSystem_WINCE3::initBackend() {
	// Instantiate our own sound mixer
	// mixer init is rerun when a game engine is selected.
	setupMixer();

	// Create the timer. CE SDL does not support multiple timers (SDL_AddTimer).
	// We work around this by using the SetTimer function, since we only use
	// one timer in scummvm (for the time being)
	_timer = _int_timer = new DefaultTimerManager();
	_timerID = NULL;	// OSystem_SDL will call removetimer with this, it's ok
	SDL_SetTimer(10, &timer_handler_wrapper);

	// Chain init
	OSystem_SDL::initBackend();

	// Query SDL for screen size and init screen dependent stuff
	OSystem_WINCE3::initScreenInfos();
	_isSmartphone = CEDevice::isSmartphone();
	create_toolbar();
	_hasSmartphoneResolution = CEDevice::hasSmartphoneResolution() || CEDevice::isSmartphone();
	if (_hasSmartphoneResolution)
		_panelVisible = false;	// init correctly in smartphones

	// Initialize global key mapping
	GUI::Actions::init();
	GUI_Actions::Instance()->initInstanceMain(this);
	if (!GUI_Actions::Instance()->loadMapping()) {	// error during loading means not present/wrong version
		warning("Setting default action mappings.");
		GUI_Actions::Instance()->saveMapping();	// write defaults
	}

	loadDeviceConfiguration();
}

int OSystem_WINCE3::getScreenWidth() {
	return _platformScreenWidth;
}

int OSystem_WINCE3::getScreenHeight() {
	return _platformScreenHeight;
}

void OSystem_WINCE3::initScreenInfos() {
	// sdl port ensures that we use correctly full screen
	_isOzone = 0;
	SDL_Rect **r;
	r = SDL_ListModes(NULL, 0);
	_platformScreenWidth = r[0]->w;
	_platformScreenHeight = r[0]->h;
}

bool OSystem_WINCE3::isOzone() {
	return _isOzone;
}

static Common::String getDefaultConfigFileName() {
	char configFile[MAXPATHLEN];
	strcpy(configFile, getcwd(NULL, MAX_PATH));
	strcat(configFile, "\\");
	strcat(configFile, DEFAULT_CONFIG_FILE);
	return configFile;
}

Common::SeekableReadStream *OSystem_WINCE3::createConfigReadStream() {
	Common::FSNode file(getDefaultConfigFileName());
	return file.createReadStream();
}

Common::WriteStream *OSystem_WINCE3::createConfigWriteStream() {
	Common::FSNode file(getDefaultConfigFileName());
	return file.createWriteStream();
}

// ********************************************************************************************


OSystem_WINCE3::OSystem_WINCE3() : OSystem_SDL(),
	_orientationLandscape(0), _newOrientation(0), _panelInitialized(false), _canBeAspectScaled(false),
	_panelVisible(true), _panelStateForced(false), _forceHideMouse(false), _unfilteredkeys(false),
	_freeLook(false), _forcePanelInvisible(false), _toolbarHighDrawn(false), _zoomUp(false), _zoomDown(false),
	_scalersChanged(false), _lastKeyPressed(0), _tapTime(0), _closeClick(false), _noDoubleTapRMB(false),
	_saveToolbarState(false), _saveActiveToolbar(NAME_MAIN_PANEL), _rbutton(false), _hasfocus(true),
	_usesEmulatedMouse(false), _mouseBackupOld(NULL), _mouseBackupToolbar(NULL), _mouseBackupDim(0)
{
	memset(&_mouseCurState, 0, sizeof(_mouseCurState));
	if (_isSmartphone) {
		_mouseCurState.x = 20;
		_mouseCurState.y = 20;
	}

	_mixer = 0;
	_screen = NULL;
}

void OSystem_WINCE3::swap_panel_visibility() {
	//if (!_forcePanelInvisible && !_panelStateForced) {
		if (_zoomDown || _zoomUp)	return;

		if (_panelVisible) {
			if (_toolbarHandler.activeName() == NAME_PANEL_KEYBOARD)
				_panelVisible = !_panelVisible;
			else
				_toolbarHandler.setActive(NAME_PANEL_KEYBOARD);
		} else {
			_toolbarHandler.setActive(NAME_MAIN_PANEL);
			_panelVisible = !_panelVisible;
		}
		_toolbarHandler.setVisible(_panelVisible);
		_toolbarHighDrawn = false;

		if (_videoMode.screenHeight > 240)
			addDirtyRect(0, 400, 640, 80);
		else
			addDirtyRect(0, 200, 320, 40);

		if (_toolbarHandler.activeName() == NAME_PANEL_KEYBOARD && _panelVisible)
			internUpdateScreen();
		else {
			update_scalers();
			hotswapGFXMode();
		}
	//}
}

void OSystem_WINCE3::swap_panel() {
	_toolbarHighDrawn = false;
	//if (!_panelStateForced) {
		if (_toolbarHandler.activeName() == NAME_PANEL_KEYBOARD && _panelVisible)
			_toolbarHandler.setActive(NAME_MAIN_PANEL);
		else
			_toolbarHandler.setActive(NAME_PANEL_KEYBOARD);

		if (_videoMode.screenHeight > 240)
			addDirtyRect(0, 400, 640, 80);
		else
			addDirtyRect(0, 200, 320, 40);

		_toolbarHandler.setVisible(true);
		if (!_panelVisible) {
			_panelVisible = true;
			update_scalers();
			hotswapGFXMode();
		}
	//}
}

void OSystem_WINCE3::swap_smartphone_keyboard() {
	_toolbarHandler.setActive(NAME_PANEL_KEYBOARD);
	_panelVisible = !_panelVisible;
	_toolbarHandler.setVisible(_panelVisible);
	if (_videoMode.screenHeight > 240)
		addDirtyRect(0, 0, 640, 80);
	else
		addDirtyRect(0, 0, 320, 40);
	internUpdateScreen();
}

void OSystem_WINCE3::smartphone_rotate_display() {
	_orientationLandscape = _newOrientation = _orientationLandscape == 1 ? 2 : 1;
	ConfMan.setInt("landscape", _orientationLandscape);
	ConfMan.flushToDisk();
	hotswapGFXMode();
}

void OSystem_WINCE3::swap_sound_master() {
	_soundMaster = !_soundMaster;
	if (_toolbarHandler.activeName() == NAME_MAIN_PANEL)
		_toolbarHandler.forceRedraw(); // redraw sound icon
}

void OSystem_WINCE3::add_right_click(bool pushed) {
	int x, y;
	retrieve_mouse_location(x, y);
	EventsBuffer::simulateMouseRightClick(x, y, pushed);
}

void OSystem_WINCE3::swap_mouse_visibility() {
	_forceHideMouse = !_forceHideMouse;
	if (_forceHideMouse)
		undrawMouse();
}

void OSystem_WINCE3::swap_freeLook() {
	_freeLook = !_freeLook;
}

void OSystem_WINCE3::swap_zoom_up() {
	if (_zoomUp) {
		// restore visibility
		_toolbarHandler.setVisible(_saveToolbarZoom);
		// restore scaler
		_scaleFactorYd = 2;
		_scalerProc = DownscaleAllByHalf;
		_zoomUp = false;
		_zoomDown = false;
	} else {
		// only active if running on a PocketPC
		if (_scalerProc != DownscaleAllByHalf && _scalerProc != DownscaleHorizByHalf)
			return;
		if (_scalerProc == DownscaleAllByHalf) {
			_saveToolbarZoom = _toolbarHandler.visible();
			_toolbarHandler.setVisible(false);
			// set zoom scaler
			_scaleFactorYd = 1;
			_scalerProc = DownscaleHorizByHalf;
		}

		_zoomDown = false;
		_zoomUp = true;
	}
	// redraw whole screen
	addDirtyRect(0, 0, 640, 480);
	internUpdateScreen();
}

void OSystem_WINCE3::swap_zoom_down() {
	if (_zoomDown) {
		// restore visibility
		_toolbarHandler.setVisible(_saveToolbarZoom);
		// restore scaler
		_scaleFactorYd = 2;
		_scalerProc = DownscaleAllByHalf;
		_zoomDown = false;
		_zoomUp = false;
	} else {
		// only active if running on a PocketPC
		if (_scalerProc != DownscaleAllByHalf && _scalerProc != DownscaleHorizByHalf)
			return;
		if (_scalerProc == DownscaleAllByHalf) {
			_saveToolbarZoom = _toolbarHandler.visible();
			_toolbarHandler.setVisible(false);
			// set zoom scaler
			_scaleFactorYd = 1;
			_scalerProc = DownscaleHorizByHalf;
		}

		_zoomUp = false;
		_zoomDown = true;
	}
	// redraw whole screen
	addDirtyRect(0, 0, 640, 480);
	internUpdateScreen();
}

// Smartphone actions
void OSystem_WINCE3::initZones() {
	int i;

	_currentZone = 0;
	for (i = 0; i < TOTAL_ZONES; i++) {
		_mouseXZone[i] = (_zones[i].x + (_zones[i].width / 2)) * _scaleFactorXm / _scaleFactorXd;
		_mouseYZone[i] = (_zones[i].y + (_zones[i].height / 2)) * _scaleFactorYm / _scaleFactorYd;
	}
}

void OSystem_WINCE3::loadDeviceConfigurationElement(String element, int &value, int defaultValue) {
	value = ConfMan.getInt(element, ConfMan.kApplicationDomain);
	if (!value) {
		value = defaultValue;
		ConfMan.setInt(element, value, ConfMan.kApplicationDomain);
	}
}

void OSystem_WINCE3::loadDeviceConfiguration() {
	loadDeviceConfigurationElement("repeatTrigger", _keyRepeatTrigger, 200);
	loadDeviceConfigurationElement("repeatX", _repeatX, 4);
	loadDeviceConfigurationElement("repeatY", _repeatY, 4);
	loadDeviceConfigurationElement("stepX1", _stepX1, 2);
	loadDeviceConfigurationElement("stepX2", _stepX2, 10);
	loadDeviceConfigurationElement("stepX3", _stepX3, 40);
	loadDeviceConfigurationElement("stepY1", _stepY1, 2);
	loadDeviceConfigurationElement("stepY2", _stepY2, 10);
	loadDeviceConfigurationElement("stepY3", _stepY3, 20);
	ConfMan.flushToDisk();
}

void OSystem_WINCE3::add_left_click(bool pushed) {
	int x, y;
	retrieve_mouse_location(x, y);
	EventsBuffer::simulateMouseLeftClick(x, y, pushed);
}

void OSystem_WINCE3::move_cursor_up() {
	int x, y;
	_usesEmulatedMouse = true;
	retrieve_mouse_location(x, y);
	if (_keyRepeat > _repeatY)
		y -= _stepY3;
	else if (_keyRepeat)
		y -= _stepY2;
	else
		y -= _stepY1;

	if (y < 0)
		y = 0;

	EventsBuffer::simulateMouseMove(x, y);
}

void OSystem_WINCE3::move_cursor_down() {
	int x, y;
	_usesEmulatedMouse = true;
	retrieve_mouse_location(x, y);
	if (_keyRepeat > _repeatY)
		y += _stepY3;
	else if (_keyRepeat)
		y += _stepY2;
	else
		y += _stepY1;

	if (y > _videoMode.screenHeight * _scaleFactorYm / _scaleFactorYd)
		y = _videoMode.screenHeight * _scaleFactorYm / _scaleFactorYd;

	EventsBuffer::simulateMouseMove(x, y);
}

void OSystem_WINCE3::move_cursor_left() {
	int x, y;
	_usesEmulatedMouse = true;
	retrieve_mouse_location(x, y);
	if (_keyRepeat > _repeatX)
		x -= _stepX3;
	else if (_keyRepeat)
		x -= _stepX2;
	else
		x -= _stepX1;

	if (x < 0)
		x = 0;

	EventsBuffer::simulateMouseMove(x, y);
}

void OSystem_WINCE3::move_cursor_right() {
	int x, y;
	_usesEmulatedMouse = true;
	retrieve_mouse_location(x, y);
	if (_keyRepeat > _repeatX)
		x += _stepX3;
	else if (_keyRepeat)
		x += _stepX2;
	else
		x += _stepX1;

	if (x > _videoMode.screenWidth * _scaleFactorXm / _scaleFactorXd)
		x = _videoMode.screenWidth * _scaleFactorXm / _scaleFactorXd;

	EventsBuffer::simulateMouseMove(x, y);
}

void OSystem_WINCE3::switch_zone() {
	int x, y;
	int i;
	retrieve_mouse_location(x, y);

	for (i = 0; i < TOTAL_ZONES; i++)
		if (x >= _zones[i].x && y >= _zones[i].y &&
			x <= _zones[i].x + _zones[i].width && y <= _zones[i].y + _zones[i].height) {
				_mouseXZone[i] = x;
				_mouseYZone[i] = y;
				break;
		}
	_currentZone = i + 1;
	if (_currentZone >= TOTAL_ZONES)
		_currentZone = 0;

	EventsBuffer::simulateMouseMove(_mouseXZone[_currentZone], _mouseYZone[_currentZone]);
}

void OSystem_WINCE3::create_toolbar() {
	PanelKeyboard *keyboard;

	// Add the keyboard
	keyboard = new PanelKeyboard(PANEL_KEYBOARD);
	_toolbarHandler.add(NAME_PANEL_KEYBOARD, *keyboard);
	_toolbarHandler.setVisible(false);
}

void OSystem_WINCE3::setupMixer() {
	SDL_AudioSpec desired;
	int thread_priority;

	compute_sample_rate();
	if (_sampleRate == 0)
		warning("setSoundCallback called with 0 _sampleRate - audio will not work");
	else if (_mixer && _mixer->getOutputRate() == _sampleRate) {
		debug(1, "Skipping sound mixer re-init: samplerate is good");
		return;
	}

	memset(&desired, 0, sizeof(desired));
	desired.freq = _sampleRate;
	desired.format = AUDIO_S16SYS;
	desired.channels = 2;
	desired.samples = 128;
	desired.callback = private_sound_proc;
	desired.userdata = this;

	// Create the mixer instance
	if (_mixer == 0)
		_mixer = new Audio::MixerImpl(this);

	// Add sound thread priority
	if (!ConfMan.hasKey("sound_thread_priority"))
		thread_priority = THREAD_PRIORITY_NORMAL;
	else
		thread_priority = ConfMan.getInt("sound_thread_priority");

	desired.thread_priority = thread_priority;

	SDL_CloseAudio();
	if (SDL_OpenAudio(&desired, NULL) != 0) {
		warning("Could not open audio device: %s", SDL_GetError());
		_mixer->setReady(false);

	} else {
		debug(1, "Sound opened OK, mixing at %d Hz", _sampleRate);

		// Re-create mixer to match the output rate
		int vol1 = _mixer->getVolumeForSoundType(Audio::Mixer::kPlainSoundType);
		int vol2 = _mixer->getVolumeForSoundType(Audio::Mixer::kMusicSoundType);
		int vol3 = _mixer->getVolumeForSoundType(Audio::Mixer::kSFXSoundType);
		int vol4 = _mixer->getVolumeForSoundType(Audio::Mixer::kSpeechSoundType);
		delete _mixer;
		_mixer = new Audio::MixerImpl(this);
		_mixer->setVolumeForSoundType(Audio::Mixer::kPlainSoundType, vol1);
		_mixer->setVolumeForSoundType(Audio::Mixer::kMusicSoundType, vol2);
		_mixer->setVolumeForSoundType(Audio::Mixer::kSFXSoundType, vol3);
		_mixer->setVolumeForSoundType(Audio::Mixer::kSpeechSoundType, vol4);
		_mixer->setOutputRate(_sampleRate);
		_mixer->setReady(true);
		SDL_PauseAudio(0);
	}
}

void OSystem_WINCE3::private_sound_proc(void *param, byte *buf, int len) {
	OSystem_WINCE3 *this_ = (OSystem_WINCE3 *)param;
	assert(this_);

	if (this_->_mixer)
		this_->_mixer->mixCallback(buf, len);
	if (!_soundMaster)
		memset(buf, 0, len);
}

#ifdef USE_VORBIS
bool OSystem_WINCE3::checkOggHighSampleRate() {
	char trackFile[255];
	FILE *testFile;
	OggVorbis_File *test_ov_file = new OggVorbis_File;

	// FIXME: The following sprintf assumes that "path" is always
	// terminated by a path separator. This is *not* true in general.
	// This code really should check for the path separator, or even
	// better, use the FSNode API.
	sprintf(trackFile, "%sTrack1.ogg", ConfMan.get("path").c_str());
	// Check if we have an OGG audio track
	testFile = fopen(trackFile, "rb");
	if (testFile) {
		if (!ov_open(testFile, test_ov_file, NULL, 0)) {
			bool highSampleRate = (ov_info(test_ov_file, -1)->rate == 22050);
			ov_clear(test_ov_file);
			delete test_ov_file;
			return highSampleRate;
		}
	}

	// Do not test for OGG samples - too big and too slow anyway :)

	delete test_ov_file;
	return false;
}
#endif

void OSystem_WINCE3::compute_sample_rate() {
	// Force at least medium quality FM synthesis for FOTAQ
	Common::String gameid(ConfMan.get("gameid"));
	if (gameid == "queen") {
		if (!((ConfMan.hasKey("FM_high_quality") && ConfMan.getBool("FM_high_quality")) ||
			(ConfMan.hasKey("FM_medium_quality") && ConfMan.getBool("FM_medium_quality")))) {
			ConfMan.setBool("FM_medium_quality", true);
			ConfMan.flushToDisk();
		}
	}
	// See if the output frequency is forced by the game
	if (gameid == "ft" || gameid == "dig" || gameid == "comi" || gameid == "queen" || gameid == "sword" || gameid == "agi")
			_sampleRate = SAMPLES_PER_SEC_NEW;
	else {
		if (ConfMan.hasKey("high_sample_rate") && ConfMan.getBool("high_sample_rate"))
			_sampleRate = SAMPLES_PER_SEC_NEW;
		else
			_sampleRate = SAMPLES_PER_SEC_OLD;
	}

#ifdef USE_VORBIS
	// Modify the sample rate on the fly if OGG is involved
	if (_sampleRate == SAMPLES_PER_SEC_OLD)
		if (checkOggHighSampleRate())
			 _sampleRate = SAMPLES_PER_SEC_NEW;
#endif
}

int OSystem_WINCE3::getOutputSampleRate() const {
	return _sampleRate;
}

void OSystem_WINCE3::engineInit() {
	check_mappings(); // called here to initialize virtual keys handling

	//update_game_settings();
	// finalize mixer init
	setupMixer();
}

bool OSystem_WINCE3::openCD(int drive) {
	return false;
}

const OSystem::GraphicsMode *OSystem_WINCE3::getSupportedGraphicsModes() const {
	if (CEDevice::hasWideResolution())
		return s_supportedGraphicsModesHigh;
	else
		return s_supportedGraphicsModesLow;
}

bool OSystem_WINCE3::hasFeature(Feature f) {
	return (f == kFeatureAutoComputeDirtyRects || f == kFeatureVirtualKeyboard);
}

void OSystem_WINCE3::setFeatureState(Feature f, bool enable) {
	switch (f) {
	case kFeatureFullscreenMode:
		return;

	case kFeatureVirtualKeyboard:
		if (_hasSmartphoneResolution)
			return;
		_toolbarHighDrawn = false;
		if (enable) {
			_panelStateForced = true;
			if (!_toolbarHandler.visible()) swap_panel_visibility();
			//_saveToolbarState = _toolbarHandler.visible();
			_saveActiveToolbar = _toolbarHandler.activeName();
			_toolbarHandler.setActive(NAME_PANEL_KEYBOARD);
			_toolbarHandler.setVisible(true);
		} else
			if (_panelStateForced) {
				_panelStateForced = false;
				_toolbarHandler.setActive(_saveActiveToolbar);
				//_toolbarHandler.setVisible(_saveToolbarState);
			}
		return;

	case kFeatureDisableKeyFiltering:
		if (_hasSmartphoneResolution)
			_unfilteredkeys = enable;
		return;

	default:
		OSystem_SDL::setFeatureState(f, enable);
	}
}

bool OSystem_WINCE3::getFeatureState(Feature f) {
	switch (f) {
	case kFeatureFullscreenMode:
		return false;
	case kFeatureVirtualKeyboard:
		return (_panelStateForced);
	}
	return OSystem_SDL::getFeatureState(f);
}

void OSystem_WINCE3::check_mappings() {
	CEActionsPocket *instance;

	Common::String gameid(ConfMan.get("gameid"));

	if (gameid.empty() || GUI_Actions::Instance()->initialized())
		return;

	GUI_Actions::Instance()->initInstanceGame();
	instance = (CEActionsPocket*)GUI_Actions::Instance();

	// Some games need to map the right click button, signal it here if it wasn't done
	if (instance->needsRightClickMapping()) {
		GUI::KeysDialog *keysDialog = new GUI::KeysDialog("Map right click action");
		while (!instance->getMapping(POCKET_ACTION_RIGHTCLICK)) {
			keysDialog->runModal();
			if (!instance->getMapping(POCKET_ACTION_RIGHTCLICK)) {
				GUI::MessageDialog alert("You must map a key to the 'Right Click' action to play this game");
				alert.runModal();
			}
		}
		delete keysDialog;
	}

	// Map the "hide toolbar" action if needed
	if (instance->needsHideToolbarMapping()) {
		GUI::KeysDialog *keysDialog = new GUI::KeysDialog("Map hide toolbar action");
		while (!instance->getMapping(POCKET_ACTION_HIDE)) {
			keysDialog->runModal();
			if (!instance->getMapping(POCKET_ACTION_HIDE)) {
				GUI::MessageDialog alert("You must map a key to the 'Hide toolbar' action to play this game");
				alert.runModal();
			}
		}
		delete keysDialog;
	}

	// Map the "zoom" actions if needed
	if (instance->needsZoomMapping()) {
		GUI::KeysDialog *keysDialog = new GUI::KeysDialog("Map Zoom Up action (optional)");
		keysDialog->runModal();
		delete keysDialog;
		keysDialog = new GUI::KeysDialog("Map Zoom Down action (optional)");
		keysDialog->runModal();
		delete keysDialog;
	}

	// Extra warning for Zak Mc Kracken
	if (strncmp(gameid.c_str(), "zak", 3) == 0 &&
		!GUI_Actions::Instance()->getMapping(POCKET_ACTION_HIDE)) {
		GUI::MessageDialog alert("Don't forget to map a key to 'Hide Toolbar' action to see the whole inventory");
		alert.runModal();
	}

}

void OSystem_WINCE3::update_game_settings() {
	Common::String gameid(ConfMan.get("gameid"));

	// Finish panel initialization
	if (!_panelInitialized && !gameid.empty()) {
		Panel *panel;
		_panelInitialized = true;
		// Add the main panel
		panel = new Panel(0, 32);
		panel->setBackground(IMAGE_PANEL);
		// Save
		panel->add(NAME_ITEM_OPTIONS, new ItemAction(ITEM_OPTIONS, POCKET_ACTION_SAVE));
		// Skip
		panel->add(NAME_ITEM_SKIP, new ItemAction(ITEM_SKIP, POCKET_ACTION_SKIP));
		// sound
		panel->add(NAME_ITEM_SOUND, new ItemSwitch(ITEM_SOUND_OFF, ITEM_SOUND_ON, &_soundMaster));
		// bind keys
		panel->add(NAME_ITEM_BINDKEYS, new ItemAction(ITEM_BINDKEYS, POCKET_ACTION_BINDKEYS));
		// portrait/landscape - screen dependent
		// FIXME : will still display the portrait/landscape icon when using a scaler (but will be disabled)
		if (ConfMan.hasKey("landscape")) {
			if (ConfMan.get("landscape")[0] > 57) {
				_newOrientation = _orientationLandscape = ConfMan.getBool("landscape");
				//ConfMan.removeKey("landscape", "");
				ConfMan.setInt("landscape", _orientationLandscape);
			} else
				_newOrientation = _orientationLandscape = ConfMan.getInt("landscape");
		} else {
			_newOrientation = _orientationLandscape = 0;
		}
		panel->add(NAME_ITEM_ORIENTATION, new ItemSwitch(ITEM_VIEW_LANDSCAPE, ITEM_VIEW_PORTRAIT, &_newOrientation, 2));
		_toolbarHandler.add(NAME_MAIN_PANEL, *panel);
		_toolbarHandler.setActive(NAME_MAIN_PANEL);
		_toolbarHandler.setVisible(true);

		if (_videoMode.mode == GFX_NORMAL && ConfMan.hasKey("landscape") && ConfMan.getInt("landscape")) {
			setGraphicsMode(GFX_NORMAL);
			hotswapGFXMode();
		}

		if (_hasSmartphoneResolution)
			panel->setVisible(false);

		_saveToolbarState = true;
	}

	if (ConfMan.hasKey("no_doubletap_rightclick"))
		_noDoubleTapRMB = ConfMan.getBool("no_doubletap_rightclick");
}

void OSystem_WINCE3::initSize(uint w, uint h, const Graphics::PixelFormat *format) {
	if (_hasSmartphoneResolution && h == 240)
		h = 200;  // mainly for the launcher

	if (_isSmartphone && !ConfMan.hasKey("landscape")) {
		ConfMan.setInt("landscape", 1);
		ConfMan.flushToDisk();
	}

	_canBeAspectScaled = false;
	if (w == 320 && h == 200 && !_hasSmartphoneResolution) {
		_canBeAspectScaled = true;
		h = 240; // use the extra 40 pixels height for the toolbar
	}

	if (h == 400)	// touche engine fixup
		h += 80;

	if (!_hasSmartphoneResolution) {
		if (h == 240)
			_toolbarHandler.setOffset(200);
		else
			_toolbarHandler.setOffset(400);
	} else {
		if (h == 240)
			_toolbarHandler.setOffset(200);
		else	// 176x220
			_toolbarHandler.setOffset(0);
	}

	if (w != (uint) _videoMode.screenWidth || h != (uint) _videoMode.screenHeight)
		_scalersChanged = false;

	_videoMode.overlayWidth = w;
	_videoMode.overlayHeight = h;

	OSystem_SDL::initSize(w, h, format);

	if (_scalersChanged) {
		unloadGFXMode();
		loadGFXMode();
		_scalersChanged = false;
	}

	update_game_settings();
}


int OSystem_WINCE3::getDefaultGraphicsMode() const {
	return GFX_NORMAL;
}

void OSystem_WINCE3::setGraphicsModeIntern() {
	// Scalers have been pre-selected for the desired mode.
	// No further tuning required.
}

bool OSystem_WINCE3::update_scalers() {
	_videoMode.aspectRatioCorrection = false;

	if (CEDevice::hasPocketPCResolution()) {
		if (_videoMode.mode != GFX_NORMAL)
			return false;

		if ((!_orientationLandscape && (_videoMode.screenWidth == 320 || !_videoMode.screenWidth))
			|| CEDevice::hasSquareQVGAResolution() ) {
			if (getScreenWidth() != 320) {
				_scaleFactorXm = 3;
				_scaleFactorXd = 4;
				_scaleFactorYm = 1;
				_scaleFactorYd = 1;
				_scalerProc = PocketPCPortrait;
				_modeFlags = 0;
			} else {
				_scaleFactorXm = 1;
				_scaleFactorXd = 1;
				_scaleFactorYm = 1;
				_scaleFactorYd = 1;
				_scalerProc = Normal1x;
				_modeFlags = 0;
			}
		} else if ( _orientationLandscape && (_videoMode.screenWidth == 320 || !_videoMode.screenWidth)) {
			if (!_panelVisible && !_hasSmartphoneResolution  && !_overlayVisible && _canBeAspectScaled) {
				_scaleFactorXm = 1;
				_scaleFactorXd = 1;
				_scaleFactorYm = 6;
				_scaleFactorYd = 5;
				_scalerProc = PocketPCLandscapeAspect;
				_modeFlags = 0;
				_videoMode.aspectRatioCorrection = true;
			} else {
				_scaleFactorXm = 1;
				_scaleFactorXd = 1;
				_scaleFactorYm = 1;
				_scaleFactorYd = 1;
				_scalerProc = Normal1x;
				_modeFlags = 0;
			}
		} else if (_videoMode.screenWidth == 640 && !(isOzone() && (getScreenWidth() >= 640 || getScreenHeight() >= 640))) {
			_scaleFactorXm = 1;
			_scaleFactorXd = 2;
			_scaleFactorYm = 1;
			_scaleFactorYd = 2;
			_scalerProc = DownscaleAllByHalf;
			_modeFlags = 0;
		} else if (_videoMode.screenWidth == 640 && (isOzone() && (getScreenWidth() >= 640 || getScreenHeight() >= 640))) {
			_scaleFactorXm = 1;
			_scaleFactorXd = 1;
			_scaleFactorYm = 1;
			_scaleFactorYd = 1;
			_scalerProc = Normal1x;
			_modeFlags = 0;
		}

		return true;
	} else if (CEDevice::hasWideResolution()) {
#ifdef USE_ARM_SCALER_ASM
		if ( _videoMode.mode == GFX_DOUBLESIZE && (_videoMode.screenWidth == 320 || !_videoMode.screenWidth) ) {
			if ( !_panelVisible && !_overlayVisible && _canBeAspectScaled ) {
				_scaleFactorXm = 2;
				_scaleFactorXd = 1;
				_scaleFactorYm = 12;
				_scaleFactorYd = 5;
				_scalerProc = Normal2xAspect;
				_modeFlags = 0;
				_videoMode.aspectRatioCorrection = true;
			} else if ( (_panelVisible || _overlayVisible) && _canBeAspectScaled ) {
				_scaleFactorXm = 2;
				_scaleFactorXd = 1;
				_scaleFactorYm = 2;
				_scaleFactorYd = 1;
				_scalerProc = Normal2x;
				_modeFlags = 0;
			}
			return true;
		}
#endif
	} else if (CEDevice::hasSmartphoneResolution()) {
		if (_videoMode.mode != GFX_NORMAL)
			return false;

		if (_videoMode.screenWidth > 320)
			error("Game resolution not supported on Smartphone");
#ifdef ARM
		_scaleFactorXm = 11;
		_scaleFactorXd = 16;
#else
		_scaleFactorXm = 2;
		_scaleFactorXd = 3;
#endif
		_scaleFactorYm = 7;
		_scaleFactorYd = 8;
		_scalerProc = SmartphoneLandscape;
		_modeFlags = 0;
		initZones();
		return true;
	}

	return false;
}

bool OSystem_WINCE3::setGraphicsMode(int mode) {

	Common::StackLock lock(_graphicsMutex);
	int oldScaleFactorXm = _scaleFactorXm;
	int oldScaleFactorXd = _scaleFactorXd;
	int oldScaleFactorYm = _scaleFactorYm;
	int oldScaleFactorYd = _scaleFactorYd;

	_scaleFactorXm = -1;
	_scaleFactorXd = -1;
	_scaleFactorYm = -1;
	_scaleFactorYd = -1;

	if (ConfMan.hasKey("landscape"))
		if (ConfMan.get("landscape")[0] > 57) {
			_newOrientation = _orientationLandscape = ConfMan.getBool("landscape");
			ConfMan.setInt("landscape", _orientationLandscape);
		} else
			_newOrientation = _orientationLandscape = ConfMan.getInt("landscape");
	else
		_newOrientation = _orientationLandscape = 0;

	if (isOzone() && (getScreenWidth() >= 640 || getScreenHeight() >= 640) && mode)
		_scaleFactorXm = -1;

	if (CEDevice::hasPocketPCResolution() && !CEDevice::hasWideResolution() && _orientationLandscape)
		_videoMode.mode = GFX_NORMAL;
	else
		_videoMode.mode = mode;

	if (_scaleFactorXm < 0) {
		/* Standard scalers, from the SDL backend */
		switch (_videoMode.mode) {
		case GFX_NORMAL:
			_videoMode.scaleFactor = 1;
			_scalerProc = Normal1x;
			break;
		case GFX_DOUBLESIZE:
			_videoMode.scaleFactor = 2;
			_scalerProc = Normal2x;
			break;
		case GFX_TRIPLESIZE:
			_videoMode.scaleFactor = 3;
			_scalerProc = Normal3x;
			break;
		case GFX_2XSAI:
			_videoMode.scaleFactor = 2;
			_scalerProc = _2xSaI;
			break;
		case GFX_SUPER2XSAI:
			_videoMode.scaleFactor = 2;
			_scalerProc = Super2xSaI;
			break;
		case GFX_SUPEREAGLE:
			_videoMode.scaleFactor = 2;
			_scalerProc = SuperEagle;
			break;
		case GFX_ADVMAME2X:
			_videoMode.scaleFactor = 2;
			_scalerProc = AdvMame2x;
			break;
		case GFX_ADVMAME3X:
			_videoMode.scaleFactor = 3;
			_scalerProc = AdvMame3x;
			break;
#ifndef DISABLE_HQ_SCALERS
		case GFX_HQ2X:
			_videoMode.scaleFactor = 2;
			_scalerProc = HQ2x;
			break;
		case GFX_HQ3X:
			_videoMode.scaleFactor = 3;
			_scalerProc = HQ3x;
			break;
#endif
		case GFX_TV2X:
			_videoMode.scaleFactor = 2;
			_scalerProc = TV2x;
			break;
		case GFX_DOTMATRIX:
			_videoMode.scaleFactor = 2;
			_scalerProc = DotMatrix;
			break;

		default:
			error("unknown gfx mode %d", mode);
		}
	}

	// Check if the scaler can be accepted, if not get back to normal scaler
	if (_videoMode.scaleFactor && ((_videoMode.scaleFactor * _videoMode.screenWidth > getScreenWidth() && _videoMode.scaleFactor * _videoMode.screenWidth > getScreenHeight())
		 || (_videoMode.scaleFactor * _videoMode.screenHeight > getScreenWidth() &&	_videoMode.scaleFactor * _videoMode.screenHeight > getScreenHeight()))) {
				_videoMode.scaleFactor = 1;
				_scalerProc = Normal1x;
	}

	// Common scaler system was used
	if (_scaleFactorXm < 0) {
		_scaleFactorXm = _videoMode.scaleFactor;
		_scaleFactorXd = 1;
		_scaleFactorYm = _videoMode.scaleFactor;
		_scaleFactorYd = 1;
	}

	_forceFull = true;

	if (oldScaleFactorXm != _scaleFactorXm ||
		oldScaleFactorXd != _scaleFactorXd ||
		oldScaleFactorYm != _scaleFactorYm ||
		oldScaleFactorYd != _scaleFactorYd) {
		_scalersChanged = true;
	}
	else
		_scalersChanged = false;


	return true;

}

bool OSystem_WINCE3::loadGFXMode() {
	int displayWidth;
	int displayHeight;
	unsigned int flags = SDL_FULLSCREEN | SDL_SWSURFACE;

	_videoMode.fullscreen = true; // forced
	_forceFull = true;

	_tmpscreen = NULL;

	// Recompute scalers if necessary
	update_scalers();

	// Create the surface that contains the 8 bit game data
	_screen = SDL_CreateRGBSurface(SDL_SWSURFACE, _videoMode.screenWidth, _videoMode.screenHeight, 8, 0, 0, 0, 0);
	if (_screen == NULL)
		error("_screen failed (%s)", SDL_GetError());

	// Create the surface that contains the scaled graphics in 16 bit mode
	// Always use full screen mode to have a "clean screen"
	if (!_videoMode.aspectRatioCorrection) {
		displayWidth = _videoMode.screenWidth * _scaleFactorXm / _scaleFactorXd;
		displayHeight = _videoMode.screenHeight * _scaleFactorYm / _scaleFactorYd;
	} else {
		displayWidth = _videoMode.screenWidth * _videoMode.scaleFactor;
		displayHeight = _videoMode.screenHeight* _videoMode.scaleFactor;
	}

	switch (_orientationLandscape) {
		case 1:
			flags |= SDL_LANDSCVIDEO;
			break;
		case 2:
			flags |= SDL_INVLNDVIDEO;
			break;
		default:
			flags |= SDL_PORTRTVIDEO;
	}
	_hwscreen = SDL_SetVideoMode(displayWidth, displayHeight, 16, flags);

	if (_hwscreen == NULL) {
		warning("SDL_SetVideoMode says we can't switch to that mode (%s)", SDL_GetError());
		quit();
	}

	// see what orientation sdl finally accepted
	if (_hwscreen->flags & SDL_PORTRTVIDEO)
		_orientationLandscape = _newOrientation	= 0;
	else if (_hwscreen->flags & SDL_LANDSCVIDEO)
		_orientationLandscape = _newOrientation	= 1;
	else
		_orientationLandscape = _newOrientation	= 2;

	// Create the surface used for the graphics in 16 bit before scaling, and also the overlay
	// Distinguish 555 and 565 mode
	if (_hwscreen->format->Rmask == 0x7C00)
		InitScalers(555);
	else
		InitScalers(565);
	_overlayFormat.bytesPerPixel = _hwscreen->format->BytesPerPixel;
	_overlayFormat.rLoss = _hwscreen->format->Rloss;
	_overlayFormat.gLoss = _hwscreen->format->Gloss;
	_overlayFormat.bLoss = _hwscreen->format->Bloss;
	_overlayFormat.aLoss = _hwscreen->format->Aloss;
	_overlayFormat.rShift = _hwscreen->format->Rshift;
	_overlayFormat.gShift = _hwscreen->format->Gshift;
	_overlayFormat.bShift = _hwscreen->format->Bshift;
	_overlayFormat.aShift = _hwscreen->format->Ashift;

	// Need some extra bytes around when using 2xSaI
	_tmpscreen = SDL_CreateRGBSurface(SDL_SWSURFACE, _videoMode.screenWidth + 3, _videoMode.screenHeight + 3, 16, _hwscreen->format->Rmask, _hwscreen->format->Gmask, _hwscreen->format->Bmask, _hwscreen->format->Amask);

	if (_tmpscreen == NULL)
		error("_tmpscreen creation failed (%s)", SDL_GetError());

	// Overlay
	if (CEDevice::hasDesktopResolution()) {
		_overlayscreen = SDL_CreateRGBSurface(SDL_SWSURFACE, _videoMode.overlayWidth * _scaleFactorXm / _scaleFactorXd, _videoMode.overlayHeight * _scaleFactorYm / _scaleFactorYd, 16, 0, 0, 0, 0);
		if (_overlayscreen == NULL)
			error("_overlayscreen failed (%s)", SDL_GetError());
		_tmpscreen2 = SDL_CreateRGBSurface(SDL_SWSURFACE, _videoMode.overlayWidth * _scaleFactorXm / _scaleFactorXd + 3, _videoMode.overlayHeight * _scaleFactorYm / _scaleFactorYd + 3, 16, 0, 0, 0, 0);
		if (_tmpscreen2 == NULL)
			error("_tmpscreen2 failed (%s)", SDL_GetError());
	} else {
		_overlayscreen = SDL_CreateRGBSurface(SDL_SWSURFACE, _videoMode.overlayWidth, _videoMode.overlayHeight, 16, 0, 0, 0, 0);
		if (_overlayscreen == NULL)
			error("_overlayscreen failed (%s)", SDL_GetError());
		_tmpscreen2 = SDL_CreateRGBSurface(SDL_SWSURFACE, _videoMode.overlayWidth + 3, _videoMode.overlayHeight + 3, 16, 0, 0, 0, 0);
		if (_tmpscreen2 == NULL)
			error("_tmpscreen2 failed (%s)", SDL_GetError());
	}

	// Toolbar
	_toolbarHighDrawn = false;
	uint16 *toolbar_screen = (uint16 *)calloc(320 * 40, sizeof(uint16));	// *not* leaking memory here
	_toolbarLow = SDL_CreateRGBSurfaceFrom(toolbar_screen, 320, 40, 16, 320 * 2, _hwscreen->format->Rmask, _hwscreen->format->Gmask, _hwscreen->format->Bmask, _hwscreen->format->Amask);

	if (_toolbarLow == NULL)
		error("_toolbarLow failed (%s)", SDL_GetError());

	if (_videoMode.screenHeight > 240) {
		uint16 *toolbar_screen = (uint16 *)calloc(640 * 80, sizeof(uint16));
		_toolbarHigh = SDL_CreateRGBSurfaceFrom(toolbar_screen, 640, 80, 16, 640 * 2, _hwscreen->format->Rmask, _hwscreen->format->Gmask, _hwscreen->format->Bmask, _hwscreen->format->Amask);

		if (_toolbarHigh == NULL)
			error("_toolbarHigh failed (%s)", SDL_GetError());
	} else
		_toolbarHigh = NULL;


	// keyboard cursor control, some other better place for it?
	_km.x_max = _videoMode.screenWidth * _scaleFactorXm / _scaleFactorXd - 1;
	_km.y_max = _videoMode.screenHeight * _scaleFactorXm / _scaleFactorXd - 1;
	_km.delay_time = 25;
	_km.last_time = 0;

	return true;
}

void OSystem_WINCE3::unloadGFXMode() {
	if (_screen) {
		SDL_FreeSurface(_screen);
		_screen = NULL;
	}

	if (_hwscreen) {
		SDL_FreeSurface(_hwscreen);
		_hwscreen = NULL;
	}

	if (_tmpscreen) {
		SDL_FreeSurface(_tmpscreen);
		_tmpscreen = NULL;
	}
}

bool OSystem_WINCE3::hotswapGFXMode() {
	if (!_screen)
		return false;

	// Keep around the old _screen & _tmpscreen so we can restore the screen data
	// after the mode switch. (also for the overlay)
	SDL_Surface *old_screen = _screen;
	SDL_Surface *old_tmpscreen = _tmpscreen;
	SDL_Surface *old_overlayscreen = _overlayscreen;
	SDL_Surface *old_tmpscreen2 = _tmpscreen2;

	// Release the HW screen surface
	SDL_FreeSurface(_hwscreen);

	// Release toolbars
	free(_toolbarLow->pixels);
	SDL_FreeSurface(_toolbarLow);
	if (_toolbarHigh) {
		free(_toolbarHigh->pixels);
		SDL_FreeSurface(_toolbarHigh);
	}

	// Setup the new GFX mode
	if (!loadGFXMode()) {
		unloadGFXMode();

		_screen = old_screen;
		_overlayscreen = old_overlayscreen;

		return false;
	}

	// reset palette
	SDL_SetColors(_screen, _currentPalette, 0, 256);

	// Restore old screen content
	SDL_BlitSurface(old_screen, NULL, _screen, NULL);
	SDL_BlitSurface(old_tmpscreen, NULL, _tmpscreen, NULL);
	if (_overlayVisible) {
		SDL_BlitSurface(old_overlayscreen, NULL, _overlayscreen, NULL);
		SDL_BlitSurface(old_tmpscreen2, NULL, _tmpscreen2, NULL);
	}

	// Free the old surfaces
	SDL_FreeSurface(old_screen);
	SDL_FreeSurface(old_tmpscreen);
	SDL_FreeSurface(old_overlayscreen);
	SDL_FreeSurface(old_tmpscreen2);

	// Blit everything back to the screen
	_toolbarHighDrawn = false;
	internUpdateScreen();

	// Make sure that a Common::EVENT_SCREEN_CHANGED gets sent later -> FIXME this crashes when no game has been loaded.
//	_modeChanged = true;

	return true;
}

void OSystem_WINCE3::internUpdateScreen() {
	SDL_Surface *srcSurf, *origSurf;
	static bool old_overlayVisible = false;
	int numRectsOut = 0;
	int16 routx, routy, routw, routh, stretch, shakestretch;

	assert(_hwscreen != NULL);

	// bail if the application is minimized, be nice to OS
	if (!_hasfocus) {
		Sleep(20);
		return;
	}

	// If the shake position changed, fill the dirty area with blackness
	if (_currentShakePos != _newShakePos) {
		SDL_Rect blackrect = {0, 0, _videoMode.screenWidth * _scaleFactorXm / _scaleFactorXd, _newShakePos * _scaleFactorYm / _scaleFactorYd};
		if (_videoMode.aspectRatioCorrection)
			blackrect.h = real2Aspect(blackrect.h - 1) + 1;
		SDL_FillRect(_hwscreen, &blackrect, 0);
		_currentShakePos = _newShakePos;
		_forceFull = true;
	}

	// Make sure the mouse is drawn, if it should be drawn.
	drawMouse();

	// Check whether the palette was changed in the meantime and update the
	// screen surface accordingly.
	if (_paletteDirtyEnd != 0) {
		SDL_SetColors(_screen, _currentPalette + _paletteDirtyStart, _paletteDirtyStart, _paletteDirtyEnd - _paletteDirtyStart);
		_paletteDirtyEnd = 0;
		_forceFull = true;
	}

	if (!_overlayVisible) {
		origSurf = _screen;
		srcSurf = _tmpscreen;
	} else {
		origSurf = _overlayscreen;
		srcSurf = _tmpscreen2;
	}

	if (old_overlayVisible != _overlayVisible) {
		old_overlayVisible = _overlayVisible;
		update_scalers();
	}

	// Force a full redraw if requested
	if (_forceFull) {
		_numDirtyRects = 1;

		_dirtyRectList[0].x = 0;
		if (!_zoomDown)
			_dirtyRectList[0].y = 0;
		else
			_dirtyRectList[0].y = _videoMode.screenHeight / 2;
		_dirtyRectList[0].w = _videoMode.screenWidth;
		if (!_zoomUp && !_zoomDown)
			_dirtyRectList[0].h = _videoMode.screenHeight;
		else
			_dirtyRectList[0].h = _videoMode.screenHeight / 2;

		_toolbarHandler.forceRedraw();
	}

	// Only draw anything if necessary
	if (_numDirtyRects > 0) {

		SDL_Rect *r, *rout;
		SDL_Rect dst;
		uint32 srcPitch, dstPitch;
		SDL_Rect *last_rect = _dirtyRectList + _numDirtyRects;
		bool toolbarVisible = _toolbarHandler.visible();
		int toolbarOffset = _toolbarHandler.getOffset();

		for (r = _dirtyRectList; r != last_rect; ++r) {
			dst = *r;
			dst.x++;	// Shift rect by one since 2xSai needs to access the data around
			dst.y++;	// any pixel to scale it, and we want to avoid mem access crashes.
					// NOTE: This is also known as BLACK MAGIC, copied from the sdl backend
			if (SDL_BlitSurface(origSurf, r, srcSurf, &dst) != 0)
				error("SDL_BlitSurface failed: %s", SDL_GetError());
		}

		SDL_LockSurface(srcSurf);
		SDL_LockSurface(_hwscreen);

		srcPitch = srcSurf->pitch;
		dstPitch = _hwscreen->pitch;

		for (r = _dirtyRectList, rout = _dirtyRectOut; r != last_rect; ++r) {

			// always clamp to enclosing, downsampled-grid-aligned rect in the downscaled image
			if (_scaleFactorXd != 1) {
				stretch = r->x % _scaleFactorXd;
				r->x -= stretch;
				r->w += stretch;
				r->w = (r->x + r->w + _scaleFactorXd - 1) / _scaleFactorXd * _scaleFactorXd - r->x;
			}
			if (_scaleFactorYd != 1) {
				stretch = r->y % _scaleFactorYd;
				r->y -= stretch;
				r->h += stretch;
				r->h = (r->y + r->h + _scaleFactorYd - 1) / _scaleFactorYd * _scaleFactorYd - r->y;
			}

			// transform
			shakestretch = _currentShakePos * _scaleFactorYm / _scaleFactorYd;
			routx = r->x * _scaleFactorXm / _scaleFactorXd;					// locate position in scaled screen
			routy = r->y * _scaleFactorYm / _scaleFactorYd + shakestretch;	// adjust for shake offset
			routw = r->w * _scaleFactorXm / _scaleFactorXd;
			routh = r->h * _scaleFactorYm / _scaleFactorYd - shakestretch;

			// clipping destination rectangle inside device screen (more strict, also more tricky but more stable)
			// note that all current scalers do not make dst rect exceed left/right, unless chosen badly (FIXME)
			if (_zoomDown)	routy -= 240;			// adjust for zoom position
			if (routy + routh < 0)	continue;
			if (routy < 0) {
				routh += routy;
				r->y -= routy * _scaleFactorYd / _scaleFactorYm;
				routy = 0;
				r->h = routh * _scaleFactorYd / _scaleFactorYm;
			}
			if (_orientationLandscape) {
				if (routy > _platformScreenWidth)	continue;
				if (routy + routh > _platformScreenWidth) {
					routh = _platformScreenWidth - routy;
					r->h = routh * _scaleFactorYd / _scaleFactorYm;
				}
			} else {
				if (routy > _platformScreenHeight)	continue;
				if (routy + routh > _platformScreenHeight) {
					routh = _platformScreenHeight - routy;
					r->h = routh * _scaleFactorYd / _scaleFactorYm;
				}
			}

			// check if the toolbar is overwritten
			if (toolbarVisible && r->y + r->h >= toolbarOffset)
				_toolbarHandler.forceRedraw();

			// blit it (with added voodoo from the sdl backend, shifting the source rect again)
			_scalerProc(	(byte *)srcSurf->pixels + (r->x * 2 + 2)+ (r->y + 1) * srcPitch, srcPitch,
					(byte *)_hwscreen->pixels + routx * 2 + routy * dstPitch, dstPitch,
					r->w, r->h - _currentShakePos);

			// add this rect to output
			rout->x = routx;	rout->y = routy - shakestretch;
			rout->w = routw;	rout->h = routh + shakestretch;
			numRectsOut++;
			rout++;

		}
		SDL_UnlockSurface(srcSurf);
		SDL_UnlockSurface(_hwscreen);
	}
	// Add the toolbar if needed
	SDL_Rect toolbar_rect[1];
	if (_panelVisible && _toolbarHandler.draw(_toolbarLow, &toolbar_rect[0])) {
		// It can be drawn, scale it
		uint32 srcPitch, dstPitch;
		SDL_Surface *toolbarSurface;
		ScalerProc *toolbarScaler;

		if (_videoMode.screenHeight > 240) {
			if (!_toolbarHighDrawn) {
				// Resize the toolbar
				SDL_LockSurface(_toolbarLow);
				SDL_LockSurface(_toolbarHigh);
				Normal2x((byte*)_toolbarLow->pixels, _toolbarLow->pitch, (byte*)_toolbarHigh->pixels, _toolbarHigh->pitch, toolbar_rect[0].w, toolbar_rect[0].h);
				SDL_UnlockSurface(_toolbarHigh);
				SDL_UnlockSurface(_toolbarLow);
				_toolbarHighDrawn = true;
			}
			toolbar_rect[0].w *= 2;
			toolbar_rect[0].h *= 2;
			toolbarSurface = _toolbarHigh;
		}
		else
			toolbarSurface = _toolbarLow;

		drawToolbarMouse(toolbarSurface, true);		// draw toolbar mouse if applicable

		// Apply the appropriate scaler
		SDL_LockSurface(toolbarSurface);
		SDL_LockSurface(_hwscreen);
		srcPitch = toolbarSurface->pitch;
		dstPitch = _hwscreen->pitch;

		toolbarScaler = _scalerProc;
		if (_videoMode.scaleFactor == 2)
			toolbarScaler = Normal2x;
		else if (_videoMode.scaleFactor == 3)
			toolbarScaler = Normal3x;
		toolbarScaler((byte *)toolbarSurface->pixels, srcPitch,
					(byte *)_hwscreen->pixels + (_toolbarHandler.getOffset() * _scaleFactorYm / _scaleFactorYd * dstPitch),
					dstPitch, toolbar_rect[0].w, toolbar_rect[0].h);
		SDL_UnlockSurface(toolbarSurface);
		SDL_UnlockSurface(_hwscreen);

		// And blit it
		toolbar_rect[0].y = _toolbarHandler.getOffset();
		toolbar_rect[0].x = toolbar_rect[0].x * _scaleFactorXm / _scaleFactorXd;
		toolbar_rect[0].y = toolbar_rect[0].y * _scaleFactorYm / _scaleFactorYd;
		toolbar_rect[0].w = toolbar_rect[0].w * _scaleFactorXm / _scaleFactorXd;
		toolbar_rect[0].h = toolbar_rect[0].h * _scaleFactorYm / _scaleFactorYd;

		SDL_UpdateRects(_hwscreen, 1, toolbar_rect);

		drawToolbarMouse(toolbarSurface, false);	// undraw toolbar mouse
	}

	// Finally, blit all our changes to the screen
	if (numRectsOut > 0)
		SDL_UpdateRects(_hwscreen, numRectsOut, _dirtyRectOut);

	_numDirtyRects = 0;
	_forceFull = false;
}

Graphics::Surface *OSystem_WINCE3::lockScreen() {
	// Make sure mouse pointer is not painted over the playfield at the time of locking
	undrawMouse();
	return OSystem_SDL::lockScreen();
}

void OSystem_WINCE3::unlockScreen() {
	OSystem_SDL::unlockScreen();
}

bool OSystem_WINCE3::saveScreenshot(const char *filename) {
	assert(_hwscreen != NULL);

	Common::StackLock lock(_graphicsMutex);	// Lock the mutex until this function ends
	SDL_SaveBMP(_hwscreen, filename);
	return true;
}

void OSystem_WINCE3::copyRectToOverlay(const OverlayColor *buf, int pitch, int x, int y, int w, int h) {
	assert (_transactionMode == kTransactionNone);

	if (_overlayscreen == NULL)
		return;

	// Clip the coordinates
	if (x < 0) {
		w += x;
		buf -= x;
		x = 0;
	}

	if (y < 0) {
		h += y; buf -= y * pitch;
		y = 0;
	}

	if (w > _videoMode.overlayWidth - x) {
		w = _videoMode.overlayWidth - x;
	}

	if (h > _videoMode.overlayHeight - y) {
		h = _videoMode.overlayHeight - y;
	}

	if (w <= 0 || h <= 0)
		return;

	// Mark the modified region as dirty
	_cksumValid = false;
	addDirtyRect(x, y, w, h);

	undrawMouse();

	if (SDL_LockSurface(_overlayscreen) == -1)
		error("SDL_LockSurface failed: %s", SDL_GetError());

	byte *dst = (byte *)_overlayscreen->pixels + y * _overlayscreen->pitch + x * 2;
	do {
		memcpy(dst, buf, w * 2);
		dst += _overlayscreen->pitch;
		buf += pitch;
	} while (--h);

	SDL_UnlockSurface(_overlayscreen);
}

void OSystem_WINCE3::copyRectToScreen(const byte *src, int pitch, int x, int y, int w, int h) {
	assert (_transactionMode == kTransactionNone);
	assert(src);

	if (_screen == NULL)
		return;

	Common::StackLock lock(_graphicsMutex);	// Lock the mutex until this function ends

	if (((long)src & 3) == 0 && pitch == _videoMode.screenWidth && x == 0 && y == 0 &&
			w == _videoMode.screenWidth && h == _videoMode.screenHeight && _modeFlags & DF_WANT_RECT_OPTIM) {
		/* Special, optimized case for full screen updates.
		 * It tries to determine what areas were actually changed,
		 * and just updates those, on the actual display. */
		addDirtyRgnAuto(src);
	} else {
		/* Clip the coordinates */
		if (x < 0) {
			w += x;
			src -= x;
			x = 0;
		}

		if (y < 0) {
			h += y;
			src -= y * pitch;
			y = 0;
		}

		if (w > _videoMode.screenWidth - x) {
			w = _videoMode.screenWidth - x;
		}

		if (h > _videoMode.screenHeight - y) {
			h = _videoMode.screenHeight - y;
		}

		if (w <= 0 || h <= 0)
			return;

		_cksumValid = false;
		addDirtyRect(x, y, w, h);
	}

	undrawMouse();

	// Try to lock the screen surface
	if (SDL_LockSurface(_screen) == -1)
		error("SDL_LockSurface failed: %s", SDL_GetError());

	byte *dst = (byte *)_screen->pixels + y * _videoMode.screenWidth + x;

	if (_videoMode.screenWidth == pitch && pitch == w) {
		memcpy(dst, src, h*w);
	} else {
		do {
			memcpy(dst, src, w);
			src += pitch;
			dst += _videoMode.screenWidth;
		} while (--h);
	}

	// Unlock the screen surface
	SDL_UnlockSurface(_screen);
}

void OSystem_WINCE3::setMouseCursor(const byte *buf, uint w, uint h, int hotspot_x, int hotspot_y, uint32 keycolor, int cursorTargetScale, const Graphics::PixelFormat *format) {

	undrawMouse();
	if (w == 0 || h == 0)
		return;

	_mouseCurState.w = w;
	_mouseCurState.h = h;

	_mouseHotspotX = hotspot_x;
	_mouseHotspotY = hotspot_y;

	_mouseKeyColor = keycolor;

	free(_mouseData);

	_mouseData = (byte *) malloc(w * h);
	memcpy(_mouseData, buf, w * h);

	if (w > _mouseBackupDim || h > _mouseBackupDim) {
		// mouse has been undrawn, adjust sprite backup area
		free(_mouseBackupOld);
		free(_mouseBackupToolbar);
		uint16 tmp = (w > h) ? w : h;
		_mouseBackupOld = (byte *) malloc(tmp * tmp * 2);	// can hold 8bpp (playfield) or 16bpp (overlay) data
		_mouseBackupToolbar = (uint16 *) malloc(tmp * tmp * 2); // 16 bpp
		_mouseBackupDim = tmp;
	}
}

void OSystem_WINCE3::setMousePos(int x, int y) {
	if (x != _mouseCurState.x || y != _mouseCurState.y) {
		undrawMouse();
		_mouseCurState.x = x;
		_mouseCurState.y = y;
		updateScreen();
	}
}


void OSystem_WINCE3::internDrawMouse() {
	if (!_mouseNeedsRedraw || !_mouseVisible || !_mouseData)
		return;

	int x = _mouseCurState.x - _mouseHotspotX;
	int y = _mouseCurState.y - _mouseHotspotY;
	int w = _mouseCurState.w;
	int h = _mouseCurState.h;
	byte color;
	const byte *src = _mouseData;		// Image representing the mouse
	int width;

	// clip the mouse rect, and adjust the src pointer accordingly
	if (x < 0) {
		w += x;
		src -= x;
		x = 0;
	}
	if (y < 0) {
		h += y;
		src -= y * _mouseCurState.w;
		y = 0;
	}

	if (w > _videoMode.screenWidth - x)
		w = _videoMode.screenWidth - x;
	if (h > _videoMode.screenHeight - y)
		h = _videoMode.screenHeight - y;

	// Quick check to see if anything has to be drawn at all
	if (w <= 0 || h <= 0)
		return;

	// Draw the mouse cursor; backup the covered area in "bak"
	if (SDL_LockSurface(_overlayVisible ? _overlayscreen : _screen) == -1)
		error("SDL_LockSurface failed: %s", SDL_GetError());

	// Mark as dirty
	addDirtyRect(x, y, w, h);

	if (!_overlayVisible) {
		byte *bak = _mouseBackupOld;		// Surface used to backup the area obscured by the mouse
		byte *dst;					// Surface we are drawing into

		dst = (byte *)_screen->pixels + y * _videoMode.screenWidth + x;
		while (h > 0) {
			width = w;
			while (width > 0) {
				*bak++ = *dst;
				color = *src++;
				if (color != _mouseKeyColor)	// transparent, don't draw
					*dst = color;
				dst++;
				width--;
			}
			src += _mouseCurState.w - w;
			bak += _mouseBackupDim - w;
			dst += _videoMode.screenWidth - w;
			h--;
		}

	} else {
		uint16 *bak = (uint16 *)_mouseBackupOld;	// Surface used to backup the area obscured by the mouse
		byte *dst;					// Surface we are drawing into

		dst = (byte *)_overlayscreen->pixels + (y + 1) * _overlayscreen->pitch + (x + 1) * 2;
		while (h > 0) {
			width = w;
			while (width > 0) {
				*bak++ = *(uint16 *)dst;
				color = *src++;
				if (color != 0xFF)	// 0xFF = transparent, don't draw
					*(uint16 *)dst = SDL_MapRGB(_overlayscreen->format, _currentPalette[color].r, _currentPalette[color].g, _currentPalette[color].b);
				dst += 2;
				width--;
			}
			src += _mouseCurState.w - w;
			bak += _mouseBackupDim - w;
			dst += _overlayscreen->pitch - w * 2;
			h--;
		}
	}

	SDL_UnlockSurface(_overlayVisible ? _overlayscreen : _screen);

	// Finally, set the flag to indicate the mouse has been drawn
	_mouseNeedsRedraw = false;
}

void OSystem_WINCE3::undrawMouse() {
	assert (_transactionMode == kTransactionNone);

	if (_mouseNeedsRedraw)
		return;

	int old_mouse_x = _mouseCurState.x - _mouseHotspotX;
	int old_mouse_y = _mouseCurState.y - _mouseHotspotY;
	int old_mouse_w = _mouseCurState.w;
	int old_mouse_h = _mouseCurState.h;

	// clip the mouse rect, and adjust the src pointer accordingly
	if (old_mouse_x < 0) {
		old_mouse_w += old_mouse_x;
		old_mouse_x = 0;
	}
	if (old_mouse_y < 0) {
		old_mouse_h += old_mouse_y;
		old_mouse_y = 0;
	}

	if (old_mouse_w > _videoMode.screenWidth - old_mouse_x)
		old_mouse_w = _videoMode.screenWidth - old_mouse_x;
	if (old_mouse_h > _videoMode.screenHeight - old_mouse_y)
		old_mouse_h = _videoMode.screenHeight - old_mouse_y;

	// Quick check to see if anything has to be drawn at all
	if (old_mouse_w <= 0 || old_mouse_h <= 0)
		return;


	if (SDL_LockSurface(_overlayVisible ? _overlayscreen : _screen) == -1)
		error("SDL_LockSurface failed: %s", SDL_GetError());

	int x, y;
	if (!_overlayVisible) {
		byte *dst, *bak = _mouseBackupOld;

		// No need to do clipping here, since drawMouse() did that already
		dst = (byte *)_screen->pixels + old_mouse_y * _videoMode.screenWidth + old_mouse_x;
		for (y = 0; y < old_mouse_h; ++y, bak += _mouseBackupDim, dst += _videoMode.screenWidth)
			memcpy(dst, bak, old_mouse_w);
	} else {
		byte *dst;
		uint16 *bak = (uint16 *)_mouseBackupOld;

		// No need to do clipping here, since drawMouse() did that already
		dst = (byte *)_overlayscreen->pixels + (old_mouse_y + 1) * _overlayscreen->pitch + (old_mouse_x + 1) * 2;
		for (y = 0; y < old_mouse_h; ++y, bak += _mouseBackupDim, dst += _overlayscreen->pitch)
			memcpy(dst, bak, old_mouse_w << 1);
	}

	addDirtyRect(old_mouse_x, old_mouse_y, old_mouse_w, old_mouse_h);

	SDL_UnlockSurface(_overlayVisible ? _overlayscreen : _screen);

	_mouseNeedsRedraw = true;
}

bool OSystem_WINCE3::showMouse(bool visible) {
	if (_mouseVisible == visible)
		return visible;

	if (visible == false)
		undrawMouse();

	bool last = _mouseVisible;
	_mouseVisible = visible;
	_mouseNeedsRedraw = true;

	return last;
}

void OSystem_WINCE3::drawToolbarMouse(SDL_Surface *surf, bool draw) {

	if (!_mouseData || !_usesEmulatedMouse)
		return;

	int x = _mouseCurState.x - _mouseHotspotX;
	int y = _mouseCurState.y - _mouseHotspotY - _toolbarHandler.getOffset();
	int w = _mouseCurState.w;
	int h = _mouseCurState.h;
	byte color;
	const byte *src = _mouseData;
	int width;

	// clip
	if (x < 0) {
		w += x;
		src -= x;
		x = 0;
	}
	if (y < 0) {
		h += y;
		src -= y * _mouseCurState.w;
		y = 0;
	}
	if (w > surf->w - x)
		w = surf->w - x;
	if (h > surf->h - y)
		h = surf->h - y;
	if (w <= 0 || h <= 0)
		return;

	if (SDL_LockSurface(surf) == -1)
		error("SDL_LockSurface failed at internDrawToolbarMouse: %s", SDL_GetError());

	uint16 *bak = _mouseBackupToolbar;	// toolbar surfaces are 16bpp
	uint16 *dst;
	dst = (uint16 *)surf->pixels + y * surf->w + x;

	if (draw) {		// blit it
		while (h > 0) {
			width = w;
			while (width > 0) {
				*bak++ = *dst;
				color = *src++;
				if (color != _mouseKeyColor)	// transparent color
					*dst = 0xFFFF;
				dst++;
				width--;
			}
			src += _mouseCurState.w - w;
			bak += _mouseBackupDim - w;
			dst += surf->w - w;
			h--;
		}
	} else {		// restore bg
		for (y = 0; y < h; ++y, bak += _mouseBackupDim, dst += surf->w)
			memcpy(dst, bak, w << 1);
	}

	SDL_UnlockSurface(surf);
}

void OSystem_WINCE3::blitCursor() {
}

void OSystem_WINCE3::showOverlay() {
	assert (_transactionMode == kTransactionNone);

	if (_overlayVisible)
		return;

	undrawMouse();
	_overlayVisible = true;
	update_scalers();
	clearOverlay();
}

void OSystem_WINCE3::hideOverlay() {
	assert (_transactionMode == kTransactionNone);

	if (!_overlayVisible)
		return;

	undrawMouse();
	_overlayVisible = false;
	clearOverlay();
	_forceFull = true;
}

void OSystem_WINCE3::drawMouse() {
	if (!(_toolbarHandler.visible() && _mouseCurState.y >= _toolbarHandler.getOffset() && !_usesEmulatedMouse) && !_forceHideMouse)
		internDrawMouse();
}

void OSystem_WINCE3::fillMouseEvent(Common::Event &event, int x, int y) {
	event.mouse.x = x;
	event.mouse.y = y;

	// Update the "keyboard mouse" coords
	_km.x = event.mouse.x;
	_km.y = event.mouse.y;

	// Adjust for the screen scaling
	if (_zoomDown)
		event.mouse.y += 240;

	event.mouse.x = event.mouse.x * _scaleFactorXd / _scaleFactorXm;
	event.mouse.y = event.mouse.y * _scaleFactorYd / _scaleFactorYm;
}

void OSystem_WINCE3::retrieve_mouse_location(int &x, int &y) {
	x = _mouseCurState.x;
	y = _mouseCurState.y;

	x = x * _scaleFactorXm / _scaleFactorXd;
	y = y * _scaleFactorYm / _scaleFactorYd;

	if (_zoomDown)
		y -= 240;
}

void OSystem_WINCE3::warpMouse(int x, int y) {
	if (_mouseCurState.x != x || _mouseCurState.y != y) {
		SDL_WarpMouse(x * _scaleFactorXm / _scaleFactorXd, y * _scaleFactorYm / _scaleFactorYd);

		// SDL_WarpMouse() generates a mouse movement event, so
		// set_mouse_pos() would be called eventually. However, the
		// cannon script in CoMI calls this function twice each time
		// the cannon is reloaded. Unless we update the mouse position
		// immediately the second call is ignored, causing the cannon
		// to change its aim.

		setMousePos(x, y);
	}
}

void OSystem_WINCE3::addDirtyRect(int x, int y, int w, int h, bool mouseRect) {

	if (_forceFull || _paletteDirtyEnd)
		return;

	OSystem_SDL::addDirtyRect(x, y, w, h, false);
}

static int mapKeyCE(SDLKey key, SDLMod mod, Uint16 unicode, bool unfilter) {
	if (GUI::Actions::Instance()->mappingActive())
		return key;

	if (unfilter) {
		switch (key) {
			case SDLK_ESCAPE:
				return SDLK_BACKSPACE;
			case SDLK_F8:
				return SDLK_ASTERISK;
			case SDLK_F9:
				return SDLK_HASH;
		}
		return key;
	}

	if (key >= SDLK_KP0 && key <= SDLK_KP9) {
		return key - SDLK_KP0 + '0';
	} else if (key >= SDLK_UP && key <= SDLK_PAGEDOWN) {
		return key;
	} else if (key >= SDLK_NUMLOCK && key <= SDLK_EURO) {
		return 0;
	}
	return key;
}

bool OSystem_WINCE3::pollEvent(Common::Event &event) {
	SDL_Event ev;
	byte b = 0;
	DWORD currentTime;
	bool keyEvent = false;
	int deltaX, deltaY;

	memset(&event, 0, sizeof(Common::Event));

	handleKbdMouse();

	// If the screen mode changed, send an Common::EVENT_SCREEN_CHANGED
	if (_modeChanged) {
		_modeChanged = false;
		event.type = Common::EVENT_SCREEN_CHANGED;
		_screenChangeCount++;
		return true;
	}

	CEDevice::wakeUp();

	currentTime = GetTickCount();

	while (SDL_PollEvent(&ev)) {
		switch (ev.type) {
		case SDL_KEYDOWN:
			debug(1, "Key down %X %s", ev.key.keysym.sym, SDL_GetKeyName((SDLKey)ev.key.keysym.sym));
			// KMOD_RESERVED is used if the key has been injected by an external buffer
			if (ev.key.keysym.mod != KMOD_RESERVED && !_unfilteredkeys) {
				keyEvent = true;
				_lastKeyPressed = ev.key.keysym.sym;
				_keyRepeatTime = currentTime;
				_keyRepeat = 0;

				if (!GUI_Actions::Instance()->mappingActive() && GUI_Actions::Instance()->performMapped(ev.key.keysym.sym, true))
					return true;
			}

			if (GUI_Actions::Instance()->mappingActive())
				event.kbd.flags = 0xFF;
			else if (ev.key.keysym.sym == SDLK_PAUSE) {
				_lastKeyPressed = 0;
				event.type = Common::EVENT_PREDICTIVE_DIALOG;
				return true;
			} 			event.type = Common::EVENT_KEYDOWN;
			if (!_unfilteredkeys)
				event.kbd.keycode = (Common::KeyCode)ev.key.keysym.sym;
			else
				event.kbd.keycode = (Common::KeyCode)mapKeyCE(ev.key.keysym.sym, ev.key.keysym.mod, ev.key.keysym.unicode, _unfilteredkeys);
			event.kbd.ascii = mapKeyCE(ev.key.keysym.sym, ev.key.keysym.mod, ev.key.keysym.unicode, _unfilteredkeys);

			if (ev.key.keysym.mod == KMOD_RESERVED && ev.key.keysym.unicode == KMOD_SHIFT) {
				event.kbd.ascii ^= 0x20;
				event.kbd.flags = Common::KBD_SHIFT;
			}

			return true;

		case SDL_KEYUP:
			debug(1, "Key up %X %s", ev.key.keysym.sym, SDL_GetKeyName((SDLKey)ev.key.keysym.sym));
			// KMOD_RESERVED is used if the key has been injected by an external buffer
			if (ev.key.keysym.mod != KMOD_RESERVED && !_unfilteredkeys) {
				keyEvent = true;
				_lastKeyPressed = 0;

				if (!GUI_Actions::Instance()->mappingActive() && GUI_Actions::Instance()->performMapped(ev.key.keysym.sym, false))
					return true;
			}

			if (GUI_Actions::Instance()->mappingActive())
				event.kbd.flags = 0xFF;
			else if (ev.key.keysym.sym == SDLK_PAUSE) {
				_lastKeyPressed = 0;
				return false;	// chew up the show agi dialog key up event
			}

			event.type = Common::EVENT_KEYUP;
			if (!_unfilteredkeys)
				event.kbd.keycode = (Common::KeyCode)ev.key.keysym.sym;
			else
				event.kbd.keycode = (Common::KeyCode)mapKeyCE(ev.key.keysym.sym, ev.key.keysym.mod, ev.key.keysym.unicode, _unfilteredkeys);
			event.kbd.ascii = mapKeyCE(ev.key.keysym.sym, ev.key.keysym.mod, ev.key.keysym.unicode, _unfilteredkeys);

			if (ev.key.keysym.mod == KMOD_RESERVED && ev.key.keysym.unicode == KMOD_SHIFT) {
				event.kbd.ascii ^= 0x20;
				event.kbd.flags = Common::KBD_SHIFT;
			}

			return true;

		case SDL_MOUSEMOTION:
			event.type = Common::EVENT_MOUSEMOVE;
			fillMouseEvent(event, ev.motion.x, ev.motion.y);
			setMousePos(event.mouse.x, event.mouse.y);
			return true;

		case SDL_MOUSEBUTTONDOWN:
			if (ev.button.button == SDL_BUTTON_LEFT)
				event.type = Common::EVENT_LBUTTONDOWN;
			else if (ev.button.button == SDL_BUTTON_RIGHT)
				event.type = Common::EVENT_RBUTTONDOWN;
			else
				break;
			fillMouseEvent(event, ev.button.x, ev.button.y);


			if (event.mouse.x > _tapX)
				deltaX = event.mouse.x - _tapX;
			else
				deltaX = _tapX - event.mouse.x;
			if (event.mouse.y > _tapY)
				deltaY = event.mouse.y - _tapY;
			else
				deltaY = _tapY - event.mouse.y;
			_closeClick = (deltaX <= 5 && deltaY <= 5);

			if (!_isSmartphone) {
				// handle double-taps
				if (_tapTime) {		// second tap
					if (_closeClick && (GetTickCount() - _tapTime < 1000)) {
						if (event.mouse.y <= 20 && _panelInitialized) {		// top of screen (show panel)
							swap_panel_visibility();
						} else if (!_noDoubleTapRMB) {		// right click
							event.type = Common::EVENT_RBUTTONDOWN;
							_rbutton = true;
						}
					}
					_tapTime = 0;
				} else {
					_tapTime = GetTickCount();
					_tapX = event.mouse.x;
					_tapY = event.mouse.y;
				}
			}

			if (_freeLook && !_closeClick) {
				_rbutton = false;
				_tapTime = 0;
				_tapX = event.mouse.x;
				_tapY = event.mouse.y;
				event.type = Common::EVENT_MOUSEMOVE;
				setMousePos(event.mouse.x, event.mouse.y);
			}


			if (_toolbarHandler.action(event.mouse.x, event.mouse.y, true)) {
				if (!_toolbarHandler.drawn()) {
					_toolbarHighDrawn = false;
					internUpdateScreen();
				}
				if (_newOrientation != _orientationLandscape){
					_orientationLandscape = _newOrientation;
					_toolbarHighDrawn = false;
					ConfMan.setInt("landscape", _orientationLandscape);
					ConfMan.flushToDisk();
					hotswapGFXMode();
				}
				return false;
			}

			return true;

		case SDL_MOUSEBUTTONUP:
			if (ev.button.button == SDL_BUTTON_LEFT)
				event.type = Common::EVENT_LBUTTONUP;
			else if (ev.button.button == SDL_BUTTON_RIGHT)
				event.type = Common::EVENT_RBUTTONUP;
			else
				break;

			if (_rbutton) {
				event.type = Common::EVENT_RBUTTONUP;
				_rbutton = false;
			}

			fillMouseEvent(event, ev.button.x, ev.button.y);

			if (_freeLook && !_closeClick) {
				_tapX = event.mouse.x;
				_tapY = event.mouse.y;
				event.type = Common::EVENT_MOUSEMOVE;
				setMousePos(event.mouse.x, event.mouse.y);
			}

			if (_toolbarHandler.action(event.mouse.x, event.mouse.y, false)) {
				if (!_toolbarHandler.drawn()) {
					_toolbarHighDrawn = false;
					internUpdateScreen();
				}
				return false;

			}
			return true;

		case SDL_VIDEOEXPOSE:
			_forceFull = true;
			break;

		case SDL_QUIT:
			event.type = Common::EVENT_QUIT;
			return true;

		case SDL_ACTIVEEVENT:
			if (ev.active.state & SDL_APPMOUSEFOCUS)
				debug(2, "%s mouse focus.", ev.active.gain ? "Got" : "Lost");
			if (ev.active.state & SDL_APPINPUTFOCUS)
				debug(2, "%s input focus.", ev.active.gain ? "Got" : "Lost");
			if (ev.active.state & SDL_APPACTIVE)
				debug(2, "%s total focus.", ev.active.gain ? "Got" : "Lost");
			if (ev.active.state & SDL_APPINPUTFOCUS) {
				_hasfocus = ev.active.gain;
				SDL_PauseAudio(!_hasfocus);
				_forceFull |= _hasfocus;
			}
			break;
		}
	}

	// Simulate repeated key for backend
	if (!keyEvent && _lastKeyPressed && currentTime > _keyRepeatTime + _keyRepeatTrigger) {
		_keyRepeatTime = currentTime;
		_keyRepeat++;
		GUI_Actions::Instance()->performMapped(_lastKeyPressed, true);
	}

	return false;
}

void OSystem_WINCE3::quit() {
	fclose(stdout_file);
	fclose(stderr_file);
	if (gDebugLevel <= 0) {
		DeleteFile(ASCIItoUnicode(stdout_fname));
		DeleteFile(ASCIItoUnicode(stderr_fname));
	}
	CEDevice::end();
	OSystem_SDL::quit();
}

void OSystem_WINCE3::getTimeAndDate(TimeDate &t) const {
	SYSTEMTIME systime;

	GetLocalTime(&systime);
	t.tm_year	= systime.wYear - 1900;
	t.tm_mon	= systime.wMonth - 1;
	t.tm_mday	= systime.wDay;
	t.tm_hour	= systime.wHour;
	t.tm_min	= systime.wMinute;
	t.tm_sec	= systime.wSecond;
}

int OSystem_WINCE3::_platformScreenWidth;
int OSystem_WINCE3::_platformScreenHeight;
bool OSystem_WINCE3::_isOzone;
OSystem_WINCE3::zoneDesc OSystem_WINCE3::_zones[TOTAL_ZONES] = {
	{ 0, 0, 320, 145 },
	{ 0, 145, 150, 55 },
	{ 150, 145, 170, 55 }
};
