#include "HyperTwistSimulation/HyperTwistVirtual3333ProjectionPlayerController.h"

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

AHyperTwistVirtual3333ProjectionPlayerController::AHyperTwistVirtual3333ProjectionPlayerController()
{
	bShowMouseCursor = true;
	bEnableClickEvents = false;
	bEnableMouseOverEvents = false;
}

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

FString AHyperTwistVirtual3333ProjectionPlayerController::GetPauseMenuTitle() const
{
	const AHyperTwistVirtual3333ProjectionActor* ProjectionActor =
		ResolveProjectionActor();
	const int32 Order = ProjectionActor != nullptr
		? ProjectionActor->PuzzleOrder
		: 3;
	return FString::Printf(
		TEXT("4D Cube %dx%dx%dx%d"),
		Order,
		Order,
		Order,
		Order);
}

FString AHyperTwistVirtual3333ProjectionPlayerController::GetPauseMenuSubtitle() const
{
	if (const AHyperTwistVirtual3333ProjectionActor* ProjectionActor =
			ResolveProjectionActor())
	{
		return FString::Printf(
			TEXT("%s | %s"),
			*ProjectionActor->GetSelectionLabel(),
			ProjectionActor->CurrentState.bIsSolved
				? TEXT("solved")
				: *ProjectionActor->LastAppliedNotation);
	}
	return TEXT("Exact-state visible-slice projection");
}

bool AHyperTwistVirtual3333ProjectionPlayerController::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->OnCycleAxisRequested.AddDynamic(
			this,
			&AHyperTwistVirtual3333ProjectionPlayerController::HandleHudCycleAxis);
		ActivePuzzleHudWidget->OnPreviousLayerRequested.AddDynamic(
			this,
			&AHyperTwistVirtual3333ProjectionPlayerController::HandleHudPreviousLayer);
		ActivePuzzleHudWidget->OnNextLayerRequested.AddDynamic(
			this,
			&AHyperTwistVirtual3333ProjectionPlayerController::HandleHudNextLayer);
		ActivePuzzleHudWidget->OnPreviousPlaneRequested.AddDynamic(
			this,
			&AHyperTwistVirtual3333ProjectionPlayerController::HandleHudPreviousPlane);
		ActivePuzzleHudWidget->OnNextPlaneRequested.AddDynamic(
			this,
			&AHyperTwistVirtual3333ProjectionPlayerController::HandleHudNextPlane);
		ActivePuzzleHudWidget->OnCounterClockwiseRequested.AddDynamic(
			this,
			&AHyperTwistVirtual3333ProjectionPlayerController::HandleHudCounterClockwise);
		ActivePuzzleHudWidget->OnClockwiseRequested.AddDynamic(
			this,
			&AHyperTwistVirtual3333ProjectionPlayerController::HandleHudClockwise);
		ActivePuzzleHudWidget->OnToggleShellRequested.AddDynamic(
			this,
			&AHyperTwistVirtual3333ProjectionPlayerController::HandleHudToggleShell);
		ActivePuzzleHudWidget->OnScrambleRequested.AddDynamic(
			this,
			&AHyperTwistVirtual3333ProjectionPlayerController::HandleHudScramble);
		ActivePuzzleHudWidget->OnResetRequested.AddDynamic(
			this,
			&AHyperTwistVirtual3333ProjectionPlayerController::HandleHudReset);
		ActivePuzzleHudWidget->OnSaveRequested.AddDynamic(
			this,
			&AHyperTwistVirtual3333ProjectionPlayerController::HandleHudSave);
		ActivePuzzleHudWidget->OnLoadRequested.AddDynamic(
			this,
			&AHyperTwistVirtual3333ProjectionPlayerController::HandleHudLoad);
		ActivePuzzleHudWidget->AddToViewport(PuzzleHudZOrder);
	}
	else if (!ActivePuzzleHudWidget->IsInViewport())
	{
		ActivePuzzleHudWidget->AddToViewport(PuzzleHudZOrder);
	}
	RefreshPuzzleHud();
	return true;
}

