#include "HyperTwistSimulation/HyperTwistClassicCubePlayerController.h"

#include "EngineUtils.h"
#include "GameFramework/GameModeBase.h"
#include "HyperTwistAlgorithm/HyperTwistAlgorithmKeyboard.h"
#include "HyperTwistSimulation/HyperTwistClassicCubeActor.h"
#include "HyperTwistSimulation/HyperTwistClassicCubeOperatorSurface.h"
#include "HyperTwistUX/HyperTwistPlayerSettings.h"
#include "InputCoreTypes.h"

namespace HyperTwistClassicCubePlayerControllerInternal
{
	UObject* ResolveOperatorSurfaceObject(const APlayerController* PlayerController)
	{
		if (PlayerController == nullptr || PlayerController->GetWorld() == nullptr)
		{
			return nullptr;
		}

		AGameModeBase* GameMode = PlayerController->GetWorld()->GetAuthGameMode();
		if (GameMode == nullptr
			|| !GameMode->GetClass()->ImplementsInterface(UHyperTwistClassicCubeOperatorSurface::StaticClass()))
		{
			return nullptr;
		}

		return GameMode;
	}
}

AHyperTwistClassicCubePlayerController::AHyperTwistClassicCubePlayerController()
{
	bShowMouseCursor = true;
	bEnableClickEvents = true;
	bEnableMouseOverEvents = true;
}

void AHyperTwistClassicCubePlayerController::BeginPlay()
{
	Super::BeginPlay();
	ApplyClassicCubeInputMode();
}

FString AHyperTwistClassicCubePlayerController::GetPauseMenuTitle() const
{
	return TEXT("Classic Cube");
}

FString AHyperTwistClassicCubePlayerController::GetPauseMenuSubtitle() const
{
	return TEXT("3x3x3 free play, timing, hints, replay, and guided practice");
}

void AHyperTwistClassicCubePlayerController::PrepareForModalUi()
{
	Super::PrepareForModalUi();
	if (UObject* OperatorSurface =
			HyperTwistClassicCubePlayerControllerInternal::ResolveOperatorSurfaceObject(this))
	{
		IHyperTwistClassicCubeOperatorSurface::
			Execute_RequestClassicCubeEndVoiceCommandCapture(OperatorSurface);
	}
	bToggleVoiceCaptureActive = false;
}

void AHyperTwistClassicCubePlayerController::SetupInputComponent()
{
	Super::SetupInputComponent();
	if (InputComponent == nullptr)
	{
		return;
	}

	InputComponent->BindKey(
		EKeys::LeftMouseButton,
		IE_Pressed,
		this,
		&AHyperTwistClassicCubePlayerController::HandlePrimaryClick
	);

	if (bEnableCounterClockwiseRightClick)
	{
		InputComponent->BindKey(
			EKeys::RightMouseButton,
			IE_Pressed,
			this,
			&AHyperTwistClassicCubePlayerController::HandleSecondaryClick
		);
	}

	if (bEnableTouchTurnInput && PlayerPreferences.bTouchInputEnabled)
	{
		InputComponent->BindTouch(
			IE_Pressed,
			this,
			&AHyperTwistClassicCubePlayerController::HandleTouchPressed
		);
	}

	if (bBindFreshAttemptShortcut)
	{
		InputComponent->BindKey(
			UHyperTwistPlayerSettingsLibrary::ResolveKeyBinding(
				PlayerPreferences,
				TEXT("puzzle.new")),
			IE_Pressed,
			this,
			&AHyperTwistClassicCubePlayerController::HandleFreshAttemptShortcut
		);
	}

	if (bBindHintShortcut)
	{
		InputComponent->BindKey(
			UHyperTwistPlayerSettingsLibrary::ResolveKeyBinding(
				PlayerPreferences,
				TEXT("puzzle.hint")),
			IE_Pressed,
			this,
			&AHyperTwistClassicCubePlayerController::HandleHintShortcut
		);
	}

	if (bBindSubmitSolveShortcut)
	{
		InputComponent->BindKey(
			UHyperTwistPlayerSettingsLibrary::ResolveKeyBinding(
				PlayerPreferences,
				TEXT("puzzle.submit")),
			IE_Pressed,
			this,
			&AHyperTwistClassicCubePlayerController::HandleSubmitSolveShortcut
		);
	}

	if (bBindModeToggleShortcut)
	{
		InputComponent->BindKey(
			UHyperTwistPlayerSettingsLibrary::ResolveKeyBinding(
				PlayerPreferences,
				TEXT("lesson.toggle")),
			IE_Pressed,
			this,
			&AHyperTwistClassicCubePlayerController::HandleModeToggleShortcut
		);
	}

	if (bBindVoiceHoldShortcut && PlayerPreferences.bSpeechInputEnabled)
	{
		const FKey PushToTalkKey =
			UHyperTwistPlayerSettingsLibrary::ResolveKeyBinding(
				PlayerPreferences,
				TEXT("speech.push-to-talk"));
		InputComponent->BindKey(
			PushToTalkKey,
			IE_Pressed,
			this,
			&AHyperTwistClassicCubePlayerController::HandleVoicePressed
		);
		InputComponent->BindKey(
			PushToTalkKey,
			IE_Released,
			this,
			&AHyperTwistClassicCubePlayerController::HandleVoiceReleased
		);
	}

	if (bBindVoiceCycleShortcut
		&& PlayerPreferences.bSpeechInputEnabled
		&& PlayerPreferences.bCoachNarrationEnabled)
	{
		InputComponent->BindKey(
			UHyperTwistPlayerSettingsLibrary::ResolveKeyBinding(
				PlayerPreferences,
				TEXT("speech.cycle-voice")),
			IE_Pressed,
			this,
			&AHyperTwistClassicCubePlayerController::HandleVoiceCycleShortcut
		);
	}
}

