// Copyright (C) 2002-2012 Nikolaus Gebhardt
// This file is part of the "Irrlicht Engine".
// For conditions of distribution and use, see copyright notice in irrlicht.h


#ifdef _IRR_COMPILE_WITH_WINDOWS_DEVICE_

#if defined (__STRICT_ANSI__)
    #error Compiling with __STRICT_ANSI__ not supported. g++ does set this when compiling with -std=c++11 or -std=c++0x. Use instead -std=gnu++11 or -std=gnu++0x. Or use -U__STRICT_ANSI__ to disable strict ansi.
#endif

#include "CIrrDeviceWin32.h"
#include "IEventReceiver.h"
#include "os.h"

#include "CTimer.h"
#include "irrString.h"
#include "COSOperator.h"
#include "dimension2d.h"
#include "IGUISpriteBank.h"
#include <winuser.h>
#include "SExposedVideoData.h"

#if defined(_IRR_COMPILE_WITH_JOYSTICK_EVENTS_)
#include <mmsystem.h>
#include <regstr.h>
#ifdef _IRR_COMPILE_WITH_DIRECTINPUT_JOYSTICK_
#define DIRECTINPUT_VERSION 0x0800
#include <dinput.h>
#endif
#endif

#if defined(_IRR_COMPILE_WITH_OGLES1_) || defined(_IRR_COMPILE_WITH_OGLES2_)
#include "CEGLManager.h"
#endif

#if defined(_IRR_COMPILE_WITH_OPENGL_)
#include "CWGLManager.h"
#endif

namespace irr
{
	namespace video
	{
#ifdef _IRR_COMPILE_WITH_OPENGL_
		IVideoDriver* createOpenGLDriver(const irr::SIrrlichtCreationParameters& params, io::IFileSystem* io, IContextManager* contextManager);
#endif

#ifdef _IRR_COMPILE_WITH_OGLES1_
        IVideoDriver* createOGLES1Driver(const irr::SIrrlichtCreationParameters& params, io::IFileSystem* io, IContextManager* contextManager);
#endif

#ifdef _IRR_COMPILE_WITH_OGLES2_
        IVideoDriver* createOGLES2Driver(const irr::SIrrlichtCreationParameters& params, io::IFileSystem* io, IContextManager* contextManager);
#endif
	}
} // end namespace irr

namespace irr
{
    struct SJoystickWin32Control
    {
        CIrrDeviceWin32* Device;

    #if defined(_IRR_COMPILE_WITH_JOYSTICK_EVENTS_) && defined(_IRR_COMPILE_WITH_DIRECTINPUT_JOYSTICK_)
        IDirectInput8* DirectInputDevice;
    #endif
    #if defined(_IRR_COMPILE_WITH_JOYSTICK_EVENTS_)
        struct JoystickInfo
        {
            u32 Index;
    #ifdef _IRR_COMPILE_WITH_DIRECTINPUT_JOYSTICK_
            core::stringc Name;
            GUID guid;
            LPDIRECTINPUTDEVICE8 lpdijoy;
            DIDEVCAPS devcaps;
            u8 axisValid[8];
    #else
            JOYCAPS Caps;
    #endif
        };
        core::array<JoystickInfo> ActiveJoysticks;
    #endif

        SJoystickWin32Control(CIrrDeviceWin32* dev);
        ~SJoystickWin32Control();

    #if defined(_IRR_COMPILE_WITH_JOYSTICK_EVENTS_) && defined(_IRR_COMPILE_WITH_DIRECTINPUT_JOYSTICK_)
        static BOOL CALLBACK EnumJoysticks(LPCDIDEVICEINSTANCE lpddi, LPVOID cp);
        void directInputAddJoystick(LPCDIDEVICEINSTANCE lpddi);
    #endif

        void pollJoysticks();
        bool activateJoysticks(core::array<SJoystickInfo> & joystickInfo);
        irr::core::stringc findJoystickName(int index, const JOYCAPS &caps) const;
    };


	SJoystickWin32Control::SJoystickWin32Control(CIrrDeviceWin32* dev) : Device(dev)
	{
#if defined(_IRR_COMPILE_WITH_JOYSTICK_EVENTS_) && defined(_IRR_COMPILE_WITH_DIRECTINPUT_JOYSTICK_)
		DirectInputDevice=0;
		if (DI_OK != (DirectInput8Create(GetModuleHandle(NULL), DIRECTINPUT_VERSION, IID_IDirectInput8, (void**)&DirectInputDevice, NULL)))
		{
			os::Printer::log("Could not create DirectInput8 Object", ELL_WARNING);
			return;
		}
#endif
	}

