//--------------------------------------------------------------------------------------
// File: GamePad.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 "GamePad.h"
#include "PlatformHelpers.h"

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


namespace
{
    float ApplyLinearDeadZone( float value, float maxValue, float deadZoneSize )
    {
        if ( value < -deadZoneSize )
        {
            // Increase negative values to remove the deadzone discontinuity.
            value += deadZoneSize;
        }
        else if ( value > deadZoneSize )
        {
            // Decrease positive values to remove the deadzone discontinuity.
            value -= deadZoneSize;
        }
        else
        {
            // Values inside the deadzone come out zero.
            return 0;
        }

        // Scale into 0-1 range.
        float scaledValue = value / (maxValue - deadZoneSize);
        return std::max( -1.f, std::min( scaledValue, 1.f ) );
    }

    void ApplyStickDeadZone( float x, float y, GamePad::DeadZone deadZoneMode, float maxValue, float deadZoneSize,
                             _Out_ float& resultX, _Out_ float& resultY)
    {
        switch( deadZoneMode )
        {
        case GamePad::DEAD_ZONE_INDEPENDENT_AXES:
            resultX = ApplyLinearDeadZone( x, maxValue, deadZoneSize );
            resultY = ApplyLinearDeadZone( y, maxValue, deadZoneSize );
            break;

        case GamePad::DEAD_ZONE_CIRCULAR:
            {
                float dist = sqrtf( x*x + y*y );
                float wanted = ApplyLinearDeadZone( dist, maxValue, deadZoneSize );

                float scale = (wanted > 0.f) ? ( wanted / dist ) : 0.f;

                resultX = std::max( -1.f, std::min( x * scale, 1.f ) );
                resultY = std::max( -1.f, std::min( y * scale, 1.f ) );
            }
            break;

        default: // GamePad::DEAD_ZONE_NONE
            resultX = ApplyLinearDeadZone( x, maxValue, 0 );
            resultY = ApplyLinearDeadZone( y, maxValue, 0 );
            break;
        }
    }
}


#if (_WIN32_WINNT >= _WIN32_WINNT_WIN10)

//======================================================================================
// Windows::Gaming::Input (Windows 10)
//======================================================================================

#pragma warning(push)
#pragma warning(disable : 4471)
#include <Windows.Gaming.Input.h>
#pragma warning(pop)

class GamePad::Impl
{
public:
    Impl(GamePad* owner) :
        mOwner(owner),
        mCtrlChanged(INVALID_HANDLE_VALUE),
        mUserChanged(INVALID_HANDLE_VALUE),
        mMostRecentGamepad(0)
    {
        using namespace Microsoft::WRL;
        using namespace Microsoft::WRL::Wrappers;
        using namespace ABI::Windows::Foundation;
        
        mAddedToken.value = 0;
        mRemovedToken.value = 0;

        memset( &mUserChangeToken, 0, sizeof(mUserChangeToken) );

        if ( s_gamePad )
        {
            throw std::exception( "GamePad is a singleton" );
        }

        s_gamePad = this;

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

        ThrowIfFailed( GetActivationFactory( HStringReference(RuntimeClass_Windows_Gaming_Input_Gamepad).Get(), mStatics.GetAddressOf() ) );

        typedef __FIEventHandler_1_Windows__CGaming__CInput__CGamepad AddedHandler;
        ThrowIfFailed( mStatics->add_GamepadAdded(Callback<AddedHandler>(GamepadAdded).Get(), &mAddedToken ) );

        typedef __FIEventHandler_1_Windows__CGaming__CInput__CGamepad RemovedHandler;
        ThrowIfFailed( mStatics->add_GamepadRemoved(Callback<RemovedHandler>(GamepadRemoved).Get(), &mRemovedToken ) );

        ScanGamePads();
    }

    ~Impl()
    {
        using namespace ABI::Windows::Gaming::Input;

        for (size_t j = 0; j < MAX_PLAYER_COUNT; ++j)
        {
            if (mGamePad[j])
            {
                ComPtr<IGameController> ctrl;
                HRESULT hr = mGamePad[j].As(&ctrl);
                if (SUCCEEDED(hr) && ctrl)
                {
                    (void)ctrl->remove_UserChanged( mUserChangeToken[j] );
                    mUserChangeToken[j].value = 0;
                }

                mGamePad[j].Reset();
            }
        }

        if ( mStatics )
        {
            (void)mStatics->remove_GamepadAdded( mAddedToken );
            mAddedToken.value = 0;

            (void)mStatics->remove_GamepadRemoved( mRemovedToken );
            mRemovedToken.value = 0;

            mStatics.Reset();
        }

        s_gamePad = nullptr;
    }

