#include "Misc/AutomationTest.h"

#include "HyperTwistSimulation/HyperTwistMagicCube5DRuntimeLibrary.h"

#if WITH_AUTOMATION_TESTS

namespace HyperTwistMagicCube5DRuntimeContractTestInternal
{
	const EHyperTwistMagicCube5DAxis Axes[] = {
		EHyperTwistMagicCube5DAxis::X,
		EHyperTwistMagicCube5DAxis::Y,
		EHyperTwistMagicCube5DAxis::Z,
		EHyperTwistMagicCube5DAxis::W,
		EHyperTwistMagicCube5DAxis::V
	};

	TArray<EHyperTwistMagicCube5DAxis> GetOrthogonalAxes(
		const EHyperTwistMagicCube5DAxis FaceAxis
	)
	{
		TArray<EHyperTwistMagicCube5DAxis> Result;
		for (const EHyperTwistMagicCube5DAxis Axis : Axes)
		{
			if (Axis != FaceAxis)
			{
				Result.Add(Axis);
			}
		}
		return Result;
	}
}

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistMagicCube5DStateProjectionContractTest,
	"HyperTwist.Permissive.MagicCube5D.StateProjectionContract",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)

bool FHyperTwistMagicCube5DStateProjectionContractTest::RunTest(
	const FString& Parameters
)
{
	static_cast<void>(Parameters);
	const FHyperTwistMagicCube5DRuntimeState State =
		UHyperTwistMagicCube5DRuntimeLibrary::BuildSolvedState(3);
	TestTrue(TEXT("Order-3 5D state must be structurally valid."), State.IsStructurallyValid());
	TestTrue(TEXT("Fresh 5D state must be solved."), State.bIsSolved);
	TestEqual(TEXT("Order-3 5D must own all 243 cubies."), State.PositionToPiece.Num(), 243);
	TestEqual(
		TEXT("Order-3 5D must own every cubie orientation."),
		State.PieceOrientations.Num(),
		243);

	const FHyperTwistMagicCube5DProjectionBuildResult Projection =
		UHyperTwistMagicCube5DRuntimeLibrary::BuildProjection(State);
	TestTrue(
		TEXT("Solved 5D projection must be exact."),
		Projection.bProjected && Projection.bExactProjection);
	TestTrue(
		TEXT("Solved 5D projection must be structurally valid."),
		Projection.Projection.IsStructurallyValid());
	TestEqual(
		TEXT("Order-3 5D projection must expose 242 non-central boundary cubies."),
		Projection.Projection.Cubies.Num(),
		242);
	TestEqual(
		TEXT("Order-3 5D projection must expose all 810 boundary facelets."),
		Projection.Projection.Facelets.Num(),
		810);

	int32 FaceColorCounts[10] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
	for (const FHyperTwistMagicCube5DProjectedFacelet& Facelet :
		 Projection.Projection.Facelets)
	{
		if (Facelet.ColorIndex >= 0 && Facelet.ColorIndex < 10)
		{
			++FaceColorCounts[Facelet.ColorIndex];
		}
	}
	for (int32 ColorIndex = 0; ColorIndex < 10; ++ColorIndex)
	{
		TestEqual(
			*FString::Printf(
				TEXT("Solved 5D color %d must own one 3^4 face."),
				ColorIndex),
			FaceColorCounts[ColorIndex],
			81);
	}
	return true;
}

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistMagicCube5DTurnIdentityContractTest,
	"HyperTwist.Permissive.MagicCube5D.TurnIdentityContract",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)

