// Copyright HyperTwist, Inc. All Rights Reserved.

#include "Misc/AutomationTest.h"

#include "HyperTwistSimulation/HyperTwistClassicCubeActor.h"

namespace HyperTwistClassicCubeActorTestInternal
{
	int32 GetAxisIndex(const TCHAR MoveFace)
	{
		switch (MoveFace)
		{
		case TEXT('U'):
		case TEXT('D'):
			return 2;
		case TEXT('F'):
		case TEXT('B'):
			return 1;
		case TEXT('L'):
		case TEXT('R'):
			return 0;
		default:
			return INDEX_NONE;
		}
	}
}

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistClassicCubePresentationDefaultsTest,
	"HyperTwist.Simulation.ClassicCube.PresentationDefaults",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)

bool FHyperTwistClassicCubePresentationDefaultsTest::RunTest(const FString& Parameters)
{
	AHyperTwistClassicCubeActor* CubeActor =
		NewObject<AHyperTwistClassicCubeActor>(GetTransientPackage());
	TestNotNull(TEXT("The classic cube actor must be constructible for presentation checks."), CubeActor);
	if (CubeActor == nullptr)
	{
		return false;
	}

	TestEqual(
		TEXT("The runtime fallback cube must own all six first-party sticker materials."),
		CubeActor->FaceMaterials.Num(),
		6
	);
	for (int32 MaterialIndex = 0; MaterialIndex < CubeActor->FaceMaterials.Num(); ++MaterialIndex)
	{
		TestNotNull(
			*FString::Printf(TEXT("Sticker material %d must be available without map-authored overrides."), MaterialIndex),
			CubeActor->FaceMaterials[MaterialIndex]
		);
	}
	TestNotNull(
		TEXT("The runtime fallback cube must own its first-party internal material."),
		CubeActor->InternalMaterial
	);
	TestTrue(
		TEXT("The base glow must keep stickers readable in sparse maps."),
		CubeActor->BaseGlowStrength > 0.0f
	);
	TestTrue(
		TEXT("The hint glow must remain stronger than the presentation baseline."),
		CubeActor->HintGlowStrength > CubeActor->BaseGlowStrength
	);
	TestEqual(
		TEXT("The presentation invariant must cover all 26 visible cubies without inventing a rendered core."),
		AHyperTwistClassicCubeActor::ExpectedRenderablePieceCount,
		26
	);
	TestTrue(
		TEXT("Classic turn animation must remain tick-capable in packaged runtime."),
		CubeActor->PrimaryActorTick.bCanEverTick
	);
	TestTrue(
		TEXT("Classic turn animation must start with actor ticking enabled."),
		CubeActor->PrimaryActorTick.bStartWithTickEnabled
	);
	TestTrue(
		TEXT("Classic turn animation must be able to finish through transient application pause states."),
		CubeActor->PrimaryActorTick.bTickEvenWhenPaused
	);
	TestTrue(
		TEXT("Classic quarter turns must retain a positive animation duration."),
		CubeActor->TurnDuration > 0.0f
	);
	return true;
}

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistClassicCubeImpactNormalMappingTest,
	"HyperTwist.Simulation.ClassicCube.ImpactNormalMapping",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)

