// Copyright Steve Streeting 2020 onwards
// Released under the MIT license
#include "StevesGameSubsystem.h"

#include "EngineUtils.h"
#include "EnhancedInputComponent.h"
#include "EnhancedInputSubsystems.h"
#include "StevesGameViewportClientBase.h"
#include "StevesPluginSettings.h"
#include "StevesUEHelpers.h"
#include "Engine/AssetManager.h"
#include "Engine/GameInstance.h"
#include "Framework/Application/SlateApplication.h"
#include "GameFramework/InputSettings.h"
#include "GameFramework/PlayerController.h"
#include "GameFramework/PlayerInput.h"
#include "StevesUI/KeySprite.h"
#include "StevesUI/StevesUI.h"
#include "TimerManager.h"
#include "AssetRegistry/AssetData.h"
#include "Engine/Engine.h"
#include "Engine/LocalPlayer.h"
#include "UnrealClient.h"
#include "Slate/SceneViewport.h"

//PRAGMA_DISABLE_OPTIMIZATION

void UStevesGameSubsystem::Initialize(FSubsystemCollectionBase& Collection)
{
    Super::Initialize(Collection);

#if !UE_SERVER
    CreateInputDetector();
    InitTheme();
    InitForegroundCheck();
    NotifyEnhancedInputMappingsChanged();
	InitViewport();
#endif
}

void UStevesGameSubsystem::Deinitialize()
{
    Super::Deinitialize();
#if !UE_SERVER
    DestroyInputDetector();
#endif
}

void UStevesGameSubsystem::InitViewport()
{
	FViewport::ViewportResizedEvent.AddUObject(this, &UStevesGameSubsystem::ViewportResized);
	if (auto GI = GetGameInstance())
	{
		if (auto VC = GI->GetGameViewportClient())
		{
			VC->OnToggleFullscreen().AddUObject(this, &UStevesGameSubsystem::FullscreenToggled);
		}
	}
}

void UStevesGameSubsystem::ViewportResized(FViewport* Viewport, unsigned Unused)
{
	FIntPoint Sz = Viewport->GetSizeXY();
	OnViewportResized.Broadcast(Sz.X, Sz.Y);
}

void UStevesGameSubsystem::FullscreenToggled(bool bFullscreen)
{
	if (auto GI = GetGameInstance())
	{
		if (auto VC = GI->GetGameViewportClient())
		{
			FIntPoint Sz = VC->GetGameViewport()->GetSizeXY();
			OnViewportResized.Broadcast(Sz.X, Sz.Y);
		}
	}
}

void UStevesGameSubsystem::CreateInputDetector()
{
#if !UE_SERVER
    if (!InputDetector.IsValid())
    {
        InputDetector = MakeShareable(new FInputModeDetector());
        FSlateApplication::Get().RegisterInputPreProcessor(InputDetector);

        InputDetector->OnInputModeChanged.BindUObject(this, &UStevesGameSubsystem::OnInputDetectorModeChanged);
        InputDetector->OnButtonInputModeChanged.BindUObject(this, &UStevesGameSubsystem::OnButtonInputDetectorModeChanged);
        InputDetector->OnAxisInputModeChanged.BindUObject(this, &UStevesGameSubsystem::OnAxisInputDetectorModeChanged);
    }
#endif
}

void UStevesGameSubsystem::DestroyInputDetector()
{
#if !UE_SERVER
    if (InputDetector.IsValid())
    {
        FSlateApplication::Get().UnregisterInputPreProcessor(InputDetector);
        InputDetector.Reset();
    }
#endif
}

void UStevesGameSubsystem::NotifyEnhancedInputMappingsChanged()
{
    // delay to ensure there's a tick in between which updates the mappings, it's not synchronous
    auto DelayedFunc = [this]()
    {
        OnEnhancedInputMappingsChanged.Broadcast();
    };
    FTimerHandle TempHandle;
    GetWorld()->GetTimerManager().SetTimer(TempHandle, FTimerDelegate::CreateLambda(DelayedFunc), 0.05, false);
}

