#include "HyperTwistSimulation/HyperTwistVirtual3333ProjectionGameMode.h"

#include "Engine/World.h"
#include "EngineUtils.h"
#include "HyperTwistDiagnostics/HyperTwistRuntimeDiagnostics.h"
#include "HyperTwistSimulation/HyperTwistVirtual3333ProjectionActor.h"
#include "HyperTwistSimulation/HyperTwistVirtual3333ProjectionOrbitPawn.h"
#include "HyperTwistSimulation/HyperTwistVirtual3333ProjectionPlayerController.h"

namespace HyperTwistVirtual3333ProjectionGameModeInternal
{
	int32 GetExpectedStatePieceCount(const int32 PuzzleOrder)
	{
		return PuzzleOrder * PuzzleOrder * PuzzleOrder * PuzzleOrder;
	}

	int32 GetExpectedVisiblePieceViewCount(const int32 PuzzleOrder)
	{
		return PuzzleOrder * PuzzleOrder * PuzzleOrder;
	}

	void AppendPresentationReadiness(
		const AHyperTwistVirtual3333ProjectionActor* ProjectionActor,
		const int32 PuzzleOrder,
		const bool bStartupStatePrepared)
	{
		if (ProjectionActor == nullptr)
		{
			HyperTwistRuntimeDiagnostics::AppendEvent(
				TEXT("FourDimensionalPresentation"),
				FString::Printf(
					TEXT(
						"Four-dimensional presentation failed for order %d: "
						"projection actor unavailable."),
					PuzzleOrder));
			return;
		}

		const int32 ExpectedStatePieceCount = GetExpectedStatePieceCount(PuzzleOrder);
		const int32 ExpectedVisiblePieceViewCount =
			GetExpectedVisiblePieceViewCount(PuzzleOrder);
		const int32 StatePieceCount = ProjectionActor->CurrentState.GetPieceCount();
		const int32 ProjectedPieceViewCount =
			ProjectionActor->CurrentProjection.Tesseracts.Num();
		const int32 RenderablePieceViewCount =
			ProjectionActor->GetRenderableTesseractCount();
		const bool bStateValid =
			bStartupStatePrepared
			&& ProjectionActor->CurrentState.GetOrder() == PuzzleOrder
			&& StatePieceCount == ExpectedStatePieceCount
			&& ProjectionActor->CurrentState.IsStructurallyValid();
		const bool bProjectionValid =
			ProjectedPieceViewCount == ExpectedVisiblePieceViewCount
			&& RenderablePieceViewCount == ExpectedVisiblePieceViewCount
			&& 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,
					RenderablePieceViewCount));
			return;
		}

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

AHyperTwistVirtual3333ProjectionGameMode::AHyperTwistVirtual3333ProjectionGameMode()
{
	PlayerControllerClass = AHyperTwistVirtual3333ProjectionPlayerController::StaticClass();
	DefaultPawnClass = AHyperTwistVirtual3333ProjectionOrbitPawn::StaticClass();
}

void AHyperTwistVirtual3333ProjectionGameMode::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;
	}

	AHyperTwistVirtual3333ProjectionActor* ProjectionActor = ResolveOrSpawnProjectionActor();
	if (ProjectionActor == nullptr)
	{
		HyperTwistVirtual3333ProjectionGameModeInternal::AppendPresentationReadiness(
			nullptr,
			PuzzleOrder,
			false);
		return;
	}

	bool bStartupStatePrepared = ProjectionActor->SetPuzzleOrder(PuzzleOrder);
	ProjectionActor->ResetToSolvedState();
	bStartupStatePrepared =
		ProjectionActor->SetSliceAxis(StartupSliceAxis) && bStartupStatePrepared;
	if (!ProjectionActor->SetSliceCoordinate(StartupSliceCoordinate))
	{
		bStartupStatePrepared =
			ProjectionActor->SetSliceLayer(PuzzleOrder) && bStartupStatePrepared;
	}
	bStartupStatePrepared =
		ProjectionActor->SetRotationAxis(StartupRotationAxis) && bStartupStatePrepared;
	if (bLoadSavedStateOnBeginPlay)
	{
		ProjectionActor->LoadRuntimeState();
	}

	HyperTwistVirtual3333ProjectionGameModeInternal::AppendPresentationReadiness(
		ProjectionActor,
		PuzzleOrder,
		bStartupStatePrepared);
}

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

AHyperTwistVirtual3333ProjectionActor*
AHyperTwistVirtual3333ProjectionGameMode::ResolveOrSpawnProjectionActor()
{
	if (GetWorld() == nullptr)
	{
		return nullptr;
	}

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

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

	if (!bAutoSpawnProjectionActor)
	{
		return nullptr;
	}

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

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

AHyperTwistVirtual4444ProjectionGameMode::AHyperTwistVirtual4444ProjectionGameMode()
{
	PuzzleOrder = 4;
	StartupSliceCoordinate = 3;
}

AHyperTwistVirtual5555ProjectionGameMode::AHyperTwistVirtual5555ProjectionGameMode()
{
	PuzzleOrder = 5;
	StartupSliceCoordinate = 2;
}

AHyperTwistVirtual6666ProjectionGameMode::AHyperTwistVirtual6666ProjectionGameMode()
{
	PuzzleOrder = 6;
	StartupSliceCoordinate = 5;
}
