// 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

#include "IrrCompileConfig.h"

#ifdef _IRR_COMPILE_WITH_WINDOWS_DEVICE_

#include "CIrrDeviceWin32.h"
#include "IGUIEditBox.h"
#include "IGUIEnvironment.h"
#include "IGUIFont.h"
#include "IGUISkin.h"
#include "IEventReceiver.h"
#include "irrList.h"
#include "os.h"

#include "utils/string_utils.hpp"
#include "utils/utf8.h"

#include "CTimer.h"
#include "irrString.h"
#include "COSOperator.h"
#include "dimension2d.h"
#include "IGUISpriteBank.h"
#include <mmsystem.h>
#include <regstr.h>
#include <winuser.h>
#if defined(_IRR_COMPILE_WITH_JOYSTICK_EVENTS_)
// This define will switch to use XInput 9.1, which does not
// require an installer and works on most windows platforms.
// See https://blogs.msdn.microsoft.com/chuckw/2012/04/25/xinput-and-windows-8/
#undef _WIN32_WINNT
#define _WIN32_WINNT 0x0601
// Don't change the case of xinput.h, otherwise cygwin compilation fails.
#include <xinput.h>
#ifdef _IRR_COMPILE_WITH_DIRECTINPUT_JOYSTICK_
#define DIRECTINPUT_VERSION 0x0800
#include <dinput.h>
#include <wbemidl.h>
#include <oleauto.h>

#ifdef _MSC_VER
#pragma comment(lib, "dinput8.lib")
#pragma comment(lib, "dxguid.lib")
#endif
#else
#ifdef _MSC_VER
#pragma comment(lib, "winmm.lib")
#endif
#endif
#endif

#include <imm.h>
#pragma comment(lib, "imm32.lib")

namespace irr
{
    namespace video
    {
        #ifdef _IRR_COMPILE_WITH_DIRECT3D_8_
        IVideoDriver* createDirectX8Driver(const irr::SIrrlichtCreationParameters& params,
            io::IFileSystem* io, HWND window);
        #endif

        #ifdef _IRR_COMPILE_WITH_DIRECT3D_9_
        IVideoDriver* createDirectX9Driver(const irr::SIrrlichtCreationParameters& params,
            io::IFileSystem* io, HWND window);
        #endif

        #ifdef _IRR_COMPILE_WITH_OPENGL_
        IVideoDriver* createOpenGLDriver(const irr::SIrrlichtCreationParameters& params,
            io::IFileSystem* io, CIrrDeviceWin32* device);
        #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
        /** For xbox-360 controller. */
        bool m_use_xinput;
    };
    core::array<JoystickInfo> ActiveJoysticks;
#endif

    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()
    {
#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_)
    static BOOL CALLBACK EnumJoysticks(LPCDIDEVICEINSTANCE lpddi, LPVOID cp)
    {
        SJoystickWin32Control* p=(SJoystickWin32Control*)cp;
        p->directInputAddJoystick(lpddi);
        return DIENUM_CONTINUE;
    }
    bool IsXInputDevice(const GUID* pGuidProductFromDirectInput);

    void directInputAddJoystick(LPCDIDEVICEINSTANCE lpddi)
    {
        //Get the GUID of the joystuck
        const GUID guid = lpddi->guidInstance;
        JoystickInfo activeJoystick;
        activeJoystick.m_use_xinput = isXInputDevice(&lpddi->guidProduct);
    
        activeJoystick.Index=ActiveJoysticks.size();
        activeJoystick.guid=guid;
        activeJoystick.Name=StringUtils::wideToUtf8(lpddi->tszProductName).c_str();
        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;
        }

        // On Xbox 360 devices left and right trigger are reported as two
        // different axes (instead of 1 with DirectInput), so we need one
        // additional axis:
        if (activeJoystick.m_use_xinput && activeJoystick.devcaps.dwAxes == 5)
            activeJoystick.devcaps.dwAxes = 6;
        ActiveJoysticks.push_back(activeJoystick);
    }

    //-----------------------------------------------------------------------------
    /** Taken from:
     *  https://msdn.microsoft.com/en-us/library/windows/desktop/dd940435(v=vs.85).aspx
     */
    template <class T> void SAFE_RELEASE(T **ppT)
    {
        if (*ppT)
        {
            (*ppT)->Release();
            *ppT = NULL;
        }
    }

    /** Taken from:
     *  https://msdn.microsoft.com/en-us/library/windows/desktop/ee417014(v=vs.85).aspx
     */
    //-----------------------------------------------------------------------------
    // Enum each PNP device using WMI and check each device ID to see if it contains 
    // "IG_" (ex. "VID_045E&PID_028E&IG_00").  If it does, then it's an XInput device
    // Unfortunately this information can not be found by just using DirectInput 
    //-----------------------------------------------------------------------------
    bool isXInputDevice(const GUID* pGuidProductFromDirectInput)
    {
        IWbemLocator*           pIWbemLocator = NULL;
        IEnumWbemClassObject*   pEnumDevices = NULL;
        IWbemClassObject*       pDevices[20] = { 0 };
        IWbemServices*          pIWbemServices = NULL;
        BSTR                    bstrNamespace = NULL;
        BSTR                    bstrDeviceID = NULL;
        BSTR                    bstrClassName = NULL;
        DWORD                   uReturned = 0;
        bool                    bIsXinputDevice = false;
        UINT                    iDevice = 0;
        VARIANT                 var;
        HRESULT                 hr;

        // CoInit if needed
        hr = CoInitialize(NULL);
        bool bCleanupCOM = SUCCEEDED(hr);

        // Create WMI
        hr = CoCreateInstance(__uuidof(WbemLocator),
            NULL,
            CLSCTX_INPROC_SERVER,
            __uuidof(IWbemLocator),
            (LPVOID*)&pIWbemLocator);
        if (FAILED(hr) || pIWbemLocator == NULL)
            goto LCleanup;

        bstrNamespace = SysAllocString(L"\\\\.\\root\\cimv2"); if (bstrNamespace == NULL) goto LCleanup;
        bstrClassName = SysAllocString(L"Win32_PNPEntity");   if (bstrClassName == NULL) goto LCleanup;
        bstrDeviceID = SysAllocString(L"DeviceID");          if (bstrDeviceID == NULL)  goto LCleanup;

        // Connect to WMI 
        hr = pIWbemLocator->ConnectServer(bstrNamespace, NULL, NULL, 0L,
            0L, NULL, NULL, &pIWbemServices);
        if (FAILED(hr) || pIWbemServices == NULL)
            goto LCleanup;

        // Switch security level to IMPERSONATE. 
        CoSetProxyBlanket(pIWbemServices, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, NULL,
            RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE);

        hr = pIWbemServices->CreateInstanceEnum(bstrClassName, 0, NULL, &pEnumDevices);
        if (FAILED(hr) || pEnumDevices == NULL)
            goto LCleanup;

        // Loop over all devices
        for (;; )
        {
            // Get 20 at a time
            hr = pEnumDevices->Next(10000, 20, pDevices, &uReturned);
            if (FAILED(hr))
                goto LCleanup;
            if (uReturned == 0)
                break;

            for (iDevice = 0; iDevice<uReturned; iDevice++)
            {
                // For each device, get its device ID
                hr = pDevices[iDevice]->Get(bstrDeviceID, 0L, &var, NULL, NULL);
                if (SUCCEEDED(hr) && var.vt == VT_BSTR && var.bstrVal != NULL)
                {
                    // Check if the device ID contains "IG_".  If it does, then it's an XInput device
                    // This information can not be found from DirectInput 
                    if (wcsstr(var.bstrVal, L"IG_"))
                    {
                        // If it does, then get the VID/PID from var.bstrVal
                        DWORD dwPid = 0, dwVid = 0;
                        WCHAR* strVid = wcsstr(var.bstrVal, L"VID_");
                        if (strVid && swscanf(strVid, L"VID_%4X", &dwVid) != 1)
                            dwVid = 0;
                        WCHAR* strPid = wcsstr(var.bstrVal, L"PID_");
                        if (strPid && swscanf(strPid, L"PID_%4X", &dwPid) != 1)
                            dwPid = 0;

                        // Compare the VID/PID to the DInput device
                        DWORD dwVidPid = MAKELONG(dwVid, dwPid);
                        if (dwVidPid == pGuidProductFromDirectInput->Data1)
                        {
                            bIsXinputDevice = true;
                            goto LCleanup;
                        }
                    }
                }
                SAFE_RELEASE(&pDevices[iDevice]);
            }
        }

    LCleanup:
        if (bstrNamespace)
            SysFreeString(bstrNamespace);
        if (bstrDeviceID)
            SysFreeString(bstrDeviceID);
        if (bstrClassName)
            SysFreeString(bstrClassName);
        for (iDevice = 0; iDevice<20; iDevice++)
            SAFE_RELEASE(&pDevices[iDevice]);
        SAFE_RELEASE(&pEnumDevices);
        SAFE_RELEASE(&pIWbemLocator);
        SAFE_RELEASE(&pIWbemServices);

        if (bCleanupCOM)
            CoUninitialize();

        return bIsXinputDevice;
    }