TSoftObjectPtr<UInputAction> UStevesGameSubsystem::FindEnhancedInputAction(const FString& Name)
{
    if (FAssetRegistryModule* AssetRegistryModule = FModuleManager::LoadModulePtr<FAssetRegistryModule>(TEXT("AssetRegistry")))
    {
        IAssetRegistry& AssetRegistry = AssetRegistryModule->Get();
        if (auto Settings = GetDefault<UStevesPluginSettings>())
        {
            for (const auto& Dir : Settings->EnhancedInputActionSearchDirectories)
            {
                if (!FPackageName::IsValidPath(Dir.Path))
                {
                    continue;
                }

                TArray<FAssetData> Assets;
                FString Package = FPaths::Combine(Dir.Path, Name);
                if (AssetRegistry.GetAssetsByPackageName(FName(*Package), Assets, true))
                {
                    for (const FAssetData& Asset : Assets)
                    {
                        if (Asset.GetClass() == UInputAction::StaticClass())
                        {
                            return TSoftObjectPtr<UInputAction>(Asset.GetSoftObjectPath());
                        }
                    }
                }

            }
        }
    }
    return nullptr;
}

void UStevesGameSubsystem::RegisterInterestInEnhancedInputAction(const UInputAction* Action, ETriggerEvent TriggerEvent)
{
    // Avoid registering duplicate interest
    FEnhancedInputInterest Interest(Action, TriggerEvent);
    if (!RegisteredEnhancedInputActionInterests.Contains(Interest))
    {
        if (auto GI = GetGameInstance())
        {
            if (auto PC = GI->GetFirstLocalPlayerController())
            {
                if (auto EIC = Cast<UEnhancedInputComponent>(PC->InputComponent))
                {
                    EIC->BindAction(Action, TriggerEvent, this, &ThisClass::EnhancedInputActionTriggered);
                    RegisteredEnhancedInputActionInterests.Add(Interest);
                }
            }
        }
    }
    
}

void UStevesGameSubsystem::UnregisterAllInterestInEnhancedInputActions()
{
    if (auto GI = GetGameInstance())
    {
        if (auto PC = GI->GetFirstLocalPlayerController())
        {
            if (auto EIC = Cast<UEnhancedInputComponent>(PC->InputComponent))
            {
            	TArray<uint32> HandlesToRemove;
	            for (auto& Binding : EIC->GetActionEventBindings())
	            {
		            if (Binding->IsBoundToObject(this))
		            {
		            	HandlesToRemove.Add(Binding->GetHandle());
		            }
	            }

            	for (const uint32 Handle : HandlesToRemove)
            	{
            		EIC->RemoveBindingByHandle(Handle);
            	}
            }
        }
    }
    RegisteredEnhancedInputActionInterests.Empty();
}

void UStevesGameSubsystem::EnhancedInputActionTriggered(const FInputActionInstance& InputActionInstance)
{
    OnEnhancedInputActionTriggered.Broadcast(InputActionInstance.GetSourceAction(), InputActionInstance.GetTriggerEvent());
}



void UStevesGameSubsystem::InitTheme()
{
    DefaultUiTheme = LoadObject<UUiTheme>(nullptr, *DefaultUiThemePath, nullptr);
}


void UStevesGameSubsystem::InitForegroundCheck()
{
    // Check foreground status every 0.5 seconds
    GetWorld()->GetTimerManager().SetTimer(ForegroundCheckHandle, this, &UStevesGameSubsystem::CheckForeground, 0.5, true);
}

