//--------------------------------------------------------------------------------------
// File: Mouse.cpp
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// http://go.microsoft.com/fwlink/?LinkId=248929
//--------------------------------------------------------------------------------------

#include "pch.h"
#include "Mouse.h"

#include "PlatformHelpers.h"

using namespace DirectX;
using Microsoft::WRL::ComPtr;


#if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP)

//======================================================================================
// Windows Store or Universal Windows Platform (UWP) app implementation
//======================================================================================

//
// For a Windows Store app or Universal Windows Platform (UWP) app, add the following to your existing
// application methods:
//
// void App::SetWindow(CoreWindow^ window )
// {
//     m_mouse->SetWindow(window);
// }
// 
// void App::OnDpiChanged(DisplayInformation^ sender, Object^ args)
// {
//     m_mouse->SetDpi(sender->LogicalDpi);
// }
//

#include <Windows.Devices.Input.h>

class Mouse::Impl
{
public:
    Impl(Mouse* owner) :
        mOwner(owner),
        mDPI(96.f),
        mMode(MODE_ABSOLUTE)
    {
        mPointerPressedToken.value = 0;
        mPointerReleasedToken.value = 0;
        mPointerMovedToken.value = 0;
        mPointerWheelToken.value = 0;
        mPointerMouseMovedToken.value = 0;
        
        if ( s_mouse )
        {
            throw std::exception( "Mouse is a singleton" );
        }

        s_mouse = this;

        memset( &mState, 0, sizeof(State) );

        mScrollWheelValue.reset( CreateEventEx(nullptr, nullptr, CREATE_EVENT_MANUAL_RESET, EVENT_MODIFY_STATE | SYNCHRONIZE) );
        mRelativeRead.reset( CreateEventEx(nullptr, nullptr, CREATE_EVENT_MANUAL_RESET, EVENT_MODIFY_STATE | SYNCHRONIZE) );
        if ( !mScrollWheelValue 
             || !mRelativeRead ) 
        {
            throw std::exception( "CreateEventEx" );
        }
    }

    ~Impl()
    {
        s_mouse = nullptr;

        RemoveHandlers();
    }

    void GetState(State& state) const
    {
        memcpy( &state, &mState, sizeof(State) );

        DWORD result = WaitForSingleObjectEx( mScrollWheelValue.get(), 0, FALSE );
        if ( result == WAIT_FAILED )
            throw std::exception( "WaitForSingleObjectEx" );

        if ( result == WAIT_OBJECT_0 )
        {
            state.scrollWheelValue = 0;
        }

        if (mMode == MODE_RELATIVE)
        {
            result = WaitForSingleObjectEx( mRelativeRead.get(), 0, FALSE );

            if (result == WAIT_FAILED)
                throw std::exception("WaitForSingleObjectEx");

            if (result == WAIT_OBJECT_0)
            {
                state.x = 0;
                state.y = 0;
            }
            else
            {
                SetEvent(mRelativeRead.get());
            }
        }

        state.positionMode = mMode;
    }

    void ResetScrollWheelValue()
    {
        SetEvent(mScrollWheelValue.get());
    }

    void SetMode(Mode mode)
    {
        using namespace Microsoft::WRL;
        using namespace Microsoft::WRL::Wrappers;
        using namespace ABI::Windows::UI::Core;
        using namespace ABI::Windows::Foundation;

        if (mMode == mode)
            return;

        ComPtr<ICoreWindowStatic> statics;
        HRESULT hr = GetActivationFactory( HStringReference(RuntimeClass_Windows_UI_Core_CoreWindow).Get(), statics.GetAddressOf() );
        ThrowIfFailed( hr );

        ComPtr<ICoreWindow> window;
        hr = statics->GetForCurrentThread( window.GetAddressOf() );
        ThrowIfFailed( hr );

        if (mode == MODE_RELATIVE)
        {
            hr = window->get_PointerCursor( mCursor.ReleaseAndGetAddressOf() );
            ThrowIfFailed(hr);

            hr = window->put_PointerCursor(nullptr);
            ThrowIfFailed(hr);

            SetEvent(mRelativeRead.get());

            mMode = MODE_RELATIVE;
        }
        else
        {
            if (!mCursor)
            {
                ComPtr<ICoreCursorFactory> factory;
                hr = GetActivationFactory( HStringReference(RuntimeClass_Windows_UI_Core_CoreCursor).Get(), factory.GetAddressOf() );
                ThrowIfFailed( hr );

                hr = factory->CreateCursor( CoreCursorType_Arrow, 0, mCursor.GetAddressOf() );
                ThrowIfFailed( hr );
            }

            hr = window->put_PointerCursor( mCursor.Get() );
            ThrowIfFailed(hr);

            mCursor.Reset();

            mMode = MODE_ABSOLUTE;
        }
    }

