#ifdef WIN32

#include "Base.h"
#include "Platform.h"
#include "FileSystem.h"
#include "Game.h"
#include "Form.h"
#include "Vector2.h"
#include "ScriptController.h"
#include <GL/wglew.h>
#include <windowsx.h>
#ifdef USE_XINPUT
#include <XInput.h>
#endif

using gameplay::print;

// Window defaults
#define DEFAULT_RESOLUTION_X 1024
#define DEFAULT_RESOLUTION_Y 768
#define DEFAULT_COLOR_BUFFER_SIZE 32
#define DEFAULT_DEPTH_BUFFER_SIZE 24
#define DEFAULT_STENCIL_BUFFER_SIZE 8

static double __timeTicksPerMillis;
static double __timeStart;
static double __timeAbsolute;
static bool __vsync = WINDOW_VSYNC;
static float __roll;
static float __pitch;
static HINSTANCE __hinstance = 0;
static HWND __attachToWindow = 0;
static HWND __hwnd = 0;
static HDC __hdc = 0;
static HGLRC __hrc = 0;
static bool __mouseCaptured = false;
static POINT __mouseCapturePoint = { 0, 0 };
static bool __cursorVisible = true;
static unsigned int __gamepadsConnected = 0;

#ifdef USE_XINPUT
struct XInputGamepad
{
    static const unsigned int BUTTON_COUNT = 14;
    static const unsigned int JOYSTICK_COUNT = 2;
    static const unsigned int TRIGGER_COUNT = 2;
    std::string id;
    int handle;
    bool connected;
    XINPUT_STATE state;
};

static XInputGamepad* __xinputGamepads = NULL;

static DWORD getXInputGamepadButtonMask(unsigned int buttonHandle)
{
    switch(buttonHandle)
    {
    case 0:
        return XINPUT_GAMEPAD_DPAD_UP;
    case 1:
        return XINPUT_GAMEPAD_DPAD_DOWN;
    case 2:
        return XINPUT_GAMEPAD_DPAD_LEFT;
    case 3:
        return XINPUT_GAMEPAD_DPAD_RIGHT;
    case 4:
        return XINPUT_GAMEPAD_START;
    case 5:
        return XINPUT_GAMEPAD_BACK;
    case 6:
        return XINPUT_GAMEPAD_LEFT_THUMB;
    case 7:
        return XINPUT_GAMEPAD_RIGHT_THUMB;
    case 8:
        return XINPUT_GAMEPAD_LEFT_SHOULDER;
    case 9:
        return XINPUT_GAMEPAD_RIGHT_SHOULDER;
    case 10:
        return XINPUT_GAMEPAD_A;
    case 11:
        return XINPUT_GAMEPAD_B;
    case 12:
        return XINPUT_GAMEPAD_X;
    case 13:
        return XINPUT_GAMEPAD_Y;
    default:
        return 0;
    }
}

static bool getXInputState(unsigned long gamepadHandle)
{
    GP_ASSERT(0 <= gamepadHandle && gamepadHandle < XUSER_MAX_COUNT);

    if (XInputGetState((DWORD)gamepadHandle, &__xinputGamepads[gamepadHandle].state) == ERROR_SUCCESS)
        return (__xinputGamepads[gamepadHandle].connected = true);
    else
        return (__xinputGamepads[gamepadHandle].connected = false);
}

static bool getXInputButtonState(unsigned long gamepadHandle, unsigned long buttonHandle)
{
    GP_ASSERT(0 <= gamepadHandle && gamepadHandle < XUSER_MAX_COUNT);
    GP_ASSERT(0 <= buttonHandle && buttonHandle < 14);
    
    WORD buttonMask = getXInputGamepadButtonMask(buttonHandle); // Conversion to button mask.

    if ((__xinputGamepads[gamepadHandle].state.Gamepad.wButtons & buttonMask) == buttonMask)
        return true;
    else
        return false;
}

static float normalizeXInputJoystickAxis(int axisValue, int deadZone)
{
    int absAxisValue = abs(axisValue);

    if (absAxisValue < deadZone)
    {
        return 0.0f;
    }
    else
    {
        int value = axisValue;
        if (value < 0)
            value = -1;
        else if (value > 0)
            value = 1;
        else
            value = 0;
        return value * (absAxisValue - deadZone) / (float)(32768 - deadZone);
    }
}

static float getXInputJoystickAxisX(unsigned long gamepadHandle, unsigned long joystickHandle)
{
    GP_ASSERT(0 <= gamepadHandle && gamepadHandle < XUSER_MAX_COUNT);
    GP_ASSERT(0 <= joystickHandle && joystickHandle < 2);
    
    switch(joystickHandle)
    {
    case 0:
        return normalizeXInputJoystickAxis(__xinputGamepads[gamepadHandle].state.Gamepad.sThumbLX, XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE);
    case 1:
        return normalizeXInputJoystickAxis(__xinputGamepads[gamepadHandle].state.Gamepad.sThumbRX, XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE);
    default: return 0.0f;
    }
}

static float getXInputJoystickAxisY(unsigned long gamepadHandle, unsigned long joystickHandle)
{
    GP_ASSERT(0 <= gamepadHandle && gamepadHandle < XUSER_MAX_COUNT);
    GP_ASSERT(0 <= joystickHandle && joystickHandle < 2);

    switch(joystickHandle)
    {
    case 0:
        return normalizeXInputJoystickAxis(__xinputGamepads[gamepadHandle].state.Gamepad.sThumbLY, XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE);
    case 1:
        return normalizeXInputJoystickAxis(__xinputGamepads[gamepadHandle].state.Gamepad.sThumbRY, XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE);
    default: return 0.0f;
    }
}

static void getXInputJoystickAxisValues(unsigned long gamepadHandle, unsigned long joystickHandle, gameplay::Vector2* outValue)
{
    GP_ASSERT(0 <= gamepadHandle && gamepadHandle < XUSER_MAX_COUNT);
    GP_ASSERT(0 <= joystickHandle && joystickHandle < 2);

    switch(joystickHandle)
    {
    case 0:
        outValue->x = normalizeXInputJoystickAxis(__xinputGamepads[gamepadHandle].state.Gamepad.sThumbLX, XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE);
        outValue->y = normalizeXInputJoystickAxis(__xinputGamepads[gamepadHandle].state.Gamepad.sThumbLY, XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE);
        break;
    case 1:
        outValue->x = normalizeXInputJoystickAxis(__xinputGamepads[gamepadHandle].state.Gamepad.sThumbRX, XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE);
        outValue->y = normalizeXInputJoystickAxis(__xinputGamepads[gamepadHandle].state.Gamepad.sThumbRY, XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE);
        break;
    }
}
#endif