void UStevesGameSubsystem::CheckForeground()
{
    bool bNewForeground = bIsForeground;

    if (IsValid(GEngine) && IsValid(GEngine->GameViewport) && GEngine->GameViewport->Viewport)
        bNewForeground = GEngine->GameViewport->Viewport->IsForegroundWindow();

    if (bNewForeground != bIsForeground)
    {
        bIsForeground = bNewForeground;
        InputDetector->bIgnoreEvents = !bIsForeground;
        
        OnWindowForegroundChanged.Broadcast(bIsForeground);
    }

    
}
void UStevesGameSubsystem::OnInputDetectorModeChanged(int PlayerIndex, EInputMode NewMode)
{
    // We can't check this during Initialize because it's too early
    if (!bCheckedViewportClient)
    {
        auto GI = GetGameInstance();        
        auto VC = Cast<UStevesGameViewportClientBase>(GI->GetGameViewportClient());
        if (!VC)
            UE_LOG(LogStevesUEHelpers, Log, TEXT("Consider using UStevesGameViewportClientBase for your GameViewportClient"))

        bCheckedViewportClient = true;
    
    }

	auto Settings = GetDefault<UStevesPluginSettings>();
	if (Settings->bHideMouseWhenGamepadUsed)
	{
		auto GI = GetGameInstance();
		auto VC = GI->GetGameViewportClient();
		auto SVC = Cast<UStevesGameViewportClientBase>(VC);
		if (VC)
		{
			if (NewMode == EInputMode::Gamepad)
			{
				// First move mouse pointer out of the way because it still generates mouse hits (unless we make source changes to Slate, ugh)
				MoveMouseOffScreen(true);
			}
			else if (NewMode == EInputMode::Mouse)
			{
				if (SVC)
					SVC->SetSuppressMouseCursor(false);
			}
		}
	}
    OnInputModeChanged.Broadcast(PlayerIndex, NewMode);
}

void UStevesGameSubsystem::MoveMouseOffScreen(bool bAlsoHide) const
{
	if (auto GI = GetGameInstance())
	{
		auto VC = GI->GetGameViewportClient();
		auto SVC = Cast<UStevesGameViewportClientBase>(VC);
		auto PC = GI->GetFirstLocalPlayerController();
	
		FVector2D Sz;
		VC->GetViewportSize(Sz);
		// -1 because if you move cursor outside window when captured, Slate blows up when you press Return, ughghh 
		// To  make sure we don't generate a mouse move by doing this
		// Bizarrely the ViewportSize is correct for moving the mouse pointer but the mouse events have different geometry
		InputDetector->IgnoreNextMouseMove();
		PC->SetMouseLocation(Sz.X-1,Sz.Y-1);

		// Now hide it
		// I've seen people use PC->bShowMouseCursor but this messes with capturing when you switch back & forth
		// especially when pausing in the editor
		// instead, I'm using a separate flag to suppress it, see UiFixGameViewportClient for usage
		if (SVC && bAlsoHide)
			SVC->SetSuppressMouseCursor(true);
	}

}

void UStevesGameSubsystem::OnButtonInputDetectorModeChanged(int PlayerIndex, EInputMode NewMode)
{
    // This is specifically for button changes; if this is a different main input mode it will also be registered in OnInputDetectorModeChanged
    // Just relay this one
    OnButtonInputModeChanged.Broadcast(PlayerIndex, NewMode);
}

void UStevesGameSubsystem::OnAxisInputDetectorModeChanged(int PlayerIndex, EInputMode NewMode)
{
    // This is specifically for button changes; if this is a different main input mode it will also be registered in OnInputDetectorModeChanged
    // Just relay this one
    OnAxisInputModeChanged.Broadcast(PlayerIndex, NewMode);
}

FFocusSystem* UStevesGameSubsystem::GetFocusSystem()
{
    return &FocusSystem;
}

UPaperSprite* UStevesGameSubsystem::GetInputImageSprite(EInputBindingType BindingType,
                                                        FName ActionOrAxis,
                                                        FKey Key,
                                                        EInputImageDevicePreference DevicePreference,
                                                        int PlayerIdx,
                                                        const UUiTheme* Theme)
{
    switch(BindingType)
    {
    case EInputBindingType::Action:
        return GetInputImageSpriteFromAction(ActionOrAxis, DevicePreference, PlayerIdx, Theme);
    case EInputBindingType::Axis:
        return GetInputImageSpriteFromAxis(ActionOrAxis, DevicePreference, PlayerIdx, Theme);
    case EInputBindingType::Key:
        return GetInputImageSpriteFromKey(Key, PlayerIdx, Theme);
    default:
        return nullptr;
    }
}