    bool IsConnected() const
    {
        using namespace Microsoft::WRL;
        using namespace Microsoft::WRL::Wrappers;
        using namespace ABI::Windows::Devices::Input;
        using namespace ABI::Windows::Foundation;

        ComPtr<IMouseCapabilities> caps;
        HRESULT hr = RoActivateInstance(HStringReference(RuntimeClass_Windows_Devices_Input_MouseCapabilities).Get(), &caps);
        ThrowIfFailed(hr);

        INT32 value;
        if (SUCCEEDED(caps->get_MousePresent(&value)))
        {
            return value != 0;
        }

        return false;
    }

    void SetWindow(ABI::Windows::UI::Core::ICoreWindow* window)
    {
        using namespace Microsoft::WRL;
        using namespace Microsoft::WRL::Wrappers;
        using namespace ABI::Windows::Foundation;
        using namespace ABI::Windows::Devices::Input;

        if (mWindow.Get() == window)
            return;

        RemoveHandlers();

        mWindow = window;

        if (!window)
        {
            mCursor.Reset();
            mMouse.Reset();
            return;
        }

        ComPtr<IMouseDeviceStatics> mouseStatics;
        HRESULT hr = GetActivationFactory( HStringReference(RuntimeClass_Windows_Devices_Input_MouseDevice).Get(), mouseStatics.GetAddressOf() );
        ThrowIfFailed( hr );

        hr = mouseStatics->GetForCurrentView(mMouse.ReleaseAndGetAddressOf());
        ThrowIfFailed(hr);

        typedef __FITypedEventHandler_2_Windows__CDevices__CInput__CMouseDevice_Windows__CDevices__CInput__CMouseEventArgs MouseMovedHandler;
        hr = mMouse->add_MouseMoved( Callback<MouseMovedHandler>(MouseMovedEvent).Get(), &mPointerMouseMovedToken );
        ThrowIfFailed(hr);

        typedef __FITypedEventHandler_2_Windows__CUI__CCore__CCoreWindow_Windows__CUI__CCore__CPointerEventArgs PointerHandler;
        auto cb = Callback<PointerHandler>(PointerEvent);

        hr = window->add_PointerPressed(cb.Get(), &mPointerPressedToken);
        ThrowIfFailed(hr);

        hr = window->add_PointerReleased(cb.Get(), &mPointerReleasedToken);
        ThrowIfFailed(hr);

        hr = window->add_PointerMoved(cb.Get(), &mPointerMovedToken);
        ThrowIfFailed(hr);

        hr = window->add_PointerWheelChanged(Callback<PointerHandler>(PointerWheel).Get(), &mPointerWheelToken);
        ThrowIfFailed(hr);
    }

    State           mState;
    Mouse*          mOwner;
    float           mDPI;

    static Mouse::Impl* s_mouse;

private:
    Mode            mMode;

    ComPtr<ABI::Windows::UI::Core::ICoreWindow> mWindow;
    ComPtr<ABI::Windows::Devices::Input::IMouseDevice> mMouse;
    ComPtr<ABI::Windows::UI::Core::ICoreCursor> mCursor;

    ScopedHandle    mScrollWheelValue;
    ScopedHandle    mRelativeRead;

    EventRegistrationToken mPointerPressedToken;
    EventRegistrationToken mPointerReleasedToken;
    EventRegistrationToken mPointerMovedToken;
    EventRegistrationToken mPointerWheelToken;
    EventRegistrationToken mPointerMouseMovedToken;