	SJoystickWin32Control::~SJoystickWin32Control()
	{
#if defined(_IRR_COMPILE_WITH_JOYSTICK_EVENTS_) && defined(_IRR_COMPILE_WITH_DIRECTINPUT_JOYSTICK_)
		for(u32 joystick = 0; joystick < ActiveJoysticks.size(); ++joystick)
		{
			LPDIRECTINPUTDEVICE8 dev = ActiveJoysticks[joystick].lpdijoy;
			if (dev)
			{
				dev->Unacquire();
				dev->Release();
			}
		}

		if (DirectInputDevice)
			DirectInputDevice->Release();
#endif
	}

#if defined(_IRR_COMPILE_WITH_JOYSTICK_EVENTS_) && defined(_IRR_COMPILE_WITH_DIRECTINPUT_JOYSTICK_)
	BOOL CALLBACK SJoystickWin32Control::EnumJoysticks(LPCDIDEVICEINSTANCE lpddi, LPVOID cp)
	{
		SJoystickWin32Control* p=(SJoystickWin32Control*)cp;
		p->directInputAddJoystick(lpddi);
		return DIENUM_CONTINUE;
	}
	void SJoystickWin32Control::directInputAddJoystick(LPCDIDEVICEINSTANCE lpddi)
	{
		//Get the GUID of the joystuck
		const GUID guid = lpddi->guidInstance;

		JoystickInfo activeJoystick;
		activeJoystick.Index=ActiveJoysticks.size();
		activeJoystick.guid=guid;
		activeJoystick.Name=lpddi->tszProductName;
		if (FAILED(DirectInputDevice->CreateDevice(guid, &activeJoystick.lpdijoy, NULL)))
		{
			os::Printer::log("Could not create DirectInput device", ELL_WARNING);
			return;
		}

		activeJoystick.devcaps.dwSize=sizeof(activeJoystick.devcaps);
		if (FAILED(activeJoystick.lpdijoy->GetCapabilities(&activeJoystick.devcaps)))
		{
			os::Printer::log("Could not create DirectInput device", ELL_WARNING);
			return;
		}

		if (FAILED(activeJoystick.lpdijoy->SetCooperativeLevel(Device->HWnd, DISCL_BACKGROUND | DISCL_EXCLUSIVE)))
		{
			os::Printer::log("Could not set DirectInput device cooperative level", ELL_WARNING);
			return;
		}

		if (FAILED(activeJoystick.lpdijoy->SetDataFormat(&c_dfDIJoystick2)))
		{
			os::Printer::log("Could not set DirectInput device data format", ELL_WARNING);
			return;
		}

		if (FAILED(activeJoystick.lpdijoy->Acquire()))
		{
			os::Printer::log("Could not set DirectInput cooperative level", ELL_WARNING);
			return;
		}

		DIJOYSTATE2 info;
		if (FAILED(activeJoystick.lpdijoy->GetDeviceState(sizeof(info),&info)))
		{
			os::Printer::log("Could not read DirectInput device state", ELL_WARNING);
			return;
		}

		ZeroMemory(activeJoystick.axisValid,sizeof(activeJoystick.axisValid));
		activeJoystick.axisValid[0]= (info.lX!=0) ? 1 : 0;
		activeJoystick.axisValid[1]= (info.lY!=0) ? 1 : 0;
		activeJoystick.axisValid[2]= (info.lZ!=0) ? 1 : 0;
		activeJoystick.axisValid[3]= (info.lRx!=0) ? 1 : 0;
		activeJoystick.axisValid[4]= (info.lRy!=0) ? 1 : 0;
		activeJoystick.axisValid[5]= (info.lRz!=0) ? 1 : 0;

		int caxis=0;
		for (u8 i=0; i<6; i++)
		{
			if (activeJoystick.axisValid[i])
				caxis++;
		}

		for (u8 i=0; i<(activeJoystick.devcaps.dwAxes)-caxis; i++)
		{
			if (i+caxis < 8)
				activeJoystick.axisValid[i+caxis]=1;
		}

		ActiveJoysticks.push_back(activeJoystick);
	}
#endif

void SJoystickWin32Control::pollJoysticks()
{
#if defined _IRR_COMPILE_WITH_JOYSTICK_EVENTS_
#ifdef _IRR_COMPILE_WITH_DIRECTINPUT_JOYSTICK_
	if(0 == ActiveJoysticks.size())
		return;

	u32 joystick;
	DIJOYSTATE2 info;

	for(joystick = 0; joystick < ActiveJoysticks.size(); ++joystick)
	{
		// needs to be reset for each joystick
		// request ALL values and POV as continuous if possible

		const DIDEVCAPS & caps = ActiveJoysticks[joystick].devcaps;
		// if no POV is available don't ask for POV values

		if (!FAILED(ActiveJoysticks[joystick].lpdijoy->GetDeviceState(sizeof(info),&info)))
		{
			SEvent event;

			event.EventType = irr::EET_JOYSTICK_INPUT_EVENT;
			event.JoystickEvent.Joystick = (u8)joystick;

			event.JoystickEvent.POV = (u16)info.rgdwPOV[0];
			// set to undefined if no POV value was returned or the value
			// is out of range
			if ((caps.dwPOVs==0) || (event.JoystickEvent.POV > 35900))
				event.JoystickEvent.POV = 65535;

			for(int axis = 0; axis < SEvent::SJoystickEvent::NUMBER_OF_AXES; ++axis)
				event.JoystickEvent.Axis[axis] = 0;

			u16 dxAxis=0;
			u16 irrAxis=0;

			while (dxAxis < 6 && irrAxis <caps.dwAxes)
			{
				bool axisFound=0;
				s32 axisValue=0;

				switch (dxAxis)
				{
				case 0:
					axisValue=info.lX;
					break;
				case 1:
					axisValue=info.lY;
					break;
				case 2:
					axisValue=info.lZ;
					break;
				case 3:
					axisValue=info.lRx;
					break;
				case 4:
					axisValue=info.lRy;
					break;
				case 5:
					axisValue=info.lRz;
					break;
				case 6:
					axisValue=info.rglSlider[0];
					break;
				case 7:
					axisValue=info.rglSlider[1];
					break;
				default:
					break;
				}

				if (ActiveJoysticks[joystick].axisValid[dxAxis]>0)
					axisFound=1;

				if (axisFound)
				{
					s32 val=axisValue - 32768;

					if (val <-32767) val=-32767;
					if (val > 32767) val=32767;
					event.JoystickEvent.Axis[irrAxis]=(s16)(val);
					irrAxis++;
				}

				dxAxis++;
			}

			u32 buttons=0;
			BYTE* bytebuttons=info.rgbButtons;
			for (u16 i=0; i<32; i++)
			{
				if (bytebuttons[i] >0)
				{
					buttons |= (1 << i);
				}
			}
			event.JoystickEvent.ButtonStates = buttons;

			(void)Device->postEventFromUser(event);
		}
	}
#else
	if (0 == ActiveJoysticks.size())
		return;

	u32 joystick;
	JOYINFOEX info;

	for(joystick = 0; joystick < ActiveJoysticks.size(); ++joystick)
	{
		// needs to be reset for each joystick
		// request ALL values and POV as continuous if possible
		info.dwSize = sizeof(info);
		info.dwFlags = JOY_RETURNALL|JOY_RETURNPOVCTS;
		const JOYCAPS & caps = ActiveJoysticks[joystick].Caps;
		// if no POV is available don't ask for POV values
		if (!(caps.wCaps & JOYCAPS_HASPOV))
			info.dwFlags &= ~(JOY_RETURNPOV|JOY_RETURNPOVCTS);
		if(JOYERR_NOERROR == joyGetPosEx(ActiveJoysticks[joystick].Index, &info))
		{
			SEvent event;

			event.EventType = irr::EET_JOYSTICK_INPUT_EVENT;
			event.JoystickEvent.Joystick = (u8)joystick;

			event.JoystickEvent.POV = (u16)info.dwPOV;
			// set to undefined if no POV value was returned or the value
			// is out of range
			if (!(info.dwFlags & JOY_RETURNPOV) || (event.JoystickEvent.POV > 35900))
				event.JoystickEvent.POV = 65535;

			for(int axis = 0; axis < SEvent::SJoystickEvent::NUMBER_OF_AXES; ++axis)
				event.JoystickEvent.Axis[axis] = 0;

			event.JoystickEvent.ButtonStates = info.dwButtons;

			switch(caps.wNumAxes)
			{
			default:
			case 6:
				event.JoystickEvent.Axis[SEvent::SJoystickEvent::AXIS_V] =
					(s16)((65535 * (info.dwVpos - caps.wVmin)) / (caps.wVmax - caps.wVmin) - 32768);

			case 5:
				event.JoystickEvent.Axis[SEvent::SJoystickEvent::AXIS_U] =
					(s16)((65535 * (info.dwUpos - caps.wUmin)) / (caps.wUmax - caps.wUmin) - 32768);

			case 4:
				event.JoystickEvent.Axis[SEvent::SJoystickEvent::AXIS_R] =
					(s16)((65535 * (info.dwRpos - caps.wRmin)) / (caps.wRmax - caps.wRmin) - 32768);

			case 3:
				event.JoystickEvent.Axis[SEvent::SJoystickEvent::AXIS_Z] =
					(s16)((65535 * (info.dwZpos - caps.wZmin)) / (caps.wZmax - caps.wZmin) - 32768);

			case 2:
				event.JoystickEvent.Axis[SEvent::SJoystickEvent::AXIS_Y] =
					(s16)((65535 * (info.dwYpos - caps.wYmin)) / (caps.wYmax - caps.wYmin) - 32768);

			case 1:
				event.JoystickEvent.Axis[SEvent::SJoystickEvent::AXIS_X] =
					(s16)((65535 * (info.dwXpos - caps.wXmin)) / (caps.wXmax - caps.wXmin) - 32768);
			}

			(void)Device->postEventFromUser(event);
		}
	}
#endif
#endif // _IRR_COMPILE_WITH_JOYSTICK_EVENTS_
}

/** This function is ported from SDL and released under zlib-license:
  * Copyright (C) 1997-2014 Sam Lantinga <slouken@libsdl.org> */
irr::core::stringc SJoystickWin32Control::findJoystickName(int index, const JOYCAPS &caps) const
{
#if defined _IRR_COMPILE_WITH_JOYSTICK_EVENTS_

    // As a default use the name given in the joystick structure.
    // It is always the same name, independent of joystick.
    irr::core::stringc result(caps.szPname);

    core::stringc key = core::stringc(REGSTR_PATH_JOYCONFIG)+ "\\" + caps.szRegKey + "\\" + REGSTR_KEY_JOYCURR;
    HKEY hTopKey = HKEY_LOCAL_MACHINE;
    HKEY hKey;
    long regresult = RegOpenKeyExA(hTopKey, key.c_str(), 0, KEY_READ, &hKey);
    if (regresult != ERROR_SUCCESS)
    {
        hTopKey = HKEY_CURRENT_USER;
        regresult = RegOpenKeyExA(hTopKey, key.c_str(), 0, KEY_READ, &hKey);
    }
    if (regresult != ERROR_SUCCESS)
        return result;

    /* find the registry key name for the joystick's properties */
    char regname[256];
    DWORD regsize = sizeof(regname);
    core::stringc regvalue = core::stringc("Joystick")+core::stringc(index+1) + REGSTR_VAL_JOYOEMNAME;
    regresult = RegQueryValueExA(hKey, regvalue.c_str(), 0, 0, (LPBYTE)regname, &regsize);
    RegCloseKey(hKey);
    if (regresult != ERROR_SUCCESS)
        return result;

    /* open that registry key */
    core::stringc regkey = core::stringc(REGSTR_PATH_JOYOEM) + "\\" + regname;
    regresult = RegOpenKeyExA(hTopKey, regkey.c_str(), 0, KEY_READ, &hKey);
    if (regresult != ERROR_SUCCESS)
        return result;

    /* find the size for the OEM name text */
    regsize = sizeof(regvalue);
    regresult = RegQueryValueEx(hKey, REGSTR_VAL_JOYOEMNAME, 0, 0,
                                 NULL, &regsize);
    if (regresult == ERROR_SUCCESS)
    {
        char *name;
        /* allocate enough memory for the OEM name text ... */
        name = new char[regsize];
        if (name)
        {
            /* ... and read it from the registry */
            regresult = RegQueryValueEx(hKey, REGSTR_VAL_JOYOEMNAME, 0, 0,
                                         (LPBYTE)name, &regsize );
            result = name;
        }
        delete[] name;
    }
    RegCloseKey(hKey);

    return result;
#endif
	return "";
}

bool SJoystickWin32Control::activateJoysticks(core::array<SJoystickInfo> & joystickInfo)
{
#if defined _IRR_COMPILE_WITH_JOYSTICK_EVENTS_
	joystickInfo.clear();
	ActiveJoysticks.clear();
#ifdef _IRR_COMPILE_WITH_DIRECTINPUT_JOYSTICK_
	if (!DirectInputDevice || (DirectInputDevice->EnumDevices(DI8DEVCLASS_GAMECTRL, SJoystickWin32Control::EnumJoysticks, this, DIEDFL_ATTACHEDONLY )))
	{
		os::Printer::log("Could not enum DirectInput8 controllers", ELL_WARNING);
		return false;
	}

	for(u32 joystick = 0; joystick < ActiveJoysticks.size(); ++joystick)
	{
		JoystickInfo& activeJoystick = ActiveJoysticks[joystick];
		SJoystickInfo info;
		info.Axes=activeJoystick.devcaps.dwAxes;
		info.Buttons=activeJoystick.devcaps.dwButtons;
		info.Name=activeJoystick.Name;
		info.PovHat = (activeJoystick.devcaps.dwPOVs  != 0)
				? SJoystickInfo::POV_HAT_PRESENT : SJoystickInfo::POV_HAT_ABSENT;
		joystickInfo.push_back(info);
	}
	return true;
#else
	const u32 numberOfJoysticks = ::joyGetNumDevs();
	JOYINFOEX info;
	info.dwSize = sizeof(info);
	info.dwFlags = JOY_RETURNALL;

	JoystickInfo activeJoystick;
	SJoystickInfo returnInfo;

	joystickInfo.reallocate(numberOfJoysticks);
	ActiveJoysticks.reallocate(numberOfJoysticks);

	u32 joystick = 0;
	for(; joystick < numberOfJoysticks; ++joystick)
	{
		if(JOYERR_NOERROR == joyGetPosEx(joystick, &info)
			&&
			JOYERR_NOERROR == joyGetDevCaps(joystick,
											&activeJoystick.Caps,
											sizeof(activeJoystick.Caps)))
		{
			activeJoystick.Index = joystick;
			ActiveJoysticks.push_back(activeJoystick);

			returnInfo.Joystick = (u8)joystick;
			returnInfo.Axes = activeJoystick.Caps.wNumAxes;
			returnInfo.Buttons = activeJoystick.Caps.wNumButtons;
			returnInfo.Name = findJoystickName(joystick, activeJoystick.Caps);
			returnInfo.PovHat = ((activeJoystick.Caps.wCaps & JOYCAPS_HASPOV) == JOYCAPS_HASPOV)
								? SJoystickInfo::POV_HAT_PRESENT : SJoystickInfo::POV_HAT_ABSENT;

			joystickInfo.push_back(returnInfo);
		}
	}

	for(joystick = 0; joystick < joystickInfo.size(); ++joystick)
	{
		char logString[256];
		snprintf_irr(logString, sizeof(logString), "Found joystick %d, %d axes, %d buttons '%s'",
			joystick, joystickInfo[joystick].Axes,
			joystickInfo[joystick].Buttons, joystickInfo[joystick].Name.c_str());
		os::Printer::log(logString, ELL_INFORMATION);
	}

	return true;
#endif
#else
	return false;
#endif // _IRR_COMPILE_WITH_JOYSTICK_EVENTS_
}
} // end namespace irr