static gameplay::Keyboard::Key getKey(WPARAM win32KeyCode, bool shiftDown)
{
    switch (win32KeyCode)
    {
    case VK_PAUSE:
        return gameplay::Keyboard::KEY_PAUSE;
    case VK_SCROLL:
        return gameplay::Keyboard::KEY_SCROLL_LOCK;
    case VK_PRINT:
        return gameplay::Keyboard::KEY_PRINT;
    case VK_ESCAPE:
        return gameplay::Keyboard::KEY_ESCAPE;
    case VK_BACK:
        return gameplay::Keyboard::KEY_BACKSPACE;
    case VK_TAB:
        return shiftDown ? gameplay::Keyboard::KEY_BACK_TAB : gameplay::Keyboard::KEY_TAB;
    case VK_RETURN:
        return gameplay::Keyboard::KEY_RETURN;
    case VK_CAPITAL:
        return gameplay::Keyboard::KEY_CAPS_LOCK;
    case VK_SHIFT:
        return gameplay::Keyboard::KEY_SHIFT;
    case VK_CONTROL:
        return gameplay::Keyboard::KEY_CTRL;
    case VK_MENU:
        return gameplay::Keyboard::KEY_ALT;
    case VK_APPS:
        return gameplay::Keyboard::KEY_MENU;
    case VK_LSHIFT:
        return gameplay::Keyboard::KEY_SHIFT;
    case VK_RSHIFT:
        return gameplay::Keyboard::KEY_SHIFT;
    case VK_LCONTROL:
        return gameplay::Keyboard::KEY_CTRL;
    case VK_RCONTROL:
        return gameplay::Keyboard::KEY_CTRL;
    case VK_LMENU:
        return gameplay::Keyboard::KEY_ALT;
    case VK_RMENU:
        return gameplay::Keyboard::KEY_ALT;
    case VK_LWIN:
    case VK_RWIN:
        return gameplay::Keyboard::KEY_HYPER;
    case VK_BROWSER_SEARCH:
        return gameplay::Keyboard::KEY_SEARCH;
    case VK_INSERT:
        return gameplay::Keyboard::KEY_INSERT;
    case VK_HOME:
        return gameplay::Keyboard::KEY_HOME;
    case VK_PRIOR:
        return gameplay::Keyboard::KEY_PG_UP;
    case VK_DELETE:
        return gameplay::Keyboard::KEY_DELETE;
    case VK_END:
        return gameplay::Keyboard::KEY_END;
    case VK_NEXT:
        return gameplay::Keyboard::KEY_PG_DOWN;
    case VK_LEFT:
        return gameplay::Keyboard::KEY_LEFT_ARROW;
    case VK_RIGHT:
        return gameplay::Keyboard::KEY_RIGHT_ARROW;
    case VK_UP:
        return gameplay::Keyboard::KEY_UP_ARROW;
    case VK_DOWN:
        return gameplay::Keyboard::KEY_DOWN_ARROW;
    case VK_NUMLOCK:
        return gameplay::Keyboard::KEY_NUM_LOCK;
    case VK_ADD:
        return gameplay::Keyboard::KEY_KP_PLUS;
    case VK_SUBTRACT:
        return gameplay::Keyboard::KEY_KP_MINUS;
    case VK_MULTIPLY:
        return gameplay::Keyboard::KEY_KP_MULTIPLY;
    case VK_DIVIDE:
        return gameplay::Keyboard::KEY_KP_DIVIDE;
    case VK_NUMPAD7:
        return gameplay::Keyboard::KEY_KP_HOME;
    case VK_NUMPAD8:
        return gameplay::Keyboard::KEY_KP_UP;
    case VK_NUMPAD9:
        return gameplay::Keyboard::KEY_KP_PG_UP;
    case VK_NUMPAD4:
        return gameplay::Keyboard::KEY_KP_LEFT;
    case VK_NUMPAD5:
        return gameplay::Keyboard::KEY_KP_FIVE;
    case VK_NUMPAD6:
        return gameplay::Keyboard::KEY_KP_RIGHT;
    case VK_NUMPAD1:
        return gameplay::Keyboard::KEY_KP_END;
    case VK_NUMPAD2:
        return gameplay::Keyboard::KEY_KP_DOWN;
    case VK_NUMPAD3:
        return gameplay::Keyboard::KEY_KP_PG_DOWN;
    case VK_NUMPAD0:
        return gameplay::Keyboard::KEY_KP_INSERT;
    case VK_DECIMAL:
        return gameplay::Keyboard::KEY_KP_DELETE;
    case VK_F1:
        return gameplay::Keyboard::KEY_F1;
    case VK_F2:
        return gameplay::Keyboard::KEY_F2;
    case VK_F3:
        return gameplay::Keyboard::KEY_F3;
    case VK_F4:
        return gameplay::Keyboard::KEY_F4;
    case VK_F5:
        return gameplay::Keyboard::KEY_F5;
    case VK_F6:
        return gameplay::Keyboard::KEY_F6;
    case VK_F7:
        return gameplay::Keyboard::KEY_F7;
    case VK_F8:
        return gameplay::Keyboard::KEY_F8;
    case VK_F9:
        return gameplay::Keyboard::KEY_F9;
    case VK_F10:
        return gameplay::Keyboard::KEY_F10;
    case VK_F11:
        return gameplay::Keyboard::KEY_F11;
    case VK_F12:
        return gameplay::Keyboard::KEY_F12;
    case VK_SPACE:
        return gameplay::Keyboard::KEY_SPACE;
    case 0x30:
        return shiftDown ? gameplay::Keyboard::KEY_RIGHT_PARENTHESIS : gameplay::Keyboard::KEY_ZERO;
    case 0x31:
        return shiftDown ? gameplay::Keyboard::KEY_EXCLAM : gameplay::Keyboard::KEY_ONE;
    case 0x32:
        return shiftDown ? gameplay::Keyboard::KEY_AT : gameplay::Keyboard::KEY_TWO;
    case 0x33:
        return shiftDown ? gameplay::Keyboard::KEY_NUMBER : gameplay::Keyboard::KEY_THREE;
    case 0x34:
        return shiftDown ? gameplay::Keyboard::KEY_DOLLAR : gameplay::Keyboard::KEY_FOUR;
    case 0x35:
        return shiftDown ? gameplay::Keyboard::KEY_PERCENT : gameplay::Keyboard::KEY_FIVE;
    case 0x36:
        return shiftDown ? gameplay::Keyboard::KEY_CIRCUMFLEX : gameplay::Keyboard::KEY_SIX;
    case 0x37:
        return shiftDown ? gameplay::Keyboard::KEY_AMPERSAND : gameplay::Keyboard::KEY_SEVEN;
    case 0x38:
        return shiftDown ? gameplay::Keyboard::KEY_ASTERISK : gameplay::Keyboard::KEY_EIGHT;
    case 0x39:
        return shiftDown ? gameplay::Keyboard::KEY_LEFT_PARENTHESIS : gameplay::Keyboard::KEY_NINE;
    case VK_OEM_PLUS:
        return shiftDown ? gameplay::Keyboard::KEY_EQUAL : gameplay::Keyboard::KEY_PLUS;
    case VK_OEM_COMMA:
        return shiftDown ? gameplay::Keyboard::KEY_LESS_THAN : gameplay::Keyboard::KEY_COMMA;
    case VK_OEM_MINUS:
        return shiftDown ? gameplay::Keyboard::KEY_UNDERSCORE : gameplay::Keyboard::KEY_MINUS;
    case VK_OEM_PERIOD:
        return shiftDown ? gameplay::Keyboard::KEY_GREATER_THAN : gameplay::Keyboard::KEY_PERIOD;
    case VK_OEM_1:
        return shiftDown ? gameplay::Keyboard::KEY_COLON : gameplay::Keyboard::KEY_SEMICOLON;
    case VK_OEM_2:
        return shiftDown ? gameplay::Keyboard::KEY_QUESTION : gameplay::Keyboard::KEY_SLASH;
    case VK_OEM_3:
        return shiftDown ? gameplay::Keyboard::KEY_TILDE : gameplay::Keyboard::KEY_GRAVE;
    case VK_OEM_4:
        return shiftDown ? gameplay::Keyboard::KEY_LEFT_BRACE : gameplay::Keyboard::KEY_LEFT_BRACKET;
    case VK_OEM_5:
        return shiftDown ? gameplay::Keyboard::KEY_BAR : gameplay::Keyboard::KEY_BACK_SLASH;
    case VK_OEM_6:
        return shiftDown ? gameplay::Keyboard::KEY_RIGHT_BRACE : gameplay::Keyboard::KEY_RIGHT_BRACKET;
    case VK_OEM_7:
        return shiftDown ? gameplay::Keyboard::KEY_QUOTE : gameplay::Keyboard::KEY_APOSTROPHE;
    case 0x41:
        return shiftDown ? gameplay::Keyboard::KEY_CAPITAL_A : gameplay::Keyboard::KEY_A;
    case 0x42:
        return shiftDown ? gameplay::Keyboard::KEY_CAPITAL_B : gameplay::Keyboard::KEY_B;
    case 0x43:
        return shiftDown ? gameplay::Keyboard::KEY_CAPITAL_C : gameplay::Keyboard::KEY_C;
    case 0x44:
        return shiftDown ? gameplay::Keyboard::KEY_CAPITAL_D : gameplay::Keyboard::KEY_D;
    case 0x45:
        return shiftDown ? gameplay::Keyboard::KEY_CAPITAL_E : gameplay::Keyboard::KEY_E;
    case 0x46:
        return shiftDown ? gameplay::Keyboard::KEY_CAPITAL_F : gameplay::Keyboard::KEY_F;
    case 0x47:
        return shiftDown ? gameplay::Keyboard::KEY_CAPITAL_G : gameplay::Keyboard::KEY_G;
    case 0x48:
        return shiftDown ? gameplay::Keyboard::KEY_CAPITAL_H : gameplay::Keyboard::KEY_H;
    case 0x49:
        return shiftDown ? gameplay::Keyboard::KEY_CAPITAL_I : gameplay::Keyboard::KEY_I;
    case 0x4A:
        return shiftDown ? gameplay::Keyboard::KEY_CAPITAL_J : gameplay::Keyboard::KEY_J;
    case 0x4B:
        return shiftDown ? gameplay::Keyboard::KEY_CAPITAL_K : gameplay::Keyboard::KEY_K;
    case 0x4C:
        return shiftDown ? gameplay::Keyboard::KEY_CAPITAL_L : gameplay::Keyboard::KEY_L;
    case 0x4D:
        return shiftDown ? gameplay::Keyboard::KEY_CAPITAL_M : gameplay::Keyboard::KEY_M;
    case 0x4E:
        return shiftDown ? gameplay::Keyboard::KEY_CAPITAL_N : gameplay::Keyboard::KEY_N;
    case 0x4F:
        return shiftDown ? gameplay::Keyboard::KEY_CAPITAL_O : gameplay::Keyboard::KEY_O;
    case 0x50:
        return shiftDown ? gameplay::Keyboard::KEY_CAPITAL_P : gameplay::Keyboard::KEY_P;
    case 0x51:
        return shiftDown ? gameplay::Keyboard::KEY_CAPITAL_Q : gameplay::Keyboard::KEY_Q;
    case 0x52:
        return shiftDown ? gameplay::Keyboard::KEY_CAPITAL_R : gameplay::Keyboard::KEY_R;
    case 0x53:
        return shiftDown ? gameplay::Keyboard::KEY_CAPITAL_S : gameplay::Keyboard::KEY_S;
    case 0x54:
        return shiftDown ? gameplay::Keyboard::KEY_CAPITAL_T : gameplay::Keyboard::KEY_T;
    case 0x55:
        return shiftDown ? gameplay::Keyboard::KEY_CAPITAL_U : gameplay::Keyboard::KEY_U;
    case 0x56:
        return shiftDown ? gameplay::Keyboard::KEY_CAPITAL_V : gameplay::Keyboard::KEY_V;
    case 0x57:
        return shiftDown ? gameplay::Keyboard::KEY_CAPITAL_W : gameplay::Keyboard::KEY_W;
    case 0x58:
        return shiftDown ? gameplay::Keyboard::KEY_CAPITAL_X : gameplay::Keyboard::KEY_X;
    case 0x59:
        return shiftDown ? gameplay::Keyboard::KEY_CAPITAL_Y : gameplay::Keyboard::KEY_Y;
    case 0x5A:
        return shiftDown ? gameplay::Keyboard::KEY_CAPITAL_Z : gameplay::Keyboard::KEY_Z;
    default:
        return gameplay::Keyboard::KEY_NONE;
    }
}