    void RemoveHandlers()
    {
        if (mWindow)
        {
            (void)mWindow->remove_PointerPressed(mPointerPressedToken);
            mPointerPressedToken.value = 0;

            (void)mWindow->remove_PointerReleased(mPointerReleasedToken);
            mPointerReleasedToken.value = 0;

            (void)mWindow->remove_PointerMoved(mPointerMovedToken);
            mPointerMovedToken.value = 0;

            (void)mWindow->remove_PointerWheelChanged(mPointerWheelToken);
            mPointerWheelToken.value = 0;
        }

        if (mMouse)
        {
            (void)mMouse->remove_MouseMoved(mPointerMouseMovedToken);
            mPointerMouseMovedToken.value = 0;
        }
    }

    static HRESULT PointerEvent( IInspectable *, ABI::Windows::UI::Core::IPointerEventArgs*args )
    {
        using namespace ABI::Windows::Foundation;
        using namespace ABI::Windows::UI::Input;
        using namespace ABI::Windows::Devices::Input;

        if (!s_mouse)
            return S_OK;

        ComPtr<IPointerPoint> currentPoint;
        HRESULT hr = args->get_CurrentPoint( currentPoint.GetAddressOf() );
        ThrowIfFailed(hr);

        ComPtr<IPointerDevice> pointerDevice;
        hr = currentPoint->get_PointerDevice( pointerDevice.GetAddressOf() );
        ThrowIfFailed(hr);

        PointerDeviceType devType;
        hr = pointerDevice->get_PointerDeviceType( &devType );
        ThrowIfFailed(hr);

        if (devType == PointerDeviceType::PointerDeviceType_Mouse)
        {
            ComPtr<IPointerPointProperties> props;
            hr = currentPoint->get_Properties( props.GetAddressOf() );
            ThrowIfFailed(hr);

            boolean value;
            hr = props->get_IsLeftButtonPressed(&value);
            ThrowIfFailed(hr);
            s_mouse->mState.leftButton = value != 0;

            hr = props->get_IsRightButtonPressed(&value);
            ThrowIfFailed(hr);
            s_mouse->mState.rightButton = value != 0;

            hr = props->get_IsMiddleButtonPressed(&value);
            ThrowIfFailed(hr);
            s_mouse->mState.middleButton = value != 0;

            hr = props->get_IsXButton1Pressed(&value);
            ThrowIfFailed(hr);
            s_mouse->mState.xButton1 = value != 0;

            hr = props->get_IsXButton2Pressed(&value);
            ThrowIfFailed(hr);
            s_mouse->mState.xButton2 = value != 0;
        }

        if (s_mouse->mMode == MODE_ABSOLUTE)
        {
            Point pos;
            hr = currentPoint->get_Position( &pos );
            ThrowIfFailed(hr);

            float dpi = s_mouse->mDPI;

            s_mouse->mState.x = static_cast<int>( pos.X * dpi / 96.f + 0.5f );
            s_mouse->mState.y = static_cast<int>( pos.Y * dpi / 96.f + 0.5f );
        }

        return S_OK;
    }

    static HRESULT PointerWheel( IInspectable *, ABI::Windows::UI::Core::IPointerEventArgs*args )
    {
        using namespace ABI::Windows::Foundation;
        using namespace ABI::Windows::UI::Input;
        using namespace ABI::Windows::Devices::Input;

        if (!s_mouse)
            return S_OK;

        ComPtr<IPointerPoint> currentPoint;
        HRESULT hr = args->get_CurrentPoint( currentPoint.GetAddressOf() );
        ThrowIfFailed(hr);

        ComPtr<IPointerDevice> pointerDevice;
        hr = currentPoint->get_PointerDevice( pointerDevice.GetAddressOf() );
        ThrowIfFailed(hr);

        PointerDeviceType devType;
        hr = pointerDevice->get_PointerDeviceType( &devType );
        ThrowIfFailed(hr);

        if (devType == PointerDeviceType::PointerDeviceType_Mouse)
        {
            ComPtr<IPointerPointProperties> props;
            hr = currentPoint->get_Properties( props.GetAddressOf() );
            ThrowIfFailed(hr);

            INT32 value;
            hr = props->get_MouseWheelDelta(&value);
            ThrowIfFailed(hr);

            HANDLE evt = s_mouse->mScrollWheelValue.get();
            if (WaitForSingleObjectEx(evt, 0, FALSE) == WAIT_OBJECT_0)
            {
                s_mouse->mState.scrollWheelValue = 0;
                ResetEvent(evt);
            }

            s_mouse->mState.scrollWheelValue += value;

            if (s_mouse->mMode == MODE_ABSOLUTE)
            {
                Point pos;
                hr = currentPoint->get_Position( &pos );
                ThrowIfFailed(hr);

                float dpi = s_mouse->mDPI;

                s_mouse->mState.x = static_cast<int>( pos.X * dpi / 96.f + 0.5f );
                s_mouse->mState.y = static_cast<int>( pos.Y * dpi / 96.f + 0.5f );
            }
        }

        return S_OK;
    }