// This is not threadsafe! But only used in UI thread in practice
TArray<FInputActionKeyMapping> GS_TempActionMap;
TArray<FInputAxisKeyMapping> GS_TempAxisMap;

UPaperSprite* UStevesGameSubsystem::GetInputImageSpriteFromAction(const FName& Name,
                                                                  EInputImageDevicePreference DevicePreference,
                                                                  int PlayerIdx,
                                                                  const UUiTheme* Theme)
{
    UInputSettings* Settings = UInputSettings::GetInputSettings();
    GS_TempActionMap.Empty();
    Settings->GetActionMappingByName(Name, GS_TempActionMap);
    
    // For default, prefer latest press keyboard/mouse for buttons
    if (DevicePreference == EInputImageDevicePreference::Auto)
        DevicePreference = EInputImageDevicePreference::Gamepad_Keyboard_Mouse_Button;

    const EInputMode LastInput = GetLastInputModeUsed(PlayerIdx);
    const EInputMode LastButtonInput = GetLastInputButtonPressed(PlayerIdx);
    const EInputMode LastAxisInput = GetLastInputAxisMoved(PlayerIdx);
    const auto Preferred = GetPreferedActionOrAxisMapping<FInputActionKeyMapping>(GS_TempActionMap, Name, DevicePreference, LastInput, LastButtonInput, LastAxisInput);
    if (Preferred)
    {
        return GetInputImageSpriteFromKey(Preferred->Key, PlayerIdx, Theme);
    }
    return nullptr;
}

UPaperSprite* UStevesGameSubsystem::GetInputImageSpriteFromAxis(const FName& Name,
                                                                EInputImageDevicePreference DevicePreference,
                                                                int PlayerIdx,
                                                                const UUiTheme* Theme)
{
    // Look up the key for this axis
    UInputSettings* Settings = UInputSettings::GetInputSettings();
    GS_TempAxisMap.Empty();
    Settings->GetAxisMappingByName(Name, GS_TempAxisMap);

    // For default, prefer mouse for axes
    if (DevicePreference == EInputImageDevicePreference::Auto)
        DevicePreference = EInputImageDevicePreference::Gamepad_Mouse_Keyboard;

    const EInputMode LastInput = GetLastInputModeUsed(PlayerIdx);
    const EInputMode LastButtonInput = GetLastInputButtonPressed(PlayerIdx);
    const EInputMode LastAxisInput = GetLastInputAxisMoved(PlayerIdx);
    const auto Preferred = GetPreferedActionOrAxisMapping<FInputAxisKeyMapping>(GS_TempAxisMap, Name, DevicePreference, LastInput, LastButtonInput, LastAxisInput);
    if (Preferred)
    {
        return GetInputImageSpriteFromKey(Preferred->Key, PlayerIdx, Theme);
    }
    return nullptr;
}

UPaperSprite* UStevesGameSubsystem::GetInputImageSpriteFromEnhancedInputAction(UInputAction* Action,
    EInputImageDevicePreference DevicePreference,
    int PlayerIdx,
    APlayerController* PC,
    UUiTheme* Theme)
{
    
    if (const UEnhancedInputLocalPlayerSubsystem* Subsystem = ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(PC->GetLocalPlayer()))
    {
        const TArray<FKey> Keys = Subsystem->QueryKeysMappedToAction(Action);

        // For default, prefer mouse for axes
        if (DevicePreference == EInputImageDevicePreference::Auto)
        {
            if (Action->ValueType == EInputActionValueType::Boolean)
            {
                DevicePreference = EInputImageDevicePreference::Gamepad_Keyboard_Mouse_Button;
            }
            else
            {
                DevicePreference = EInputImageDevicePreference::Gamepad_Mouse_Keyboard;
            }
        }
        const EInputMode LastInput = GetLastInputModeUsed(PlayerIdx);
        const EInputMode LastButtonInput = GetLastInputButtonPressed(PlayerIdx);
        const EInputMode LastAxisInput = GetLastInputAxisMoved(PlayerIdx);
        if (const FKey* PreferredKey = GetPreferedKeyMapping(Keys, DevicePreference, LastInput, LastButtonInput, LastAxisInput))
        {
            return GetInputImageSpriteFromKey(*PreferredKey, PlayerIdx, Theme);
        }
    }

    return nullptr;
}