    void GetState( int player, _Out_ State& state, DeadZone deadZoneMode )
    {
        using namespace Microsoft::WRL;
        using namespace ABI::Windows::Gaming::Input;

        if ( WaitForSingleObjectEx( mChanged.get(), 0, FALSE ) == WAIT_OBJECT_0 )
        {
            ScanGamePads();
        }

        if (player == -1)
            player = mMostRecentGamepad;

        if ( ( player >= 0 ) && ( player < MAX_PLAYER_COUNT ) )
        {
            if ( mGamePad[ player ] )
            {
                GamepadReading reading;
                HRESULT hr = mGamePad[ player ]->GetCurrentReading( &reading );
                if ( SUCCEEDED(hr) )
                {
                    state.connected = true;
                    state.packet = reading.Timestamp;

                    state.buttons.a  = (reading.Buttons & GamepadButtons::GamepadButtons_A) != 0;
                    state.buttons.b  = (reading.Buttons & GamepadButtons::GamepadButtons_B) != 0;
                    state.buttons.x  = (reading.Buttons & GamepadButtons::GamepadButtons_X) != 0;
                    state.buttons.y  = (reading.Buttons & GamepadButtons::GamepadButtons_Y) != 0;

                    state.buttons.leftStick = (reading.Buttons & GamepadButtons::GamepadButtons_LeftThumbstick) != 0;
                    state.buttons.rightStick = (reading.Buttons & GamepadButtons::GamepadButtons_RightThumbstick) != 0;

                    state.buttons.leftShoulder = (reading.Buttons & GamepadButtons::GamepadButtons_LeftShoulder) != 0;
                    state.buttons.rightShoulder = (reading.Buttons & GamepadButtons::GamepadButtons_RightShoulder) != 0;

                    state.buttons.back = (reading.Buttons & GamepadButtons::GamepadButtons_View) != 0;
                    state.buttons.start = (reading.Buttons & GamepadButtons::GamepadButtons_Menu) != 0;

                    state.dpad.up = (reading.Buttons & GamepadButtons::GamepadButtons_DPadUp) != 0;
                    state.dpad.down = (reading.Buttons & GamepadButtons::GamepadButtons_DPadDown) != 0;
                    state.dpad.right = (reading.Buttons & GamepadButtons::GamepadButtons_DPadRight) != 0;
                    state.dpad.left = (reading.Buttons & GamepadButtons::GamepadButtons_DPadLeft) != 0;

                    ApplyStickDeadZone( static_cast<float>(reading.LeftThumbstickX), static_cast<float>(reading.LeftThumbstickY),
                                        deadZoneMode, 1.f, .24f /* Recommended Xbox One deadzone */,
                                        state.thumbSticks.leftX, state.thumbSticks.leftY );

                    ApplyStickDeadZone( static_cast<float>(reading.RightThumbstickX), static_cast<float>(reading.RightThumbstickY),
                                        deadZoneMode, 1.f, .24f /* Recommended Xbox One deadzone */,
                                        state.thumbSticks.rightX, state.thumbSticks.rightY );

                    state.triggers.left = static_cast<float>(reading.LeftTrigger);
                    state.triggers.right = static_cast<float>(reading.RightTrigger);
                     
                    return;
                }
            }
        }

        memset( &state, 0, sizeof(State) );
    }

    void GetCapabilities( int player, Capabilities& caps )
    {
        using namespace Microsoft::WRL;
        using namespace ABI::Windows::System;
        using namespace ABI::Windows::Gaming::Input;

        if ( WaitForSingleObjectEx( mChanged.get(), 0, FALSE ) == WAIT_OBJECT_0 )
        {
            ScanGamePads();
        }

        if (player == -1)
            player = mMostRecentGamepad;

        if ( ( player >= 0 ) && ( player < MAX_PLAYER_COUNT ) )
        {
            if ( mGamePad[ player ] )
            {
                caps.connected = true;
                caps.gamepadType = Capabilities::GAMEPAD;
                caps.id.clear();

                ComPtr<IGameController> ctrl;
                HRESULT hr = mGamePad[player].As(&ctrl);
                if (SUCCEEDED(hr) && ctrl)
                {
                    ComPtr<IUser> user;
                    hr = ctrl->get_User(user.GetAddressOf());
                    if (SUCCEEDED(hr) && user != nullptr)
                    {
                        Wrappers::HString str;
                        hr = user->get_NonRoamableId(str.GetAddressOf());
                        if (SUCCEEDED(hr))
                        {
                            caps.id = str.GetRawBuffer(nullptr);
                        }
                    }
                }
                return;
            }
        }

        caps.id.clear();
        caps = {};
    }

    bool SetVibration( int player, float leftMotor, float rightMotor, float leftTrigger, float rightTrigger )
    {
        using namespace ABI::Windows::Gaming::Input;

        if (player == -1)
            player = mMostRecentGamepad;

        if ( ( player >= 0 ) && ( player < MAX_PLAYER_COUNT ) )
        {
            if ( mGamePad[ player ] )
            {
                GamepadVibration vib;
                vib.LeftMotor = leftMotor;
                vib.RightMotor = rightMotor;
                vib.LeftTrigger = leftTrigger;
                vib.RightTrigger = rightTrigger;
                HRESULT hr = mGamePad[ player ]->put_Vibration(vib);

                if ( SUCCEEDED(hr) )
                    return true;
            }
        }

        return false;
    }

    void Suspend()
    {
        for( size_t j = 0; j < MAX_PLAYER_COUNT; ++j )
        {
            mGamePad[ j ].Reset();
        }
    }

    void Resume()
    {
        // Make sure we rescan gamepads
        SetEvent( mChanged.get() );
    }

    GamePad*    mOwner;

    static GamePad::Impl* s_gamePad;

    HANDLE mCtrlChanged;
    HANDLE mUserChanged;

private:
    int mMostRecentGamepad;

    void ScanGamePads()
    {
        using namespace Microsoft::WRL;
        using namespace ABI::Windows::Foundation::Collections;
        using namespace ABI::Windows::Gaming::Input;

        ComPtr<IVectorView<Gamepad*>> pads;
        ThrowIfFailed( mStatics->get_Gamepads( pads.GetAddressOf() ) );

        unsigned int count = 0;
        ThrowIfFailed( pads->get_Size( &count ) );

        // Check for removed gamepads
        for( size_t j = 0; j < MAX_PLAYER_COUNT; ++j )
        {
            if ( mGamePad[ j ] )
            {
                unsigned int k = 0;
                for( ; k < count; ++k )
                {
                    ComPtr<IGamepad> pad;
                    HRESULT hr = pads->GetAt( k, pad.GetAddressOf() );
                    if ( SUCCEEDED(hr) && ( pad == mGamePad[ j ] ) )
                    {
                        break;
                    }
                }

                if ( k >= count )
                {
                    ComPtr<IGameController> ctrl;
                    HRESULT hr = mGamePad[ j ].As(&ctrl);
                    if (SUCCEEDED(hr) && ctrl)
                    {
                        (void)ctrl->remove_UserChanged( mUserChangeToken[ j ] );
                        mUserChangeToken[j].value = 0;
                    }

                    mGamePad[ j ].Reset();
                }
            }
        }

        // Check for added gamepads
        for( unsigned int j = 0; j < count; ++j )
        {
            ComPtr<IGamepad> pad;
            HRESULT hr = pads->GetAt( j, pad.GetAddressOf() );
            if ( SUCCEEDED(hr) )
            {
                size_t empty = MAX_PLAYER_COUNT;
                size_t k = 0;
                for( ; k < MAX_PLAYER_COUNT; ++k )
                {
                    if ( mGamePad[ k ] == pad )
                    {
                        if (j == (count - 1))
                            mMostRecentGamepad = static_cast<int>(k);
                        break;
                    }
                    else if ( !mGamePad[ k ] )
                    {
                        if ( empty >= MAX_PLAYER_COUNT )
                            empty = k;
                    }
                }

                if ( k >= MAX_PLAYER_COUNT )
                {
                    // Silently ignore "extra" gamepads as there's no hard limit
                    if ( empty < MAX_PLAYER_COUNT )
                    {
                        mGamePad[ empty ] = pad;
                        if (j == (count - 1))
                            mMostRecentGamepad = static_cast<int>(empty);

                        ComPtr<IGameController> ctrl;
                        hr = pad.As(&ctrl);
                        if (SUCCEEDED(hr) && ctrl)
                        {
                            typedef __FITypedEventHandler_2_Windows__CGaming__CInput__CIGameController_Windows__CSystem__CUserChangedEventArgs UserHandler;
                            ThrowIfFailed(ctrl->add_UserChanged(Callback<UserHandler>(UserChanged).Get(), &mUserChangeToken[ empty ]));
                        }
                    }
                }
            }
        }
    }

