#include "Misc/AutomationTest.h"

#include "HyperTwistSimulation/HyperTwistFourDimensionalSaveGame.h"
#include "HyperTwistSimulation/HyperTwistMelindaProjectionActor.h"
#include "HyperTwistSimulation/HyperTwistMelindaProjectionGameMode.h"
#include "HyperTwistSimulation/HyperTwistVirtual3333ProjectionActor.h"
#include "HyperTwistSimulation/HyperTwistVirtual3333ProjectionGameMode.h"
#include "HyperTwistSimulation/HyperTwistVirtual3333ProjectionLibrary.h"

#if WITH_AUTOMATION_TESTS

namespace HyperTwistVirtualNxNxNxNRuntimeContractTestInternal
{
	const EHyperTwistVirtual3333Axis Axes[] = {
		EHyperTwistVirtual3333Axis::X,
		EHyperTwistVirtual3333Axis::Y,
		EHyperTwistVirtual3333Axis::Z,
		EHyperTwistVirtual3333Axis::W
	};

	FString OrderLabel(const int32 Order)
	{
		return FString::Printf(TEXT("%dx%dx%dx%d"), Order, Order, Order, Order);
	}
}

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistVirtualNxNxNxNStateAndProjectionContractTest,
	"HyperTwist.CleanRoom.HactarCE.VirtualNxNxNxN.StateAndProjectionContract",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)

bool FHyperTwistVirtualNxNxNxNStateAndProjectionContractTest::RunTest(
	const FString& Parameters
)
{
	static_cast<void>(Parameters);
	using namespace HyperTwistVirtualNxNxNxNRuntimeContractTestInternal;

	for (int32 Order = 3; Order <= 6; ++Order)
	{
		const FString Label = OrderLabel(Order);
		const int32 ExpectedPieceCount = Order * Order * Order * Order;
		const int32 ExpectedVisiblePieceCount = Order * Order * Order;
		const FHyperTwistVirtual3333RuntimeState SolvedState =
			UHyperTwistVirtual3333ProjectionLibrary::BuildSolvedState(Order);

		TestTrue(
			*FString::Printf(TEXT("%s solved state must be structurally valid."), *Label),
			SolvedState.IsStructurallyValid());
		TestTrue(
			*FString::Printf(TEXT("%s solved state must report solved."), *Label),
			SolvedState.bIsSolved);
		TestEqual(
			*FString::Printf(TEXT("%s must own every exact runtime piece."), *Label),
			SolvedState.PositionToPiece.Num(),
			ExpectedPieceCount);
		TestEqual(
			*FString::Printf(TEXT("%s must own every piece orientation."), *Label),
			SolvedState.PieceOrientations.Num(),
			ExpectedPieceCount);

		const TArray<int32> SliceCoordinates =
			UHyperTwistVirtual3333ProjectionLibrary::GetSliceCoordinatesForOrder(Order);
		TestEqual(
			*FString::Printf(TEXT("%s must expose every layer."), *Label),
			SliceCoordinates.Num(),
			Order);

		for (const EHyperTwistVirtual3333Axis VisibleAxis : Axes)
		{
			for (const int32 SliceCoordinate : SliceCoordinates)
			{
				const FHyperTwistVirtual3333ProjectionBuildResult ProjectionResult =
					UHyperTwistVirtual3333ProjectionLibrary::BuildVisibleProjection(
						SolvedState,
						VisibleAxis,
						SliceCoordinate);

				TestTrue(
					*FString::Printf(
						TEXT("%s axis %d slice %d must project exactly."),
						*Label,
						static_cast<int32>(VisibleAxis),
						SliceCoordinate),
					ProjectionResult.bProjected && ProjectionResult.bExactProjection);
				TestTrue(
					*FString::Printf(
						TEXT("%s axis %d slice %d projection must be valid."),
						*Label,
						static_cast<int32>(VisibleAxis),
						SliceCoordinate),
					ProjectionResult.Projection.IsStructurallyValid());
				TestEqual(
					*FString::Printf(
						TEXT("%s axis %d slice %d must expose N^3 tesseracts."),
						*Label,
						static_cast<int32>(VisibleAxis),
						SliceCoordinate),
					ProjectionResult.Projection.Tesseracts.Num(),
					ExpectedVisiblePieceCount);

				int32 StickerCount = 0;
				for (const FHyperTwistVirtual3333ProjectedTesseract& Tesseract :
					 ProjectionResult.Projection.Tesseracts)
				{
					for (const FHyperTwistVirtual3333ProjectedCell& Cell :
						 Tesseract.VisibleCells)
					{
						StickerCount += Cell.bHasSticker ? 1 : 0;
					}
				}
				TestEqual(
					*FString::Printf(
						TEXT("%s solved projection must expose three N^2 positive stickers."),
						*Label),
					StickerCount,
					3 * Order * Order);
			}
		}
	}

	return true;
}

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistVirtualNxNxNxNTurnIdentityContractTest,
	"HyperTwist.CleanRoom.HactarCE.VirtualNxNxNxN.TurnIdentityContract",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)