TSoftObjectPtr<UDataTable> UStevesGameSubsystem::GetGamepadImages(int PlayerIndex, const UUiTheme* Theme)
{
    // TODO: determine type of controller
    return Theme->XboxControllerImages;    
}

UPaperSprite* UStevesGameSubsystem::GetInputImageSpriteFromKey(const FKey& InKey, int PlayerIndex, const UUiTheme* Theme)
{
    if (!IsValid(Theme))
        Theme = GetDefaultUiTheme();
    
    if (Theme)
    {
        if (InKey.IsGamepadKey())
            return GetImageSpriteFromTable(InKey,  GetGamepadImages(PlayerIndex, Theme));
        else
            return GetImageSpriteFromTable(InKey, Theme->KeyboardMouseImages);
    }

    return nullptr;
}


UPaperSprite* UStevesGameSubsystem::GetImageSpriteFromTable(const FKey& InKey,
    const TSoftObjectPtr<UDataTable>& Asset)
{
    // Sync load for simplicity for now
    const auto Table = Asset.LoadSynchronous();
    // Rows are named the same as the key name
    const auto SpriteRow = Table->FindRow<FKeySprite>(InKey.GetFName(), "Find Key Image");
    if (SpriteRow)
    {
        return SpriteRow->Sprite;
    }
    return nullptr;
}

void UStevesGameSubsystem::SetBrushFromAtlas(FSlateBrush* Brush, TScriptInterface<ISlateTextureAtlasInterface> AtlasRegion, bool bMatchSize)
{
    if(Brush->GetResourceObject() != AtlasRegion.GetObject())
    {
        Brush->SetResourceObject(AtlasRegion.GetObject());

        if (bMatchSize)
        {
            if (AtlasRegion)
            {
                const FSlateAtlasData AtlasData = AtlasRegion->GetSlateAtlasData();
                Brush->ImageSize = AtlasData.GetSourceDimensions();
            }
            else
            {
                Brush->ImageSize = FVector2D(0, 0);
            }
        }
    }
}

FStevesTextureRenderTargetPoolPtr UStevesGameSubsystem::GetTextureRenderTargetPool(FName Name, bool bAutoCreate)
{
    // On the assumption there won't be *loads* of pools, not worth a map, just iterate
    for (auto Tex : TextureRenderTargetPools)
    {
        if (Tex->GetName() == Name)
            return Tex;
    }

    if (bAutoCreate)
    {
        FStevesTextureRenderTargetPoolPtr Pool = MakeShared<FStevesTextureRenderTargetPool>(Name, this);
        TextureRenderTargetPools.Add(Pool);
        return Pool;
    }

    return nullptr;
    
}


bool UStevesGameSubsystem::FInputModeDetector::ShouldProcessInputEvents() const
{
    return !bIgnoreEvents;
}

UStevesGameSubsystem::FInputModeDetector::FInputModeDetector()
{
    // 4 local players should be plenty usually (will expand if necessary)
    LastInputModeByPlayer.Init(DefaultInputMode, 4);
    LastButtonPressByPlayer.Init(DefaultButtonInputMode, 4);
    LastAxisMoveByPlayer.Init(DefaultAxisInputMode, 4);
}