void UpdateCapture(LPARAM lParam)
{
    if ((lParam & MK_LBUTTON) || (lParam & MK_MBUTTON) || (lParam & MK_RBUTTON))
        SetCapture(__hwnd);
    else
        ReleaseCapture();
}

void WarpMouse(int clientX, int clientY)
{
    POINT p = { clientX, clientY };
    ClientToScreen(__hwnd, &p);
    SetCursorPos(p.x, p.y);
}

LRESULT CALLBACK __WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    static gameplay::Game* game = gameplay::Game::getInstance();

    if (!game->isInitialized() || hwnd != __hwnd)
    {
        // Ignore messages that are not for our game window.
        return DefWindowProc(hwnd, msg, wParam, lParam);
    }

    static int lx, ly;

    static bool shiftDown = false;
    static bool capsOn = false;

    switch (msg)
    {
    case WM_CLOSE:
        DestroyWindow(__hwnd);
        return 0;

    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;

    case WM_LBUTTONDOWN:
    {
        int x = GET_X_LPARAM(lParam);
        int y = GET_Y_LPARAM(lParam);

        UpdateCapture(wParam);
        if (!gameplay::Platform::mouseEventInternal(gameplay::Mouse::MOUSE_PRESS_LEFT_BUTTON, x, y, 0))
        {
            gameplay::Platform::touchEventInternal(gameplay::Touch::TOUCH_PRESS, x, y, 0);
        }
        return 0;
    }
    case WM_LBUTTONUP:
    {
        int x = GET_X_LPARAM(lParam);
        int y = GET_Y_LPARAM(lParam);

        if (!gameplay::Platform::mouseEventInternal(gameplay::Mouse::MOUSE_RELEASE_LEFT_BUTTON, x, y, 0))
        {
            gameplay::Platform::touchEventInternal(gameplay::Touch::TOUCH_RELEASE, x, y, 0);
        }
        UpdateCapture(wParam);
        return 0;
    }
    case WM_RBUTTONDOWN:
        UpdateCapture(wParam);
        lx = GET_X_LPARAM(lParam);
        ly = GET_Y_LPARAM(lParam);
        gameplay::Platform::mouseEventInternal(gameplay::Mouse::MOUSE_PRESS_RIGHT_BUTTON, lx, ly, 0);
        break;

    case WM_RBUTTONUP:
        gameplay::Platform::mouseEventInternal(gameplay::Mouse::MOUSE_RELEASE_RIGHT_BUTTON, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), 0);
        UpdateCapture(wParam);
        break;

    case WM_MBUTTONDOWN:
        UpdateCapture(wParam);
        gameplay::Platform::mouseEventInternal(gameplay::Mouse::MOUSE_PRESS_MIDDLE_BUTTON, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), 0);
        break;

    case WM_MBUTTONUP:
        gameplay::Platform::mouseEventInternal(gameplay::Mouse::MOUSE_RELEASE_MIDDLE_BUTTON, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), 0);
        UpdateCapture(wParam);
        break;

    case WM_MOUSEMOVE:
    {
        int x = GET_X_LPARAM(lParam);
        int y = GET_Y_LPARAM(lParam);

        if (__mouseCaptured)
        {
            // If the incoming position is the mouse capture point, ignore this event
            // since this is the event that warped the cursor back.
            if (x == __mouseCapturePoint.x && y == __mouseCapturePoint.y)
                break;

            // Convert to deltas
            x -= __mouseCapturePoint.x;
            y -= __mouseCapturePoint.y;

            // Warp mouse back to center of screen.
            WarpMouse(__mouseCapturePoint.x, __mouseCapturePoint.y);
        }

        // Allow Game::mouseEvent a chance to handle (and possibly consume) the event.
        if (!gameplay::Platform::mouseEventInternal(gameplay::Mouse::MOUSE_MOVE, x, y, 0))
        {
            if ((wParam & MK_LBUTTON) == MK_LBUTTON)
            {
                // Mouse move events should be interpreted as touch move only if left mouse is held and the game did not consume the mouse event.
                gameplay::Platform::touchEventInternal(gameplay::Touch::TOUCH_MOVE, x, y, 0);
                return 0;
            }
            else if ((wParam & MK_RBUTTON) == MK_RBUTTON)
            {
                // Scale factors for the mouse movement used to simulate the accelerometer.
                RECT clientRect;
                GetClientRect(__hwnd, &clientRect);
                const float xFactor = 90.0f / clientRect.right;
                const float yFactor = 90.0f / clientRect.bottom;

                // Update the pitch and roll by adding the scaled deltas.
                __roll += (float)(x - lx) * xFactor;
                __pitch += -(float)(y - ly) * yFactor;

                // Clamp the values to the valid range.
                __roll = max(min(__roll, 90.0f), -90.0f);
                __pitch = max(min(__pitch, 90.0f), -90.0f);

                // Update the last X/Y values.
                lx = x;
                ly = y;
            }
        }
        break;
    }

    case WM_MOUSEWHEEL:
        tagPOINT point;
        point.x = GET_X_LPARAM(lParam);
        point.y = GET_Y_LPARAM(lParam);
        ScreenToClient(__hwnd, &point);
        gameplay::Platform::mouseEventInternal(gameplay::Mouse::MOUSE_WHEEL, point.x, point.y, GET_WHEEL_DELTA_WPARAM(wParam) / 120);
        break;

    case WM_KEYDOWN:
        if (wParam == VK_SHIFT || wParam == VK_LSHIFT || wParam == VK_RSHIFT)
            shiftDown = true;

        if (wParam == VK_CAPITAL)
            capsOn = !capsOn;

        // Suppress key repeats.
        if ((lParam & 0x40000000) == 0)
            gameplay::Platform::keyEventInternal(gameplay::Keyboard::KEY_PRESS, getKey(wParam, shiftDown ^ capsOn));
        break;
        
    case WM_KEYUP:
        if (wParam == VK_SHIFT || wParam == VK_LSHIFT || wParam == VK_RSHIFT)
            shiftDown = false;

        gameplay::Platform::keyEventInternal(gameplay::Keyboard::KEY_RELEASE, getKey(wParam, shiftDown ^ capsOn));
        break;

    case WM_CHAR:
        // Suppress key repeats.
        if ((lParam & 0x40000000) == 0)
            gameplay::Platform::keyEventInternal(gameplay::Keyboard::KEY_CHAR, wParam);
        break;

    case WM_UNICHAR:
        // Suppress key repeats.
        if ((lParam & 0x40000000) == 0)
            gameplay::Platform::keyEventInternal(gameplay::Keyboard::KEY_CHAR, wParam);
        break;

    case WM_SETFOCUS:
        break;

    case WM_KILLFOCUS:
        break;
    }
    
    return DefWindowProc(hwnd, msg, wParam, lParam); 
}