    ComPtr<ABI::Windows::Gaming::Input::IGamepadStatics> mStatics;
    ComPtr<ABI::Windows::Gaming::Input::IGamepad> mGamePad[ MAX_PLAYER_COUNT ];
    EventRegistrationToken mUserChangeToken[ MAX_PLAYER_COUNT ];

    EventRegistrationToken mAddedToken;
    EventRegistrationToken mRemovedToken;

    ScopedHandle mChanged;

    static HRESULT GamepadAdded( IInspectable *, ABI::Windows::Gaming::Input::IGamepad* )
    {
        if ( s_gamePad )
        {
            SetEvent( s_gamePad->mChanged.get() );

            if (s_gamePad->mCtrlChanged != INVALID_HANDLE_VALUE)
            {
                SetEvent( s_gamePad->mCtrlChanged );
            }
        }
        return S_OK;
    }

    static HRESULT GamepadRemoved( IInspectable *, ABI::Windows::Gaming::Input::IGamepad* )
    {
        if ( s_gamePad )
        {
            SetEvent( s_gamePad->mChanged.get() );

            if (s_gamePad->mCtrlChanged != INVALID_HANDLE_VALUE)
            {
                SetEvent( s_gamePad->mCtrlChanged );
            }
        }
        return S_OK;
    }

    static HRESULT UserChanged( ABI::Windows::Gaming::Input::IGameController*, ABI::Windows::System::IUserChangedEventArgs* )
    {
        if (s_gamePad)
        {
            if (s_gamePad->mUserChanged != INVALID_HANDLE_VALUE)
            {
                SetEvent( s_gamePad->mUserChanged );
            }
        }
        return S_OK;
    }
};

GamePad::Impl* GamePad::Impl::s_gamePad = nullptr;

#elif defined(_XBOX_ONE)

//======================================================================================
// Windows::Xbox::Input (Xbox One)
//======================================================================================

#include <Windows.Xbox.Input.h>

#include <Windows.Foundation.Collections.h>

class GamePad::Impl
{
public:
    class GamepadAddedListener : public Microsoft::WRL::RuntimeClass<Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::ClassicCom>,
        ABI::Windows::Foundation::IEventHandler<ABI::Windows::Xbox::Input::GamepadAddedEventArgs *>,
        Microsoft::WRL::FtmBase>
    {
    public:
        GamepadAddedListener(HANDLE event) : mEvent(event) {}

        STDMETHOD(Invoke)(_In_ IInspectable *, _In_ ABI::Windows::Xbox::Input::IGamepadAddedEventArgs *) override
        {
            SetEvent(mEvent);

            auto pad = GamePad::Impl::s_gamePad;

            if (pad && pad->mCtrlChanged != INVALID_HANDLE_VALUE)
            {
                SetEvent(pad->mCtrlChanged);
            }
            return S_OK;
        }

    private:
        HANDLE mEvent;
    };

    class GamepadRemovedListener : public Microsoft::WRL::RuntimeClass<Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::ClassicCom>,
        ABI::Windows::Foundation::IEventHandler<ABI::Windows::Xbox::Input::GamepadRemovedEventArgs *>,
        Microsoft::WRL::FtmBase>
    {
    public:
        GamepadRemovedListener(HANDLE event) : mEvent(event) {}

        STDMETHOD(Invoke)(_In_ IInspectable *, _In_ ABI::Windows::Xbox::Input::IGamepadRemovedEventArgs *) override
        {
            SetEvent(mEvent);

            auto pad = GamePad::Impl::s_gamePad;

            if (pad && pad->mCtrlChanged != INVALID_HANDLE_VALUE)
            {
                SetEvent(pad->mCtrlChanged);
            }
            return S_OK;
        }

    private:
        HANDLE mEvent;
    };

    class UserPairingListener : public Microsoft::WRL::RuntimeClass<Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::ClassicCom>,
        ABI::Windows::Foundation::IEventHandler<ABI::Windows::Xbox::Input::ControllerPairingChangedEventArgs *>,
        Microsoft::WRL::FtmBase>
    {
    public:
        UserPairingListener() {}

        STDMETHOD(Invoke)(_In_ IInspectable *, _In_ ABI::Windows::Xbox::Input::IControllerPairingChangedEventArgs *) override
        {
            auto pad = GamePad::Impl::s_gamePad;

            if (pad && pad->mUserChanged != INVALID_HANDLE_VALUE)
            {
                SetEvent(pad->mUserChanged);
            }
            return S_OK;
        }
    };