    static HRESULT MouseMovedEvent( IInspectable *, ABI::Windows::Devices::Input::IMouseEventArgs* args )
    {
        using namespace ABI::Windows::Devices::Input;

        if (!s_mouse)
            return S_OK;

        if (s_mouse->mMode == MODE_RELATIVE)
        {
            MouseDelta delta;
            HRESULT hr = args->get_MouseDelta(&delta);
            ThrowIfFailed(hr);

            s_mouse->mState.x = delta.X;
            s_mouse->mState.y = delta.Y;

            ResetEvent( s_mouse->mRelativeRead.get() );
        }

        return S_OK;
    }
};


Mouse::Impl* Mouse::Impl::s_mouse = nullptr;


void Mouse::SetWindow(ABI::Windows::UI::Core::ICoreWindow* window)
{
    pImpl->SetWindow(window);
}


void Mouse::SetDpi(float dpi)
{
    auto pImpl = Impl::s_mouse;

    if (!pImpl)
        return;

    pImpl->mDPI = dpi;
}


#elif defined(_XBOX_ONE) || ( defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP) )

//======================================================================================
// Null device for Windows Phone and Xbox One
//======================================================================================

class Mouse::Impl
{
public:
    Impl(Mouse* owner) :
        mOwner(owner)
    {
        if ( s_mouse )
        {
            throw std::exception( "Mouse is a singleton" );
        }

        s_mouse = this;
    }

    ~Impl()
    {
        s_mouse = nullptr;
    }

    void GetState(State& state) const
    {
        memset( &state, 0, sizeof(State) );
    }

    void ResetScrollWheelValue()
    {
    }

    void SetMode(Mode mode)
    {
        UNREFERENCED_PARAMETER(mode);
    }

    bool IsConnected() const
    {
        return false;
    }

    Mouse*  mOwner;

    static Mouse::Impl* s_mouse;
};

Mouse::Impl* Mouse::Impl::s_mouse = nullptr;

#else

//======================================================================================
// Win32 desktop implementation
//======================================================================================

//
// For a Win32 desktop application, in your window setup be sure to call this method:
//
// m_mouse->SetWindow(hwnd);
//
// And call this static function from your Window Message Procedure
//
// LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
// {
//     switch (message)
//     {
//     case WM_ACTIVATEAPP:
//     case WM_INPUT:
//     case WM_MOUSEMOVE:
//     case WM_LBUTTONDOWN:
//     case WM_LBUTTONUP:
//     case WM_RBUTTONDOWN:
//     case WM_RBUTTONUP:
//     case WM_MBUTTONDOWN:
//     case WM_MBUTTONUP:
//     case WM_MOUSEWHEEL:
//     case WM_XBUTTONDOWN:
//     case WM_XBUTTONUP:
//     case WM_MOUSEHOVER:
//         Mouse::ProcessMessage(message, wParam, lParam);
//         break;
//
//     }
// }
//

class Mouse::Impl
{
public:
    Impl(Mouse* owner) :
        mOwner(owner),
        mWindow(nullptr),
        mMode(MODE_ABSOLUTE),
        mLastX(0),
        mLastY(0),
        mRelativeX(INT32_MAX),
        mRelativeY(INT32_MAX),
        mInFocus(true)
    {
        if ( s_mouse )
        {
            throw std::exception( "Mouse is a singleton" );
        }

        s_mouse = this;

        memset( &mState, 0, sizeof(State) );

        mScrollWheelValue.reset( CreateEventEx(nullptr, nullptr, CREATE_EVENT_MANUAL_RESET, EVENT_MODIFY_STATE | SYNCHRONIZE) );
        mRelativeRead.reset( CreateEventEx(nullptr, nullptr, CREATE_EVENT_MANUAL_RESET, EVENT_MODIFY_STATE | SYNCHRONIZE) );
        mAbsoluteMode.reset( CreateEventEx(nullptr, nullptr, 0, EVENT_MODIFY_STATE | SYNCHRONIZE) );
        mRelativeMode.reset( CreateEventEx(nullptr, nullptr, 0, EVENT_MODIFY_STATE | SYNCHRONIZE) );
        if ( !mScrollWheelValue 
             || !mRelativeRead
             || !mAbsoluteMode 
             || !mRelativeMode ) 
        {
            throw std::exception( "CreateEventEx" );
        }
    }