bool FHyperTwistVirtualNxNxNxNTurnIdentityContractTest::RunTest(
	const FString& Parameters
)
{
	static_cast<void>(Parameters);
	using namespace HyperTwistVirtualNxNxNxNRuntimeContractTestInternal;

	for (int32 Order = 3; Order <= 6; ++Order)
	{
		const FString Label = OrderLabel(Order);
		const FHyperTwistVirtual3333RuntimeState SolvedState =
			UHyperTwistVirtual3333ProjectionLibrary::BuildSolvedState(Order);
		const FString SerializedSolved =
			UHyperTwistVirtual3333ProjectionLibrary::SerializeRuntimeStateToJson(SolvedState);
		const TArray<int32> SliceCoordinates =
			UHyperTwistVirtual3333ProjectionLibrary::GetSliceCoordinatesForOrder(Order);

		for (const EHyperTwistVirtual3333Axis SliceAxis : Axes)
		{
			const TArray<EHyperTwistVirtual3333Axis> RotationAxes =
				UHyperTwistVirtual3333ProjectionLibrary::
					GetAvailableRotationAxesForSliceAxis(SliceAxis);
			TestEqual(
				*FString::Printf(TEXT("%s must expose three rotation axes."), *Label),
				RotationAxes.Num(),
				3);

			for (const int32 SliceCoordinate : SliceCoordinates)
			{
				for (const EHyperTwistVirtual3333Axis RotationAxis : RotationAxes)
				{
					FHyperTwistVirtual3333SliceTurnRequest Request;
					Request.SliceAxis = SliceAxis;
					Request.SliceCoordinate = SliceCoordinate;
					Request.RotationAxis = RotationAxis;
					Request.Direction =
						EHyperTwistVirtual3333TurnDirection::Clockwise;

					const FHyperTwistVirtual3333SliceTurnResult TurnResult =
						UHyperTwistVirtual3333ProjectionLibrary::ApplySliceTurn(
							SolvedState,
							Request);
					TestTrue(
						*FString::Printf(
							TEXT("%s axis %d slice %d rotation %d must turn exactly."),
							*Label,
							static_cast<int32>(SliceAxis),
							SliceCoordinate,
							static_cast<int32>(RotationAxis)),
						TurnResult.bApplied && TurnResult.bExactStateUpdate);
					TestFalse(
						*FString::Printf(
							TEXT("%s quarter turn must leave solved state."),
							*Label),
						TurnResult.State.bIsSolved);

					FHyperTwistVirtual3333SliceTurnRequest InverseRequest = Request;
					InverseRequest.Direction =
						EHyperTwistVirtual3333TurnDirection::CounterClockwise;
					const FHyperTwistVirtual3333SliceTurnResult InverseResult =
						UHyperTwistVirtual3333ProjectionLibrary::ApplySliceTurn(
							TurnResult.State,
							InverseRequest);
					TestTrue(
						*FString::Printf(TEXT("%s inverse turn must be exact."), *Label),
						InverseResult.bApplied && InverseResult.bExactStateUpdate);
					TestEqual(
						*FString::Printf(
							TEXT("%s turn plus inverse must recover exact identity."),
							*Label),
						UHyperTwistVirtual3333ProjectionLibrary::SerializeRuntimeStateToJson(
							InverseResult.State),
						SerializedSolved);

					FHyperTwistVirtual3333RuntimeState FourTurnState = SolvedState;
					for (int32 TurnIndex = 0; TurnIndex < 4; ++TurnIndex)
					{
						const FHyperTwistVirtual3333SliceTurnResult FourTurnResult =
							UHyperTwistVirtual3333ProjectionLibrary::ApplySliceTurn(
								FourTurnState,
								Request);
						TestTrue(
							*FString::Printf(
								TEXT("%s four-turn identity step must remain exact."),
								*Label),
							FourTurnResult.bApplied
								&& FourTurnResult.bExactStateUpdate);
						FourTurnState = FourTurnResult.State;
					}
					TestEqual(
						*FString::Printf(
							TEXT("%s four quarter turns must recover exact identity."),
							*Label),
						UHyperTwistVirtual3333ProjectionLibrary::SerializeRuntimeStateToJson(
							FourTurnState),
						SerializedSolved);
				}
			}
		}
	}

	return true;
}

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistVirtualNxNxNxNStateIntegrityContractTest,
	"HyperTwist.CleanRoom.HactarCE.VirtualNxNxNxN.StateIntegrityContract",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)