    Impl(GamePad *owner) :
        mOwner(owner),
        mCtrlChanged(INVALID_HANDLE_VALUE),
        mUserChanged(INVALID_HANDLE_VALUE),
        mMostRecentGamepad(0)
    {
        using namespace Microsoft::WRL;
        using namespace Microsoft::WRL::Wrappers;
        using namespace ABI::Windows::Foundation;
        
        mAddedToken.value = 0;
        mRemovedToken.value = 0;
        mUserParingToken.value = 0;

        if ( s_gamePad )
        {
            throw std::exception( "GamePad is a singleton" );
        }

        s_gamePad = this;

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

        ThrowIfFailed( GetActivationFactory( HStringReference(RuntimeClass_Windows_Xbox_Input_Gamepad).Get(), mStatics.GetAddressOf() ) );

        ThrowIfFailed( GetActivationFactory( HStringReference(RuntimeClass_Windows_Xbox_Input_Controller).Get(), mStaticsCtrl.GetAddressOf() ) );

        ThrowIfFailed( mStatics->add_GamepadAdded(Make<GamepadAddedListener>(mChanged.get()).Get(), &mAddedToken ) );

        ThrowIfFailed( mStatics->add_GamepadRemoved(Make<GamepadRemovedListener>(mChanged.get()).Get(), &mRemovedToken ) );

        ThrowIfFailed( mStaticsCtrl->add_ControllerPairingChanged(Make<UserPairingListener>().Get(), &mUserParingToken ) );

        ScanGamePads();
    }

    ~Impl()
    {
        if ( mStatics )
        {
            (void)mStatics->remove_GamepadAdded( mAddedToken );
            mAddedToken.value = 0;

            (void)mStatics->remove_GamepadRemoved( mRemovedToken );
            mRemovedToken.value = 0;

            mStatics.Reset();
        }

        if (mStaticsCtrl)
        {
            (void)mStaticsCtrl->remove_ControllerPairingChanged( mUserParingToken );
            mUserParingToken.value = 0;

            mStaticsCtrl.Reset();
        }

        s_gamePad = nullptr;
    }

    void GetState( int player, _Out_ State& state, DeadZone deadZoneMode )
    {
        using namespace Microsoft::WRL;
        using namespace ABI::Windows::Xbox::Input;

        if ( WaitForSingleObjectEx( mChanged.get(), 0, FALSE ) == WAIT_OBJECT_0 )
        {
            ScanGamePads();
        }

        if (player == -1)
            player = mMostRecentGamepad;

        if ( ( player >= 0 ) && ( player < MAX_PLAYER_COUNT ) )
        {
            if ( mGamePad[ player ] )
            {
                RawGamepadReading reading;
                HRESULT hr = mGamePad[ player ]->GetRawCurrentReading( &reading );
                if ( SUCCEEDED(hr) )
                {
                    state.connected = true;
                    state.packet = reading.Timestamp;

                    state.buttons.a  = (reading.Buttons & GamepadButtons::GamepadButtons_A) != 0;
                    state.buttons.b  = (reading.Buttons & GamepadButtons::GamepadButtons_B) != 0;
                    state.buttons.x  = (reading.Buttons & GamepadButtons::GamepadButtons_X) != 0;
                    state.buttons.y  = (reading.Buttons & GamepadButtons::GamepadButtons_Y) != 0;

                    state.buttons.leftStick = (reading.Buttons & GamepadButtons::GamepadButtons_LeftThumbstick) != 0;
                    state.buttons.rightStick = (reading.Buttons & GamepadButtons::GamepadButtons_RightThumbstick) != 0;

                    state.buttons.leftShoulder = (reading.Buttons & GamepadButtons::GamepadButtons_LeftShoulder) != 0;
                    state.buttons.rightShoulder = (reading.Buttons & GamepadButtons::GamepadButtons_RightShoulder) != 0;

                    state.buttons.back = (reading.Buttons & GamepadButtons::GamepadButtons_View) != 0;
                    state.buttons.start = (reading.Buttons & GamepadButtons::GamepadButtons_Menu) != 0;

                    state.dpad.up = (reading.Buttons & GamepadButtons::GamepadButtons_DPadUp) != 0;
                    state.dpad.down = (reading.Buttons & GamepadButtons::GamepadButtons_DPadDown) != 0;
                    state.dpad.right = (reading.Buttons & GamepadButtons::GamepadButtons_DPadRight) != 0;
                    state.dpad.left = (reading.Buttons & GamepadButtons::GamepadButtons_DPadLeft) != 0;

                    ApplyStickDeadZone( reading.LeftThumbstickX, reading.LeftThumbstickY,
                                        deadZoneMode, 1.f, .24f /* Recommended Xbox One deadzone */,
                                        state.thumbSticks.leftX, state.thumbSticks.leftY );

                    ApplyStickDeadZone( reading.RightThumbstickX, reading.RightThumbstickY,
                                        deadZoneMode, 1.f, .24f /* Recommended Xbox One deadzone */,
                                        state.thumbSticks.rightX, state.thumbSticks.rightY );

                    state.triggers.left = reading.LeftTrigger;
                    state.triggers.right = reading.RightTrigger;
                     
                    return;
                }
            }
        }

        memset( &state, 0, sizeof(State) );
    }

