#include "HyperTwistSimulation/HyperTwistMelindaProjectionPlayerController.h"

#include "EngineUtils.h"
#include "HyperTwistSimulation/HyperTwistMelindaProjectionActor.h"
#include "HyperTwistUX/HyperTwistFourDimensionalHUDWidget.h"
#include "InputCoreTypes.h"

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

void AHyperTwistMelindaProjectionPlayerController::BeginPlay()
{
	Super::BeginPlay();
	ApplyInputMode();
	ShowPuzzleHud();
}

FString AHyperTwistMelindaProjectionPlayerController::GetPauseMenuTitle() const
{
	return TEXT("4D Cube 2x2x2x2");
}

FString AHyperTwistMelindaProjectionPlayerController::GetPauseMenuSubtitle() const
{
	if (const AHyperTwistMelindaProjectionActor* ProjectionActor =
			ResolveProjectionActor())
	{
		return ProjectionActor->CurrentState.bIsSolved
			? TEXT("Solved cell-first projection")
			: FString::Printf(
				TEXT("Active exact state | last turn: %s"),
				*ProjectionActor->LastAppliedNotation);
	}
	return TEXT("Cell-first projection with exact four-dimensional turns");
}

bool AHyperTwistMelindaProjectionPlayerController::ShowPuzzleHud()
{
	if (!bShowPuzzleHud)
	{
		return false;
	}
	if (ActivePuzzleHudWidget == nullptr)
	{
		TSubclassOf<UHyperTwistFourDimensionalHUDWidget> ResolvedClass =
			PuzzleHudWidgetClass;
		if (*ResolvedClass == nullptr)
		{
			ResolvedClass = UHyperTwistFourDimensionalHUDWidget::StaticClass();
		}
		ActivePuzzleHudWidget =
			CreateWidget<UHyperTwistFourDimensionalHUDWidget>(
				this,
				ResolvedClass);
		if (ActivePuzzleHudWidget == nullptr
			|| !ActivePuzzleHudWidget->PrepareHudSurface())
		{
			ActivePuzzleHudWidget = nullptr;
			return false;
		}
		ActivePuzzleHudWidget->OnScrambleRequested.AddDynamic(
			this,
			&AHyperTwistMelindaProjectionPlayerController::HandleHudScramble);
		ActivePuzzleHudWidget->OnResetRequested.AddDynamic(
			this,
			&AHyperTwistMelindaProjectionPlayerController::HandleHudReset);
		ActivePuzzleHudWidget->OnSaveRequested.AddDynamic(
			this,
			&AHyperTwistMelindaProjectionPlayerController::HandleHudSave);
		ActivePuzzleHudWidget->OnLoadRequested.AddDynamic(
			this,
			&AHyperTwistMelindaProjectionPlayerController::HandleHudLoad);
		ActivePuzzleHudWidget->AddToViewport(PuzzleHudZOrder);
	}
	else if (!ActivePuzzleHudWidget->IsInViewport())
	{
		ActivePuzzleHudWidget->AddToViewport(PuzzleHudZOrder);
	}
	RefreshPuzzleHud();
	return true;
}

void AHyperTwistMelindaProjectionPlayerController::RefreshPuzzleHud()
{
	if (ActivePuzzleHudWidget == nullptr)
	{
		return;
	}
	const AHyperTwistMelindaProjectionActor* ProjectionActor =
		ResolveProjectionActor();
	if (ProjectionActor == nullptr)
	{
		ActivePuzzleHudWidget->ConfigureSurface(
			TEXT("4D Cube 2x2x2x2"),
			TEXT("WAITING FOR RUNTIME"),
			TEXT("Cell-first projection actor is not ready."),
			TEXT("waiting"),
			TEXT("not loaded"),
			true);
		return;
	}
	ActivePuzzleHudWidget->ConfigureSurface(
		TEXT("4D Cube 2x2x2x2"),
		FString::Printf(
			TEXT("%s  |  16 exact pieces  |  8 projected cells"),
			ProjectionActor->CurrentState.bIsSolved
				? TEXT("SOLVED")
				: TEXT("ACTIVE")),
		TEXT("Direct cell projection | click any visible cell to turn"),
		ProjectionActor->LastAppliedNotation,
		ProjectionActor->LastPersistenceStatus,
		true);
}

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

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

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

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

	if (bBindResetShortcut)
	{
		InputComponent->BindKey(
			EKeys::R,
			IE_Pressed,
			this,
			&AHyperTwistMelindaProjectionPlayerController::HandleResetShortcut
		);
	}

	if (bBindRandomizeShortcut)
	{
		InputComponent->BindKey(
			EKeys::G,
			IE_Pressed,
			this,
			&AHyperTwistMelindaProjectionPlayerController::HandleRandomizeShortcut
		);
	}

	InputComponent->BindKey(
		EKeys::S,
		IE_Pressed,
		this,
		&AHyperTwistMelindaProjectionPlayerController::HandleSaveShortcut);
	InputComponent->BindKey(
		EKeys::L,
		IE_Pressed,
		this,
		&AHyperTwistMelindaProjectionPlayerController::HandleLoadShortcut);
}

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

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