bool FHyperTwistVirtualNxNxNxNStateIntegrityContractTest::RunTest(
	const FString& Parameters
)
{
	static_cast<void>(Parameters);
	for (int32 Order = 3; Order <= 6; ++Order)
	{
		FHyperTwistVirtual3333RuntimeState StaleSolvedFlag =
			UHyperTwistVirtual3333ProjectionLibrary::BuildSolvedState(Order);
		StaleSolvedFlag.bIsSolved = false;
		TestFalse(
			*FString::Printf(
				TEXT("%dx state must reject a stale solved flag."),
				Order),
			StaleSolvedFlag.IsStructurallyValid());

		FHyperTwistVirtual3333RuntimeState ReflectedOrientation =
			UHyperTwistVirtual3333ProjectionLibrary::BuildSolvedState(Order);
		ReflectedOrientation.PieceOrientations[0].Basis[0].bPositiveDirection = false;
		ReflectedOrientation.bIsSolved = false;
		TestFalse(
			*FString::Printf(
				TEXT("%dx state must reject an impossible reflected orientation."),
				Order),
			ReflectedOrientation.IsStructurallyValid());

		const FHyperTwistPuzzleState SafeEnvelope =
			UHyperTwistVirtual3333ProjectionLibrary::BuildPuzzleStateEnvelope(
				ReflectedOrientation);
		TestTrue(
			*FString::Printf(
				TEXT("%dx invalid input must produce a valid fallback envelope."),
				Order),
			SafeEnvelope.IsStructurallyValid());
		TestTrue(
			*FString::Printf(
				TEXT("%dx invalid input must fall back atomically to order 3."),
				Order),
			SafeEnvelope.Definition.SizeVector.Num() == 4
				&& SafeEnvelope.Definition.SizeVector[0] == 3
				&& SafeEnvelope.Definition.SizeVector[1] == 3
				&& SafeEnvelope.Definition.SizeVector[2] == 3
				&& SafeEnvelope.Definition.SizeVector[3] == 3);
	}
	return true;
}

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistVirtualNxNxNxNScrambleContractTest,
	"HyperTwist.CleanRoom.HactarCE.VirtualNxNxNxN.ScrambleContract",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)

bool FHyperTwistVirtualNxNxNxNScrambleContractTest::RunTest(
	const FString& Parameters
)
{
	static_cast<void>(Parameters);
	for (int32 Order = 3; Order <= 6; ++Order)
	{
		const int32 MoveCount = Order * 10;
		const int32 Seed = 2400 + Order;
		const FHyperTwistVirtual3333ScrambleResult First =
			UHyperTwistVirtual3333ProjectionLibrary::GenerateScramble(
				Order,
				MoveCount,
				Seed);
		const FHyperTwistVirtual3333ScrambleResult Repeat =
			UHyperTwistVirtual3333ProjectionLibrary::GenerateScramble(
				Order,
				MoveCount,
				Seed);
		TestTrue(
			*FString::Printf(TEXT("%dx scramble must generate exactly."), Order),
			First.bGenerated
				&& First.bExactStateUpdate
				&& First.State.IsStructurallyValid());
		TestFalse(
			*FString::Printf(TEXT("%dx scramble must leave solved state."), Order),
			First.State.bIsSolved);
		TestEqual(
			*FString::Printf(TEXT("%dx scramble must retain every generated move."), Order),
			First.AppliedMoves.Num(),
			MoveCount);
		TestEqual(
			*FString::Printf(TEXT("%dx scramble must be deterministic by seed."), Order),
			UHyperTwistVirtual3333ProjectionLibrary::SerializeRuntimeStateToJson(
				First.State),
			UHyperTwistVirtual3333ProjectionLibrary::SerializeRuntimeStateToJson(
				Repeat.State));
		TestTrue(
			*FString::Printf(TEXT("%dx scramble selection must remain valid."), Order),
			First.FinalSelection.IsStructurallyValid());
		const FHyperTwistVirtual3333ProjectionBuildResult Projection =
			UHyperTwistVirtual3333ProjectionLibrary::BuildVisibleProjection(
				First.State,
				First.FinalSelection.SliceAxis,
				First.FinalSelection.SliceCoordinate);
		TestTrue(
			*FString::Printf(TEXT("%dx scrambled state must project exactly."), Order),
			Projection.bProjected
				&& Projection.bExactProjection
				&& Projection.Projection.IsStructurallyValid());
	}

	const FHyperTwistVirtual3333ScrambleResult Invalid =
		UHyperTwistVirtual3333ProjectionLibrary::GenerateScramble(6, 0, 1);
	TestFalse(
		TEXT("A zero-move scramble must be rejected."),
		Invalid.bGenerated);
	TestTrue(
		TEXT("A rejected scramble must explain its failure."),
		!Invalid.Warnings.IsEmpty());
	return true;
}

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistVirtualNxNxNxNPersistenceDefaultsContractTest,
	"HyperTwist.CleanRoom.HactarCE.VirtualNxNxNxN.PersistenceDefaultsContract",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)