    ~Impl()
    {
        s_mouse = nullptr;
    }

    void GetState(State& state) const
    {
        memcpy( &state, &mState, sizeof(State) );
        state.positionMode = mMode;

        DWORD result = WaitForSingleObjectEx( mScrollWheelValue.get(), 0, FALSE );
        if ( result == WAIT_FAILED )
            throw std::exception( "WaitForSingleObjectEx" );

        if ( result == WAIT_OBJECT_0 )
        {
            state.scrollWheelValue = 0;
        }

        if (state.positionMode == MODE_RELATIVE)
        {
            result = WaitForSingleObjectEx( mRelativeRead.get(), 0, FALSE );

            if (result == WAIT_FAILED)
                throw std::exception("WaitForSingleObjectEx");

            if (result == WAIT_OBJECT_0)
            {
                state.x = 0;
                state.y = 0;
            }
            else
            {
                SetEvent(mRelativeRead.get());
            }
        }
    }

    void ResetScrollWheelValue()
    {
        SetEvent(mScrollWheelValue.get());
    }

    void SetMode(Mode mode)
    {
        if (mMode == mode)
            return;

        SetEvent((mode == MODE_ABSOLUTE) ? mAbsoluteMode.get() : mRelativeMode.get());

        assert(mWindow != 0);

        TRACKMOUSEEVENT tme;
        tme.cbSize = sizeof(tme);
        tme.dwFlags = TME_HOVER;
        tme.hwndTrack = mWindow;
        tme.dwHoverTime = 1;
        if (!TrackMouseEvent(&tme))
        {
            throw std::exception("TrackMouseEvent");
        }
    }

    bool IsConnected() const
    {
        return GetSystemMetrics(SM_MOUSEPRESENT) != 0;
    }

    void SetWindow(HWND window)
    {
        if (mWindow == window)
            return;

        assert(window != 0);

        RAWINPUTDEVICE Rid;
        Rid.usUsagePage = 0x1 /* HID_USAGE_PAGE_GENERIC */;
        Rid.usUsage = 0x2 /* HID_USAGE_GENERIC_MOUSE */;
        Rid.dwFlags = RIDEV_INPUTSINK;
        Rid.hwndTarget = window;
        if ( !RegisterRawInputDevices(&Rid, 1, sizeof(RAWINPUTDEVICE)) )
        {
            throw std::exception("RegisterRawInputDevices");
        }

        mWindow = window;
    }

    State           mState;

    Mouse*          mOwner;

    static Mouse::Impl* s_mouse;

private:
    HWND            mWindow;
    Mode            mMode;

    ScopedHandle    mScrollWheelValue;
    ScopedHandle    mRelativeRead;
    ScopedHandle    mAbsoluteMode;
    ScopedHandle    mRelativeMode;

    int             mLastX;
    int             mLastY;
    int             mRelativeX;
    int             mRelativeY;

    bool            mInFocus;

    friend void Mouse::ProcessMessage(UINT message, WPARAM wParam, LPARAM lParam);

    void ClipToWindow()
    {
        assert(mWindow != 0);

        RECT rect;
        GetClientRect(mWindow, &rect);

        POINT ul;
        ul.x = rect.left;
        ul.y = rect.top;

        POINT lr;
        lr.x = rect.right;
        lr.y = rect.bottom;

        MapWindowPoints(mWindow, nullptr, &ul, 1);
        MapWindowPoints(mWindow, nullptr, &lr, 1);

        rect.left = ul.x;
        rect.top = ul.y;

        rect.right = lr.x;
        rect.bottom = lr.y;

        ClipCursor(&rect);
    }
};


Mouse::Impl* Mouse::Impl::s_mouse = nullptr;


void Mouse::SetWindow( HWND window )
{
    pImpl->SetWindow(window);
}