bool AHyperTwistClassicCubePlayerController::InputKey(
	const FInputKeyEventArgs& Params
)
{
	const bool bHandledByBase = Super::InputKey(Params);
	if (bHandledByBase)
	{
		return true;
	}
	return TryApplyKeyboardPuzzleMove(Params);
}

bool AHyperTwistClassicCubePlayerController::TryProcessCubeClickFromCursor(const bool bCounterClockwise)
{
	float ScreenX = 0.0f;
	float ScreenY = 0.0f;
	if (!GetMousePosition(ScreenX, ScreenY))
	{
		return false;
	}

	return TryProcessCubeClickFromScreenPosition(FVector2D(ScreenX, ScreenY), bCounterClockwise);
}

bool AHyperTwistClassicCubePlayerController::TryProcessCubeClickFromScreenPosition(
	const FVector2D& ScreenPosition,
	const bool bCounterClockwise
)
{
	FVector RayOrigin = FVector::ZeroVector;
	FVector RayDirection = FVector::ZeroVector;
	if (!DeprojectScreenPositionToWorld(ScreenPosition.X, ScreenPosition.Y, RayOrigin, RayDirection))
	{
		return false;
	}

	if (UObject* OperatorSurface =
			HyperTwistClassicCubePlayerControllerInternal::ResolveOperatorSurfaceObject(this))
	{
		if (!IHyperTwistClassicCubeOperatorSurface::Execute_CanClassicCubeAcceptGameplayMoveInput(
				OperatorSurface))
		{
			return false;
		}
	}

	if (AHyperTwistClassicCubeActor* CubeActor = ResolveClassicCubeActor())
	{
		return CubeActor->ProcessClick(RayOrigin, RayDirection, bCounterClockwise);
	}

	return false;
}

void AHyperTwistClassicCubePlayerController::RequestFreshAttempt()
{
	if (UObject* OperatorSurface =
			HyperTwistClassicCubePlayerControllerInternal::ResolveOperatorSurfaceObject(this))
	{
		IHyperTwistClassicCubeOperatorSurface::Execute_RequestClassicCubeFreshAttempt(
			OperatorSurface
		);
	}
}

void AHyperTwistClassicCubePlayerController::ApplyClassicCubeInputMode()
{
	if (bUseGameAndUiInputMode)
	{
		ApplyGameAndUiInputMode();
	}
}

bool AHyperTwistClassicCubePlayerController::TryApplyKeyboardPuzzleMove(
	const FInputKeyEventArgs& Params
)
{
	if (Params.Event != IE_Pressed
		|| ActivePauseMenuWidget != nullptr
		|| (GetWorld() != nullptr && GetWorld()->IsPaused()))
	{
		return false;
	}

	if (UObject* OperatorSurface =
			HyperTwistClassicCubePlayerControllerInternal::ResolveOperatorSurfaceObject(this))
	{
		if (!IHyperTwistClassicCubeOperatorSurface::
			Execute_CanClassicCubeAcceptGameplayMoveInput(OperatorSurface))
		{
			return false;
		}
	}

	FHyperTwistAlgorithmKeyboardEvent KeyboardEvent;
	KeyboardEvent.Key = Params.Key;
	KeyboardEvent.bCtrlPressed =
		IsInputKeyDown(EKeys::LeftControl) || IsInputKeyDown(EKeys::RightControl);
	KeyboardEvent.bAltPressed =
		IsInputKeyDown(EKeys::LeftAlt) || IsInputKeyDown(EKeys::RightAlt);
	KeyboardEvent.bShiftPressed =
		IsInputKeyDown(EKeys::LeftShift) || IsInputKeyDown(EKeys::RightShift);
	KeyboardEvent.bMetaPressed =
		IsInputKeyDown(EKeys::LeftCommand) || IsInputKeyDown(EKeys::RightCommand);
	KeyboardEvent.bIsRepeat = false;

	const FHyperTwistAlgorithmKeyboardMappingResult Mapping =
		UHyperTwistAlgorithmKeyboardLibrary::MapKeyboardEvent(
			KeyboardEvent,
			UHyperTwistPlayerSettingsLibrary::BuildClassicKeyboardProfile(
				PlayerPreferences));
	if (!Mapping.bHasMoveIntent || Mapping.CanonicalText.IsEmpty())
	{
		return false;
	}

	if (AHyperTwistClassicCubeActor* CubeActor = ResolveClassicCubeActor())
	{
		return CubeActor->TryQueueMoveNotation(Mapping.CanonicalText, true);
	}
	return false;
}

