#include "HyperTwistSimulation/HyperTwistMelindaProjectionGameMode.h"

#include "Engine/World.h"
#include "EngineUtils.h"
#include "HyperTwistCore/HyperTwistCoreLibrary.h"
#include "HyperTwistDiagnostics/HyperTwistRuntimeDiagnostics.h"
#include "HyperTwistSimulation/HyperTwistMelindaProjectionActor.h"
#include "HyperTwistSimulation/HyperTwistMelindaProjectionOrbitPawn.h"
#include "HyperTwistSimulation/HyperTwistMelindaProjectionPlayerController.h"

namespace HyperTwistMelindaProjectionGameModeInternal
{
	constexpr int32 PuzzleOrder = 2;
	constexpr int32 ExpectedStatePieceCount = 16;
	constexpr int32 ExpectedProjectedCellCount = 8;
	constexpr int32 ExpectedRenderableCubieViewCount = 64;

	int32 ResolveStatePieceCount(const FHyperTwistPuzzleState& State)
	{
		if (State.Definition.Dimension != 4 || State.Definition.SizeVector.Num() != 4)
		{
			return 0;
		}

		int32 PieceCount = 1;
		for (const int32 AxisSize : State.Definition.SizeVector)
		{
			if (AxisSize != PuzzleOrder)
			{
				return 0;
			}
			PieceCount *= AxisSize;
		}
		return PieceCount;
	}

	void AppendPresentationReadiness(
		const AHyperTwistMelindaProjectionActor* ProjectionActor,
		const bool bStartupStatePrepared)
	{
		if (ProjectionActor == nullptr)
		{
			HyperTwistRuntimeDiagnostics::AppendEvent(
				TEXT("FourDimensionalPresentation"),
				TEXT("Four-dimensional presentation failed for order 2: projection actor unavailable."));
			return;
		}

		const FHyperTwistPuzzleStateValidationResult StateValidation =
			UHyperTwistCoreLibrary::ValidatePuzzleState(ProjectionActor->CurrentState);
		const int32 StatePieceCount = ResolveStatePieceCount(ProjectionActor->CurrentState);
		const int32 ProjectedCellCount = ProjectionActor->CurrentProjection.Cells.Num();
		const int32 RenderableCubieViewCount =
			ProjectionActor->GetRenderableCubieViewCount();
		const bool bStateValid =
			bStartupStatePrepared
			&& StatePieceCount == ExpectedStatePieceCount
			&& StateValidation.bStateSupported
			&& StateValidation.bStructureValid
			&& StateValidation.bIsSolvable;
		const bool bProjectionValid =
			ProjectedCellCount == ExpectedProjectedCellCount
			&& RenderableCubieViewCount == ExpectedRenderableCubieViewCount
			&& ProjectionActor->HasValidProjection();

		if (bStateValid && bProjectionValid)
		{
			HyperTwistRuntimeDiagnostics::AppendEvent(
				TEXT("FourDimensionalPresentation"),
				FString::Printf(
					TEXT(
						"Four-dimensional presentation initialized for order %d with %d exact "
						"state pieces and %d visible projected piece views; state valid, projection valid."),
					PuzzleOrder,
					StatePieceCount,
					RenderableCubieViewCount));
			return;
		}

		HyperTwistRuntimeDiagnostics::AppendEvent(
			TEXT("FourDimensionalPresentation"),
			FString::Printf(
				TEXT(
					"Four-dimensional presentation failed for order %d with %d state pieces, "
					"%d projected cells, and %d visible projected piece views; state %s, projection %s."),
				PuzzleOrder,
				StatePieceCount,
				ProjectedCellCount,
				RenderableCubieViewCount,
				bStateValid ? TEXT("valid") : TEXT("invalid"),
				bProjectionValid ? TEXT("valid") : TEXT("invalid")));
	}
}

AHyperTwistMelindaProjectionGameMode::AHyperTwistMelindaProjectionGameMode()
{
	PlayerControllerClass = AHyperTwistMelindaProjectionPlayerController::StaticClass();
	DefaultPawnClass = AHyperTwistMelindaProjectionOrbitPawn::StaticClass();
}

void AHyperTwistMelindaProjectionGameMode::BeginPlay()
{
	Super::BeginPlay();

	HyperTwistRuntimeDiagnostics::AppendEvent(
		TEXT("MapReady"),
		FString::Printf(
			TEXT("Runtime map ready: %s."),
			GetWorld() != nullptr ? *GetWorld()->GetPathName() : TEXT("unknown")));

	if (APlayerController* PlayerController =
			GetWorld() != nullptr ? GetWorld()->GetFirstPlayerController() : nullptr)
	{
		PlayerController->bShowMouseCursor = true;
		PlayerController->bEnableClickEvents = true;
		PlayerController->bEnableMouseOverEvents = true;
	}

	AHyperTwistMelindaProjectionActor* ProjectionActor = ResolveOrSpawnProjectionActor();
	if (ProjectionActor == nullptr)
	{
		HyperTwistMelindaProjectionGameModeInternal::AppendPresentationReadiness(
			nullptr,
			false);
		return;
	}

	bool bStartupStatePrepared = true;
	if (bRandomizeOnBeginPlay)
	{
		bStartupStatePrepared =
			ProjectionActor->GenerateRandomState(StartupRandomSeed);
	}
	else
	{
		ProjectionActor->ResetToSolvedState();
		if (bLoadSavedStateOnBeginPlay)
		{
			ProjectionActor->LoadRuntimeState();
		}
	}

	HyperTwistMelindaProjectionGameModeInternal::AppendPresentationReadiness(
		ProjectionActor,
		bStartupStatePrepared);
}

void AHyperTwistMelindaProjectionGameMode::ResetProjectionToSolved()
{
	if (AHyperTwistMelindaProjectionActor* ProjectionActor = ResolveOrSpawnProjectionActor())
	{
		ProjectionActor->ResetToSolvedState();
	}
}

bool AHyperTwistMelindaProjectionGameMode::GenerateProjectionRandomState(const int32 RandomSeed)
{
	if (AHyperTwistMelindaProjectionActor* ProjectionActor = ResolveOrSpawnProjectionActor())
	{
		return ProjectionActor->GenerateRandomState(RandomSeed);
	}

	return false;
}

AHyperTwistMelindaProjectionActor* AHyperTwistMelindaProjectionGameMode::ResolveOrSpawnProjectionActor()
{
	if (GetWorld() == nullptr)
	{
		return nullptr;
	}

	if (ActiveProjectionActor != nullptr)
	{
		return ActiveProjectionActor;
	}

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

	if (!bAutoSpawnProjectionActor)
	{
		return nullptr;
	}

	TSubclassOf<AHyperTwistMelindaProjectionActor> SpawnClass = ProjectionActorClass;
	if (SpawnClass == nullptr)
	{
		SpawnClass = AHyperTwistMelindaProjectionActor::StaticClass();
	}

	FActorSpawnParameters SpawnParameters;
	SpawnParameters.SpawnCollisionHandlingOverride =
		ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
	ActiveProjectionActor = GetWorld()->SpawnActor<AHyperTwistMelindaProjectionActor>(
		SpawnClass,
		ProjectionSpawnLocation,
		ProjectionSpawnRotation,
		SpawnParameters
	);
	return ActiveProjectionActor;
}