bool UStevesGameSubsystem::FInputModeDetector::HandleKeyDownEvent(FSlateApplication& SlateApp, const FKeyEvent& InKeyEvent)
{
    if (ShouldProcessInputEvents())
    {
        // Key down also registers for gamepad buttons
        ProcessKeyOrButton(InKeyEvent.GetUserIndex(), InKeyEvent.GetKey());
    }

    // Don't consume
    return false;
}

bool UStevesGameSubsystem::FInputModeDetector::HandleAnalogInputEvent(FSlateApplication& SlateApp,
    const FAnalogInputEvent& InAnalogInputEvent)
{
    if (ShouldProcessInputEvents())
    {
        if (InAnalogInputEvent.GetAnalogValue() > GamepadAxisThreshold)
            SetMode(InAnalogInputEvent.GetUserIndex(), EInputMode::Gamepad, false);
    }
    
    // Don't consume
    return false;
}

bool UStevesGameSubsystem::FInputModeDetector::HandleMouseMoveEvent(FSlateApplication& SlateApp, const FPointerEvent& MouseEvent)
{
    if (ShouldProcessInputEvents())
    {
    	if (bIgnoreNextMouseMove)
    	{
    		bIgnoreNextMouseMove = false;
    	}
    	else
    	{
    		FVector2D Dist = MouseEvent.GetScreenSpacePosition() - MouseEvent.GetLastScreenSpacePosition();
    		if (FMath::Abs(Dist.X) > MouseMoveThreshold || FMath::Abs(Dist.Y) > MouseMoveThreshold)
    		{
    			SetMode(MouseEvent.GetUserIndex(), EInputMode::Mouse, false);
    		}
    	}
    }
    // Don't consume
    return false;
}

bool UStevesGameSubsystem::FInputModeDetector::HandleMouseButtonDownEvent(FSlateApplication& SlateApp, const FPointerEvent& MouseEvent)
{
    if (ShouldProcessInputEvents())
    {
        // We don't care which button
        SetMode(MouseEvent.GetUserIndex(), EInputMode::Mouse, true);
    }
    
    // Don't consume
    return false;
}

bool UStevesGameSubsystem::FInputModeDetector::HandleMouseWheelOrGestureEvent(FSlateApplication& SlateApp, const FPointerEvent& InWheelEvent,
    const FPointerEvent* InGestureEvent)
{
    if (ShouldProcessInputEvents())
    {
        SetMode(InWheelEvent.GetUserIndex(), EInputMode::Mouse, false);
    }
    
    // Don't consume
    return false;
}

EInputMode UStevesGameSubsystem::FInputModeDetector::GetLastInputMode(int PlayerIndex)
{
    if (PlayerIndex >= 0 && PlayerIndex < LastInputModeByPlayer.Num())
        return LastInputModeByPlayer[PlayerIndex];

    // Assume default if never told
    return DefaultInputMode;
}

EInputMode UStevesGameSubsystem::FInputModeDetector::GetLastButtonInputMode(int PlayerIndex)
{
    if (PlayerIndex >= 0 && PlayerIndex < LastButtonPressByPlayer.Num())
        return LastButtonPressByPlayer[PlayerIndex];

    // Assume default if never told
    return DefaultButtonInputMode;
}

EInputMode UStevesGameSubsystem::FInputModeDetector::GetLastAxisInputMode(int PlayerIndex)
{
    if (PlayerIndex >= 0 && PlayerIndex < LastAxisMoveByPlayer.Num())
        return LastAxisMoveByPlayer[PlayerIndex];

    // Assume default if never told
    return DefaultAxisInputMode;
}

void UStevesGameSubsystem::FInputModeDetector::ProcessKeyOrButton(int PlayerIndex, FKey Key)
{
    if (Key.IsGamepadKey())
    {
        SetMode(PlayerIndex, EInputMode::Gamepad, IsAGamepadButton(Key));
    }
    else if (Key.IsMouseButton())
    {
        // Assuming mice don't have analog buttons!
        SetMode(PlayerIndex, EInputMode::Mouse, true);
    }
    else
    {
        // We assume anything that's not mouse and not gamepad is a keyboard
        // Assuming keyboards don't have analog buttons!
        SetMode(PlayerIndex, EInputMode::Keyboard, true);
    }

}