namespace gameplay
{

struct WindowCreationParams
{
    RECT rect;
    std::wstring windowName;
    bool fullscreen;
    int samples;
};

extern void print(const char* format, ...)
{
    va_list argptr;
    va_start(argptr, format);
    int sz = vfprintf(stderr, format, argptr);
    if (sz > 0)
    {
        char* buf = new char[sz + 1];
        vsprintf(buf, format, argptr);
        buf[sz] = 0;
        OutputDebugStringA(buf);
        SAFE_DELETE_ARRAY(buf);
    }
    va_end(argptr);
}

Platform::Platform(Game* game)
    : _game(game)
{
}

Platform::~Platform()
{
    if (__hwnd)
    {
        DestroyWindow(__hwnd);
        __hwnd = 0;
    }
#ifdef USE_XINPUT
    SAFE_DELETE_ARRAY(__xinputGamepads);
#endif
}

bool createWindow(WindowCreationParams* params, HWND* hwnd, HDC* hdc)
{
    bool fullscreen = false;
    RECT rect = { CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT };
    std::wstring windowName;
    if (params)
    {
        windowName = params->windowName;
        memcpy(&rect, &params->rect, sizeof(RECT));
        fullscreen = params->fullscreen;
    }

    // Set the window style.
    DWORD style   = (fullscreen ? WS_POPUP : WS_POPUP | WS_BORDER | WS_CAPTION | WS_SYSMENU) | WS_CLIPCHILDREN | WS_CLIPSIBLINGS;
    DWORD styleEx = (fullscreen ? WS_EX_APPWINDOW : WS_EX_APPWINDOW | WS_EX_WINDOWEDGE);

    // Adjust the window rectangle so the client size is the requested size.
    AdjustWindowRectEx(&rect, style, FALSE, styleEx);

    // Create the native Windows window.
    *hwnd = CreateWindowEx(styleEx, L"gameplay", windowName.c_str(), style, 0, 0, rect.right - rect.left, rect.bottom - rect.top, NULL, NULL, __hinstance, NULL);
    if (*hwnd == NULL)
    {
        GP_ERROR("Failed to create window.");
        return false;
    }

    // Get the drawing context.
    *hdc = GetDC(*hwnd);
    if (*hdc == NULL)
    {
        GP_ERROR("Failed to get device context.");
        return false;
    }

    // Center the window
    GetWindowRect(*hwnd, &rect);
    const int screenX = (GetSystemMetrics(SM_CXSCREEN) - rect.right) / 2;
    const int screenY = (GetSystemMetrics(SM_CYSCREEN) - rect.bottom) / 2;
    SetWindowPos(*hwnd, *hwnd, screenX, screenY, -1, -1, SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE);

    return true;
}

bool initializeGL(WindowCreationParams* params)
{
    // Create a temporary window and context to we can initialize GLEW and get access
    // to additional OpenGL extension functions. This is a neccessary evil since the
    // function for querying GL extensions is a GL extension itself.
    HWND hwnd = NULL;
    HDC hdc = NULL;
    if (!createWindow(NULL, &hwnd, &hdc))
        return false;

    PIXELFORMATDESCRIPTOR pfd;
    memset(&pfd, 0, sizeof(PIXELFORMATDESCRIPTOR));
    pfd.nSize  = sizeof(PIXELFORMATDESCRIPTOR);
    pfd.nVersion   = 1;
    pfd.dwFlags    = PFD_DOUBLEBUFFER | PFD_SUPPORT_OPENGL | PFD_DRAW_TO_WINDOW;
    pfd.iPixelType = PFD_TYPE_RGBA;
    pfd.cColorBits = DEFAULT_COLOR_BUFFER_SIZE;
    pfd.cDepthBits = DEFAULT_DEPTH_BUFFER_SIZE;
    pfd.cStencilBits = DEFAULT_STENCIL_BUFFER_SIZE;
    pfd.iLayerType = PFD_MAIN_PLANE;

    int pixelFormat = ChoosePixelFormat(hdc, &pfd);
    if (pixelFormat == 0)
    {
        DestroyWindow(hwnd);
        GP_ERROR("Failed to choose a pixel format.");
        return false;
    }

    if (!SetPixelFormat(hdc, pixelFormat, &pfd))
    {
        DestroyWindow(hwnd);
        GP_ERROR("Failed to set the pixel format.");
        return false;
    }

    HGLRC tempContext = wglCreateContext(hdc);
    if (!tempContext)
    {
        DestroyWindow(hwnd);
        GP_ERROR("Failed to create temporary context for initialization.");
        return false;
    }
    wglMakeCurrent(hdc, tempContext);

    // Initialize GLEW
    if (GLEW_OK != glewInit())
    {
        wglDeleteContext(tempContext);
        DestroyWindow(hwnd);
        GP_ERROR("Failed to initialize GLEW.");
        return false;
    }

    // Choose pixel format using wglChoosePixelFormatARB, which allows us to specify
    // additional attributes such as multisampling.
    //
    // Note: Keep multisampling attributes at the start of the attribute lists since code below
    // assumes they are array elements 0 through 3.
    int attribList[] = {
        WGL_SAMPLES_ARB, params->samples,
        WGL_SAMPLE_BUFFERS_ARB, params->samples > 0 ? 1 : 0,
        WGL_DRAW_TO_WINDOW_ARB, GL_TRUE,
        WGL_SUPPORT_OPENGL_ARB, GL_TRUE,
        WGL_DOUBLE_BUFFER_ARB, GL_TRUE,
        WGL_PIXEL_TYPE_ARB, WGL_TYPE_RGBA_ARB,
        WGL_COLOR_BITS_ARB, DEFAULT_COLOR_BUFFER_SIZE,
        WGL_DEPTH_BITS_ARB, DEFAULT_DEPTH_BUFFER_SIZE,
        WGL_STENCIL_BITS_ARB, DEFAULT_STENCIL_BUFFER_SIZE,
        0
    };
    UINT numFormats;
    if (!wglChoosePixelFormatARB(hdc, attribList, NULL, 1, &pixelFormat, &numFormats) || numFormats == 0)
    {
        bool valid = false;
        if (params->samples > 0)
        {
            GP_WARN("Failed to choose pixel format with WGL_SAMPLES_ARB == %d. Attempting to fallback to lower samples setting.", params->samples);
            while (params->samples > 0)
            {
                params->samples /= 2;
                attribList[1] = params->samples;
                attribList[3] = params->samples > 0 ? 1 : 0;
                if (wglChoosePixelFormatARB(hdc, attribList, NULL, 1, &pixelFormat, &numFormats) && numFormats > 0)
                {
                    valid = true;
                    GP_WARN("Found pixel format with WGL_SAMPLES_ARB == %d.", params->samples);
                    break;
                }
            }
        }

        if (!valid)
        {
            wglDeleteContext(tempContext);
            DestroyWindow(hwnd);
            GP_ERROR("Failed to choose a pixel format.");
            return false;
        }
    }

    // Destroy old window
    DestroyWindow(hwnd);
    hwnd = NULL;
    hdc = NULL;

    // Create new/final window if needed
    if (params)
    {
        if (!createWindow(params, &__hwnd, &__hdc))
        {
            wglDeleteContext(tempContext);
            return false;
        }
    }

    // Set final pixel format for window
    if (!SetPixelFormat(__hdc, pixelFormat, &pfd))
    {
        GP_ERROR("Failed to set the pixel format: %d.", (int)GetLastError());
        return false;
    }

    // Create our new GL context
    int attribs[] =
    {
        WGL_CONTEXT_MAJOR_VERSION_ARB, 3,
        WGL_CONTEXT_MINOR_VERSION_ARB, 1,
        0
    };

    if (!(__hrc = wglCreateContextAttribsARB(__hdc, 0, attribs) ) )
    {
        wglDeleteContext(tempContext);
        GP_ERROR("Failed to create OpenGL context.");
        return false;
    }

    // Delete the old/temporary context and window
    wglDeleteContext(tempContext);

    // Make the new context current
    if (!wglMakeCurrent(__hdc, __hrc) || !__hrc)
    {
        GP_ERROR("Failed to make the window current.");
        return false;
    }

    // Vertical sync.
    wglSwapIntervalEXT(__vsync ? 1 : 0);

    return true;
}

Platform* Platform::create(Game* game, void* attachToWindow)
{
    GP_ASSERT(game);

    FileSystem::setResourcePath("./");
    Platform* platform = new Platform(game);

    // Get the application module handle.
    __hinstance = ::GetModuleHandle(NULL);

    __attachToWindow = (HWND)attachToWindow;

    // Read window settings from config.
    WindowCreationParams params;
    params.fullscreen = false;
    params.rect.left = 0;
    params.rect.top = 0;
    params.rect.right = 0;
    params.rect.bottom = 0;
    params.samples = 0;
    if (game->getConfig())
    {
        Properties* config = game->getConfig()->getNamespace("window", true);
        if (config)
        {
            // Read window title.
            const char* title = config->getString("title");
            if (title)
            {
                int len = MultiByteToWideChar(CP_ACP, 0, title, -1, NULL, 0);
                wchar_t* wtitle = new wchar_t[len];
                MultiByteToWideChar(CP_ACP, 0, title, -1, wtitle, len);
                params.windowName = wtitle;
                SAFE_DELETE_ARRAY(wtitle);
            }

            // Read window rect.
            int x = config->getInt("x");
            if (x != 0)
                params.rect.left = x;
            int y = config->getInt("y");
            if (y != 0)
                params.rect.top = y;
            int width = config->getInt("width");
            if (width != 0)
                params.rect.right = params.rect.left + width;
            int height = config->getInt("height");
            if (height != 0)
                params.rect.bottom = params.rect.top + height;

            // Read fullscreen state.
            params.fullscreen = config->getBool("fullscreen");

            // Read multisampling state.
            params.samples = config->getInt("samples");
        }
    }

    // If window size was not specified, set it to a default value
    if (params.rect.right == 0)
        params.rect.right = params.rect.left + DEFAULT_RESOLUTION_X;
    if (params.rect.bottom == 0)
        params.rect.bottom = params.rect.top + DEFAULT_RESOLUTION_Y;
    int width = params.rect.right - params.rect.left;
    int height = params.rect.bottom - params.rect.top;

    if (params.fullscreen)
    {
        // Enumerate all supposed display settings
        bool modeSupported = false;
        DWORD modeNum = 0;
        DEVMODE devMode;
        memset(&devMode, 0, sizeof(DEVMODE));
        devMode.dmSize = sizeof(DEVMODE);
        devMode.dmDriverExtra = 0;
        while (EnumDisplaySettings(NULL, modeNum++, &devMode) != 0)
        {
            // Is mode supported?
            if (devMode.dmPelsWidth == width &&
                devMode.dmPelsHeight == height &&
                devMode.dmBitsPerPel == DEFAULT_COLOR_BUFFER_SIZE)
            {
                modeSupported = true;
                break;
            }
        }

        // If the requested mode is not supported, fall back to a safe default
        if (!modeSupported)
        {
            width = DEFAULT_RESOLUTION_X;
            height = DEFAULT_RESOLUTION_Y;
            params.rect.right = params.rect.left + width;
            params.rect.bottom = params.rect.top + height;
        }
    }

    if (!__attachToWindow)
    {
        // Register our window class.
        WNDCLASSEX wc;
        wc.cbSize = sizeof(WNDCLASSEX);
        wc.style          = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
        wc.lpfnWndProc    = (WNDPROC)__WndProc;
        wc.cbClsExtra     = 0;
        wc.cbWndExtra     = 0;
        wc.hInstance      = __hinstance;
        wc.hIcon          = LoadIcon(NULL, IDI_APPLICATION);
        wc.hIconSm        = NULL;
        wc.hCursor        = LoadCursor(NULL, IDC_ARROW);
        wc.hbrBackground  = NULL;  // No brush - we are going to paint our own background
        wc.lpszMenuName   = NULL;  // No default menu
        wc.lpszClassName  = L"gameplay";

        if (!::RegisterClassEx(&wc))
        {
            GP_ERROR("Failed to register window class.");
            goto error;
        }

        if (params.fullscreen)
        {
            DEVMODE dm;
            memset(&dm, 0, sizeof(dm));
            dm.dmSize= sizeof(dm);
            dm.dmPelsWidth	= width;
            dm.dmPelsHeight	= height;
            dm.dmBitsPerPel	= DEFAULT_COLOR_BUFFER_SIZE;
            dm.dmFields = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT;

            // Try to set selected mode and get results. NOTE: CDS_FULLSCREEN gets rid of start bar.
            if (ChangeDisplaySettings(&dm, CDS_FULLSCREEN) != DISP_CHANGE_SUCCESSFUL)
            {
                params.fullscreen = false;
                GP_ERROR("Failed to start game in full-screen mode with resolution %dx%d.", width, height);
                goto error;
            }
        }

        if (!initializeGL(&params))
            goto error;

        // Show the window.
        ShowWindow(__hwnd, SW_SHOW);
    }
    else
    {
        // Attach to a previous windows
        __hwnd = (HWND)__attachToWindow;
        __hdc = GetDC(__hwnd);

        SetWindowLongPtr(__hwnd, GWLP_WNDPROC, (LONG)(WNDPROC)__WndProc);

        if (!initializeGL(NULL))
            goto error;
    }

#ifdef USE_XINPUT
    // Initialize XInputGamepads.
    __xinputGamepads = new XInputGamepad[XUSER_MAX_COUNT];
    for (unsigned int i = 0; i < XUSER_MAX_COUNT; i++)
    {
        switch(i)
        {
        case 0:
            __xinputGamepads[i].id = "XINPUT 1";
            break;
        case 1:
            __xinputGamepads[i].id = "XINPUT 2";
            break;
        case 2:
            __xinputGamepads[i].id = "XINPUT 3";
            break;
        case 3:
            __xinputGamepads[i].id = "XINPUT 4";
            break;
        }
        __xinputGamepads[i].connected = false;
        __xinputGamepads[i].handle = -1;
    }
#endif

    return platform;

error:

    exit(0);
    return NULL;
}

int Platform::enterMessagePump()
{
    GP_ASSERT(_game);

    // Get the initial time.
    LARGE_INTEGER tps;
    QueryPerformanceFrequency(&tps);
    __timeTicksPerMillis = (double)(tps.QuadPart / 1000L);
    LARGE_INTEGER queryTime;
    QueryPerformanceCounter(&queryTime);
    GP_ASSERT(__timeTicksPerMillis);
    __timeStart = queryTime.QuadPart / __timeTicksPerMillis;

    // Set the initial pitch and roll values.
    __pitch = 0.0;
    __roll = 0.0;

    SwapBuffers(__hdc);

    if (_game->getState() != Game::RUNNING)
        _game->run();

    if (__attachToWindow)
        return 0;

    // Enter event dispatch loop.
    MSG msg;
    while (true)
    {
        if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);

            if (msg.message == WM_QUIT)
            {
                _game->exit();
                return msg.wParam;
            }
        }
        else
        {
            _game->frame();
            SwapBuffers(__hdc);
        }