AHyperTwistClassicCubeActor* AHyperTwistClassicCubePlayerController::ResolveClassicCubeActor() const
{
	if (GetWorld() == nullptr)
	{
		return nullptr;
	}

	for (TActorIterator<AHyperTwistClassicCubeActor> ActorIt(GetWorld()); ActorIt; ++ActorIt)
	{
		return *ActorIt;
	}

	return nullptr;
}

void AHyperTwistClassicCubePlayerController::HandlePrimaryClick()
{
	if (UObject* OperatorSurface =
			HyperTwistClassicCubePlayerControllerInternal::ResolveOperatorSurfaceObject(this))
	{
		if (IHyperTwistClassicCubeOperatorSurface::Execute_IsClassicCubeAnyActionButtonHovered(
				OperatorSurface))
		{
			return;
		}
	}

	TryProcessCubeClickFromCursor(false);
}

void AHyperTwistClassicCubePlayerController::HandleSecondaryClick()
{
	if (IsInputKeyDown(EKeys::LeftShift) || IsInputKeyDown(EKeys::RightShift))
	{
		return;
	}

	if (UObject* OperatorSurface =
			HyperTwistClassicCubePlayerControllerInternal::ResolveOperatorSurfaceObject(this))
	{
		if (IHyperTwistClassicCubeOperatorSurface::Execute_IsClassicCubeAnyActionButtonHovered(
				OperatorSurface))
		{
			return;
		}
	}

	TryProcessCubeClickFromCursor(true);
}

void AHyperTwistClassicCubePlayerController::HandleFreshAttemptShortcut()
{
	RequestFreshAttempt();
}

void AHyperTwistClassicCubePlayerController::HandleHintShortcut()
{
	if (UObject* OperatorSurface =
			HyperTwistClassicCubePlayerControllerInternal::ResolveOperatorSurfaceObject(this))
	{
		IHyperTwistClassicCubeOperatorSurface::Execute_RequestClassicCubeHint(OperatorSurface);
	}
}

void AHyperTwistClassicCubePlayerController::HandleSubmitSolveShortcut()
{
	if (UObject* OperatorSurface =
			HyperTwistClassicCubePlayerControllerInternal::ResolveOperatorSurfaceObject(this))
	{
		IHyperTwistClassicCubeOperatorSurface::Execute_RequestClassicCubeSubmitSolve(
			OperatorSurface
		);
	}
}

void AHyperTwistClassicCubePlayerController::HandleModeToggleShortcut()
{
	if (UObject* OperatorSurface =
			HyperTwistClassicCubePlayerControllerInternal::ResolveOperatorSurfaceObject(this))
	{
		IHyperTwistClassicCubeOperatorSurface::Execute_RequestClassicCubeToggleFollowAlongMode(
			OperatorSurface
		);
	}
}

void AHyperTwistClassicCubePlayerController::HandleVoicePressed()
{
	if (UObject* OperatorSurface =
			HyperTwistClassicCubePlayerControllerInternal::ResolveOperatorSurfaceObject(this))
	{
		if (PlayerPreferences.SpeechActivationMode == TEXT("toggle"))
		{
			if (bToggleVoiceCaptureActive)
			{
				IHyperTwistClassicCubeOperatorSurface::
					Execute_RequestClassicCubeEndVoiceCommandCapture(OperatorSurface);
			}
			else
			{
				IHyperTwistClassicCubeOperatorSurface::
					Execute_RequestClassicCubeBeginVoiceCommandCapture(OperatorSurface);
			}
			bToggleVoiceCaptureActive = !bToggleVoiceCaptureActive;
			return;
		}
		IHyperTwistClassicCubeOperatorSurface::
			Execute_RequestClassicCubeBeginVoiceCommandCapture(OperatorSurface);
	}
}

void AHyperTwistClassicCubePlayerController::HandleVoiceReleased()
{
	if (PlayerPreferences.SpeechActivationMode == TEXT("toggle"))
	{
		return;
	}
	if (UObject* OperatorSurface =
			HyperTwistClassicCubePlayerControllerInternal::ResolveOperatorSurfaceObject(this))
	{
		IHyperTwistClassicCubeOperatorSurface::Execute_RequestClassicCubeEndVoiceCommandCapture(
			OperatorSurface
		);
	}
}

void AHyperTwistClassicCubePlayerController::HandleVoiceCycleShortcut()
{
	if (UObject* OperatorSurface =
			HyperTwistClassicCubePlayerControllerInternal::ResolveOperatorSurfaceObject(this))
	{
		IHyperTwistClassicCubeOperatorSurface::Execute_RequestClassicCubeCycleVoiceProfile(
			OperatorSurface
		);
	}
}

void AHyperTwistClassicCubePlayerController::HandleTouchPressed(
	const ETouchIndex::Type FingerIndex,
	const FVector Location
)
{
	static_cast<void>(FingerIndex);
	TryProcessCubeClickFromScreenPosition(FVector2D(Location.X, Location.Y), false);
}