bool FHyperTwistClassicCubeImpactNormalMappingTest::RunTest(const FString& Parameters)
{
	EHyperTwistClassicCubeFace ResolvedFace = EHyperTwistClassicCubeFace::Up;
	TestTrue(
		TEXT("Positive Z normals must resolve to the Up face."),
		AHyperTwistClassicCubeActor::TryResolveFaceFromImpactNormal(FVector(0.01f, 0.02f, 1.0f), ResolvedFace)
			&& ResolvedFace == EHyperTwistClassicCubeFace::Up
	);
	TestTrue(
		TEXT("Negative Z normals must resolve to the Down face."),
		AHyperTwistClassicCubeActor::TryResolveFaceFromImpactNormal(FVector(0.0f, -0.02f, -1.0f), ResolvedFace)
			&& ResolvedFace == EHyperTwistClassicCubeFace::Down
	);
	TestTrue(
		TEXT("Positive Y normals must resolve to the Front face."),
		AHyperTwistClassicCubeActor::TryResolveFaceFromImpactNormal(FVector(0.01f, 1.0f, 0.2f), ResolvedFace)
			&& ResolvedFace == EHyperTwistClassicCubeFace::Front
	);
	TestTrue(
		TEXT("Negative Y normals must resolve to the Back face."),
		AHyperTwistClassicCubeActor::TryResolveFaceFromImpactNormal(FVector(0.01f, -1.0f, 0.2f), ResolvedFace)
			&& ResolvedFace == EHyperTwistClassicCubeFace::Back
	);
	TestTrue(
		TEXT("Positive X normals must resolve to the Right face."),
		AHyperTwistClassicCubeActor::TryResolveFaceFromImpactNormal(FVector(1.0f, 0.1f, 0.1f), ResolvedFace)
			&& ResolvedFace == EHyperTwistClassicCubeFace::Right
	);
	TestTrue(
		TEXT("Negative X normals must resolve to the Left face."),
		AHyperTwistClassicCubeActor::TryResolveFaceFromImpactNormal(FVector(-1.0f, 0.1f, 0.1f), ResolvedFace)
			&& ResolvedFace == EHyperTwistClassicCubeFace::Left
	);
	TestFalse(
		TEXT("A zero impact normal must not resolve to a face."),
		AHyperTwistClassicCubeActor::TryResolveFaceFromImpactNormal(FVector::ZeroVector, ResolvedFace)
	);

	return true;
}

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistClassicCubeScrambleQualityTest,
	"HyperTwist.Simulation.ClassicCube.ScrambleQuality",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)

bool FHyperTwistClassicCubeScrambleQualityTest::RunTest(const FString& Parameters)
{
	AHyperTwistClassicCubeActor* CubeActor = NewObject<AHyperTwistClassicCubeActor>(GetTransientPackage());
	TestNotNull(TEXT("The classic cube actor must be constructible for scramble checks."), CubeActor);
	if (CubeActor == nullptr)
	{
		return false;
	}

	const TArray<FString> Scramble = CubeActor->GenerateScramble(32);
	TestEqual(TEXT("The scramble generator must respect the requested move count."), Scramble.Num(), 32);

	for (int32 MoveIndex = 0; MoveIndex < Scramble.Num(); ++MoveIndex)
	{
		const FString& Move = Scramble[MoveIndex];
		TestTrue(TEXT("Each scramble move must be non-empty."), !Move.IsEmpty());
		TestTrue(TEXT("Each scramble move must be one or two characters long."), Move.Len() >= 1 && Move.Len() <= 2);

		const TCHAR Face = Move[0];
		const int32 AxisIndex = HyperTwistClassicCubeActorTestInternal::GetAxisIndex(Face);
		TestTrue(TEXT("Each scramble move must start with a legal face token."), AxisIndex != INDEX_NONE);

		if (Move.Len() == 2)
		{
			TestTrue(
				TEXT("Each scramble suffix must be either a prime marker or a double-turn marker."),
				Move[1] == TEXT('\'') || Move[1] == TEXT('2')
			);
		}

		if (MoveIndex == 0)
		{
			continue;
		}

		const FString& PreviousMove = Scramble[MoveIndex - 1];
		TestNotEqual(
			TEXT("The scramble generator must avoid repeating the same face consecutively."),
			Face,
			PreviousMove[0]
		);
		TestNotEqual(
			TEXT("The scramble generator must avoid immediate same-axis repetitions."),
			AxisIndex,
			HyperTwistClassicCubeActorTestInternal::GetAxisIndex(PreviousMove[0])
		);
	}

	return true;
}