void AHyperTwistVirtual3333ProjectionPlayerController::RefreshPuzzleHud()
{
	if (ActivePuzzleHudWidget == nullptr)
	{
		return;
	}
	const AHyperTwistVirtual3333ProjectionActor* ProjectionActor =
		ResolveProjectionActor();
	if (ProjectionActor == nullptr)
	{
		ActivePuzzleHudWidget->ConfigureSurface(
			TEXT("4D Cube"),
			TEXT("WAITING FOR RUNTIME"),
			TEXT("Projection actor is not ready."),
			TEXT("waiting"),
			TEXT("not loaded"),
			false);
		return;
	}

	const int32 Order = ProjectionActor->PuzzleOrder;
	const int32 PieceCount = Order * Order * Order * Order;
	const int32 VisiblePieceCount = Order * Order * Order;
	ActivePuzzleHudWidget->ConfigureSurface(
		FString::Printf(
			TEXT("4D Cube %dx%dx%dx%d"),
			Order,
			Order,
			Order,
			Order),
		FString::Printf(
			TEXT("%s  |  %d exact pieces  |  %d visible"),
			ProjectionActor->CurrentState.bIsSolved ? TEXT("SOLVED") : TEXT("ACTIVE"),
			PieceCount,
			VisiblePieceCount),
		ProjectionActor->GetSelectionLabel(),
		ProjectionActor->LastAppliedNotation,
		ProjectionActor->LastPersistenceStatus,
		false);
}

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

	InputComponent->BindKey(
		EKeys::X,
		IE_Pressed,
		this,
		&AHyperTwistVirtual3333ProjectionPlayerController::HandleSliceAxisX
	);
	InputComponent->BindKey(
		EKeys::Y,
		IE_Pressed,
		this,
		&AHyperTwistVirtual3333ProjectionPlayerController::HandleSliceAxisY
	);
	InputComponent->BindKey(
		EKeys::Z,
		IE_Pressed,
		this,
		&AHyperTwistVirtual3333ProjectionPlayerController::HandleSliceAxisZ
	);
	InputComponent->BindKey(
		EKeys::W,
		IE_Pressed,
		this,
		&AHyperTwistVirtual3333ProjectionPlayerController::HandleSliceAxisW
	);

	InputComponent->BindKey(
		EKeys::One,
		IE_Pressed,
		this,
		&AHyperTwistVirtual3333ProjectionPlayerController::HandleSliceCoordinateNegative
	);
	InputComponent->BindKey(
		EKeys::Two,
		IE_Pressed,
		this,
		&AHyperTwistVirtual3333ProjectionPlayerController::HandleSliceCoordinateMiddle
	);
	InputComponent->BindKey(
		EKeys::Three,
		IE_Pressed,
		this,
		&AHyperTwistVirtual3333ProjectionPlayerController::HandleSliceCoordinatePositive
	);
	InputComponent->BindKey(
		EKeys::Four,
		IE_Pressed,
		this,
		&AHyperTwistVirtual3333ProjectionPlayerController::HandleSliceLayerFour
	);
	InputComponent->BindKey(
		EKeys::Five,
		IE_Pressed,
		this,
		&AHyperTwistVirtual3333ProjectionPlayerController::HandleSliceLayerFive
	);
	InputComponent->BindKey(
		EKeys::Six,
		IE_Pressed,
		this,
		&AHyperTwistVirtual3333ProjectionPlayerController::HandleSliceLayerSix
	);
	InputComponent->BindKey(
		EKeys::LeftBracket,
		IE_Pressed,
		this,
		&AHyperTwistVirtual3333ProjectionPlayerController::HandlePreviousSliceLayer
	);
	InputComponent->BindKey(
		EKeys::RightBracket,
		IE_Pressed,
		this,
		&AHyperTwistVirtual3333ProjectionPlayerController::HandleNextSliceLayer
	);

	InputComponent->BindKey(
		EKeys::Q,
		IE_Pressed,
		this,
		&AHyperTwistVirtual3333ProjectionPlayerController::HandleCycleRotationAxisBackward
	);
	InputComponent->BindKey(
		EKeys::E,
		IE_Pressed,
		this,
		&AHyperTwistVirtual3333ProjectionPlayerController::HandleCycleRotationAxisForward
	);

	InputComponent->BindKey(
		EKeys::Comma,
		IE_Pressed,
		this,
		&AHyperTwistVirtual3333ProjectionPlayerController::HandleApplyCounterClockwise
	);
	InputComponent->BindKey(
		EKeys::Period,
		IE_Pressed,
		this,
		&AHyperTwistVirtual3333ProjectionPlayerController::HandleApplyClockwise
	);

	InputComponent->BindKey(
		EKeys::R,
		IE_Pressed,
		this,
		&AHyperTwistVirtual3333ProjectionPlayerController::HandleResetShortcut
	);
	InputComponent->BindKey(
		EKeys::G,
		IE_Pressed,
		this,
		&AHyperTwistVirtual3333ProjectionPlayerController::HandleScrambleShortcut
	);
	InputComponent->BindKey(
		EKeys::T,
		IE_Pressed,
		this,
		&AHyperTwistVirtual3333ProjectionPlayerController::HandleToggleShellShortcut
	);
	InputComponent->BindKey(
		EKeys::S,
		IE_Pressed,
		this,
		&AHyperTwistVirtual3333ProjectionPlayerController::HandleSaveShortcut
	);
	InputComponent->BindKey(
		EKeys::L,
		IE_Pressed,
		this,
		&AHyperTwistVirtual3333ProjectionPlayerController::HandleLoadShortcut
	);
}

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

