#include "HyperTwistCore/HyperTwistCoreLibrary.h"

#include "JsonObjectConverter.h"

namespace HyperTwistCoreLibraryInternal
{
	template <typename TStruct>
	bool DeserializePayload(const FHyperTwistSerializedPayload& Payload, TStruct& OutValue)
	{
		if (Payload.PayloadJson.IsEmpty())
		{
			return false;
		}

		return FJsonObjectConverter::JsonObjectStringToUStruct(Payload.PayloadJson, &OutValue, 0, 0);
	}

	bool IsIdentityPermutation(const TArray<int32>& Values)
	{
		for (int32 Index = 0; Index < Values.Num(); ++Index)
		{
			if (Values[Index] != Index)
			{
				return false;
			}
		}

		return true;
	}

	bool IsZeroOrientation(const TArray<int32>& Values)
	{
		for (const int32 Value : Values)
		{
			if (Value != 0)
			{
				return false;
			}
		}

		return true;
	}

	bool IsUniformFace(const TArray<FString>& Facelets)
	{
		if (Facelets.Num() == 0)
		{
			return false;
		}

		const FString& Reference = Facelets[0];
		for (const FString& Facelet : Facelets)
		{
			if (Facelet != Reference)
			{
				return false;
			}
		}

		return true;
	}

	bool IsCanonicalLabelSequence(const TArray<FString>& Labels, const FString& Prefix)
	{
		for (int32 Index = 0; Index < Labels.Num(); ++Index)
		{
			if (Labels[Index] != FString::Printf(TEXT("%s%d"), *Prefix, Index))
			{
				return false;
			}
		}

		return Labels.Num() > 0;
	}
}

FHyperTwistNotationNormalizationResult UHyperTwistCoreLibrary::NormalizeNotation(const FString& RawNotation)
{
	FHyperTwistNotationNormalizationResult Result;
	Result.InputNotation = RawNotation;

	FString Working = RawNotation;
	Working.TrimStartAndEndInline();
	Working.ReplaceInline(TEXT("\r"), TEXT(" "));
	Working.ReplaceInline(TEXT("\n"), TEXT(" "));
	Working.ReplaceInline(TEXT("\t"), TEXT(" "));
	Working.ReplaceInline(TEXT("’"), TEXT("'"));
	Working.ReplaceInline(TEXT("`"), TEXT("'"));

	while (Working.Contains(TEXT("  ")))
	{
		Working.ReplaceInline(TEXT("  "), TEXT(" "));
	}

	Result.NormalizedNotation = Working;
	Result.bIsValid = !Working.IsEmpty();
	Result.bWasModified = Working != RawNotation;

	if (Result.bWasModified)
	{
		Result.Warnings.Add(TEXT("notation-normalized"));
	}

	if (!Result.bIsValid)
	{
		Result.Warnings.Add(TEXT("notation-empty-after-normalization"));
	}

	return Result;
}

bool UHyperTwistCoreLibrary::IsSolved(const FHyperTwistPuzzleState& State)
{
	if (!State.IsStructurallyValid())
	{
		return false;
	}

	if (State.bIsSolved)
	{
		return true;
	}

	if (State.StateEncoding.EncodingProfile == TEXT("classic-piece-orbit-v1"))
	{
		FHyperTwistClassicPieceOrbitState OrbitState;
		if (!HyperTwistCoreLibraryInternal::DeserializePayload(State.StateEncoding, OrbitState))
		{
			return false;
		}

		return HyperTwistCoreLibraryInternal::IsIdentityPermutation(OrbitState.Corners.Permutation)
			&& HyperTwistCoreLibraryInternal::IsZeroOrientation(OrbitState.Corners.Orientation)
			&& HyperTwistCoreLibraryInternal::IsIdentityPermutation(OrbitState.Edges.Permutation)
			&& HyperTwistCoreLibraryInternal::IsZeroOrientation(OrbitState.Edges.Orientation)
			&& HyperTwistCoreLibraryInternal::IsIdentityPermutation(OrbitState.Centers.Permutation)
			&& HyperTwistCoreLibraryInternal::IsZeroOrientation(OrbitState.Centers.Orientation);
	}

	if (State.StateEncoding.EncodingProfile == TEXT("classic-facelet-snapshot-v1"))
	{
		FHyperTwistClassicFaceletSnapshotState FaceletState;
		if (!HyperTwistCoreLibraryInternal::DeserializePayload(State.StateEncoding, FaceletState))
		{
			return false;
		}

		return HyperTwistCoreLibraryInternal::IsUniformFace(FaceletState.U)
			&& HyperTwistCoreLibraryInternal::IsUniformFace(FaceletState.R)
			&& HyperTwistCoreLibraryInternal::IsUniformFace(FaceletState.F)
			&& HyperTwistCoreLibraryInternal::IsUniformFace(FaceletState.D)
			&& HyperTwistCoreLibraryInternal::IsUniformFace(FaceletState.L)
			&& HyperTwistCoreLibraryInternal::IsUniformFace(FaceletState.B);
	}

	if (State.StateEncoding.EncodingProfile == TEXT("hypercube-piece-map-v1"))
	{
		FHyperTwistHypercubePieceMapState HyperState;
		if (!HyperTwistCoreLibraryInternal::DeserializePayload(State.StateEncoding, HyperState))
		{
			return false;
		}

		return HyperTwistCoreLibraryInternal::IsCanonicalLabelSequence(HyperState.Corners, TEXT("c"))
			&& HyperTwistCoreLibraryInternal::IsCanonicalLabelSequence(HyperState.Edges, TEXT("e"))
			&& HyperTwistCoreLibraryInternal::IsCanonicalLabelSequence(HyperState.Faces, TEXT("f"))
			&& HyperTwistCoreLibraryInternal::IsCanonicalLabelSequence(HyperState.Cells, TEXT("cell"));
	}

	return false;
}

FHyperTwistApplyTransformationResult UHyperTwistCoreLibrary::ApplyTransformation(const FHyperTwistPuzzleState& State, const FHyperTwistTransformation& Transformation)
{
	FHyperTwistApplyTransformationResult Result;
	Result.State = State;

	if (!State.IsStructurallyValid())
	{
		Result.Warnings.Add(TEXT("invalid-input-state"));
		return Result;
	}

	if (!Transformation.IsStructurallyValid())
	{
		Result.Warnings.Add(TEXT("invalid-transformation"));
		return Result;
	}

	const FHyperTwistNotationNormalizationResult Normalization = NormalizeNotation(Transformation.Notation);
	Result.AppliedNotation = Normalization.NormalizedNotation;
	Result.Warnings.Append(Normalization.Warnings);

	if (!Normalization.bIsValid)
	{
		return Result;
	}

	Result.bApplied = true;

	if (Normalization.NormalizedNotation.Equals(TEXT("I"), ESearchCase::IgnoreCase)
		|| Normalization.NormalizedNotation.Equals(TEXT("identity"), ESearchCase::IgnoreCase))
	{
		Result.bExactStateUpdate = true;
		Result.State.bIsSolved = IsSolved(State);
		return Result;
	}

	Result.bExactStateUpdate = false;
	Result.State.bIsSolved = false;
	Result.State.Source = EHyperTwistStateSource::Runtime;
	Result.State.Notes = FString::Printf(
		TEXT("placeholder-transform-application:%s"),
		*Normalization.NormalizedNotation
	);
	Result.Warnings.Add(TEXT("exact-piece-update-pending"));
	return Result;
}