    void GetCapabilities( int player, _Out_ Capabilities& caps )
    {
        using namespace Microsoft::WRL;
        using namespace ABI::Windows::Xbox::Input;

        if ( WaitForSingleObjectEx( mChanged.get(), 0, FALSE ) == WAIT_OBJECT_0 )
        {
            ScanGamePads();
        }

        if (player == -1)
            player = mMostRecentGamepad;

        if ( ( player >= 0 ) && ( player < MAX_PLAYER_COUNT ) )
        {
            if ( mGamePad[ player ] )
            {
                caps.connected = true;
                caps.gamepadType = Capabilities::UNKNOWN;

                ComPtr<IController> ctrl;
                HRESULT hr = mGamePad[ player ].As( &ctrl );
                if ( SUCCEEDED(hr) && ctrl )
                {
                    hr = ctrl->get_Id( &caps.id );
                    if ( FAILED(hr) )
                        caps.id = 0;

                    Wrappers::HString str;
                    hr = ctrl->get_Type(str.GetAddressOf());
                    if ( SUCCEEDED(hr) )
                    {
                        const wchar_t* typeStr = str.GetRawBuffer(nullptr);
                        if ( _wcsicmp(typeStr, L"Windows.Xbox.Input.Gamepad") == 0 )
                        {
                            caps.gamepadType = Capabilities::GAMEPAD;
                        }
                        else if ( _wcsicmp(typeStr, L"Microsoft.Xbox.Input.ArcadeStick") == 0 )
                        {
                            caps.gamepadType = Capabilities::ARCADE_STICK;
                        }
                        else if ( _wcsicmp(typeStr, L"Microsoft.Xbox.Input.Wheel") == 0 )
                        {
                            caps.gamepadType = Capabilities::WHEEL;
                        }
                    }
                }
                else
                    caps.id = 0;
                
                return;
            }
        }

        memset( &caps, 0, sizeof(Capabilities) );
    }

    bool SetVibration( int player, float leftMotor, float rightMotor, float leftTrigger, float rightTrigger )
    {
        using namespace ABI::Windows::Xbox::Input;

        if (player == -1)
            player = mMostRecentGamepad;

        if ( ( player >= 0 ) && ( player < MAX_PLAYER_COUNT ) )
        {
            if ( mGamePad[ player ] )
            {
                HRESULT hr;
                try
                {
                    GamepadVibration vib;
                    vib.LeftMotorLevel = leftMotor;
                    vib.RightMotorLevel = rightMotor;
                    vib.LeftTriggerLevel = leftTrigger;
                    vib.RightTriggerLevel = rightTrigger;
                    hr = mGamePad[ player ]->SetVibration(vib);
                }
                catch( ... )
                {
                    // Handle case where gamepad might be invalid
                    hr = E_FAIL;
                }

                if ( SUCCEEDED(hr) )
                    return true;
            }
        }

        return false;
    }

    void Suspend()
    {
        for( size_t j = 0; j < MAX_PLAYER_COUNT; ++j )
        {
            mGamePad[ j ].Reset();
        }
    }

    void Resume()
    {
        // Make sure we rescan gamepads
        SetEvent( mChanged.get() );
    }

    GamePad*    mOwner;

    static GamePad::Impl* s_gamePad;

    HANDLE mCtrlChanged;
    HANDLE mUserChanged;

private:
    int mMostRecentGamepad;

    void ScanGamePads()
    {
        using namespace ABI::Windows::Foundation::Collections;
        using namespace ABI::Windows::Xbox::Input;

        ComPtr<IVectorView<IGamepad*>> pads;
        ThrowIfFailed( mStatics->get_Gamepads( pads.GetAddressOf() ) );

        unsigned int count = 0;
        ThrowIfFailed( pads->get_Size( &count ) );

        // Check for removed gamepads
        for( size_t j = 0; j < MAX_PLAYER_COUNT; ++j )
        {
            if ( mGamePad[ j ] )
            {
                unsigned int k = 0;
                for( ; k < count; ++k )
                {
                    ComPtr<IGamepad> pad;
                    HRESULT hr = pads->GetAt( k, pad.GetAddressOf() );
                    if ( SUCCEEDED(hr) && ( pad == mGamePad[ j ] ) )
                    {
                        break;
                    }
                }

                if ( k >= count )
                {
                    mGamePad[ j ].Reset();
                }
            }
        }

        // Check for added gamepads
        for( unsigned int j = 0; j < count; ++j )
        {
            ComPtr<IGamepad> pad;
            HRESULT hr = pads->GetAt( j, pad.GetAddressOf() );
            if ( SUCCEEDED(hr) )
            {
                size_t empty = MAX_PLAYER_COUNT;
                size_t k = 0;
                for( ; k < MAX_PLAYER_COUNT; ++k )
                {
                    if ( mGamePad[ k ] == pad )
                    {
                        if (!j)
                            mMostRecentGamepad = static_cast<int>(k);
                        break;
                    }
                    else if ( !mGamePad[ k ] )
                    {
                        if ( empty >= MAX_PLAYER_COUNT )
                            empty = k;
                    }
                }

                if ( k >= MAX_PLAYER_COUNT )
                {
                    if ( empty >= MAX_PLAYER_COUNT )
                    {
                        throw std::exception( "Too many gamepads found" );
                    }

                    mGamePad[ empty ] = pad;
                    if (!j)
                        mMostRecentGamepad = static_cast<int>(empty);
                }
            }
        }
    }

    ComPtr<ABI::Windows::Xbox::Input::IGamepadStatics> mStatics;
    ComPtr<ABI::Windows::Xbox::Input::IControllerStatics> mStaticsCtrl;
    ComPtr<ABI::Windows::Xbox::Input::IGamepad> mGamePad[ MAX_PLAYER_COUNT ];

    EventRegistrationToken mAddedToken;
    EventRegistrationToken mRemovedToken;
    EventRegistrationToken mUserParingToken;

    ScopedHandle mChanged;
};

GamePad::Impl* GamePad::Impl::s_gamePad = nullptr;

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

//======================================================================================
// Null device for Windows Phone
//======================================================================================

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

        s_gamePad = this;
    }

    ~Impl()
    {
        s_gamePad = nullptr;
    }

    void GetState(int player, _Out_ State& state, DeadZone)
    {
        UNREFERENCED_PARAMETER(player);

        memset( &state, 0, sizeof(State) );
    }

    void GetCapabilities(int player, _Out_ Capabilities& caps)
    {
        UNREFERENCED_PARAMETER(player);

        memset( &caps, 0, sizeof(Capabilities) );
    }

    bool SetVibration(int player, float leftMotor, float rightMotor, float leftTrigger, float rightTrigger)
    {
        UNREFERENCED_PARAMETER(player);
        UNREFERENCED_PARAMETER(leftMotor);
        UNREFERENCED_PARAMETER(rightMotor);
        UNREFERENCED_PARAMETER(leftTrigger);
        UNREFERENCED_PARAMETER(rightTrigger);

        return false;
    }

    void Suspend()
    {
    }

    void Resume()
    {
    }

    GamePad*    mOwner;

    static GamePad::Impl* s_gamePad;
};