namespace
{
	struct SEnvMapper
	{
		HWND hWnd;
		irr::CIrrDeviceWin32* irrDev;
	};
	// NOTE: This is global. We can have more than one Irrlicht Device at same time.
	irr::core::array<SEnvMapper> EnvMap;

	HKL KEYBOARD_INPUT_HKL=0;
}

irr::CIrrDeviceWin32* getDeviceFromHWnd(HWND hWnd)
{
	const irr::u32 end = EnvMap.size();
	for ( irr::u32 i=0; i < end; ++i )
	{
		const SEnvMapper& env = EnvMap[i];
		if ( env.hWnd == hWnd )
			return env.irrDev;
	}

	return 0;
}


LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	#ifndef WHEEL_DELTA
	#define WHEEL_DELTA 120
	#endif

	irr::CIrrDeviceWin32* dev = 0;
	irr::SEvent event;

	static irr::s32 ClickCount=0;
	if (GetCapture() != hWnd && ClickCount > 0)
		ClickCount = 0;


	struct messageMap
	{
		irr::s32 group;
		UINT winMessage;
		irr::s32 irrMessage;
	};

	static messageMap mouseMap[] =
	{
		{0, WM_LBUTTONDOWN, irr::EMIE_LMOUSE_PRESSED_DOWN},
		{1, WM_LBUTTONUP,   irr::EMIE_LMOUSE_LEFT_UP},
		{0, WM_RBUTTONDOWN, irr::EMIE_RMOUSE_PRESSED_DOWN},
		{1, WM_RBUTTONUP,   irr::EMIE_RMOUSE_LEFT_UP},
		{0, WM_MBUTTONDOWN, irr::EMIE_MMOUSE_PRESSED_DOWN},
		{1, WM_MBUTTONUP,   irr::EMIE_MMOUSE_LEFT_UP},
		{2, WM_MOUSEMOVE,   irr::EMIE_MOUSE_MOVED},
		{3, WM_MOUSEWHEEL,  irr::EMIE_MOUSE_WHEEL},
		{-1, 0, 0}
	};

	// handle grouped events
	messageMap * m = mouseMap;
	while ( m->group >=0 && m->winMessage != message )
		m += 1;

	if ( m->group >= 0 )
	{
		if ( m->group == 0 )	// down
		{
			ClickCount++;
			SetCapture(hWnd);
		}
		else
		if ( m->group == 1 )	// up
		{
			ClickCount--;
			if (ClickCount<1)
			{
				ClickCount=0;
				ReleaseCapture();
			}
		}

		event.EventType = irr::EET_MOUSE_INPUT_EVENT;
		event.MouseInput.Event = (irr::EMOUSE_INPUT_EVENT) m->irrMessage;
		event.MouseInput.X = (short)LOWORD(lParam);
		event.MouseInput.Y = (short)HIWORD(lParam);
		event.MouseInput.Shift = ((LOWORD(wParam) & MK_SHIFT) != 0);
		event.MouseInput.Control = ((LOWORD(wParam) & MK_CONTROL) != 0);
		// left and right mouse buttons
		event.MouseInput.ButtonStates = wParam & ( MK_LBUTTON | MK_RBUTTON);
		// middle and extra buttons
		if (wParam & MK_MBUTTON)
			event.MouseInput.ButtonStates |= irr::EMBSM_MIDDLE;
		if (wParam & MK_XBUTTON1)
			event.MouseInput.ButtonStates |= irr::EMBSM_EXTRA1;
		if (wParam & MK_XBUTTON2)
			event.MouseInput.ButtonStates |= irr::EMBSM_EXTRA2;
		event.MouseInput.Wheel = 0.f;

		// wheel
		if ( m->group == 3 )
		{
			POINT p; // fixed by jox
			p.x = 0; p.y = 0;
			ClientToScreen(hWnd, &p);
			event.MouseInput.X -= p.x;
			event.MouseInput.Y -= p.y;
			event.MouseInput.Wheel = ((irr::f32)((short)HIWORD(wParam))) / (irr::f32)WHEEL_DELTA;
		}

		dev = getDeviceFromHWnd(hWnd);
		if (dev)
		{
			dev->postEventFromUser(event);

			if ( event.MouseInput.Event >= irr::EMIE_LMOUSE_PRESSED_DOWN && event.MouseInput.Event <= irr::EMIE_MMOUSE_PRESSED_DOWN )
			{
				irr::u32 clicks = dev->checkSuccessiveClicks(event.MouseInput.X, event.MouseInput.Y, event.MouseInput.Event);
				if ( clicks == 2 )
				{
					event.MouseInput.Event = (irr::EMOUSE_INPUT_EVENT)(irr::EMIE_LMOUSE_DOUBLE_CLICK + event.MouseInput.Event-irr::EMIE_LMOUSE_PRESSED_DOWN);
					dev->postEventFromUser(event);
				}
				else if ( clicks == 3 )
				{
					event.MouseInput.Event = (irr::EMOUSE_INPUT_EVENT)(irr::EMIE_LMOUSE_TRIPLE_CLICK + event.MouseInput.Event-irr::EMIE_LMOUSE_PRESSED_DOWN);
					dev->postEventFromUser(event);
				}
			}
		}
		return 0;
	}

	switch (message)
	{
	case WM_PAINT:
		{
			PAINTSTRUCT ps;
			BeginPaint(hWnd, &ps);
			EndPaint(hWnd, &ps);
		}
		return 0;

	case WM_ERASEBKGND:
		return 0;

	case WM_SYSKEYDOWN:
	case WM_SYSKEYUP:
	case WM_KEYDOWN:
	case WM_KEYUP:
		{
			BYTE allKeys[256];

			event.EventType = irr::EET_KEY_INPUT_EVENT;
			event.KeyInput.Key = (irr::EKEY_CODE)wParam;
			event.KeyInput.PressedDown = (message==WM_KEYDOWN || message == WM_SYSKEYDOWN);

			if ( event.KeyInput.Key == irr::KEY_SHIFT )
			{
				event.KeyInput.Key = (irr::EKEY_CODE)MapVirtualKey( ((lParam>>16) & 255), MAPVK_VSC_TO_VK_EX );
			}
			if ( event.KeyInput.Key == irr::KEY_CONTROL )
			{
				event.KeyInput.Key = (irr::EKEY_CODE)MapVirtualKey( ((lParam>>16) & 255), MAPVK_VSC_TO_VK_EX );
				// some keyboards will just return LEFT for both - left and right keys. So also check extend bit.
				if (lParam & 0x1000000)
					event.KeyInput.Key = irr::KEY_RCONTROL;
			}
			if ( event.KeyInput.Key == irr::KEY_MENU )
			{
				event.KeyInput.Key = (irr::EKEY_CODE)MapVirtualKey( ((lParam>>16) & 255), MAPVK_VSC_TO_VK_EX );
				if (lParam & 0x1000000)
					event.KeyInput.Key = irr::KEY_RMENU;
			}

			GetKeyboardState(allKeys);

			event.KeyInput.Shift = ((allKeys[VK_SHIFT] & 0x80)!=0);
			event.KeyInput.Control = ((allKeys[VK_CONTROL] & 0x80)!=0);

			// Handle unicode and deadkeys
			WCHAR keyChars[2];
			UINT scanCode = HIWORD(lParam);
			int conversionResult = ToUnicodeEx(static_cast<UINT>(wParam),scanCode,allKeys,keyChars,2,0,KEYBOARD_INPUT_HKL);
			if (conversionResult == 1)
				event.KeyInput.Char = keyChars[0];
			else
				event.KeyInput.Char = 0;

			// allow composing characters like '@' with Alt Gr on non-US keyboards
			if ((allKeys[VK_MENU] & 0x80) != 0)
				event.KeyInput.Control = 0;

			dev = getDeviceFromHWnd(hWnd);
			if (dev)
				dev->postEventFromUser(event);

			if (message == WM_SYSKEYDOWN || message == WM_SYSKEYUP)
				return DefWindowProcW(hWnd, message, wParam, lParam);
			else
				return 0;
		}

	case WM_SIZE:
		{
			// resize
			dev = getDeviceFromHWnd(hWnd);
			if (dev)
				dev->OnResized();
		}
		return 0;

	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;

	case WM_SYSCOMMAND:
		// prevent screensaver or monitor powersave mode from starting
		if ((wParam & 0xFFF0) == SC_SCREENSAVE ||
			(wParam & 0xFFF0) == SC_MONITORPOWER ||
			(wParam & 0xFFF0) == SC_KEYMENU
			)
			return 0;

		break;

	case WM_USER:
		event.EventType = irr::EET_USER_EVENT;
		event.UserEvent.UserData1 = static_cast<size_t>(wParam);
		event.UserEvent.UserData2 = static_cast<size_t>(lParam);
		dev = getDeviceFromHWnd(hWnd);

		if (dev)
			dev->postEventFromUser(event);

		return 0;

	case WM_SETCURSOR:
		// because Windows forgot about that in the meantime
		dev = getDeviceFromHWnd(hWnd);
		if (dev)
		{
			dev->getCursorControl()->setActiveIcon( dev->getCursorControl()->getActiveIcon() );
			dev->getCursorControl()->setVisible( dev->getCursorControl()->isVisible() );
		}
		break;

	case WM_INPUTLANGCHANGE:
		// get the new codepage used for keyboard input
		KEYBOARD_INPUT_HKL = GetKeyboardLayout(0);
		return 0;
	}
	return DefWindowProcW(hWnd, message, wParam, lParam);
}