        // If we are done, then exit.
        if (_game->getState() == Game::UNINITIALIZED)
            break;
    }
    return 0;
}

void Platform::signalShutdown() 
{
    // nothing to do  
}

bool Platform::canExit()
{
    return true;
}

unsigned int Platform::getDisplayWidth()
{
    static RECT rect;
    GetClientRect(__hwnd, &rect);
    return rect.right;
}

unsigned int Platform::getDisplayHeight()
{
    static RECT rect;
    GetClientRect(__hwnd, &rect);
    return rect.bottom;
}

double Platform::getAbsoluteTime()
{
    LARGE_INTEGER queryTime;
    QueryPerformanceCounter(&queryTime);
    GP_ASSERT(__timeTicksPerMillis);
    __timeAbsolute = queryTime.QuadPart / __timeTicksPerMillis;

    return __timeAbsolute - __timeStart;
}

void Platform::setAbsoluteTime(double time)
{
    __timeAbsolute = time;
}

bool Platform::isVsync()
{
    return __vsync;
}

void Platform::setVsync(bool enable)
{
     wglSwapIntervalEXT(enable ? 1 : 0);
    __vsync = enable;
}

void Platform::swapBuffers()
{
    if (__hdc)
        SwapBuffers(__hdc);
}

void Platform::sleep(long ms)
{
    Sleep(ms);
}