GamePad::Impl* GamePad::Impl::s_gamePad = nullptr;


#else

//======================================================================================
// XInput
//======================================================================================

#include <xinput.h>

static_assert( GamePad::MAX_PLAYER_COUNT == XUSER_MAX_COUNT, "xinput.h mismatch" );

class GamePad::Impl
{
public:
    Impl(GamePad* owner) :
        mOwner(owner)
    {
        for( int j = 0; j < XUSER_MAX_COUNT; ++j )
        {
            ClearSlot( j, 0 );
        }

#if (_WIN32_WINNT < _WIN32_WINNT_WIN8)
        mSuspended = false;
#endif

        if ( s_gamePad )
        {
            throw std::exception( "GamePad is a singleton" );
        }

        s_gamePad = this;
    }

    ~Impl()
    {
        s_gamePad = nullptr;
    }

    void GetState( int player, _Out_ State& state, DeadZone deadZoneMode )
    {
        if (player == -1)
            player = GetMostRecent();

        ULONGLONG time = GetTickCount64();

        if ( !ThrottleRetry(player, time) )
        {
#if (_WIN32_WINNT < _WIN32_WINNT_WIN8)
            if ( mSuspended )
            {
                memset( &state, 0, sizeof(State) );
                state.connected = mConnected[ player ];
                return;
            }
#endif

            XINPUT_STATE xstate;
            DWORD result = XInputGetState( DWORD(player), &xstate );
            if ( result == ERROR_DEVICE_NOT_CONNECTED )
            {
                ClearSlot( player, time );
            }
            else
            {
                if (!mConnected[player])
                    mLastReadTime[player] = time;

                mConnected[ player ] = true;

                state.connected = true;
                state.packet = xstate.dwPacketNumber;

                WORD xbuttons = xstate.Gamepad.wButtons;
                state.buttons.a = (xbuttons & XINPUT_GAMEPAD_A) != 0;
                state.buttons.b = (xbuttons & XINPUT_GAMEPAD_B) != 0;
                state.buttons.x = (xbuttons & XINPUT_GAMEPAD_X) != 0;
                state.buttons.y = (xbuttons & XINPUT_GAMEPAD_Y) != 0;
                state.buttons.leftStick = (xbuttons & XINPUT_GAMEPAD_LEFT_THUMB) != 0;
                state.buttons.rightStick = (xbuttons & XINPUT_GAMEPAD_RIGHT_THUMB) != 0;
                state.buttons.leftShoulder = (xbuttons & XINPUT_GAMEPAD_LEFT_SHOULDER) != 0;
                state.buttons.rightShoulder = (xbuttons & XINPUT_GAMEPAD_RIGHT_SHOULDER) != 0;
                state.buttons.back = (xbuttons & XINPUT_GAMEPAD_BACK) != 0;
                state.buttons.start = (xbuttons & XINPUT_GAMEPAD_START) != 0;

                state.dpad.up = (xbuttons & XINPUT_GAMEPAD_DPAD_UP) != 0;
                state.dpad.down = (xbuttons & XINPUT_GAMEPAD_DPAD_DOWN) != 0;
                state.dpad.right = (xbuttons & XINPUT_GAMEPAD_DPAD_RIGHT) != 0;
                state.dpad.left = (xbuttons & XINPUT_GAMEPAD_DPAD_LEFT) != 0;

                if ( deadZoneMode == DEAD_ZONE_NONE )
                {
                    state.triggers.left = ApplyLinearDeadZone( float(xstate.Gamepad.bLeftTrigger), 255.f, 0.f );
                    state.triggers.right = ApplyLinearDeadZone( float(xstate.Gamepad.bRightTrigger), 255.f, 0.f );
                }
                else
                {
                    state.triggers.left = ApplyLinearDeadZone( float(xstate.Gamepad.bLeftTrigger), 255.f, float(XINPUT_GAMEPAD_TRIGGER_THRESHOLD) );
                    state.triggers.right = ApplyLinearDeadZone( float(xstate.Gamepad.bRightTrigger), 255.f, float(XINPUT_GAMEPAD_TRIGGER_THRESHOLD) );
                }
            
                ApplyStickDeadZone( float(xstate.Gamepad.sThumbLX), float(xstate.Gamepad.sThumbLY),
                                    deadZoneMode, 32767.f, float(XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE),
                                    state.thumbSticks.leftX, state.thumbSticks.leftY );

                ApplyStickDeadZone( float(xstate.Gamepad.sThumbRX), float(xstate.Gamepad.sThumbRY),
                                    deadZoneMode, 32767.f, float(XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE),
                                    state.thumbSticks.rightX, state.thumbSticks.rightY );

                return;
            }
        }

        memset( &state, 0, sizeof(State) );
    }