bool AHyperTwistMelindaProjectionPlayerController::TryProcessProjectionClickFromScreenPosition(
	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 (AHyperTwistMelindaProjectionActor* ProjectionActor = ResolveProjectionActor())
	{
		const bool bApplied = ProjectionActor->ProcessClick(
			RayOrigin,
			RayDirection,
			bCounterClockwise);
		RefreshPuzzleHud();
		return bApplied;
	}

	return false;
}

void AHyperTwistMelindaProjectionPlayerController::ResetProjectionToSolved()
{
	if (AHyperTwistMelindaProjectionActor* ProjectionActor = ResolveProjectionActor())
	{
		ProjectionActor->ResetToSolvedState();
	}
	RefreshPuzzleHud();
}

bool AHyperTwistMelindaProjectionPlayerController::GenerateProjectionRandomState()
{
	if (AHyperTwistMelindaProjectionActor* ProjectionActor = ResolveProjectionActor())
	{
		const bool bGenerated = ProjectionActor->GenerateRandomState(NextRandomSeed++);
		RefreshPuzzleHud();
		return bGenerated;
	}

	return false;
}

void AHyperTwistMelindaProjectionPlayerController::ApplyInputMode()
{
	if (bUseGameAndUiInputMode)
	{
		ApplyGameAndUiInputMode();
	}
}

AHyperTwistMelindaProjectionActor* AHyperTwistMelindaProjectionPlayerController::ResolveProjectionActor() const
{
	if (GetWorld() == nullptr)
	{
		return nullptr;
	}

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

	return nullptr;
}

void AHyperTwistMelindaProjectionPlayerController::HandlePrimaryClick()
{
	TryProcessProjectionClickFromCursor(false);
}

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

	TryProcessProjectionClickFromCursor(true);
}

void AHyperTwistMelindaProjectionPlayerController::HandleResetShortcut()
{
	ResetProjectionToSolved();
}

void AHyperTwistMelindaProjectionPlayerController::HandleRandomizeShortcut()
{
	GenerateProjectionRandomState();
}

void AHyperTwistMelindaProjectionPlayerController::HandleSaveShortcut()
{
	if (AHyperTwistMelindaProjectionActor* ProjectionActor = ResolveProjectionActor())
	{
		ProjectionActor->SaveRuntimeState();
	}
	RefreshPuzzleHud();
}

void AHyperTwistMelindaProjectionPlayerController::HandleLoadShortcut()
{
	if (AHyperTwistMelindaProjectionActor* ProjectionActor = ResolveProjectionActor())
	{
		ProjectionActor->LoadRuntimeState();
	}
	RefreshPuzzleHud();
}

void AHyperTwistMelindaProjectionPlayerController::HandleHudScramble()
{
	GenerateProjectionRandomState();
}

void AHyperTwistMelindaProjectionPlayerController::HandleHudReset()
{
	ResetProjectionToSolved();
}

void AHyperTwistMelindaProjectionPlayerController::HandleHudSave()
{
	HandleSaveShortcut();
}

void AHyperTwistMelindaProjectionPlayerController::HandleHudLoad()
{
	HandleLoadShortcut();
}

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