void Platform::setMultiTouch(bool enabled)
{
    // not supported
}

bool Platform::isMultiTouch()
{
    return false;
}

void Platform::getAccelerometerValues(float* pitch, float* roll)
{
    GP_ASSERT(pitch);
    GP_ASSERT(roll);

    *pitch = __pitch;
    *roll = __roll;
}

bool Platform::hasMouse()
{
    return true;
}

void Platform::setMouseCaptured(bool captured)
{
    if (captured != __mouseCaptured)
    {
        if (captured)
        {
            // Hide the cursor and warp it to the center of the screen
            __mouseCapturePoint.x = getDisplayWidth() / 2;
            __mouseCapturePoint.y = getDisplayHeight() / 2;

            ShowCursor(FALSE);
            WarpMouse(__mouseCapturePoint.x, __mouseCapturePoint.y);
        }
        else
        {
            // Restore cursor
            WarpMouse(__mouseCapturePoint.x, __mouseCapturePoint.y);
            ShowCursor(TRUE);
        }

        __mouseCaptured = captured;
    }
}

bool Platform::isMouseCaptured()
{
    return __mouseCaptured;
}

void Platform::setCursorVisible(bool visible)
{
    if (visible != __cursorVisible)
    {
        ShowCursor(visible ? TRUE : FALSE);
        __cursorVisible = visible;
    }
}