    void GetCapabilities( int player, _Out_ Capabilities& caps )
    {
        if (player == -1)
            player = GetMostRecent();

        ULONGLONG time = GetTickCount64();

        if ( !ThrottleRetry(player, time) )
        {
            XINPUT_CAPABILITIES xcaps;
            DWORD result = XInputGetCapabilities( DWORD(player), 0, &xcaps );
            if ( result == ERROR_DEVICE_NOT_CONNECTED )
            {
                ClearSlot( player, time );
            }
            else
            {
                if (!mConnected[player])
                    mLastReadTime[player] = time;

                mConnected[ player ] = true;

                caps.connected = true;
                caps.id = uint64_t( player );
                if ( xcaps.Type == XINPUT_DEVTYPE_GAMEPAD )
                {
                    static_assert(Capabilities::GAMEPAD == XINPUT_DEVSUBTYPE_GAMEPAD, "xinput.h mismatch");
#if (_WIN32_WINNT >= _WIN32_WINNT_WIN8)
                    static_assert( XINPUT_DEVSUBTYPE_WHEEL == Capabilities::WHEEL, "xinput.h mismatch");
                    static_assert( XINPUT_DEVSUBTYPE_ARCADE_STICK == Capabilities::ARCADE_STICK, "xinput.h mismatch");
                    static_assert( XINPUT_DEVSUBTYPE_FLIGHT_STICK == Capabilities::FLIGHT_STICK, "xinput.h mismatch");
                    static_assert( XINPUT_DEVSUBTYPE_DANCE_PAD == Capabilities::DANCE_PAD, "xinput.h mismatch");
                    static_assert( XINPUT_DEVSUBTYPE_GUITAR == Capabilities::GUITAR, "xinput.h mismatch");
                    static_assert( XINPUT_DEVSUBTYPE_GUITAR_ALTERNATE == Capabilities::GUITAR_ALTERNATE, "xinput.h mismatch");
                    static_assert( XINPUT_DEVSUBTYPE_DRUM_KIT == Capabilities::DRUM_KIT, "xinput.h mismatch");
                    static_assert( XINPUT_DEVSUBTYPE_GUITAR_BASS == Capabilities::GUITAR_BASS, "xinput.h mismatch");
                    static_assert( XINPUT_DEVSUBTYPE_ARCADE_PAD == Capabilities::ARCADE_PAD, "xinput.h mismatch");
#endif

                    caps.gamepadType = Capabilities::Type(xcaps.SubType);
                }

                return;
            }
        }

        memset( &caps, 0, sizeof(Capabilities) );
    }

    bool SetVibration( int player, float leftMotor, float rightMotor, float leftTrigger, float rightTrigger )
    {
        if (player == -1)
            player = GetMostRecent();

        ULONGLONG time = GetTickCount64();

        if ( ThrottleRetry(player, time) )
        {
            return false;
        }

        // XInput does not provide a way to set the left/right trigger impulse motors on the Xbox One Controller,
        // and these motors are not present on the Xbox 360 Common Controller
        UNREFERENCED_PARAMETER(leftTrigger);
        UNREFERENCED_PARAMETER(rightTrigger);

#if (_WIN32_WINNT < _WIN32_WINNT_WIN8)
        mLeftMotor[ player ] = leftMotor;
        mRightMotor[ player ] = rightMotor;

        if ( mSuspended )
            return mConnected[ player ];
#endif

        XINPUT_VIBRATION xvibration;
        xvibration.wLeftMotorSpeed = WORD( leftMotor * 0xFFFF );
        xvibration.wRightMotorSpeed = WORD( rightMotor * 0xFFFF );
        DWORD result = XInputSetState( DWORD(player), &xvibration );
        if ( result == ERROR_DEVICE_NOT_CONNECTED )
        {
            ClearSlot( player, time );
            return false;
        }
        else
        {
            if (!mConnected[player])
                mLastReadTime[player] = time;

            mConnected[ player ] = true;
            return (result == ERROR_SUCCESS);
        }
    }

    void Suspend()
    {
#if (_WIN32_WINNT >= _WIN32_WINNT_WIN8)
        XInputEnable( FALSE );
#else
        // For XInput 9.1.0, we have to emulate the behavior of XInputEnable( FALSE )
        if ( !mSuspended )
        {
            for( size_t j = 0; j < XUSER_MAX_COUNT; ++j )
            {
                if ( mConnected[ j ] )
                {
                    XINPUT_VIBRATION xvibration;
                    xvibration.wLeftMotorSpeed = xvibration.wRightMotorSpeed = 0;
                    (void)XInputSetState( DWORD(j), &xvibration );
                }
            }

            mSuspended = true;
        }
#endif
    }

    void Resume()
    {
#if (_WIN32_WINNT >= _WIN32_WINNT_WIN8)
        XInputEnable( TRUE );
#else
        // For XInput 9.1.0, we have to emulate the behavior of XInputEnable( TRUE )
        if ( mSuspended )
        {
            ULONGLONG time = GetTickCount64();

            for( int j = 0; j < XUSER_MAX_COUNT; ++j )
            {
                if ( mConnected[ j ] )
                {
                    XINPUT_VIBRATION xvibration;
                    xvibration.wLeftMotorSpeed = WORD( mLeftMotor[ j ] * 0xFFFF );
                    xvibration.wRightMotorSpeed = WORD( mRightMotor[ j ] * 0xFFFF );
                    DWORD result = XInputSetState( DWORD(j), &xvibration );
                    if ( result == ERROR_DEVICE_NOT_CONNECTED )
                    {
                        ClearSlot( j, time );
                    }
                }
            }

            mSuspended = false;
        }
#endif
    }

    GamePad*    mOwner;

    static GamePad::Impl* s_gamePad;

private:
    bool        mConnected[ XUSER_MAX_COUNT ];
    ULONGLONG   mLastReadTime[ XUSER_MAX_COUNT ];

#if (_WIN32_WINNT < _WIN32_WINNT_WIN8)
    // Variables for emulating XInputEnable on XInput 9.1.0
    float       mLeftMotor[ XUSER_MAX_COUNT ];
    float       mRightMotor[ XUSER_MAX_COUNT ];
    bool        mSuspended;
#endif

    bool ThrottleRetry( int player, ULONGLONG time )
    {
        // This function minimizes a potential performance issue with XInput on Windows when
        // checking a disconnected controller slot which requires device enumeration.
        // This throttling keeps checks for newly connected gamepads to about once a second

        if ( ( player < 0 ) || ( player >= XUSER_MAX_COUNT ) )
            return true;

        if ( mConnected[ player ] )
            return false;

        for( size_t j = 0; j < XUSER_MAX_COUNT; ++j )
        {
            if ( !mConnected[j] )
            {
                LONGLONG delta = time - mLastReadTime[j];

                LONGLONG interval = 1000;
                if ( (int)j != player )
                    interval /= 4;

                if ( (delta >= 0) && (delta < interval) )
                    return true;
            }
        }

        return false;
    }