void Mouse::ProcessMessage(UINT message, WPARAM wParam, LPARAM lParam)
{
    auto pImpl = Impl::s_mouse;

    if (!pImpl)
        return;

    HANDLE evts[3];
    evts[0] = pImpl->mScrollWheelValue.get();
    evts[1] = pImpl->mAbsoluteMode.get();
    evts[2] = pImpl->mRelativeMode.get();
    switch (WaitForMultipleObjectsEx(_countof(evts), evts, FALSE, 0, FALSE))
    {
    case WAIT_OBJECT_0:
        pImpl->mState.scrollWheelValue = 0;
        ResetEvent(evts[0]);
        break;

    case (WAIT_OBJECT_0 + 1) :
        {
            pImpl->mMode = MODE_ABSOLUTE;
            ClipCursor(nullptr);

            POINT point;
            point.x = pImpl->mLastX;
            point.y = pImpl->mLastY;
            if (MapWindowPoints(pImpl->mWindow, nullptr, &point, 1))
            {
                SetCursorPos(point.x, point.y);
            }
            ShowCursor(TRUE);
            pImpl->mState.x = pImpl->mLastX;
            pImpl->mState.y = pImpl->mLastY;
        }
        break;

    case (WAIT_OBJECT_0 + 2) :
        {
            ResetEvent( pImpl->mRelativeRead.get() );

            pImpl->mMode = MODE_RELATIVE;
            pImpl->mState.x = pImpl->mState.y = 0;
            pImpl->mRelativeX = INT32_MAX;
            pImpl->mRelativeY = INT32_MAX;

            ShowCursor(FALSE);

            pImpl->ClipToWindow();
        }
        break;

    case WAIT_FAILED:
        throw std::exception("WaitForMultipleObjectsEx");
    }

    switch (message)
    {
    case WM_ACTIVATEAPP:
        if (wParam)
        {
            pImpl->mInFocus = true;

            if (pImpl->mMode == MODE_RELATIVE)
            {
                pImpl->mState.x = pImpl->mState.y = 0;

                ShowCursor(FALSE);

                pImpl->ClipToWindow();
            }
        }
        else
        {
            int scrollWheel = pImpl->mState.scrollWheelValue;
            memset(&pImpl->mState, 0, sizeof(State));
            pImpl->mState.scrollWheelValue = scrollWheel;

            pImpl->mInFocus = false;
        }
        return;

    case WM_INPUT:
        if (pImpl->mInFocus && pImpl->mMode == MODE_RELATIVE)
        {
            RAWINPUT raw;
            UINT rawSize = sizeof(raw);

            UINT resultData = GetRawInputData(reinterpret_cast<HRAWINPUT>(lParam), RID_INPUT, &raw, &rawSize, sizeof(RAWINPUTHEADER));
            if (resultData == UINT(-1))
            {
                throw std::exception("GetRawInputData");
            }

            if (raw.header.dwType == RIM_TYPEMOUSE)
            {
                if (!(raw.data.mouse.usFlags & MOUSE_MOVE_ABSOLUTE))
                {
                    pImpl->mState.x = raw.data.mouse.lLastX;
                    pImpl->mState.y = raw.data.mouse.lLastY;

                    ResetEvent(pImpl->mRelativeRead.get());
                }
                else if (raw.data.mouse.usFlags & MOUSE_VIRTUAL_DESKTOP)
                {
                    // This is used to make Remote Desktop sessons work
                    const int width = GetSystemMetrics(SM_CXVIRTUALSCREEN);
                    const int height = GetSystemMetrics(SM_CYVIRTUALSCREEN);

                    int x = static_cast<int>((float(raw.data.mouse.lLastX) / 65535.0f) * width);
                    int y = static_cast<int>((float(raw.data.mouse.lLastY) / 65535.0f) * height);

                    if (pImpl->mRelativeX == INT32_MAX)
                    {
                        pImpl->mState.x = pImpl->mState.y = 0;
                    }
                    else
                    {
                        pImpl->mState.x = x - pImpl->mRelativeX;
                        pImpl->mState.y = y - pImpl->mRelativeY;
                    }

                    pImpl->mRelativeX = x;
                    pImpl->mRelativeY = y;

                    ResetEvent(pImpl->mRelativeRead.get());
                }
            }
        }
        return;

    case WM_MOUSEMOVE:
        break;

    case WM_LBUTTONDOWN:
        pImpl->mState.leftButton = true;
        break;

    case WM_LBUTTONUP:
        pImpl->mState.leftButton = false;
        break;

    case WM_RBUTTONDOWN:
        pImpl->mState.rightButton = true;
        break;

    case WM_RBUTTONUP:
        pImpl->mState.rightButton = false;
        break;

    case WM_MBUTTONDOWN:
        pImpl->mState.middleButton = true;
        break;

    case WM_MBUTTONUP:
        pImpl->mState.middleButton = false;
        break;

    case WM_MOUSEWHEEL:
        pImpl->mState.scrollWheelValue += GET_WHEEL_DELTA_WPARAM(wParam);
        return;

    case WM_XBUTTONDOWN:
        switch (GET_XBUTTON_WPARAM(wParam))
        {
        case XBUTTON1:
            pImpl->mState.xButton1 = true;
            break;

        case XBUTTON2:
            pImpl->mState.xButton2 = true;
            break;
        }
        break;

    case WM_XBUTTONUP:
        switch (GET_XBUTTON_WPARAM(wParam))
        {
        case XBUTTON1:
            pImpl->mState.xButton1 = false;
            break;

        case XBUTTON2:
            pImpl->mState.xButton2 = false;
            break;
        }
        break;

    case WM_MOUSEHOVER:
        break;

    default:
        // Not a mouse message, so exit
        return;
    }

    if (pImpl->mMode == MODE_ABSOLUTE)
    {
        // All mouse messages provide a new pointer position
        int xPos = static_cast<short>(LOWORD(lParam)); // GET_X_LPARAM(lParam);
        int yPos = static_cast<short>(HIWORD(lParam)); // GET_Y_LPARAM(lParam);

        pImpl->mState.x = pImpl->mLastX = xPos;
        pImpl->mState.y = pImpl->mLastY = yPos;
    }
}