bool Platform::isCursorVisible()
{
    return __cursorVisible;
}

void Platform::displayKeyboard(bool display)
{
    // Do nothing.
}

bool Platform::isGestureSupported(Gesture::GestureEvent evt)
{
    return false;
}

void Platform::registerGesture(Gesture::GestureEvent evt)
{
}

void Platform::unregisterGesture(Gesture::GestureEvent evt)
{
}
    
bool Platform::isGestureRegistered(Gesture::GestureEvent evt)
{
    return false;
}

unsigned int Platform::getGamepadsConnected()
{
    // Check to see what xbox360 gamepads are connected.
    __gamepadsConnected = 0;

#ifdef USE_XINPUT
    for (unsigned int i = 0; i < XUSER_MAX_COUNT; i++)
    {
        if (isGamepadConnected(i))
            __gamepadsConnected++;
    }    
#endif
    return __gamepadsConnected;
}

bool Platform::isGamepadConnected(unsigned int gamepadHandle)
{

#ifdef USE_XINPUT
    GP_ASSERT(0 <= gamepadHandle);
    GP_ASSERT(gamepadHandle < XUSER_MAX_COUNT);
    Game* game = Game::getInstance();
    GP_ASSERT(game);

    if (__xinputGamepads[gamepadHandle].handle == -1)
        __xinputGamepads[gamepadHandle].handle = game->createGamepad(__xinputGamepads[gamepadHandle].id.c_str(), gamepadHandle, __xinputGamepads[gamepadHandle].BUTTON_COUNT, 
            __xinputGamepads[gamepadHandle].JOYSTICK_COUNT, __xinputGamepads[gamepadHandle].TRIGGER_COUNT);

    bool isConnected = __xinputGamepads[gamepadHandle].connected;
    if (getXInputState(gamepadHandle))
    {
        if (!isConnected)
        {
            __xinputGamepads[gamepadHandle].connected = true;
            
            Gamepad* gamepad = game->getGamepad(__xinputGamepads[gamepadHandle].handle);
            GP_ASSERT(gamepad);
            if (game->isInitialized())
                game->gamepadEvent(Gamepad::CONNECTED_EVENT, game->getGamepad(__xinputGamepads[gamepadHandle].handle)); 
        }
        return true;
    }
    else
    {
        if (isConnected)
        {
            // if it was connected, but now isn't pass the detached event to gamepadEvent()
            __xinputGamepads[gamepadHandle].connected = false;
            
            Gamepad* gamepad = game->getGamepad(__xinputGamepads[gamepadHandle].handle);
            GP_ASSERT(gamepad);
            if (game->isInitialized())
                game->gamepadEvent(Gamepad::DISCONNECTED_EVENT, game->getGamepad(__xinputGamepads[gamepadHandle].handle));
        }
        return false;
    }
#endif

    return false;
}