bool AHyperTwistVirtual3333ProjectionPlayerController::ApplySelectionClockwise()
{
	if (AHyperTwistVirtual3333ProjectionActor* ProjectionActor = ResolveProjectionActor())
	{
		const bool bApplied = ProjectionActor->ApplyCurrentSelectionClockwise();
		RefreshPuzzleHud();
		return bApplied;
	}

	return false;
}

bool AHyperTwistVirtual3333ProjectionPlayerController::ApplySelectionCounterClockwise()
{
	if (AHyperTwistVirtual3333ProjectionActor* ProjectionActor = ResolveProjectionActor())
	{
		const bool bApplied = ProjectionActor->ApplyCurrentSelectionCounterClockwise();
		RefreshPuzzleHud();
		return bApplied;
	}

	return false;
}

bool AHyperTwistVirtual3333ProjectionPlayerController::SetSliceAxis(
	const EHyperTwistVirtual3333Axis SliceAxis
)
{
	if (AHyperTwistVirtual3333ProjectionActor* ProjectionActor = ResolveProjectionActor())
	{
		const bool bSet = ProjectionActor->SetSliceAxis(SliceAxis);
		RefreshPuzzleHud();
		return bSet;
	}

	return false;
}

bool AHyperTwistVirtual3333ProjectionPlayerController::SetSliceCoordinate(
	const int32 SliceCoordinate
)
{
	if (AHyperTwistVirtual3333ProjectionActor* ProjectionActor = ResolveProjectionActor())
	{
		const bool bSet = ProjectionActor->SetSliceCoordinate(SliceCoordinate);
		RefreshPuzzleHud();
		return bSet;
	}

	return false;
}

bool AHyperTwistVirtual3333ProjectionPlayerController::SetSliceLayer(
	const int32 OneBasedLayer
)
{
	if (AHyperTwistVirtual3333ProjectionActor* ProjectionActor = ResolveProjectionActor())
	{
		const bool bSet = ProjectionActor->SetSliceLayer(OneBasedLayer);
		RefreshPuzzleHud();
		return bSet;
	}
	return false;
}

bool AHyperTwistVirtual3333ProjectionPlayerController::CycleSliceLayer(
	const int32 DirectionStep
)
{
	if (AHyperTwistVirtual3333ProjectionActor* ProjectionActor = ResolveProjectionActor())
	{
		const bool bCycled = ProjectionActor->CycleSliceCoordinate(DirectionStep);
		RefreshPuzzleHud();
		return bCycled;
	}
	return false;
}

bool AHyperTwistVirtual3333ProjectionPlayerController::CycleRotationAxis(
	const int32 DirectionStep
)
{
	if (AHyperTwistVirtual3333ProjectionActor* ProjectionActor = ResolveProjectionActor())
	{
		const bool bCycled = ProjectionActor->CycleRotationAxis(DirectionStep);
		RefreshPuzzleHud();
		return bCycled;
	}

	return false;
}

void AHyperTwistVirtual3333ProjectionPlayerController::ToggleCellShellRendering()
{
	if (AHyperTwistVirtual3333ProjectionActor* ProjectionActor = ResolveProjectionActor())
	{
		ProjectionActor->ToggleCellShellRendering();
	}
	RefreshPuzzleHud();
}

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

AHyperTwistVirtual3333ProjectionActor*
AHyperTwistVirtual3333ProjectionPlayerController::ResolveProjectionActor() const
{
	if (GetWorld() == nullptr)
	{
		return nullptr;
	}

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

	return nullptr;
}

void AHyperTwistVirtual3333ProjectionPlayerController::HandleSliceAxisX()
{
	SetSliceAxis(EHyperTwistVirtual3333Axis::X);
}