    // ------------------------------------------------------------------------

#endif

void 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)
    {
        SEvent event;

        event.EventType = irr::EET_JOYSTICK_INPUT_EVENT;
        event.JoystickEvent.Joystick = (u8)joystick;

        if (ActiveJoysticks[joystick].m_use_xinput)
        {
            XINPUT_STATE state;
            memset(&state, 0, sizeof(state));
            DWORD result = XInputGetState(ActiveJoysticks[joystick].Index, &state);
            // XInput reports the buttons in a different order. So to keep
            // old configs to work as expected, remap the buttons.
            // Map to 0x001 - 0x008
            int abxy     = (state.Gamepad.wButtons & (XINPUT_GAMEPAD_A | 
                                                      XINPUT_GAMEPAD_B |
                                                      XINPUT_GAMEPAD_X |
                                                      XINPUT_GAMEPAD_Y   )
                           ) >> 12;
            // Map to 0x010 - 0x020
            int shoulder = (state.Gamepad.wButtons & (XINPUT_GAMEPAD_LEFT_SHOULDER|
                                                      XINPUT_GAMEPAD_RIGHT_SHOULDER)
                           ) >> 4;
            // Map to 0x040
            int start    = (state.Gamepad.wButtons & XINPUT_GAMEPAD_START) << 3;
            // Map to 0x080
            int back     = (state.Gamepad.wButtons & XINPUT_GAMEPAD_BACK ) << 1;
            // Map to 0x100 to 0x200
            int stick_buttons = (state.Gamepad.wButtons & (XINPUT_GAMEPAD_LEFT_THUMB |
                                                           XINPUT_GAMEPAD_RIGHT_THUMB)
                                ) << 2;

            event.JoystickEvent.ButtonStates = abxy | shoulder | start | back | stick_buttons;
            int angle = 65535;
            if (state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_DOWN)
            {
                if (state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_LEFT) angle = 22500;
                else if (state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_RIGHT) angle = 13500;
                else angle = 18000;
            }
            else if (state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_UP)
            {
                if (state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_LEFT) angle = 31500;
                else if (state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_RIGHT) angle = 4500;
                else angle = 0;
            }
            else if (state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_RIGHT) angle = 9000;
            else if (state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_LEFT)  angle = 27000;
            event.JoystickEvent.POV = angle;
                // Map the axis as they were previously, so existing configs
            // still work as expected. The Y axis needs to be reversed: 
            // -32768 --> 32767, ..., 32767 --> -32768
            // Inverting the bits with ~ does that (-sThumbLY would map -32768 to -32768!!)
            event.JoystickEvent.Axis[SEvent::SJoystickEvent::AXIS_X] =  state.Gamepad.sThumbLX;
            event.JoystickEvent.Axis[SEvent::SJoystickEvent::AXIS_Y] = ~state.Gamepad.sThumbLY;
            event.JoystickEvent.Axis[SEvent::SJoystickEvent::AXIS_Z] =  state.Gamepad.bLeftTrigger * 128;
            event.JoystickEvent.Axis[SEvent::SJoystickEvent::AXIS_R] = ~state.Gamepad.sThumbRY;
            event.JoystickEvent.Axis[SEvent::SJoystickEvent::AXIS_U] =  state.Gamepad.sThumbRX;
            event.JoystickEvent.Axis[SEvent::SJoystickEvent::AXIS_V] =  state.Gamepad.bRightTrigger * 128;
            (void)Device->postEventFromUser(event);

            continue;
        }
        // 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)))
        {
            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
    u32 joystick;

    for(joystick = 0; joystick < ActiveJoysticks.size(); ++joystick)
    {
        SEvent event;

        event.EventType = irr::EET_JOYSTICK_INPUT_EVENT;
        event.JoystickEvent.Joystick = (u8)joystick;
        for (int axis = 0; axis < SEvent::SJoystickEvent::NUMBER_OF_AXES; ++axis)
            event.JoystickEvent.Axis[axis] = 0;

        if (ActiveJoysticks[joystick].m_use_xinput)
        {
            XINPUT_STATE state;
            memset(&state, 0, sizeof(state));
            DWORD result = XInputGetState(ActiveJoysticks[joystick].Index, &state);
            event.JoystickEvent.ButtonStates = state.Gamepad.wButtons;
            // Thumb values are in [-32768, 32767]
            event.JoystickEvent.Axis[SEvent::SJoystickEvent::AXIS_V] = state.Gamepad.sThumbRX;
            event.JoystickEvent.Axis[SEvent::SJoystickEvent::AXIS_X] = state.Gamepad.sThumbRY;
            event.JoystickEvent.Axis[SEvent::SJoystickEvent::AXIS_Y] = state.Gamepad.sThumbLX;
            event.JoystickEvent.Axis[SEvent::SJoystickEvent::AXIS_Z] = state.Gamepad.sThumbLY;
            event.JoystickEvent.Axis[SEvent::SJoystickEvent::AXIS_R] = state.Gamepad.bLeftTrigger*128;
            event.JoystickEvent.Axis[SEvent::SJoystickEvent::AXIS_U] = state.Gamepad.bRightTrigger*128;
        }
        else   // old API
        { 
            JOYINFOEX info;
            // 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 (joyGetPosEx(ActiveJoysticks[joystick].Index, &info) != JOYERR_NOERROR)
                continue;


            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;

            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);
            }   // switch
        }   // if XInput ... else
        (void)Device->postEventFromUser(event);
    }   // for all joysticks
#endif
#endif // _IRR_COMPILE_WITH_JOYSTICK_EVENTS_
}

/** This function is ported from SDL and released under zlip:
 *  Copyright (C) 1997-2014 Sam Lantinga <slouken@libsdl.org>
 */