bool FHyperTwistMagicCube5DTurnIdentityContractTest::RunTest(
	const FString& Parameters
)
{
	static_cast<void>(Parameters);
	using namespace HyperTwistMagicCube5DRuntimeContractTestInternal;
	const FHyperTwistMagicCube5DRuntimeState SolvedState =
		UHyperTwistMagicCube5DRuntimeLibrary::BuildSolvedState(3);

	for (const EHyperTwistMagicCube5DAxis FaceAxis : Axes)
	{
		const TArray<EHyperTwistMagicCube5DAxis> OrthogonalAxes =
			GetOrthogonalAxes(FaceAxis);
		for (int32 AxisAIndex = 0; AxisAIndex < OrthogonalAxes.Num(); ++AxisAIndex)
		{
			for (int32 AxisBIndex = AxisAIndex + 1;
				 AxisBIndex < OrthogonalAxes.Num();
				 ++AxisBIndex)
			{
				FHyperTwistMagicCube5DTurnRequest Request;
				Request.FaceAxis = FaceAxis;
				Request.bPositiveFace = true;
				Request.SliceMask = 1;
				Request.RotationAxisA = OrthogonalAxes[AxisAIndex];
				Request.RotationAxisB = OrthogonalAxes[AxisBIndex];
				Request.Direction =
					EHyperTwistMagicCube5DTurnDirection::PositiveQuarterTurn;

				const FHyperTwistMagicCube5DTurnResult Turn =
					UHyperTwistMagicCube5DRuntimeLibrary::ApplyTurn(
						SolvedState,
						Request);
				TestTrue(
					TEXT("Every signed-face orthogonal plane move must apply exactly."),
					Turn.bApplied && Turn.bExactStateUpdate);
				TestFalse(TEXT("A single 5D quarter turn must not be solved."), Turn.State.bIsSolved);

				FHyperTwistMagicCube5DTurnRequest Inverse = Request;
				Inverse.Direction =
					EHyperTwistMagicCube5DTurnDirection::NegativeQuarterTurn;
				const FHyperTwistMagicCube5DTurnResult Undo =
					UHyperTwistMagicCube5DRuntimeLibrary::ApplyTurn(
						Turn.State,
						Inverse);
				TestTrue(
					TEXT("A 5D move followed by its inverse must remain exact."),
					Undo.bApplied && Undo.bExactStateUpdate);
				TestTrue(
					TEXT("A 5D move followed by its inverse must recover solved identity."),
					Undo.State.bIsSolved);

				FHyperTwistMagicCube5DRuntimeState FourTurnState = SolvedState;
				for (int32 TurnIndex = 0; TurnIndex < 4; ++TurnIndex)
				{
					const FHyperTwistMagicCube5DTurnResult QuarterTurn =
						UHyperTwistMagicCube5DRuntimeLibrary::ApplyTurn(
							FourTurnState,
							Request);
					TestTrue(
						TEXT("Every four-turn identity step must remain exact."),
						QuarterTurn.bApplied && QuarterTurn.bExactStateUpdate);
					FourTurnState = QuarterTurn.State;
				}
				TestTrue(
					TEXT("Four 5D quarter turns in one plane must recover identity."),
					FourTurnState.bIsSolved);
			}
		}
	}
	return true;
}

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistMagicCube5DScramblePersistenceContractTest,
	"HyperTwist.Permissive.MagicCube5D.ScramblePersistenceContract",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)

bool FHyperTwistMagicCube5DScramblePersistenceContractTest::RunTest(
	const FString& Parameters
)
{
	static_cast<void>(Parameters);
	const FHyperTwistMagicCube5DScrambleResult Scramble =
		UHyperTwistMagicCube5DRuntimeLibrary::GenerateScramble(3, 60, 51205);
	const FHyperTwistMagicCube5DScrambleResult Repeat =
		UHyperTwistMagicCube5DRuntimeLibrary::GenerateScramble(3, 60, 51205);
	TestTrue(
		TEXT("5D scramble must generate a valid exact state."),
		Scramble.bGenerated
			&& Scramble.bExactStateUpdate
			&& Scramble.State.IsStructurallyValid());
	TestFalse(TEXT("5D scramble must leave solved identity."), Scramble.State.bIsSolved);
	TestEqual(TEXT("5D scramble must retain every move."), Scramble.Moves.Num(), 60);
	TestEqual(
		TEXT("Equal seeds must produce identical exact 5D state."),
		UHyperTwistMagicCube5DRuntimeLibrary::SerializeRuntimeStateToJson(Scramble.State),
		UHyperTwistMagicCube5DRuntimeLibrary::SerializeRuntimeStateToJson(Repeat.State));

	const FHyperTwistMagicCube5DProjectionBuildResult Projection =
		UHyperTwistMagicCube5DRuntimeLibrary::BuildProjection(Scramble.State);
	TestTrue(
		TEXT("Scrambled 5D state must retain an exact 810-facelet projection."),
		Projection.bProjected
			&& Projection.bExactProjection
			&& Projection.Projection.Facelets.Num() == 810);

	const FString Json =
		UHyperTwistMagicCube5DRuntimeLibrary::SerializeRuntimeStateToJson(Scramble.State);
	FHyperTwistMagicCube5DRuntimeState Restored;
	TestTrue(
		TEXT("Exact 5D state must round-trip through persistence JSON."),
		UHyperTwistMagicCube5DRuntimeLibrary::TryDeserializeRuntimeStateFromJson(
			Json,
			Restored));
	TestEqual(
		TEXT("Restored 5D state must be byte-semantically stable when reserialized."),
		UHyperTwistMagicCube5DRuntimeLibrary::SerializeRuntimeStateToJson(Restored),
		Json);

	FHyperTwistMagicCube5DRuntimeState Reflected =
		UHyperTwistMagicCube5DRuntimeLibrary::BuildSolvedState(3);
	Reflected.PieceOrientations[0].Basis[0].bPositiveDirection = false;
	Reflected.bIsSolved = false;
	TestFalse(
		TEXT("5D state must reject an impossible reflected orientation."),
		Reflected.IsStructurallyValid());
	return true;
}

#endif