bool UStevesGameSubsystem::FInputModeDetector::IsAGamepadButton(const FKey& Key)
{
    
    // Key.IsButtonAxis() returns true for some thumbstick movement events, because the axis type is EInputAxisType::Button for
    // some reason. That means you get button events for thumbstick movements, which is super dumb. 
    // See core engine InputCoreTypes.cpp for the stick axes which are defined FKeyDetails::GamepadKey | FKeyDetails::ButtonAxis
    // This is for some kind of virtual input but it's a nasty hack, omit them
    return Key.IsGamepadKey() &&
        Key != EKeys::Gamepad_LeftStick_Up &&
        Key != EKeys::Gamepad_LeftStick_Down &&
        Key != EKeys::Gamepad_LeftStick_Left &&
        Key != EKeys::Gamepad_LeftStick_Right &&
        Key != EKeys::Gamepad_RightStick_Up &&
        Key != EKeys::Gamepad_RightStick_Down &&
        Key != EKeys::Gamepad_RightStick_Left &&
        Key != EKeys::Gamepad_RightStick_Right;

}

void UStevesGameSubsystem::FInputModeDetector::SetMode(int PlayerIndex, EInputMode NewMode, bool bIsButton)
{
    bool bButtonChanged = false;
    bool bAxisChanged = false;
    bool bMainChanged = false;
    
    if (bIsButton)
    {
        if (NewMode != EInputMode::Unknown && NewMode != GetLastButtonInputMode(PlayerIndex))
        {
            if (PlayerIndex >= LastButtonPressByPlayer.Num())
                LastButtonPressByPlayer.SetNum(PlayerIndex + 1);
            
            LastButtonPressByPlayer[PlayerIndex] = NewMode;

            bButtonChanged = true;
        }
    }
    else
    {
        if (NewMode != EInputMode::Unknown && NewMode != GetLastAxisInputMode(PlayerIndex))
        {
            if (PlayerIndex >= LastAxisMoveByPlayer.Num())
                LastAxisMoveByPlayer.SetNum(PlayerIndex + 1);
            
            LastAxisMoveByPlayer[PlayerIndex] = NewMode;

            bAxisChanged = true;
        }
        
    }
    // Whether it's a button or not it can affect the main input mode
    if (NewMode != EInputMode::Unknown && NewMode != GetLastInputMode(PlayerIndex))
    {
        if (PlayerIndex >= LastInputModeByPlayer.Num())
            LastInputModeByPlayer.SetNum(PlayerIndex + 1);
        
        LastInputModeByPlayer[PlayerIndex] = NewMode;

        bMainChanged = true;
    }

    // Raise events at the end once all state has changed
    if (bButtonChanged)
    {
        // ReSharper disable once CppExpressionWithoutSideEffects
        OnButtonInputModeChanged.ExecuteIfBound(PlayerIndex, NewMode);
        //UE_LOG(LogStevesUEHelpers, Display, TEXT("Button mode for player %d changed: %s"), PlayerIndex, *UEnum::GetValueAsString(NewMode));
    }
    if (bAxisChanged)
    {
        // ReSharper disable once CppExpressionWithoutSideEffects
        OnAxisInputModeChanged.ExecuteIfBound(PlayerIndex, NewMode);
        //UE_LOG(LogStevesUEHelpers, Display, TEXT("Axis mode for player %d changed: %s"), PlayerIndex, *UEnum::GetValueAsString(NewMode));
    }
    if (bMainChanged)
    {
        // ReSharper disable once CppExpressionWithoutSideEffects
        OnInputModeChanged.ExecuteIfBound(PlayerIndex, NewMode);
        //UE_LOG(LogStevesUEHelpers, Display, TEXT("Input mode for player %d changed: %s"), PlayerIndex, *UEnum::GetValueAsString(NewMode));
    }

}

//PRAGMA_ENABLE_OPTIMIZATION