const char* Platform::getGamepadId(unsigned int gamepadHandle) 
{

#ifdef USE_XINPUT
    GP_ASSERT(0 <= gamepadHandle);
    GP_ASSERT(gamepadHandle < XUSER_MAX_COUNT);

    return __xinputGamepads[gamepadHandle].id.c_str();
#endif
    return NULL;
}

unsigned int Platform::getGamepadButtonCount(unsigned int gamepadHandle)
{

#ifdef USE_XINPUT
    GP_ASSERT(0 <= gamepadHandle);
    GP_ASSERT(gamepadHandle < XUSER_MAX_COUNT);
    if (!__xinputGamepads[gamepadHandle].connected)
        return 0;

    return XInputGamepad::BUTTON_COUNT;
#endif

    return 0;
}

bool Platform::getGamepadButtonState(unsigned int gamepadHandle, unsigned int buttonIndex)
{

#ifdef USE_XINPUT
    GP_ASSERT(0 <= gamepadHandle);
    GP_ASSERT(gamepadHandle < XUSER_MAX_COUNT);
    return getXInputButtonState(gamepadHandle, buttonIndex);
#endif

    return false;
}

unsigned int Platform::getGamepadJoystickCount(unsigned int gamepadHandle)
{

#ifdef USE_XINPUT
    GP_ASSERT(0 <= gamepadHandle);
    GP_ASSERT(gamepadHandle < XUSER_MAX_COUNT);

    if (!__xinputGamepads[gamepadHandle].connected)
        return 0;

    return XInputGamepad::JOYSTICK_COUNT;
#endif

    return 0;
}

float Platform::getGamepadJoystickAxisX(unsigned int gamepadHandle, unsigned int joystickIndex)
{
    
#ifdef USE_XINPUT
    GP_ASSERT(0 <= gamepadHandle);
    GP_ASSERT(gamepadHandle < XUSER_MAX_COUNT);

    return getXInputJoystickAxisX(gamepadHandle, joystickIndex);
#endif 

    return 0.0f;
}

float Platform::getGamepadJoystickAxisY(unsigned int gamepadHandle, unsigned int joystickIndex)
{

#ifdef USE_XINPUT
    GP_ASSERT(0 <= gamepadHandle);
    GP_ASSERT(gamepadHandle < XUSER_MAX_COUNT);

    return getXInputJoystickAxisY(gamepadHandle, joystickIndex);
#endif

    return 0.0f;
}

void Platform::getGamepadJoystickAxisValues(unsigned int gamepadHandle, unsigned int joystickIndex, Vector2* outValue)
{
    
#ifdef USE_XINPUT
    GP_ASSERT(0 <= gamepadHandle);
    GP_ASSERT(gamepadHandle < XUSER_MAX_COUNT);

    getXInputJoystickAxisValues(gamepadHandle, joystickIndex, outValue);
#endif

}

bool Platform::isGamepadJoystickActive(unsigned int gamepadHandle, unsigned int joystickIndex)
{
    
#ifdef USE_XINPUT
    GP_ASSERT(0 <= gamepadHandle);
    GP_ASSERT(gamepadHandle < XUSER_MAX_COUNT);

    return (getXInputJoystickAxisX(gamepadHandle, joystickIndex) != 0.0f || getXInputJoystickAxisY(gamepadHandle, joystickIndex) != 0.0f);
#endif 

    return false;
}

unsigned int Platform::getGamepadTriggerCount(unsigned int gamepadHandle)
{

#ifdef USE_XINPUT
    GP_ASSERT(0 <= gamepadHandle);
    GP_ASSERT(gamepadHandle < XUSER_MAX_COUNT);

    return XInputGamepad::TRIGGER_COUNT;
#endif

    return 0;
}

float Platform::getGamepadTriggerValue(unsigned int gamepadHandle, unsigned int triggerIndex)
{
    
#ifdef USE_XINPUT
    GP_ASSERT(0 <= gamepadHandle);
    GP_ASSERT(gamepadHandle < XUSER_MAX_COUNT);
    //TODO:
#endif
    return 0.0f;
}

void Platform::touchEventInternal(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex)
{
    if (!Form::touchEventInternal(evt, x, y, contactIndex))
    {
        Game::getInstance()->touchEvent(evt, x, y, contactIndex);
        Game::getInstance()->getScriptController()->touchEvent(evt, x, y, contactIndex);
    }
}

void Platform::keyEventInternal(Keyboard::KeyEvent evt, int key)
{
    if (!Form::keyEventInternal(evt, key))
    {
        Game::getInstance()->keyEvent(evt, key);
        Game::getInstance()->getScriptController()->keyEvent(evt, key);
    }
}

bool Platform::mouseEventInternal(Mouse::MouseEvent evt, int x, int y, int wheelDelta)
{
    if (Form::mouseEventInternal(evt, x, y, wheelDelta))
    {
        return true;
    }
    else if (Game::getInstance()->mouseEvent(evt, x, y, wheelDelta))
    {
        return true;
    }
    else
    {
        return Game::getInstance()->getScriptController()->mouseEvent(evt, x, y, wheelDelta);
    }
}

}

#endif