    void ClearSlot( int player, ULONGLONG time )
    {
        mConnected[ player ] = false;
        mLastReadTime[ player ] = time;
#if (_WIN32_WINNT < _WIN32_WINNT_WIN8)
        mLeftMotor[ player ] = mRightMotor[ player ] = 0.f;
#endif
    }

    int GetMostRecent()
    {
        int player = -1;
        ULONGLONG time = 0;

        for (size_t j = 0; j < XUSER_MAX_COUNT; ++j)
        {
            if (mConnected[j] && (mLastReadTime[j] > time))
            {
                time = mLastReadTime[j];
                player = static_cast<int>(j);
            }
        }

        return player;
    }
};

GamePad::Impl* GamePad::Impl::s_gamePad = nullptr;

#endif

#pragma warning( disable : 4355 )

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


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


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


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


GamePad::State GamePad::GetState(int player, DeadZone deadZoneMode)
{
    State state;
    pImpl->GetState(player, state, deadZoneMode);
    return state;
}


GamePad::Capabilities GamePad::GetCapabilities(int player)
{
    Capabilities caps;
    pImpl->GetCapabilities(player, caps);
    return caps;
}


bool GamePad::SetVibration( int player, float leftMotor, float rightMotor, float leftTrigger, float rightTrigger )
{
    return pImpl->SetVibration( player, leftMotor, rightMotor, leftTrigger, rightTrigger );
}


void GamePad::Suspend()
{
    pImpl->Suspend();
}


void GamePad::Resume()
{
    pImpl->Resume();
}


#if (_WIN32_WINNT >= 0x0A00 /*_WIN32_WINNT_WIN10*/ ) || defined(_XBOX_ONE)
void GamePad::RegisterEvents(HANDLE ctrlChanged, HANDLE userChanged)
{
    pImpl->mCtrlChanged = (!ctrlChanged) ? INVALID_HANDLE_VALUE : ctrlChanged;
    pImpl->mUserChanged = (!userChanged) ? INVALID_HANDLE_VALUE : userChanged;
}
#endif


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

    return *Impl::s_gamePad->mOwner;
}



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

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

void GamePad::ButtonStateTracker::Update( const GamePad::State& state )
{
    UPDATE_BUTTON_STATE(a);

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

    UPDATE_BUTTON_STATE(b);
    UPDATE_BUTTON_STATE(x);
    UPDATE_BUTTON_STATE(y);

    UPDATE_BUTTON_STATE(leftStick);
    UPDATE_BUTTON_STATE(rightStick);

    UPDATE_BUTTON_STATE(leftShoulder);
    UPDATE_BUTTON_STATE(rightShoulder);

    UPDATE_BUTTON_STATE(back);
    UPDATE_BUTTON_STATE(start);

    dpadUp = static_cast<ButtonState>( ( !!state.dpad.up ) | ( ( !!state.dpad.up ^ !!lastState.dpad.up ) << 1 ) );
    dpadDown = static_cast<ButtonState>( ( !!state.dpad.down ) | ( ( !!state.dpad.down ^ !!lastState.dpad.down ) << 1 ) );
    dpadLeft = static_cast<ButtonState>( ( !!state.dpad.left ) | ( ( !!state.dpad.left ^ !!lastState.dpad.left ) << 1 ) );
    dpadRight = static_cast<ButtonState>( ( !!state.dpad.right ) | ( ( !!state.dpad.right ^ !!lastState.dpad.right ) << 1 ) );

    assert( ( !state.dpad.up && !lastState.dpad.up ) == ( dpadUp == UP ) );
    assert( ( state.dpad.up && lastState.dpad.up ) == ( dpadUp == HELD ) );
    assert( ( !state.dpad.up && lastState.dpad.up ) == ( dpadUp == RELEASED ) );
    assert( ( state.dpad.up && !lastState.dpad.up ) == ( dpadUp == PRESSED ) );

    // Handle 'threshold' tests which emulate buttons

    bool threshold = state.IsLeftThumbStickUp();
    leftStickUp = static_cast<ButtonState>( ( !!threshold) | ( ( !!threshold ^ !!lastState.IsLeftThumbStickUp() ) << 1 ) );

    threshold = state.IsLeftThumbStickDown();
    leftStickDown = static_cast<ButtonState>((!!threshold) | ((!!threshold ^ !!lastState.IsLeftThumbStickDown()) << 1));

    threshold = state.IsLeftThumbStickLeft();
    leftStickLeft = static_cast<ButtonState>((!!threshold) | ((!!threshold ^ !!lastState.IsLeftThumbStickLeft()) << 1));

    threshold = state.IsLeftThumbStickRight();
    leftStickRight = static_cast<ButtonState>((!!threshold) | ((!!threshold ^ !!lastState.IsLeftThumbStickRight()) << 1));

    threshold = state.IsRightThumbStickUp();
    rightStickUp = static_cast<ButtonState>((!!threshold) | ((!!threshold ^ !!lastState.IsRightThumbStickUp()) << 1));

    threshold = state.IsRightThumbStickDown();
    rightStickDown = static_cast<ButtonState>((!!threshold) | ((!!threshold ^ !!lastState.IsRightThumbStickDown()) << 1));

    threshold = state.IsRightThumbStickLeft();
    rightStickLeft = static_cast<ButtonState>((!!threshold) | ((!!threshold ^ !!lastState.IsRightThumbStickLeft()) << 1));

    threshold = state.IsRightThumbStickRight();
    rightStickRight = static_cast<ButtonState>((!!threshold) | ((!!threshold ^ !!lastState.IsRightThumbStickRight()) << 1));

    threshold = state.IsLeftTriggerPressed();
    leftTrigger = static_cast<ButtonState>((!!threshold) | ((!!threshold ^ !!lastState.IsLeftTriggerPressed()) << 1));

    threshold = state.IsRightTriggerPressed();
    rightTrigger = static_cast<ButtonState>((!!threshold) | ((!!threshold ^ !!lastState.IsRightTriggerPressed()) << 1));

    lastState = state;
}

#undef UPDATE_BUTTON_STATE


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