#endif

#pragma warning( disable : 4355 )

// Public constructor.
Mouse::Mouse()
    : pImpl( new Impl(this) )
{
}


// Move constructor.
Mouse::Mouse(Mouse&& moveFrom)
  : pImpl(std::move(moveFrom.pImpl))
{
    pImpl->mOwner = this;
}


// Move assignment.
Mouse& Mouse::operator= (Mouse&& moveFrom)
{
    pImpl = std::move(moveFrom.pImpl);
    pImpl->mOwner = this;
    return *this;
}


// Public destructor.
Mouse::~Mouse()
{
}


Mouse::State Mouse::GetState() const
{
    State state;
    pImpl->GetState(state);
    return state;
}


void Mouse::ResetScrollWheelValue()
{
    pImpl->ResetScrollWheelValue();
}


void Mouse::SetMode(Mode mode)
{
    pImpl->SetMode(mode);
}


bool Mouse::IsConnected() const
{
    return pImpl->IsConnected();
}


Mouse& Mouse::Get()
{
    if ( !Impl::s_mouse || !Impl::s_mouse->mOwner )
        throw std::exception( "Mouse is a singleton" );

    return *Impl::s_mouse->mOwner;
}



//======================================================================================
// ButtonStateTracker
//======================================================================================

#define UPDATE_BUTTON_STATE(field) field = static_cast<ButtonState>( ( !!state.field ) | ( ( !!state.field ^ !!lastState.field ) << 1 ) );

void Mouse::ButtonStateTracker::Update( const Mouse::State& state )
{
    UPDATE_BUTTON_STATE(leftButton);

    assert( ( !state.leftButton && !lastState.leftButton ) == ( leftButton == UP ) );
    assert( ( state.leftButton && lastState.leftButton ) == ( leftButton == HELD ) );
    assert( ( !state.leftButton && lastState.leftButton ) == ( leftButton == RELEASED ) );
    assert( ( state.leftButton && !lastState.leftButton ) == ( leftButton == PRESSED ) );

    UPDATE_BUTTON_STATE(middleButton);
    UPDATE_BUTTON_STATE(rightButton);
    UPDATE_BUTTON_STATE(xButton1);
    UPDATE_BUTTON_STATE(xButton2);

    lastState = state;
}

#undef UPDATE_BUTTON_STATE


void Mouse::ButtonStateTracker::Reset()
{
    memset( this, 0, sizeof(ButtonStateTracker) );
}