namespace irr
{

//! constructor
CIrrDeviceWin32::CIrrDeviceWin32(const SIrrlichtCreationParameters& params)
: CIrrDeviceStub(params), HWnd(0), Resized(false),
	ExternalWindow(false), Win32CursorControl(0), JoyControl(0),
	WindowMaximized(params.WindowMaximized)
{
	#ifdef _DEBUG
	setDebugName("CIrrDeviceWin32");
	#endif

	// get windows version and create OS operator
	core::stringc winversion;
	getWindowsVersion(winversion);
	Operator = new COSOperator(winversion);
	os::Printer::log(winversion.c_str(), ELL_INFORMATION);

	// get handle to exe file
	HINSTANCE hInstance = GetModuleHandle(0);

	// create the window if we need to and we do not use the null device
	if (!CreationParams.WindowId && CreationParams.DriverType != video::EDT_NULL)
	{
		const wchar_t* ClassName = L"CIrrDeviceWin32";

		// Register Class
		WNDCLASSEXW wcex;
		wcex.cbSize			= sizeof(WNDCLASSEXW);
		wcex.style			= CS_HREDRAW | CS_VREDRAW;
		wcex.lpfnWndProc	= WndProc;
		wcex.cbClsExtra		= 0;
		wcex.cbWndExtra		= 0;
		wcex.hInstance		= hInstance;
		wcex.hIcon			= NULL;
		wcex.hCursor		= 0; // LoadCursor(NULL, IDC_ARROW);
		wcex.hbrBackground	= (HBRUSH)(COLOR_WINDOW+1);
		wcex.lpszMenuName	= 0;
		wcex.lpszClassName	= ClassName;
		wcex.hIconSm		= 0;

		RegisterClassExW(&wcex);

		// calculate client size

		RECT clientSize;
		clientSize.top = 0;
		clientSize.left = 0;
		clientSize.right = CreationParams.WindowSize.Width;
		clientSize.bottom = CreationParams.WindowSize.Height;

		DWORD style = getWindowStyle(CreationParams.Fullscreen, CreationParams.WindowResizable > 0 ? true : false);
		AdjustWindowRect(&clientSize, style, FALSE);

		const s32 realWidth = clientSize.right - clientSize.left;
		const s32 realHeight = clientSize.bottom - clientSize.top;

		s32 windowLeft = (CreationParams.WindowPosition.X == -1 ?
		                     (GetSystemMetrics(SM_CXSCREEN) - realWidth) / 2 :
		                     CreationParams.WindowPosition.X);
		s32 windowTop = (CreationParams.WindowPosition.Y == -1 ?
		                     (GetSystemMetrics(SM_CYSCREEN) - realHeight) / 2 :
		                     CreationParams.WindowPosition.Y);

		if ( windowLeft < 0 )
			windowLeft = 0;
		if ( windowTop < 0 )
			windowTop = 0;	// make sure window menus are in screen on creation

		if (CreationParams.Fullscreen)
		{
			windowLeft = 0;
			windowTop = 0;
		}

		// create window
		HWnd = CreateWindowW( ClassName, L"", style, windowLeft, windowTop,
					realWidth, realHeight, NULL, NULL, hInstance, NULL);
		if (!HWnd)
		{
			os::Printer::log("Window could not be created.", ELL_ERROR);
		}

		CreationParams.WindowId = HWnd;
//		CreationParams.WindowSize.Width = realWidth;
//		CreationParams.WindowSize.Height = realHeight;

		ShowWindow(HWnd, SW_SHOWNORMAL);
		UpdateWindow(HWnd);

		// fix ugly ATI driver bugs. Thanks to ariaci
		MoveWindow(HWnd, windowLeft, windowTop, realWidth, realHeight, TRUE);

		// make sure everything gets updated to the real sizes
		Resized = true;
	}
	else if (CreationParams.WindowId)
	{
		// attach external window
		HWnd = static_cast<HWND>(CreationParams.WindowId);
		RECT r;
		GetWindowRect(HWnd, &r);
		CreationParams.WindowSize.Width = r.right - r.left;
		CreationParams.WindowSize.Height = r.bottom - r.top;
		CreationParams.Fullscreen = false;
		ExternalWindow = true;
	}

	// create cursor control

	Win32CursorControl = new CCursorControl(this, CreationParams.WindowSize, HWnd, CreationParams.Fullscreen);
	CursorControl = Win32CursorControl;
	JoyControl = new SJoystickWin32Control(this);

	// initialize doubleclicks with system values
	MouseMultiClicks.DoubleClickTime = GetDoubleClickTime();

	// create driver

	createDriver();

	if (VideoDriver)
		createGUIAndScene();

	// register environment

	SEnvMapper em;
	em.irrDev = this;
	em.hWnd = HWnd;
	EnvMap.push_back(em);

	// set this as active window
	if (!ExternalWindow)
	{
		SetActiveWindow(HWnd);
		SetForegroundWindow(HWnd);
	}

	KEYBOARD_INPUT_HKL = GetKeyboardLayout(0);

	// inform driver about the window size etc.
	resizeIfNecessary();

	if (params.WindowMaximized)
		maximizeWindow();
}


//! destructor
CIrrDeviceWin32::~CIrrDeviceWin32()
{
	delete JoyControl;

	// unregister environment
	for (u32 i=0; i< EnvMap.size(); ++i)
	{
		if (EnvMap[i].hWnd == HWnd)
		{
			EnvMap.erase(i);
			break;
		}
	}
}


//! create the driver
void CIrrDeviceWin32::createDriver()
{
	switch(CreationParams.DriverType)
	{
	case video::EDT_OPENGL:
#ifdef _IRR_COMPILE_WITH_OPENGL_
		switchToFullScreen();

		ContextManager = new video::CWGLManager();
		ContextManager->initialize(CreationParams, video::SExposedVideoData(HWnd));

		VideoDriver = video::createOpenGLDriver(CreationParams, FileSystem, ContextManager);

		if (!VideoDriver)
			os::Printer::log("Could not create OpenGL driver.", ELL_ERROR);
#else
		os::Printer::log("OpenGL driver was not compiled in.", ELL_ERROR);
#endif
		break;
	case video::EDT_OGLES1:
#ifdef _IRR_COMPILE_WITH_OGLES1_
		switchToFullScreen();

		ContextManager = new video::CEGLManager();
		ContextManager->initialize(CreationParams, video::SExposedVideoData(HWnd));

		VideoDriver = video::createOGLES1Driver(CreationParams, FileSystem, ContextManager);

		if (!VideoDriver)
			os::Printer::log("Could not create OpenGL-ES1 driver.", ELL_ERROR);
#else
		os::Printer::log("OpenGL-ES1 driver was not compiled in.", ELL_ERROR);
#endif
		break;
	case video::EDT_OGLES2:
#ifdef _IRR_COMPILE_WITH_OGLES2_
		switchToFullScreen();

		ContextManager = new video::CEGLManager();
		ContextManager->initialize(CreationParams, video::SExposedVideoData(HWnd));

		VideoDriver = video::createOGLES2Driver(CreationParams, FileSystem, ContextManager);

		if (!VideoDriver)
			os::Printer::log("Could not create OpenGL-ES2 driver.", ELL_ERROR);
#else
		os::Printer::log("OpenGL-ES2 driver was not compiled in.", ELL_ERROR);
#endif
		break;
	case video::EDT_WEBGL1:
		os::Printer::log("WebGL1 driver not supported on Win32 device.", ELL_ERROR);
		break;
	case video::EDT_NULL:
		VideoDriver = video::createNullDriver(FileSystem, CreationParams.WindowSize);
		break;
	default:
		os::Printer::log("Unable to create video driver of unknown type.", ELL_ERROR);
		break;
	}
}


//! runs the device. Returns false if device wants to be deleted
bool CIrrDeviceWin32::run()
{
	os::Timer::tick();

	static_cast<CCursorControl*>(CursorControl)->update();

	handleSystemMessages();

	if (!Close)
		resizeIfNecessary();

	if(!Close && JoyControl)
		JoyControl->pollJoysticks();

	return !Close;
}


//! Pause the current process for the minimum time allowed only to allow other processes to execute
void CIrrDeviceWin32::yield()
{
	Sleep(0);
}

//! Pause execution and let other processes to run for a specified amount of time.
void CIrrDeviceWin32::sleep(u32 timeMs, bool pauseTimer)
{
	const bool wasStopped = Timer ? Timer->isStopped() : true;
	if (pauseTimer && !wasStopped)
		Timer->stop();

	Sleep(timeMs);

	if (pauseTimer && !wasStopped)
		Timer->start();
}


void CIrrDeviceWin32::resizeIfNecessary()
{
	if (!Resized || !getVideoDriver())
		return;

	RECT r;
	GetClientRect(HWnd, &r);

	char tmp[255];

	if (r.right < 2 || r.bottom < 2)
	{
		snprintf_irr(tmp, sizeof(tmp), "Ignoring resize operation to (%ld %ld)", r.right, r.bottom);
		os::Printer::log(tmp);
	}
	else
	{
		snprintf_irr(tmp, sizeof(tmp), "Resizing window (%ld %ld)", r.right, r.bottom);
		os::Printer::log(tmp);

		getVideoDriver()->OnResize(irr::core::dimension2du((u32)r.right, (u32)r.bottom));
		getWin32CursorControl()->OnResize(getVideoDriver()->getScreenSize());
	}

	Resized = false;
}


DWORD CIrrDeviceWin32::getWindowStyle(bool fullscreen, bool resizable) const
{
	if ( fullscreen )
		return WS_POPUP;

	if ( resizable )
		return WS_THICKFRAME | WS_SYSMENU | WS_CAPTION | WS_CLIPCHILDREN | WS_CLIPSIBLINGS | WS_MINIMIZEBOX | WS_MAXIMIZEBOX;

	return WS_BORDER | WS_SYSMENU | WS_CAPTION | WS_CLIPCHILDREN | WS_CLIPSIBLINGS;
}

//! sets the caption of the window
void CIrrDeviceWin32::setWindowCaption(const wchar_t* text)
{
	// We use SendMessage instead of SetText to ensure proper
	// function even in cases where the HWND was created in a different thread
	DWORD_PTR dwResult;
	SendMessageTimeoutW(HWnd, WM_SETTEXT, 0,
			reinterpret_cast<LPARAM>(text),
			SMTO_ABORTIFHUNG, 2000, &dwResult);
}


//! Sets the window icon.
bool CIrrDeviceWin32::setWindowIcon(const video::IImage *img)
{
	// Ignore the img, instead load the ICON from resource file
	// (This is minetest-specific!)
	const HICON hicon = LoadIcon(GetModuleHandle(NULL),
			MAKEINTRESOURCE(130) // The ID of the ICON defined in
					     // winresource.rc
	);

	if (hicon) {
		SendMessage(HWnd, WM_SETICON, ICON_BIG, reinterpret_cast<LPARAM>(hicon));
		SendMessage(HWnd, WM_SETICON, ICON_SMALL,
				reinterpret_cast<LPARAM>(hicon));
		return true;
	}
	return false;
}


//! notifies the device that it should close itself
void CIrrDeviceWin32::closeDevice()
{
	if (!ExternalWindow)
	{
		MSG msg;
		PeekMessage(&msg, NULL, WM_QUIT, WM_QUIT, PM_REMOVE);
		PostQuitMessage(0);
		PeekMessage(&msg, NULL, WM_QUIT, WM_QUIT, PM_REMOVE);
		DestroyWindow(HWnd);
		const wchar_t* ClassName = L"CIrrDeviceWin32";
		HINSTANCE hInstance = GetModuleHandle(0);
		UnregisterClassW(ClassName, hInstance);
	}
	Close=true;
}


//! returns if window is active. if not, nothing needs to be drawn
bool CIrrDeviceWin32::isWindowActive() const
{
	return (GetActiveWindow() == HWnd);
}


//! returns if window has focus
bool CIrrDeviceWin32::isWindowFocused() const
{
	bool ret = (GetFocus() == HWnd);
	return ret;
}


//! returns if window is minimized
bool CIrrDeviceWin32::isWindowMinimized() const
{
	WINDOWPLACEMENT plc;
	plc.length=sizeof(WINDOWPLACEMENT);
	bool ret=false;
	if (GetWindowPlacement(HWnd,&plc))
		ret = plc.showCmd == SW_SHOWMINIMIZED;
	return ret;
}


//! returns last state from maximizeWindow() and restoreWindow()
bool CIrrDeviceWin32::isWindowMaximized() const
{
	return WindowMaximized;
}


//! switches to fullscreen
bool CIrrDeviceWin32::switchToFullScreen()
{
	if (!CreationParams.Fullscreen)
		return true;

	// No border, title bar, etc. is already set up through getWindowStyle()
	// We only set the window size to match the monitor.

	MONITORINFO mi;
	mi.cbSize = sizeof(mi);
	if (GetMonitorInfo(MonitorFromWindow(HWnd,MONITOR_DEFAULTTOPRIMARY),&mi))
	{
		UINT flags = SWP_NOCOPYBITS|SWP_NOOWNERZORDER|SWP_FRAMECHANGED;
		SetWindowPos(HWnd, HWND_TOP, mi.rcMonitor.left, mi.rcMonitor.top,
			mi.rcMonitor.right - mi.rcMonitor.left,
			mi.rcMonitor.bottom - mi.rcMonitor.top, flags);
	}
	else
	{
		CreationParams.Fullscreen = false;
	}

	return CreationParams.Fullscreen;
}


//! returns the win32 cursor control
CIrrDeviceWin32::CCursorControl* CIrrDeviceWin32::getWin32CursorControl()
{
	return Win32CursorControl;
}

void CIrrDeviceWin32::getWindowsVersion(core::stringc& out)
{
	OSVERSIONINFO osvi;

	ZeroMemory(&osvi, sizeof(OSVERSIONINFO));
	osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
	GetVersionEx(&osvi);

	char tmp[255];
	snprintf(tmp, sizeof(tmp), "Microsoft Windows %lu.%lu %s", osvi.dwMajorVersion, osvi.dwMinorVersion, osvi.szCSDVersion);
	out.append(tmp);
}

//! Notifies the device, that it has been resized
void CIrrDeviceWin32::OnResized()
{
	Resized = true;
}

//! Resize the render window.
void CIrrDeviceWin32::setWindowSize(const irr::core::dimension2d<u32>& size)
{
	if (ExternalWindow || !getVideoDriver() || CreationParams.Fullscreen)
		return;

	// get size of the window for the give size of the client area
	DWORD style = static_cast<DWORD>(GetWindowLongPtr(HWnd, GWL_STYLE));
	DWORD exStyle = static_cast<DWORD>(GetWindowLongPtr(HWnd, GWL_EXSTYLE));
	RECT clientSize;
	clientSize.top = 0;
	clientSize.left = 0;
	clientSize.right = size.Width;
	clientSize.bottom = size.Height;
	AdjustWindowRectEx(&clientSize, style, false, exStyle);
	const s32 realWidth = clientSize.right - clientSize.left;
	const s32 realHeight = clientSize.bottom - clientSize.top;

	UINT flags = SWP_NOACTIVATE|SWP_NOCOPYBITS|SWP_NOMOVE|SWP_NOOWNERZORDER|SWP_NOZORDER;
	SetWindowPos(HWnd, HWND_TOP, 0, 0, realWidth, realHeight, flags);
}

//! Sets if the window should be resizable in windowed mode.
void CIrrDeviceWin32::setResizable(bool resize)
{
	if (ExternalWindow || !getVideoDriver() || CreationParams.Fullscreen)
		return;

	LONG_PTR style = (LONG_PTR)getWindowStyle(false, resize);
	if (!SetWindowLongPtr(HWnd, GWL_STYLE, style))
		os::Printer::log("Could not change window style.");

	RECT clientSize;
	clientSize.top = 0;
	clientSize.left = 0;
	clientSize.right = getVideoDriver()->getScreenSize().Width;
	clientSize.bottom = getVideoDriver()->getScreenSize().Height;

	AdjustWindowRect(&clientSize, static_cast<DWORD>(style), FALSE);

	const s32 realWidth = clientSize.right - clientSize.left;
	const s32 realHeight = clientSize.bottom - clientSize.top;

	const s32 windowLeft = (GetSystemMetrics(SM_CXSCREEN) - realWidth) / 2;
	const s32 windowTop = (GetSystemMetrics(SM_CYSCREEN) - realHeight) / 2;

	SetWindowPos(HWnd, HWND_TOP, windowLeft, windowTop, realWidth, realHeight,
		SWP_FRAMECHANGED | SWP_NOMOVE | SWP_SHOWWINDOW);

	static_cast<CCursorControl*>(CursorControl)->updateBorderSize(CreationParams.Fullscreen, resize);
}


//! Minimizes the window.
void CIrrDeviceWin32::minimizeWindow()
{
	WINDOWPLACEMENT wndpl;
	wndpl.length = sizeof(WINDOWPLACEMENT);
	GetWindowPlacement(HWnd, &wndpl);
	wndpl.showCmd = SW_SHOWMINNOACTIVE;
	SetWindowPlacement(HWnd, &wndpl);
}


//! Maximizes the window.
void CIrrDeviceWin32::maximizeWindow()
{
	WINDOWPLACEMENT wndpl;
	wndpl.length = sizeof(WINDOWPLACEMENT);
	GetWindowPlacement(HWnd, &wndpl);
	wndpl.showCmd = SW_SHOWMAXIMIZED;
	SetWindowPlacement(HWnd, &wndpl);

	WindowMaximized = true;
}


//! Restores the window to its original size.
void CIrrDeviceWin32::restoreWindow()
{
	WINDOWPLACEMENT wndpl;
	wndpl.length = sizeof(WINDOWPLACEMENT);
	GetWindowPlacement(HWnd, &wndpl);
	wndpl.showCmd = SW_SHOWNORMAL;
	SetWindowPlacement(HWnd, &wndpl);

	WindowMaximized = false;
}

core::position2di CIrrDeviceWin32::getWindowPosition()
{
	WINDOWPLACEMENT wndpl;
	wndpl.length = sizeof(WINDOWPLACEMENT);
	if (GetWindowPlacement(HWnd, &wndpl))
	{
		return core::position2di((int)wndpl.rcNormalPosition.left,
		                         (int)wndpl.rcNormalPosition.top);
	}
	else
	{
		// No reason for this to happen
		os::Printer::log("Failed to retrieve window location", ELL_ERROR);
		return core::position2di(-1, -1);
	}
}

bool CIrrDeviceWin32::activateJoysticks(core::array<SJoystickInfo> & joystickInfo)
{
	if (JoyControl)
		return JoyControl->activateJoysticks(joystickInfo);
	else
		return false;
}


//! Process system events
void CIrrDeviceWin32::handleSystemMessages()
{
	MSG msg;

	while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
	{
		if (ExternalWindow && msg.hwnd == HWnd)
		{
			if (msg.hwnd == HWnd)
            {
				WndProc(HWnd, msg.message, msg.wParam, msg.lParam);
            }
            else
            {
                TranslateMessage(&msg);
                DispatchMessage(&msg);
            }
		}
		else
		{
			// No message translation because we don't use WM_CHAR and it would conflict with our
			// deadkey handling.
			DispatchMessage(&msg);
		}

		if (msg.message == WM_QUIT)
			Close = true;
	}
}


//! Remove all messages pending in the system message loop
void CIrrDeviceWin32::clearSystemMessages()
{
	MSG msg;
	while (PeekMessage(&msg, NULL, WM_KEYFIRST, WM_KEYLAST, PM_REMOVE))
	{}
	while (PeekMessage(&msg, NULL, WM_MOUSEFIRST, WM_MOUSELAST, PM_REMOVE))
	{}
}


//! Get the display density in dots per inch.
float CIrrDeviceWin32::getDisplayDensity() const
{
	HDC hdc = GetDC(HWnd);
	float dpi = GetDeviceCaps(hdc, LOGPIXELSX);
	ReleaseDC(HWnd, hdc);
	return dpi;
}


// Convert an Irrlicht texture to a Windows cursor
// Based on http://www.codeguru.com/cpp/w-p/win32/cursors/article.php/c4529/
HCURSOR CIrrDeviceWin32::TextureToCursor(HWND hwnd, irr::video::ITexture * tex, const core::rect<s32>& sourceRect, const core::position2d<s32> &hotspot)
{
	//
	// create the bitmaps needed for cursors from the texture

	HDC dc = GetDC(hwnd);
	HDC andDc = CreateCompatibleDC(dc);
	HDC xorDc = CreateCompatibleDC(dc);
	HBITMAP andBitmap = CreateCompatibleBitmap(dc, sourceRect.getWidth(), sourceRect.getHeight());
	HBITMAP xorBitmap = CreateCompatibleBitmap(dc, sourceRect.getWidth(), sourceRect.getHeight());

	HBITMAP oldAndBitmap = (HBITMAP)SelectObject(andDc, andBitmap);
	HBITMAP oldXorBitmap = (HBITMAP)SelectObject(xorDc, xorBitmap);


	video::ECOLOR_FORMAT format = tex->getColorFormat();
	u32 bytesPerPixel = video::IImage::getBitsPerPixelFromFormat(format) / 8;
	u32 bytesLeftGap = sourceRect.UpperLeftCorner.X * bytesPerPixel;
	u32 bytesRightGap = tex->getPitch() - sourceRect.LowerRightCorner.X * bytesPerPixel;
	const u8* data = (const u8*)tex->lock(video::ETLM_READ_ONLY, 0);
	data += sourceRect.UpperLeftCorner.Y*tex->getPitch();
	for ( s32 y = 0; y < sourceRect.getHeight(); ++y )
	{
		data += bytesLeftGap;
		for ( s32 x = 0; x < sourceRect.getWidth(); ++x )
		{
			video::SColor pixelCol;
			pixelCol.setData((const void*)data, format);
			data += bytesPerPixel;

			if ( pixelCol.getAlpha() == 0 )	// transparent
			{
				SetPixel(andDc, x, y, RGB(255,255,255));
				SetPixel(xorDc, x, y, RGB(0,0,0));
			}
			else	// color
			{
				SetPixel(andDc, x, y, RGB(0,0,0));
				SetPixel(xorDc, x, y, RGB(pixelCol.getRed(), pixelCol.getGreen(), pixelCol.getBlue()));
			}
		}
		data += bytesRightGap;
	}
	tex->unlock();

	SelectObject(andDc, oldAndBitmap);
	SelectObject(xorDc, oldXorBitmap);

	DeleteDC(xorDc);
	DeleteDC(andDc);

	ReleaseDC(hwnd, dc);

	// create the cursor

	ICONINFO iconinfo;
	iconinfo.fIcon = false;	// type is cursor not icon
	iconinfo.xHotspot = hotspot.X;
	iconinfo.yHotspot = hotspot.Y;
	iconinfo.hbmMask = andBitmap;
	iconinfo.hbmColor = xorBitmap;

	HCURSOR cursor = CreateIconIndirect(&iconinfo);

	DeleteObject(andBitmap);
	DeleteObject(xorBitmap);

	return cursor;
}


CIrrDeviceWin32::CCursorControl::CCursorControl(CIrrDeviceWin32* device, const core::dimension2d<u32>& wsize, HWND hwnd, bool fullscreen)
	: Device(device), WindowSize(wsize), InvWindowSize(0.0f, 0.0f),
		HWnd(hwnd), BorderX(0), BorderY(0),
		UseReferenceRect(false), IsVisible(true)
		, ActiveIcon(gui::ECI_NORMAL), ActiveIconStartTime(0)
{
	if (WindowSize.Width!=0)
		InvWindowSize.Width = 1.0f / WindowSize.Width;

	if (WindowSize.Height!=0)
		InvWindowSize.Height = 1.0f / WindowSize.Height;

	updateBorderSize(fullscreen, false);
	initCursors();
}

CIrrDeviceWin32::CCursorControl::~CCursorControl()
{
	for ( u32 i=0; i < Cursors.size(); ++i )
	{
		for ( u32 f=0; f < Cursors[i].Frames.size(); ++f )
		{
			DestroyCursor(Cursors[i].Frames[f].IconHW);
		}
	}
}


void CIrrDeviceWin32::CCursorControl::initCursors()
{
	Cursors.push_back( CursorW32(LoadCursor(NULL, IDC_ARROW)) );
	Cursors.push_back( CursorW32(LoadCursor(NULL, IDC_CROSS)) );
	Cursors.push_back( CursorW32(LoadCursor(NULL, IDC_HAND)) );
	Cursors.push_back( CursorW32(LoadCursor(NULL, IDC_HELP)) );
	Cursors.push_back( CursorW32(LoadCursor(NULL, IDC_IBEAM)) );
	Cursors.push_back( CursorW32(LoadCursor(NULL, IDC_NO)) );
	Cursors.push_back( CursorW32(LoadCursor(NULL, IDC_WAIT)) );
	Cursors.push_back( CursorW32(LoadCursor(NULL, IDC_SIZEALL)) );
	Cursors.push_back( CursorW32(LoadCursor(NULL, IDC_SIZENESW)) );
	Cursors.push_back( CursorW32(LoadCursor(NULL, IDC_SIZENWSE)) );
	Cursors.push_back( CursorW32(LoadCursor(NULL, IDC_SIZENS)) );
	Cursors.push_back( CursorW32(LoadCursor(NULL, IDC_SIZEWE)) );
	Cursors.push_back( CursorW32(LoadCursor(NULL, IDC_UPARROW)) );
}


void CIrrDeviceWin32::CCursorControl::update()
{
	if ( !Cursors[ActiveIcon].Frames.empty() && Cursors[ActiveIcon].FrameTime )
	{
		// update animated cursors. This could also be done by X11 in case someone wants to figure that out (this way was just easier to implement)
		u32 now = Device->getTimer()->getRealTime();
		u32 frame = ((now - ActiveIconStartTime) / Cursors[ActiveIcon].FrameTime) % Cursors[ActiveIcon].Frames.size();
		SetCursor( Cursors[ActiveIcon].Frames[frame].IconHW );
	}
}

//! Sets the active cursor icon
void CIrrDeviceWin32::CCursorControl::setActiveIcon(gui::ECURSOR_ICON iconId)
{
	if ( iconId >= (s32)Cursors.size() )
		return;

	ActiveIcon = iconId;
	ActiveIconStartTime = Device->getTimer()->getRealTime();
	if ( Cursors[ActiveIcon].Frames.size() )
		SetCursor( Cursors[ActiveIcon].Frames[0].IconHW );
}


//! Add a custom sprite as cursor icon.
gui::ECURSOR_ICON CIrrDeviceWin32::CCursorControl::addIcon(const gui::SCursorSprite& icon)
{
	if ( icon.SpriteId >= 0 )
	{
		CursorW32 cW32;
		cW32.FrameTime = icon.SpriteBank->getSprites()[icon.SpriteId].frameTime;

		for ( u32 i=0; i < icon.SpriteBank->getSprites()[icon.SpriteId].Frames.size(); ++i )
		{
			irr::u32 texId = icon.SpriteBank->getSprites()[icon.SpriteId].Frames[i].textureNumber;
			irr::u32 rectId = icon.SpriteBank->getSprites()[icon.SpriteId].Frames[i].rectNumber;
			irr::core::rect<s32> rectIcon = icon.SpriteBank->getPositions()[rectId];

			HCURSOR hc = Device->TextureToCursor(HWnd, icon.SpriteBank->getTexture(texId), rectIcon, icon.HotSpot);
			cW32.Frames.push_back( CursorFrameW32(hc) );
		}

		Cursors.push_back( cW32 );
		return (gui::ECURSOR_ICON)(Cursors.size() - 1);
	}
	return gui::ECI_NORMAL;
}


//! replace the given cursor icon.
void CIrrDeviceWin32::CCursorControl::changeIcon(gui::ECURSOR_ICON iconId, const gui::SCursorSprite& icon)
{
	if ( iconId >= (s32)Cursors.size() )
		return;

	for ( u32 i=0; i < Cursors[iconId].Frames.size(); ++i )
		DestroyCursor(Cursors[iconId].Frames[i].IconHW);

	if ( icon.SpriteId >= 0 )
	{
		CursorW32 cW32;
		cW32.FrameTime = icon.SpriteBank->getSprites()[icon.SpriteId].frameTime;
		for ( u32 i=0; i < icon.SpriteBank->getSprites()[icon.SpriteId].Frames.size(); ++i )
		{
			irr::u32 texId = icon.SpriteBank->getSprites()[icon.SpriteId].Frames[i].textureNumber;
			irr::u32 rectId = icon.SpriteBank->getSprites()[icon.SpriteId].Frames[i].rectNumber;
			irr::core::rect<s32> rectIcon = icon.SpriteBank->getPositions()[rectId];

			HCURSOR hc = Device->TextureToCursor(HWnd, icon.SpriteBank->getTexture(texId), rectIcon, icon.HotSpot);
			cW32.Frames.push_back( CursorFrameW32(hc) );
		}

		Cursors[iconId] = cW32;
	}
}


//! Return a system-specific size which is supported for cursors. Larger icons will fail, smaller icons might work.
core::dimension2di CIrrDeviceWin32::CCursorControl::getSupportedIconSize() const
{
	core::dimension2di result;

	result.Width = GetSystemMetrics(SM_CXCURSOR);
	result.Height = GetSystemMetrics(SM_CYCURSOR);

	return result;
}



} // end namespace

#endif // _IRR_COMPILE_WITH_WINDOWS_DEVICE_