void AHyperTwistVirtual3333ProjectionPlayerController::HandleSliceAxisY()
{
	SetSliceAxis(EHyperTwistVirtual3333Axis::Y);
}

void AHyperTwistVirtual3333ProjectionPlayerController::HandleSliceAxisZ()
{
	SetSliceAxis(EHyperTwistVirtual3333Axis::Z);
}

void AHyperTwistVirtual3333ProjectionPlayerController::HandleSliceAxisW()
{
	SetSliceAxis(EHyperTwistVirtual3333Axis::W);
}

void AHyperTwistVirtual3333ProjectionPlayerController::HandleSliceCoordinateNegative()
{
	SetSliceLayer(1);
}

void AHyperTwistVirtual3333ProjectionPlayerController::HandleSliceCoordinateMiddle()
{
	SetSliceLayer(2);
}

void AHyperTwistVirtual3333ProjectionPlayerController::HandleSliceCoordinatePositive()
{
	SetSliceLayer(3);
}

void AHyperTwistVirtual3333ProjectionPlayerController::HandleSliceLayerFour()
{
	SetSliceLayer(4);
}

void AHyperTwistVirtual3333ProjectionPlayerController::HandleSliceLayerFive()
{
	SetSliceLayer(5);
}

void AHyperTwistVirtual3333ProjectionPlayerController::HandleSliceLayerSix()
{
	SetSliceLayer(6);
}

void AHyperTwistVirtual3333ProjectionPlayerController::HandlePreviousSliceLayer()
{
	CycleSliceLayer(-1);
}

void AHyperTwistVirtual3333ProjectionPlayerController::HandleNextSliceLayer()
{
	CycleSliceLayer(1);
}

void AHyperTwistVirtual3333ProjectionPlayerController::HandleCycleRotationAxisBackward()
{
	CycleRotationAxis(-1);
}

void AHyperTwistVirtual3333ProjectionPlayerController::HandleCycleRotationAxisForward()
{
	CycleRotationAxis(1);
}

void AHyperTwistVirtual3333ProjectionPlayerController::HandleApplyCounterClockwise()
{
	ApplySelectionCounterClockwise();
}

void AHyperTwistVirtual3333ProjectionPlayerController::HandleApplyClockwise()
{
	ApplySelectionClockwise();
}

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

void AHyperTwistVirtual3333ProjectionPlayerController::HandleScrambleShortcut()
{
	if (AHyperTwistVirtual3333ProjectionActor* ProjectionActor =
			ResolveProjectionActor())
	{
		ProjectionActor->GenerateScramble(
			FMath::Clamp(ScrambleMoveCount, 1, 500),
			NextScrambleSeed++);
	}
	RefreshPuzzleHud();
}

void AHyperTwistVirtual3333ProjectionPlayerController::HandleToggleShellShortcut()
{
	ToggleCellShellRendering();
}

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

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

void AHyperTwistVirtual3333ProjectionPlayerController::HandleHudCycleAxis()
{
	if (const AHyperTwistVirtual3333ProjectionActor* ProjectionActor =
			ResolveProjectionActor())
	{
		const int32 NextAxis =
			(static_cast<int32>(ProjectionActor->CurrentSliceAxis) + 1) % 4;
		SetSliceAxis(static_cast<EHyperTwistVirtual3333Axis>(NextAxis));
	}
}

void AHyperTwistVirtual3333ProjectionPlayerController::HandleHudPreviousLayer()
{
	CycleSliceLayer(-1);
}

void AHyperTwistVirtual3333ProjectionPlayerController::HandleHudNextLayer()
{
	CycleSliceLayer(1);
}

void AHyperTwistVirtual3333ProjectionPlayerController::HandleHudPreviousPlane()
{
	CycleRotationAxis(-1);
}

void AHyperTwistVirtual3333ProjectionPlayerController::HandleHudNextPlane()
{
	CycleRotationAxis(1);
}

void AHyperTwistVirtual3333ProjectionPlayerController::HandleHudCounterClockwise()
{
	ApplySelectionCounterClockwise();
}

void AHyperTwistVirtual3333ProjectionPlayerController::HandleHudClockwise()
{
	ApplySelectionClockwise();
}

void AHyperTwistVirtual3333ProjectionPlayerController::HandleHudToggleShell()
{
	ToggleCellShellRendering();
}

void AHyperTwistVirtual3333ProjectionPlayerController::HandleHudScramble()
{
	HandleScrambleShortcut();
}

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

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

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