void setJoystickName(int index, const JOYCAPS &caps, SJoystickInfo *joystick)
{
    // As a default use the name given in the joystick structure
    // - though that is always the same name, independent of joystick :(
    joystick->Name              = StringUtils::wideToUtf8(caps.szPname).c_str();
    joystick->HasGenericName = true;

    core::stringw key = core::stringw(REGSTR_PATH_JOYCONFIG)+"\\"+caps.szRegKey
                      + "\\"+REGSTR_KEY_JOYCURR;
    HKEY hTopKey = HKEY_LOCAL_MACHINE;
    HKEY hKey;
    long regresult = RegOpenKeyEx(hTopKey, key.c_str(), 0, KEY_READ, &hKey);
    if (regresult != ERROR_SUCCESS)
    {
        hTopKey = HKEY_CURRENT_USER;
        regresult = RegOpenKeyEx(hTopKey, key.c_str(), 0, KEY_READ, &hKey);
    }
    if (regresult != ERROR_SUCCESS) return;

    /* find the registry key name for the joystick's properties */
    wchar_t regname[256];
    DWORD regsize = sizeof(regname);
    core::stringw regvalue = core::stringw(L"Joystick")+core::stringw(index+1)
                           + REGSTR_VAL_JOYOEMNAME;
    regresult = RegQueryValueEx(hKey, regvalue.c_str(), 0, 0,
                                 (LPBYTE)regname, &regsize);
    RegCloseKey(hKey);
    if (regresult != ERROR_SUCCESS) return;

    /* open that registry key */
    core::stringw regkey = core::stringw(REGSTR_PATH_JOYOEM)+"\\"+regname;
    regresult = RegOpenKeyEx(hTopKey, regkey.c_str(), 0, KEY_READ, &hKey);
    if (regresult != ERROR_SUCCESS) return;

    /* 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            );
            joystick->Name = name;
            joystick->HasGenericName = false;
        }   // if name
    }    // if SUCCESS
    RegCloseKey(hKey);
}


bool activateJoysticks(core::array<SJoystickInfo> & joystickInfo)
{
#if defined _IRR_COMPILE_WITH_JOYSTICK_EVENTS_
#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
    joystickInfo.clear();
    ActiveJoysticks.clear();

    // This number includes XInput and legacy devices
    const u32 numberOfJoysticks = ::joyGetNumDevs();
    JOYINFOEX info;
    info.dwSize = sizeof(info);
    info.dwFlags = JOY_RETURNALL;

    JoystickInfo activeJoystick;
    SJoystickInfo returnInfo;

    joystickInfo.reallocate(numberOfJoysticks);
    ActiveJoysticks.reallocate(numberOfJoysticks);

    // First discover all Xbox 360 controllers, which need to use
    // XInput to get the state (otherwise the two triggers can not
    // be used at the same time).
    u32 joystick = 0;
    for (int i = 0; i < XUSER_MAX_COUNT; i++)
    {
        XINPUT_STATE state;
        memset(&state, 0, sizeof(state));

        DWORD result = XInputGetState(i, &state);
        // Not connected or not an XInput device
        if (result != ERROR_SUCCESS) continue;

        activeJoystick.Index = i;
        activeJoystick.m_use_xinput = true;
        ActiveJoysticks.push_back(activeJoystick);

        // This information is returned to the calling program
        SJoystickInfo joy_info;
        // 2 sticks with 2 directions each plus two triggers
        joy_info.Axes           = 6;
        joy_info.Buttons        = 30;
        joy_info.Name           = "XBOX 360 Gamepad";
        joy_info.HasGenericName = false;
        joy_info.PovHat         = SJoystickInfo::POV_HAT_PRESENT;
        joystickInfo.push_back(joy_info);
    }   // for i < XUSER_MAX_COUNT

    for(; joystick < numberOfJoysticks; ++joystick)
    {
        if(JOYERR_NOERROR == joyGetPosEx(joystick, &info)
            &&
            JOYERR_NOERROR == joyGetDevCaps(joystick,
                                            &activeJoystick.Caps,
                                            sizeof(activeJoystick.Caps)))
        {
            setJoystickName(joystick, activeJoystick.Caps, &returnInfo);
            core::stringc low_name = returnInfo.Name.make_lower();
            // Ignore xbox controller, which are handled using XInput above
            if (low_name.find("xbox") != -1) continue;
            
            activeJoystick.Index = joystick;
            activeJoystick.m_use_xinput = false;
            ActiveJoysticks.push_back(activeJoystick);
            returnInfo.Joystick = (u8)joystick;
            returnInfo.Axes = activeJoystick.Caps.wNumAxes;
            returnInfo.Buttons = activeJoystick.Caps.wNumButtons;
            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];
        (void)sprintf(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

// Get the codepage from the locale language id
// Based on the table from http://www.science.co.il/Language/Locale-Codes.asp?s=decimal
static unsigned int LocaleIdToCodepage(unsigned int lcid)
{
    switch ( lcid )
    {
        case 1098:  // Telugu
        case 1095:  // Gujarati
        case 1094:  // Punjabi
        case 1103:  // Sanskrit
        case 1111:  // Konkani
        case 1114:  // Syriac
        case 1099:  // Kannada
        case 1102:  // Marathi
        case 1125:  // Divehi
        case 1067:  // Armenian
        case 1081:  // Hindi
        case 1079:  // Georgian
        case 1097:  // Tamil
            return 0;
        case 1054:  // Thai
            return 874;
        case 1041:  // Japanese
            return 932;
        case 2052:  // Chinese (PRC)
        case 4100:  // Chinese (Singapore)
            return 936;
        case 1042:  // Korean
            return 949;
        case 5124:  // Chinese (Macau S.A.R.)
        case 3076:  // Chinese (Hong Kong S.A.R.)
        case 1028:  // Chinese (Taiwan)
            return 950;
        case 1048:  // Romanian
        case 1060:  // Slovenian
        case 1038:  // Hungarian
        case 1051:  // Slovak
        case 1045:  // Polish
        case 1052:  // Albanian
        case 2074:  // Serbian (Latin)
        case 1050:  // Croatian
        case 1029:  // Czech
            return 1250;
        case 1104:  // Mongolian (Cyrillic)
        case 1071:  // FYRO Macedonian
        case 2115:  // Uzbek (Cyrillic)
        case 1058:  // Ukrainian
        case 2092:  // Azeri (Cyrillic)
        case 1092:  // Tatar
        case 1087:  // Kazakh
        case 1059:  // Belarusian
        case 1088:  // Kyrgyz (Cyrillic)
        case 1026:  // Bulgarian
        case 3098:  // Serbian (Cyrillic)
        case 1049:  // Russian
            return 1251;
        case 8201:  // English (Jamaica)
        case 3084:  // French (Canada)
        case 1036:  // French (France)
        case 5132:  // French (Luxembourg)
        case 5129:  // English (New Zealand)
        case 6153:  // English (Ireland)
        case 1043:  // Dutch (Netherlands)
        case 9225:  // English (Caribbean)
        case 4108:  // French (Switzerland)
        case 4105:  // English (Canada)
        case 1110:  // Galician
        case 10249:  // English (Belize)
        case 3079:  // German (Austria)
        case 6156:  // French (Monaco)
        case 12297:  // English (Zimbabwe)
        case 1069:  // Basque
        case 2067:  // Dutch (Belgium)
        case 2060:  // French (Belgium)
        case 1035:  // Finnish
        case 1080:  // Faroese
        case 1031:  // German (Germany)
        case 3081:  // English (Australia)
        case 1033:  // English (United States)
        case 2057:  // English (United Kingdom)
        case 1027:  // Catalan
        case 11273:  // English (Trinidad)
        case 7177:  // English (South Africa)
        case 1030:  // Danish
        case 13321:  // English (Philippines)
        case 15370:  // Spanish (Paraguay)
        case 9226:  // Spanish (Colombia)
        case 5130:  // Spanish (Costa Rica)
        case 7178:  // Spanish (Dominican Republic)
        case 12298:  // Spanish (Ecuador)
        case 17418:  // Spanish (El Salvador)
        case 4106:  // Spanish (Guatemala)
        case 18442:  // Spanish (Honduras)
        case 3082:  // Spanish (International Sort)
        case 13322:  // Spanish (Chile)
        case 19466:  // Spanish (Nicaragua)
        case 2058:  // Spanish (Mexico)
        case 10250:  // Spanish (Peru)
        case 20490:  // Spanish (Puerto Rico)
        case 1034:  // Spanish (Traditional Sort)
        case 14346:  // Spanish (Uruguay)
        case 8202:  // Spanish (Venezuela)
        case 1089:  // Swahili
        case 1053:  // Swedish
        case 2077:  // Swedish (Finland)
        case 5127:  // German (Liechtenstein)
        case 1078:  // Afrikaans
        case 6154:  // Spanish (Panama)
        case 4103:  // German (Luxembourg)
        case 16394:  // Spanish (Bolivia)
        case 2055:  // German (Switzerland)
        case 1039:  // Icelandic
        case 1057:  // Indonesian
        case 1040:  // Italian (Italy)
        case 2064:  // Italian (Switzerland)
        case 2068:  // Norwegian (Nynorsk)
        case 11274:  // Spanish (Argentina)
        case 1046:  // Portuguese (Brazil)
        case 1044:  // Norwegian (Bokmal)
        case 1086:  // Malay (Malaysia)
        case 2110:  // Malay (Brunei Darussalam)
        case 2070:  // Portuguese (Portugal)
            return 1252;
        case 1032:  // Greek
            return 1253;
        case 1091:  // Uzbek (Latin)
        case 1068:  // Azeri (Latin)
        case 1055:  // Turkish
            return 1254;
        case 1037:  // Hebrew
            return 1255;
        case 5121:  // Arabic (Algeria)
        case 15361:  // Arabic (Bahrain)
        case 9217:  // Arabic (Yemen)
        case 3073:  // Arabic (Egypt)
        case 2049:  // Arabic (Iraq)
        case 11265:  // Arabic (Jordan)
        case 13313:  // Arabic (Kuwait)
        case 12289:  // Arabic (Lebanon)
        case 4097:  // Arabic (Libya)
        case 6145:  // Arabic (Morocco)
        case 8193:  // Arabic (Oman)
        case 16385:  // Arabic (Qatar)
        case 1025:  // Arabic (Saudi Arabia)
        case 10241:  // Arabic (Syria)
        case 14337:  // Arabic (U.A.E.)
        case 1065:  // Farsi
        case 1056:  // Urdu
        case 7169:  // Arabic (Tunisia)
            return 1256;
        case 1061:  // Estonian
        case 1062:  // Latvian
        case 1063:  // Lithuanian
            return 1257;
        case 1066:  // Vietnamese
            return 1258;
    }
    return 65001;   // utf-8
}

namespace
{
    struct SEnvMapper
    {
        HWND hWnd;
        irr::CIrrDeviceWin32* irrDev;
    };
    irr::core::list<SEnvMapper> EnvMap;

    HKL KEYBOARD_INPUT_HKL=0;
    unsigned int KEYBOARD_INPUT_CODEPAGE = 1252;
}

SEnvMapper* getEnvMapperFromHWnd(HWND hWnd)
{
    irr::core::list<SEnvMapper>::Iterator it = EnvMap.begin();
    for (; it!= EnvMap.end(); ++it)
        if ((*it).hWnd == hWnd)
            return &(*it);

    return 0;
}


irr::CIrrDeviceWin32* getDeviceFromHWnd(HWND hWnd)
{
    irr::core::list<SEnvMapper>::Iterator it = EnvMap.begin();
    for (; it!= EnvMap.end(); ++it)
        if ((*it).hWnd == hWnd)
            return (*it).irrDev;

    return 0;
}

static void updateIMECompositonPosition(irr::core::position2di pos, HWND hwnd, HIMC himc)
{
    COMPOSITIONFORM cf;
    cf.dwStyle = CFS_POINT;
    cf.ptCurrentPos.x = pos.X;
    cf.ptCurrentPos.y = pos.Y;
    ImmSetCompositionWindow(himc, &cf);
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    #ifndef WM_MOUSEWHEEL
    #define WM_MOUSEWHEEL 0x020A
    #endif
    #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(_WIN32_WINNT >= 0x0500)
        if (wParam & MK_XBUTTON1)
            event.MouseInput.ButtonStates |= irr::EMBSM_EXTRA1;
        if (wParam & MK_XBUTTON2)
            event.MouseInput.ButtonStates |= irr::EMBSM_EXTRA2;
#endif
        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->isIMEComposingStarted())
        {
            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_SETFOCUS:
    case WM_KILLFOCUS:
        {
            dev = getDeviceFromHWnd(hWnd);
            if (dev)
                dev->setIMEComposingStarted(false);
            return 0;
        }
    case WM_IME_SETCONTEXT:
        {
            // application wants to draw composition text by itself.
            lParam &= ~(ISC_SHOWUICOMPOSITIONWINDOW);
            break;
        }
    case WM_IME_STARTCOMPOSITION:
    case WM_IME_ENDCOMPOSITION:
        {
            dev = getDeviceFromHWnd(hWnd);
            if (!dev)
                return 0;

            irr::gui::IGUIEnvironment* env = dev->getGUIEnvironment();
            if (!env)
                return 0;

            irr::gui::IGUISkin* skin = env->getSkin();
            if (!skin)
                return 0;

            irr::gui::IGUIFont* font = skin->getFont();
            if (!font)
                return 0;

            irr::gui::IGUIEditBox* box = dynamic_cast<irr::gui::IGUIEditBox*>(env->getFocus());
            if (!box)
                return 0;

            box->clearComposingText();
            dev->setIMEComposingStarted(message == WM_IME_STARTCOMPOSITION);
            HIMC imc = ImmGetContext(hWnd);
            if (!imc)
                return 0;
            if (message == WM_IME_STARTCOMPOSITION)
            {
                updateIMECompositonPosition(box->getICPos(), hWnd, imc);
                // Same height as system font so the composition window is
                // positioned correctly vertically
                LOGFONT lFont = {0};
                lFont.lfHeight = font->getHeightPerLine();
                lFont.lfCharSet = OEM_CHARSET;
                ImmSetCompositionFont(imc, &lFont);
            }
            ImmReleaseContext(hWnd, imc);
            return 0;
        }
    case WM_IME_COMPOSITION:
        {
            dev = getDeviceFromHWnd(hWnd);
            if (!dev)
                return 0;

            irr::gui::IGUIEnvironment* env = dev->getGUIEnvironment();
            if (!env)
                return 0;

            irr::gui::IGUIEditBox* box = dynamic_cast<irr::gui::IGUIEditBox*>(env->getFocus());
            if (!box)
                return 0;
            bool get_comp_str = (lParam & GCS_COMPSTR) != 0;
            bool get_result_str = (lParam & GCS_RESULTSTR) != 0;
            if (get_comp_str || get_result_str)
            {
                HIMC imc = ImmGetContext(hWnd);
                if (!imc)
                    return 0;
                LONG vec_size = ImmGetCompositionString(imc, get_comp_str ? GCS_COMPSTR : GCS_RESULTSTR, (void*)NULL, 0);
                if ((vec_size == IMM_ERROR_NODATA) ||
                    (vec_size == IMM_ERROR_GENERAL))
                {
                    ImmReleaseContext(hWnd, imc);
                    return 0;
                }
                std::vector<wchar_t> ct;
                // NULL terminator
                ct.resize(vec_size / sizeof(wchar_t) + 1, 0);
                ImmGetCompositionString(imc, get_comp_str ? GCS_COMPSTR : GCS_RESULTSTR, ct.data(), vec_size);
                std::u32string result = StringUtils::wideToUtf32(ct.data());
                if (get_comp_str)
                {
                    box->setComposingText(result);
                }
                else
                {
                    for (char32_t c : result)
                    {
                        event.EventType = irr::EET_KEY_INPUT_EVENT;
                        event.KeyInput.PressedDown = true;
                        event.KeyInput.Char = (char32_t)c;
                        event.KeyInput.Key = irr::IRR_KEY_UNKNOWN;
                        event.KeyInput.Shift = false;
                        event.KeyInput.Control = false;
                        dev->postEventFromUser(event);
                    }
                }
                if (get_result_str)
                    updateIMECompositonPosition(box->getICPos(), hWnd, imc);
                ImmReleaseContext(hWnd, imc);
            }
            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);

            const UINT MY_MAPVK_VSC_TO_VK_EX = 3; // MAPVK_VSC_TO_VK_EX should be in SDK according to MSDN, but isn't in mine.
            if ( event.KeyInput.Key == irr::IRR_KEY_SHIFT )
            {
                // this will fail on systems before windows NT/2000/XP, not sure _what_ will return there instead.
                event.KeyInput.Key = (irr::EKEY_CODE)MapVirtualKey( ((lParam>>16) & 255), MY_MAPVK_VSC_TO_VK_EX );
            }
            if ( event.KeyInput.Key == irr::IRR_KEY_CONTROL )
            {
                event.KeyInput.Key = (irr::EKEY_CODE)MapVirtualKey( ((lParam>>16) & 255), MY_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::IRR_KEY_RCONTROL;
            }
            if ( event.KeyInput.Key == irr::IRR_KEY_MENU )
            {
                event.KeyInput.Key = (irr::EKEY_CODE)MapVirtualKey( ((lParam>>16) & 255), MY_MAPVK_VSC_TO_VK_EX );
                if (lParam & 0x1000000)
                    event.KeyInput.Key = irr::IRR_KEY_RMENU;
            }

            GetKeyboardState(allKeys);

            event.KeyInput.Shift = ((allKeys[VK_SHIFT] & 0x80)!=0);
            event.KeyInput.Control = ((allKeys[VK_CONTROL] & 0x80)!=0);
            event.KeyInput.Char = 0;

            wchar_t keyChars[10] = {0};
            UINT scanCode = HIWORD(lParam);
            int conversionResult = ToUnicodeEx((UINT)wParam,scanCode,allKeys,keyChars,_countof(keyChars),0,KEYBOARD_INPUT_HKL);
            // 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->isIMEComposingStarted())
            {
                if (conversionResult >= 1)
                {
                    for (int i = 0; i < conversionResult; i++)
                    {
                        event.KeyInput.Char = keyChars[i];
                        dev->postEventFromUser(event);
                    }
                }
                else
                   dev->postEventFromUser(event);
            }

            if (message == WM_SYSKEYDOWN || message == WM_SYSKEYUP)
                return DefWindowProc(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_ACTIVATE:
        // we need to take care for screen changes, e.g. Alt-Tab
        dev = getDeviceFromHWnd(hWnd);
        if (dev && dev->isFullscreen())
        {
            if ((wParam&0xFF)==WA_INACTIVE)
            {
                // If losing focus we minimize the app to show other one
                ShowWindow(hWnd,SW_MINIMIZE);
                // and switch back to default resolution
                dev->switchToFullScreen(true);
            }
            else
            {
                // Otherwise we retore the fullscreen Irrlicht app
                SetForegroundWindow(hWnd);
                ShowWindow(hWnd, SW_RESTORE);
                // and set the fullscreen resolution again
                dev->switchToFullScreen();
            }
        }
        break;

    case WM_USER:
        event.EventType = irr::EET_USER_EVENT;
        event.UserEvent.UserData1 = (irr::s32)wParam;
        event.UserEvent.UserData2 = (irr::s32)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);
        KEYBOARD_INPUT_CODEPAGE = LocaleIdToCodepage( LOWORD(KEYBOARD_INPUT_HKL) );
        return 0;
    }
    return DefWindowProc(hWnd, message, wParam, lParam);
}


namespace irr
{

//! constructor
CIrrDeviceWin32::CIrrDeviceWin32(const SIrrlichtCreationParameters& params)
: CIrrDeviceStub(params), HWnd(0), ChangedToFullScreen(false), Resized(false),
    ExternalWindow(false), Win32CursorControl(0), JoyControl(0)
{
    #ifdef _DEBUG
    setDebugName("CIrrDeviceWin32");
    #endif

    m_ime_composing_started = false;
    // 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);

    // Store original desktop mode.

    memset(&DesktopMode, 0, sizeof(DesktopMode));
    DesktopMode.dmSize = sizeof(DesktopMode);

    EnumDisplaySettings(NULL, ENUM_CURRENT_SETTINGS, &DesktopMode);

    // 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
        WNDCLASSEX wcex;
        wcex.cbSize         = sizeof(WNDCLASSEX);
        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;

        // if there is an icon, load it
        // Check icon_rc.template in tools/windows_installer for define
        HICON icon = (HICON)LoadImage(hInstance, MAKEINTRESOURCE(100),
            IMAGE_ICON, GetSystemMetrics(SM_CXICON), GetSystemMetrics(SM_CXICON),
            LR_DEFAULTCOLOR);
        if (icon != NULL)
            wcex.hIcon = icon;
        HICON icon_sm = (HICON)LoadImage(hInstance, MAKEINTRESOURCE(100),
            IMAGE_ICON, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CXSMICON),
            LR_DEFAULTCOLOR);
        if (icon_sm != NULL)
            wcex.hIconSm = icon_sm;

        RegisterClassEx(&wcex);

        // calculate client size

        RECT clientSize;
        clientSize.top = 0;
        clientSize.left = 0;
        clientSize.right = CreationParams.WindowSize.Width;
        clientSize.bottom = CreationParams.WindowSize.Height;

        DWORD style = WS_POPUP;

        if (!CreationParams.Fullscreen)
            style = WS_SYSMENU | WS_BORDER | WS_CAPTION | WS_CLIPCHILDREN | WS_CLIPSIBLINGS;

        AdjustWindowRect(&clientSize, style, FALSE);

        const s32 realWidth = clientSize.right - clientSize.left;
        const s32 realHeight = clientSize.bottom - clientSize.top;

        s32 windowLeft = (GetSystemMetrics(SM_CXSCREEN) - realWidth) / 2;
        s32 windowTop = (GetSystemMetrics(SM_CYSCREEN) - realHeight) / 2;

        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 = CreateWindow( ClassName, __TEXT(""), style, windowLeft, windowTop,
                    realWidth, realHeight, NULL, NULL, hInstance, NULL);
        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);
    }

    // get the codepage used for keyboard input
    KEYBOARD_INPUT_HKL = GetKeyboardLayout(0);
    KEYBOARD_INPUT_CODEPAGE = LocaleIdToCodepage( LOWORD(KEYBOARD_INPUT_HKL) );

    // inform driver about the window size etc.
    resizeIfNecessary();

}


//! destructor
CIrrDeviceWin32::~CIrrDeviceWin32()
{
    delete JoyControl;

    // unregister environment

    irr::core::list<SEnvMapper>::Iterator it = EnvMap.begin();
    for (; it!= EnvMap.end(); ++it)
    {
        if ((*it).hWnd == HWnd)
        {
            EnvMap.erase(it);
            break;
        }
    }

    switchToFullScreen(true);
}


//! create the driver
void CIrrDeviceWin32::createDriver()
{
    switch(CreationParams.DriverType)
    {
    case video::EDT_DIRECT3D8:
        #ifdef _IRR_COMPILE_WITH_DIRECT3D_8_

        VideoDriver = video::createDirectX8Driver(CreationParams, FileSystem, HWnd);

        if (!VideoDriver)
        {
            os::Printer::log("Could not create DIRECT3D8 Driver.", ELL_ERROR);
        }
        #else
        os::Printer::log("DIRECT3D8 Driver was not compiled into this dll. Try another one.", ELL_ERROR);
        #endif // _IRR_COMPILE_WITH_DIRECT3D_8_

        break;

    case video::EDT_DIRECT3D9:
        #ifdef _IRR_COMPILE_WITH_DIRECT3D_9_

        VideoDriver = video::createDirectX9Driver(CreationParams, FileSystem, HWnd);

        if (!VideoDriver)
        {
            os::Printer::log("Could not create DIRECT3D9 Driver.", ELL_ERROR);
        }
        #else
        os::Printer::log("DIRECT3D9 Driver was not compiled into this dll. Try another one.", ELL_ERROR);
        #endif // _IRR_COMPILE_WITH_DIRECT3D_9_

        break;

    case video::EDT_OPENGL:

        #ifdef _IRR_COMPILE_WITH_OPENGL_
        switchToFullScreen();

        VideoDriver = video::createOpenGLDriver(CreationParams, FileSystem, this);
        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_SOFTWARE:

        #ifdef _IRR_COMPILE_WITH_SOFTWARE_
        switchToFullScreen();

        VideoDriver = video::createSoftwareDriver(CreationParams.WindowSize, CreationParams.Fullscreen, FileSystem, this);
        #else
        os::Printer::log("Software driver was not compiled in.", ELL_ERROR);
        #endif

        break;

    case video::EDT_BURNINGSVIDEO:
        #ifdef _IRR_COMPILE_WITH_BURNINGSVIDEO_
        switchToFullScreen();

        VideoDriver = video::createBurningVideoDriver(CreationParams, FileSystem, this);
        #else
        os::Printer::log("Burning's Video driver was not compiled in.", ELL_ERROR);
        #endif
        break;

    case video::EDT_NULL:
        // create null driver
        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();

    _IRR_IMPLEMENT_MANAGED_MARSHALLING_BUGFIX;
    return !Close;
}


//! Pause the current process for the minimum time allowed only to allow other processes to execute
void CIrrDeviceWin32::yield()
{
    Sleep(1);
}

//! 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)
    {
        sprintf(tmp, "Ignoring resize operation to (%ld %ld)", r.right, r.bottom);
        os::Printer::log(tmp);
    }
    else
    {
        sprintf(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;
}


//! 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);
}


//! presents a surface in the client area
bool CIrrDeviceWin32::present(video::IImage* image, void* windowId, core::rect<s32>* src)
{
    HWND hwnd = HWnd;
    if ( windowId )
        hwnd = reinterpret_cast<HWND>(windowId);

    HDC dc = GetDC(hwnd);

    if ( dc )
    {
        RECT rect;
        GetClientRect(hwnd, &rect);
        const void* memory = (const void *)image->lock();

        BITMAPV4HEADER bi;
        ZeroMemory (&bi, sizeof(bi));
        bi.bV4Size = sizeof(BITMAPINFOHEADER);
        bi.bV4BitCount = (WORD)image->getBitsPerPixel();
        bi.bV4Planes = 1;
        bi.bV4Width = image->getDimension().Width;
        bi.bV4Height = -((s32)image->getDimension().Height);
        bi.bV4V4Compression = BI_BITFIELDS;
        bi.bV4AlphaMask = image->getAlphaMask();
        bi.bV4RedMask = image->getRedMask();
        bi.bV4GreenMask = image->getGreenMask();
        bi.bV4BlueMask = image->getBlueMask();

        if ( src )
        {
            StretchDIBits(dc, 0,0, rect.right, rect.bottom,
                    src->UpperLeftCorner.X, src->UpperLeftCorner.Y,
                    src->getWidth(), src->getHeight(),
                    memory, (const BITMAPINFO*)(&bi), DIB_RGB_COLORS, SRCCOPY);
        }
        else
        {
            StretchDIBits(dc, 0,0, rect.right, rect.bottom,
                    0, 0, image->getDimension().Width, image->getDimension().Height,
                    memory, (const BITMAPINFO*)(&bi), DIB_RGB_COLORS, SRCCOPY);
        }

        image->unlock();

        ReleaseDC(hwnd, dc);
    }
    return true;
}


//! notifies the device that it should close itself
void CIrrDeviceWin32::closeDevice()
{
    MSG msg;
    PeekMessage(&msg, NULL, WM_QUIT, WM_QUIT, PM_REMOVE);
    PostQuitMessage(0);
    PeekMessage(&msg, NULL, WM_QUIT, WM_QUIT, PM_REMOVE);
    if (!ExternalWindow)
    {
        DestroyWindow(HWnd);
        const wchar_t* ClassName = L"CIrrDeviceWin32";
        HINSTANCE hInstance = GetModuleHandle(0);
        UnregisterClass(ClassName, hInstance);
    }
    Close=true;
}


//! returns if window is active. if not, nothing needs to be drawn
bool CIrrDeviceWin32::isWindowActive() const
{
    _IRR_IMPLEMENT_MANAGED_MARSHALLING_BUGFIX;
    return (GetActiveWindow() == HWnd);
}


//! returns if window has focus
bool CIrrDeviceWin32::isWindowFocused() const
{
    bool ret = (GetFocus() == HWnd);
    _IRR_IMPLEMENT_MANAGED_MARSHALLING_BUGFIX;
    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)!=0;
    _IRR_IMPLEMENT_MANAGED_MARSHALLING_BUGFIX;
    return ret;
}


//! switches to fullscreen
bool CIrrDeviceWin32::switchToFullScreen(bool reset)
{
    if (!CreationParams.Fullscreen)
        return true;

    if (reset)
    {
        if (ChangedToFullScreen)
        {
            return (ChangeDisplaySettings(&DesktopMode,0)==DISP_CHANGE_SUCCESSFUL);
        }
        else
            return true;
    }

    // use default values from current setting

    DEVMODE dm;
    memset(&dm, 0, sizeof(dm));
    dm.dmSize = sizeof(dm);

    EnumDisplaySettings(NULL, ENUM_CURRENT_SETTINGS, &dm);
    dm.dmPelsWidth = CreationParams.WindowSize.Width;
    dm.dmPelsHeight = CreationParams.WindowSize.Height;
    dm.dmBitsPerPel = CreationParams.Bits;
    dm.dmFields = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT | DM_DISPLAYFREQUENCY;

    LONG res = ChangeDisplaySettings(&dm, CDS_FULLSCREEN);
    if (res != DISP_CHANGE_SUCCESSFUL)
    { // try again without forcing display frequency
        dm.dmFields = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT;
        res = ChangeDisplaySettings(&dm, CDS_FULLSCREEN);
    }

    bool ret = false;
    switch(res)
    {
    case DISP_CHANGE_SUCCESSFUL:
        ChangedToFullScreen = true;
        ret = true;
        break;
    case DISP_CHANGE_RESTART:
        os::Printer::log("Switch to fullscreen: The computer must be restarted in order for the graphics mode to work.", ELL_ERROR);
        break;
    case DISP_CHANGE_BADFLAGS:
        os::Printer::log("Switch to fullscreen: An invalid set of flags was passed in.", ELL_ERROR);
        break;
    case DISP_CHANGE_BADPARAM:
        os::Printer::log("Switch to fullscreen: An invalid parameter was passed in. This can include an invalid flag or combination of flags.", ELL_ERROR);
        break;
    case DISP_CHANGE_FAILED:
        os::Printer::log("Switch to fullscreen: The display driver failed the specified graphics mode.", ELL_ERROR);
        break;
    case DISP_CHANGE_BADMODE:
        os::Printer::log("Switch to fullscreen: The graphics mode is not supported.", ELL_ERROR);
        break;
    default:
        os::Printer::log("An unknown error occured while changing to fullscreen.", ELL_ERROR);
        break;
    }
    _IRR_IMPLEMENT_MANAGED_MARSHALLING_BUGFIX;
    return ret;
}


//! returns the win32 cursor control
CIrrDeviceWin32::CCursorControl* CIrrDeviceWin32::getWin32CursorControl()
{
    return Win32CursorControl;
}


//! \return Returns a pointer to a list with all video modes supported
//! by the gfx adapter.
video::IVideoModeList* CIrrDeviceWin32::getVideoModeList()
{
    if (!VideoModeList.getVideoModeCount())
    {
        // enumerate video modes.
        DWORD i=0;
        DEVMODE mode;
        memset(&mode, 0, sizeof(mode));
        mode.dmSize = sizeof(mode);

        while (EnumDisplaySettings(NULL, i, &mode))
        {
            VideoModeList.addMode(core::dimension2d<u32>(mode.dmPelsWidth, mode.dmPelsHeight),
                mode.dmBitsPerPel);

            ++i;
        }

        if (EnumDisplaySettings(NULL, ENUM_CURRENT_SETTINGS, &mode))
            VideoModeList.setDesktop(mode.dmBitsPerPel, core::dimension2d<u32>(mode.dmPelsWidth, mode.dmPelsHeight));
    }

    return &VideoModeList;
}

typedef BOOL (WINAPI *PGPI)(DWORD, DWORD, DWORD, DWORD, PDWORD);
// Needed for old windows apis
// depending on the SDK version and compilers some defines might be available
// or not
#ifndef PRODUCT_ULTIMATE
#define PRODUCT_ULTIMATE     0x00000001
#define PRODUCT_HOME_BASIC   0x00000002
#define PRODUCT_HOME_PREMIUM 0x00000003
#define PRODUCT_ENTERPRISE   0x00000004
#define PRODUCT_HOME_BASIC_N 0x00000005
#define PRODUCT_BUSINESS     0x00000006
#define PRODUCT_STARTER      0x0000000B
#endif
#ifndef PRODUCT_ULTIMATE_N
#define PRODUCT_BUSINESS_N      0x00000010
#define PRODUCT_HOME_PREMIUM_N  0x0000001A
#define PRODUCT_ENTERPRISE_N    0x0000001B
#define PRODUCT_ULTIMATE_N      0x0000001C
#endif
#ifndef PRODUCT_STARTER_N
#define PRODUCT_STARTER_N       0x0000002F
#endif
#ifndef PRODUCT_PROFESSIONAL
#define PRODUCT_PROFESSIONAL    0x00000030
#define PRODUCT_PROFESSIONAL_N  0x00000031
#endif
#ifndef PRODUCT_ULTIMATE_E
#define PRODUCT_STARTER_E       0x00000042
#define PRODUCT_HOME_BASIC_E    0x00000043
#define PRODUCT_HOME_PREMIUM_E  0x00000044
#define PRODUCT_PROFESSIONAL_E  0x00000045
#define PRODUCT_ENTERPRISE_E    0x00000046
#define PRODUCT_ULTIMATE_E      0x00000047
#endif

void CIrrDeviceWin32::getWindowsVersion(core::stringc& out)
{
    OSVERSIONINFOEX osvi;
    PGPI pGPI;
    BOOL bOsVersionInfoEx;

    ZeroMemory(&osvi, sizeof(OSVERSIONINFOEX));
    osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);

    bOsVersionInfoEx = GetVersionEx((OSVERSIONINFO*) &osvi);
    if (!bOsVersionInfoEx)
    {
        osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
        if (! GetVersionEx((OSVERSIONINFO *) &osvi))
            return;
    }

    switch (osvi.dwPlatformId)
    {
    case VER_PLATFORM_WIN32_NT:
        if (osvi.dwMajorVersion <= 4)
            out.append("Microsoft Windows NT ");
        else
        if (osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 0)
            out.append("Microsoft Windows 2000 ");
        else
        if (osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 1)
            out.append("Microsoft Windows XP ");
        else
        if (osvi.dwMajorVersion == 6 )
        {
            if (osvi.dwMinorVersion == 0)
            {
                if (osvi.wProductType == VER_NT_WORKSTATION)
                    out.append("Microsoft Windows Vista ");
                else
                    out.append("Microsoft Windows Server 2008 ");
            }
            else if (osvi.dwMinorVersion == 1)
            {
                if (osvi.wProductType == VER_NT_WORKSTATION)
                    out.append("Microsoft Windows 7 ");
                else
                    out.append("Microsoft Windows Server 2008 R2 ");
            }
            else if (osvi.dwMinorVersion == 2)
            {
                if (osvi.wProductType == VER_NT_WORKSTATION)
                    out.append("Microsoft Windows 8 ");
                else
                    out.append("Microsoft Windows Server 2012 ");
            }
            else if (osvi.dwMinorVersion == 3)
            {
                if (osvi.wProductType == VER_NT_WORKSTATION)
                    out.append("Microsoft Windows 8.1 ");
                else
                    out.append("Microsoft Windows Server 2012 R2 ");
            }
        }

        if (bOsVersionInfoEx)
        {
            if (osvi.dwMajorVersion == 6)
            {
                DWORD dwType;
                pGPI = (PGPI)GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), "GetProductInfo");
                pGPI(osvi.dwMajorVersion, osvi.dwMinorVersion, 0, 0, &dwType);

                switch (dwType)
                {
                case PRODUCT_ULTIMATE:
                case PRODUCT_ULTIMATE_E:
                case PRODUCT_ULTIMATE_N:
                    out.append("Ultimate Edition ");
                    break;
                case PRODUCT_PROFESSIONAL:
                case PRODUCT_PROFESSIONAL_E:
                case PRODUCT_PROFESSIONAL_N:
                    out.append("Professional Edition ");
                    break;
                case PRODUCT_HOME_BASIC:
                case PRODUCT_HOME_BASIC_E:
                case PRODUCT_HOME_BASIC_N:
                    out.append("Home Basic Edition ");
                    break;
                case PRODUCT_HOME_PREMIUM:
                case PRODUCT_HOME_PREMIUM_E:
                case PRODUCT_HOME_PREMIUM_N:
                    out.append("Home Premium Edition ");
                    break;
                case PRODUCT_ENTERPRISE:
                case PRODUCT_ENTERPRISE_E:
                case PRODUCT_ENTERPRISE_N:
                    out.append("Enterprise Edition ");
                    break;
                case PRODUCT_BUSINESS:
                case PRODUCT_BUSINESS_N:
                    out.append("Business Edition ");
                    break;
                case PRODUCT_STARTER:
                case PRODUCT_STARTER_E:
                case PRODUCT_STARTER_N:
                    out.append("Starter Edition ");
                    break;
                }
            }
#ifdef VER_SUITE_ENTERPRISE
            else
            if (osvi.wProductType == VER_NT_WORKSTATION)
            {
#ifndef __BORLANDC__
                if( osvi.wSuiteMask & VER_SUITE_PERSONAL )
                    out.append("Personal ");
                else
                    out.append("Professional ");
#endif
            }
            else if (osvi.wProductType == VER_NT_SERVER)
            {
                if( osvi.wSuiteMask & VER_SUITE_DATACENTER )
                    out.append("DataCenter Server ");
                else if( osvi.wSuiteMask & VER_SUITE_ENTERPRISE )
                    out.append("Advanced Server ");
                else
                    out.append("Server ");
            }
#endif
        }
        else
        {
            HKEY hKey;
            char szProductType[80];
            DWORD dwBufLen;

            RegOpenKeyEx( HKEY_LOCAL_MACHINE,
                    __TEXT("SYSTEM\\CurrentControlSet\\Control\\ProductOptions"),
                    0, KEY_QUERY_VALUE, &hKey );
            RegQueryValueEx( hKey, __TEXT("ProductType"), NULL, NULL,
                    (LPBYTE) szProductType, &dwBufLen);
            RegCloseKey( hKey );

            if (_strcmpi( "WINNT", szProductType) == 0 )
                out.append("Professional ");
            if (_strcmpi( "LANMANNT", szProductType) == 0)
                out.append("Server ");
            if (_strcmpi( "SERVERNT", szProductType) == 0)
                out.append("Advanced Server ");
        }

        // Display version, service pack (if any), and build number.

        char tmp[255];

        if (osvi.dwMajorVersion <= 4 )
        {
            sprintf(tmp, "version %ld.%ld %s (Build %ld)",
                    osvi.dwMajorVersion,
                    osvi.dwMinorVersion,
                    irr::core::stringc(osvi.szCSDVersion).c_str(),
                    osvi.dwBuildNumber & 0xFFFF);
        }
        else
        {
            sprintf(tmp, "%s (Build %ld)", irr::core::stringc(osvi.szCSDVersion).c_str(),
            osvi.dwBuildNumber & 0xFFFF);
        }

        out.append(tmp);
        break;

    case VER_PLATFORM_WIN32_WINDOWS:

        if (osvi.dwMajorVersion == 4 && osvi.dwMinorVersion == 0)
        {
            out.append("Microsoft Windows 95 ");
            if ( osvi.szCSDVersion[1] == 'C' || osvi.szCSDVersion[1] == 'B' )
                out.append("OSR2 " );
        }

        if (osvi.dwMajorVersion == 4 && osvi.dwMinorVersion == 10)
        {
            out.append("Microsoft Windows 98 ");
            if ( osvi.szCSDVersion[1] == 'A' )
                out.append( "SE " );
        }

        if (osvi.dwMajorVersion == 4 && osvi.dwMinorVersion == 90)
            out.append("Microsoft Windows Me ");

        break;

    case VER_PLATFORM_WIN32s:
        out.append("Microsoft Win32s ");
        break;
    }
}

//! Notifies the device, that it has been resized
void CIrrDeviceWin32::OnResized()
{
    Resized = true;
}

//! Sets if the window should be resizable in windowed mode.
void CIrrDeviceWin32::setResizable(bool resize)
{
    if (ExternalWindow || !getVideoDriver() || CreationParams.Fullscreen)
        return;

    LONG_PTR style = WS_POPUP;

    if (!resize)
        style = WS_SYSMENU | WS_BORDER | WS_CAPTION | WS_CLIPCHILDREN | WS_CLIPSIBLINGS;
    else
        style = WS_THICKFRAME | WS_SYSMENU | WS_CAPTION | WS_CLIPCHILDREN | WS_CLIPSIBLINGS | WS_MINIMIZEBOX | WS_MAXIMIZEBOX;

    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, 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);
}


//! 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);
}

//! Move window to requested position
bool CIrrDeviceWin32::moveWindow(int x, int y)
{
    if (CreationParams.DriverType == video::EDT_NULL || CreationParams.Fullscreen)
        return false;
		
    bool success = SetWindowPos(HWnd, HWND_TOP, x, y, -1, -1,
                                SWP_NOOWNERZORDER | SWP_NOSIZE);
    
    return success;
}

//! Get current window position.
bool CIrrDeviceWin32::getWindowPosition(int* x, int* y)
{
    if (CreationParams.DriverType == video::EDT_NULL || CreationParams.Fullscreen)
        return false;
    
    WINDOWPLACEMENT placement;
    placement.length = sizeof(WINDOWPLACEMENT);
    
    bool success = GetWindowPlacement(HWnd, &placement);
    
    if (!success)
        return false;
    
    *x = (int)placement.rcNormalPosition.left;
    *y = (int)placement.rcNormalPosition.top;
    
    return true;
}


bool CIrrDeviceWin32::activateJoysticks(core::array<SJoystickInfo> & joystickInfo)
{
    if (JoyControl)
        return JoyControl->activateJoysticks(joystickInfo);
    else
        return false;
}


//! Set the current Gamma Value for the Display
bool CIrrDeviceWin32::setGammaRamp( f32 red, f32 green, f32 blue, f32 brightness, f32 contrast )
{
    bool r;
    u16 ramp[3][256];

    calculateGammaRamp( ramp[0], red, brightness, contrast );
    calculateGammaRamp( ramp[1], green, brightness, contrast );
    calculateGammaRamp( ramp[2], blue, brightness, contrast );

    HDC dc = GetDC(0);
    r = SetDeviceGammaRamp ( dc, ramp ) == TRUE;
    ReleaseDC(HWnd, dc);
    return r;
}

//! Get the current Gamma Value for the Display
bool CIrrDeviceWin32::getGammaRamp( f32 &red, f32 &green, f32 &blue, f32 &brightness, f32 &contrast )
{
    bool r;
    u16 ramp[3][256];

    HDC dc = GetDC(0);
    r = GetDeviceGammaRamp ( dc, ramp ) == TRUE;
    ReleaseDC(HWnd, dc);

    if ( r )
    {
        calculateGammaFromRamp(red, ramp[0]);
        calculateGammaFromRamp(green, ramp[1]);
        calculateGammaFromRamp(blue, ramp[2]);
    }

    brightness = 0.f;
    contrast = 0.f;

    return r;

}


//! Process system events
void CIrrDeviceWin32::handleSystemMessages()
{
    MSG msg;

    while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
    {
        // No message translation because we don't use WM_CHAR and it would conflict with our
        // deadkey handling.

        TranslateMessage(&msg);

        if (ExternalWindow && msg.hwnd == HWnd)
            WndProc(HWnd, msg.message, msg.wParam, msg.lParam);
        else
            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))
    {}
}

// shows last error in a messagebox to help internal debugging.
void CIrrDeviceWin32::ReportLastWinApiError()
{
    // (based on code from ovidiucucu from http://www.codeguru.com/forum/showthread.php?t=318721)
    LPCTSTR pszCaption = __TEXT("Windows SDK Error Report");
    DWORD dwError = GetLastError();

    if(NOERROR == dwError)
    {
        MessageBox(NULL, __TEXT("No error"), pszCaption, MB_OK);
    }
    else
    {
        const DWORD dwFormatControl = FORMAT_MESSAGE_ALLOCATE_BUFFER |
                                        FORMAT_MESSAGE_IGNORE_INSERTS |
                                        FORMAT_MESSAGE_FROM_SYSTEM;

        LPVOID pTextBuffer = NULL;
        DWORD dwCount = FormatMessage(dwFormatControl,
                                        NULL,
                                        dwError,
                                        0,
                                        (LPTSTR) &pTextBuffer,
                                        0,
                                        NULL);
        if(0 != dwCount)
        {
            MessageBox(NULL, (LPCTSTR)pTextBuffer, pszCaption, MB_OK|MB_ICONERROR);
            LocalFree(pTextBuffer);
        }
        else
        {
            MessageBox(NULL, __TEXT("Unknown error"), pszCaption, MB_OK|MB_ICONERROR);
        }
    }
}

// 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_