bool FHyperTwistVirtualNxNxNxNPersistenceDefaultsContractTest::RunTest(
	const FString& Parameters
)
{
	static_cast<void>(Parameters);
	const UHyperTwistFourDimensionalSaveGame* SaveDefaults =
		GetDefault<UHyperTwistFourDimensionalSaveGame>();
	TestNotNull(
		TEXT("The 4D save-game schema must be constructible."),
		SaveDefaults);
	TestEqual(
		TEXT("The 4D save-game schema must retain its current version."),
		SaveDefaults != nullptr ? SaveDefaults->SchemaVersion : 0,
		1);

	TestEqual(
		TEXT("The 2x runtime must own a stable dedicated save slot."),
		AHyperTwistMelindaProjectionActor::GetRuntimeSaveSlotName(),
		FString(TEXT("HyperTwist_4D_2x2x2x2_v1")));
	const AHyperTwistVirtual3333ProjectionActor* VirtualActorDefaults =
		GetDefault<AHyperTwistVirtual3333ProjectionActor>();
	TestEqual(
		TEXT("The visible-slice runtime must derive a stable order-specific slot."),
		VirtualActorDefaults != nullptr
			? VirtualActorDefaults->GetRuntimeSaveSlotName()
			: FString(),
		FString(TEXT("HyperTwist_4D_3x3x3x3_v1")));

	const AHyperTwistMelindaProjectionGameMode* CellFirstGameMode =
		GetDefault<AHyperTwistMelindaProjectionGameMode>();
	const AHyperTwistVirtual3333ProjectionGameMode* Order3GameMode =
		GetDefault<AHyperTwistVirtual3333ProjectionGameMode>();
	const AHyperTwistVirtual4444ProjectionGameMode* Order4GameMode =
		GetDefault<AHyperTwistVirtual4444ProjectionGameMode>();
	const AHyperTwistVirtual5555ProjectionGameMode* Order5GameMode =
		GetDefault<AHyperTwistVirtual5555ProjectionGameMode>();
	const AHyperTwistVirtual6666ProjectionGameMode* Order6GameMode =
		GetDefault<AHyperTwistVirtual6666ProjectionGameMode>();
	TestTrue(
		TEXT("Every 4D game mode must restore a valid local session by default."),
		CellFirstGameMode != nullptr
			&& CellFirstGameMode->bLoadSavedStateOnBeginPlay
			&& Order3GameMode != nullptr
			&& Order3GameMode->bLoadSavedStateOnBeginPlay
			&& Order4GameMode != nullptr
			&& Order4GameMode->bLoadSavedStateOnBeginPlay
			&& Order5GameMode != nullptr
			&& Order5GameMode->bLoadSavedStateOnBeginPlay
			&& Order6GameMode != nullptr
			&& Order6GameMode->bLoadSavedStateOnBeginPlay);
	TestTrue(
		TEXT("The dedicated visible-slice game modes must retain orders 3 through 6."),
		Order3GameMode != nullptr
			&& Order3GameMode->PuzzleOrder == 3
			&& Order4GameMode != nullptr
			&& Order4GameMode->PuzzleOrder == 4
			&& Order5GameMode != nullptr
			&& Order5GameMode->PuzzleOrder == 5
			&& Order6GameMode != nullptr
			&& Order6GameMode->PuzzleOrder == 6);
	return true;
}

#endif
