#include "HyperTwistSimulation/HyperTwistClassicCubeCommandLibrary.h"

namespace HyperTwistClassicCubeCommandLibraryInternal
{
	TCHAR GetFaceLetter(const EHyperTwistClassicCubeFace Face)
	{
		switch (Face)
		{
		case EHyperTwistClassicCubeFace::Up:
			return TEXT('U');
		case EHyperTwistClassicCubeFace::Down:
			return TEXT('D');
		case EHyperTwistClassicCubeFace::Front:
			return TEXT('F');
		case EHyperTwistClassicCubeFace::Back:
			return TEXT('B');
		case EHyperTwistClassicCubeFace::Left:
			return TEXT('L');
		case EHyperTwistClassicCubeFace::Right:
			return TEXT('R');
		}

		return TEXT('U');
	}

	FString BuildNotation(
		const EHyperTwistClassicCubeFace Face,
		const EHyperTwistRotationDirection Direction,
		const int32 QuarterTurns
	)
	{
		FString Notation;
		Notation.AppendChar(GetFaceLetter(Face));
		if (QuarterTurns >= 2)
		{
			Notation.AppendChar(TEXT('2'));
		}
		else if (Direction == EHyperTwistRotationDirection::CounterClockwise)
		{
			Notation.AppendChar(TEXT('\''));
		}
		return Notation;
	}

	bool TryResolveFaceToken(
		const FString& Token,
		EHyperTwistClassicCubeFace& OutFace
	)
	{
		if (Token == TEXT("u") || Token == TEXT("up") || Token == TEXT("upper"))
		{
			OutFace = EHyperTwistClassicCubeFace::Up;
			return true;
		}
		if (Token == TEXT("d") || Token == TEXT("down") || Token == TEXT("lower"))
		{
			OutFace = EHyperTwistClassicCubeFace::Down;
			return true;
		}
		if (Token == TEXT("f") || Token == TEXT("front"))
		{
			OutFace = EHyperTwistClassicCubeFace::Front;
			return true;
		}
		if (Token == TEXT("b") || Token == TEXT("back"))
		{
			OutFace = EHyperTwistClassicCubeFace::Back;
			return true;
		}
		if (Token == TEXT("l") || Token == TEXT("left"))
		{
			OutFace = EHyperTwistClassicCubeFace::Left;
			return true;
		}
		if (Token == TEXT("r") || Token == TEXT("right"))
		{
			OutFace = EHyperTwistClassicCubeFace::Right;
			return true;
		}

		return false;
	}

	bool ContainsPhrase(const FString& NormalizedTranscript, const TCHAR* Phrase)
	{
		return NormalizedTranscript.Contains(Phrase, ESearchCase::IgnoreCase);
	}

	bool ContainsAnyPhrase(const FString& NormalizedTranscript, const TArray<FString>& Phrases)
	{
		for (const FString& Phrase : Phrases)
		{
			if (!Phrase.IsEmpty() && NormalizedTranscript.Contains(Phrase, ESearchCase::IgnoreCase))
			{
				return true;
			}
		}

		return false;
	}

	bool ContainsAnyToken(const TArray<FString>& Tokens, const TArray<FString>& Candidates)
	{
		for (const FString& Candidate : Candidates)
		{
			if (Tokens.Contains(Candidate))
			{
				return true;
			}
		}

		return false;
	}

	bool AreTokensExplicitMovePhrase(const TArray<FString>& Tokens)
	{
		if (Tokens.IsEmpty())
		{
			return false;
		}

		bool bResolvedFace = false;
		for (const FString& Token : Tokens)
		{
			EHyperTwistClassicCubeFace IgnoredFace = EHyperTwistClassicCubeFace::Up;
			if (TryResolveFaceToken(Token, IgnoredFace))
			{
				bResolvedFace = true;
				continue;
			}

			if (Token == TEXT("prime")
				|| Token == TEXT("clockwise")
				|| Token == TEXT("cw")
				|| Token == TEXT("ccw")
				|| Token == TEXT("counterclockwise")
				|| Token == TEXT("counter")
				|| Token == TEXT("anti")
				|| Token == TEXT("anticlockwise")
				|| Token == TEXT("inverse")
				|| Token == TEXT("2")
				|| Token == TEXT("two")
				|| Token == TEXT("double")
				|| Token == TEXT("twice")
				|| Token == TEXT("turn")
				|| Token == TEXT("rotate")
				|| Token == TEXT("move")
				|| Token == TEXT("twist")
				|| Token == TEXT("face")
				|| Token == TEXT("side")
				|| Token == TEXT("the")
				|| Token == TEXT("cube"))
			{
				continue;
			}

			return false;
		}

		return bResolvedFace;
	}

	void SetCommandResult(
		FHyperTwistClassicCubeVoiceCommand& OutCommand,
		const EHyperTwistClassicCubeVoiceCommandKind CommandKind,
		const FString& TranscriptText,
		const FString& FeedbackLine
	);

	bool TryResolveControlCommand(
		const FString& NormalizedTranscript,
		FHyperTwistClassicCubeVoiceCommand& OutCommand
	)
	{
		if (ContainsAnyPhrase(
				NormalizedTranscript,
				{
					TEXT("new scramble"),
					TEXT("scramble again"),
					TEXT("reset cube"),
					TEXT("start over")
				}))
		{
			SetCommandResult(
				OutCommand,
				EHyperTwistClassicCubeVoiceCommandKind::NewScramble,
				NormalizedTranscript,
				TEXT("starting a fresh scramble")
			);
			return true;
		}

		if (ContainsAnyPhrase(
				NormalizedTranscript,
				{ TEXT("hint"), TEXT("next move"), TEXT("help me") }))
		{
			SetCommandResult(
				OutCommand,
				EHyperTwistClassicCubeVoiceCommandKind::RequestHint,
				NormalizedTranscript,
				TEXT("requesting the next optimal move")
			);
			return true;
		}

		if (ContainsAnyPhrase(
				NormalizedTranscript,
				{
					TEXT("submit solve"),
					TEXT("submit time"),
					TEXT("stop timer"),
					TEXT("mark solved")
				}))
		{
			SetCommandResult(
				OutCommand,
				EHyperTwistClassicCubeVoiceCommandKind::SubmitSolve,
				NormalizedTranscript,
				TEXT("submitting the current solve")
			);
			return true;
		}

		if (ContainsAnyPhrase(
				NormalizedTranscript,
				{
					TEXT("follow along"),
					TEXT("training mode"),
					TEXT("free play")
				}))
		{
			SetCommandResult(
				OutCommand,
				EHyperTwistClassicCubeVoiceCommandKind::ToggleFollowAlong,
				NormalizedTranscript,
				TEXT("switching the training lane")
			);
			return true;
		}

		if (ContainsAnyPhrase(
				NormalizedTranscript,
				{
					TEXT("cycle voice"),
					TEXT("change voice"),
					TEXT("next voice"),
					TEXT("coach voice")
				}))
		{
			SetCommandResult(
				OutCommand,
				EHyperTwistClassicCubeVoiceCommandKind::CycleVoiceProfile,
				NormalizedTranscript,
				TEXT("cycling the coach voice")
			);
			return true;
		}

		if (ContainsAnyPhrase(
				NormalizedTranscript,
				{ TEXT("ready coach"), TEXT("coach ready") }))
		{
			SetCommandResult(
				OutCommand,
				EHyperTwistClassicCubeVoiceCommandKind::ReadyCoach,
				NormalizedTranscript,
				TEXT("coach lane is ready")
			);
			return true;
		}

		return false;
	}

	bool TryBuildMoveDescriptorFromTokens(
		const TArray<FString>& Tokens,
		FHyperTwistClassicCubeMoveDescriptor& OutMove
	)
	{
		if (!AreTokensExplicitMovePhrase(Tokens))
		{
			return false;
		}

		EHyperTwistClassicCubeFace Face = EHyperTwistClassicCubeFace::Up;
		bool bResolvedFace = false;
		for (const FString& Token : Tokens)
		{
			if (TryResolveFaceToken(Token, Face))
			{
				bResolvedFace = true;
				break;
			}
		}

		if (!bResolvedFace)
		{
			return false;
		}

		const bool bCounterClockwise = ContainsAnyToken(
			Tokens,
			{
				TEXT("prime"),
				TEXT("ccw"),
				TEXT("counterclockwise"),
				TEXT("counter-clockwise"),
				TEXT("counter"),
				TEXT("anti"),
				TEXT("anticlockwise"),
				TEXT("anti-clockwise"),
				TEXT("inverse")
			}
		);
		const bool bDoubleTurn = ContainsAnyToken(
			Tokens,
			{ TEXT("2"), TEXT("two"), TEXT("double"), TEXT("twice") }
		);

		OutMove.Face = Face;
		OutMove.Direction = bCounterClockwise
			? EHyperTwistRotationDirection::CounterClockwise
			: EHyperTwistRotationDirection::Clockwise;
		OutMove.QuarterTurns = bDoubleTurn ? 2 : 1;
		OutMove.Notation = BuildNotation(OutMove.Face, OutMove.Direction, OutMove.QuarterTurns);
		OutMove.bGameplayMove = true;
		return OutMove.IsStructurallyValid();
	}

	void SetCommandResult(
		FHyperTwistClassicCubeVoiceCommand& OutCommand,
		const EHyperTwistClassicCubeVoiceCommandKind CommandKind,
		const FString& TranscriptText,
		const FString& FeedbackLine
	)
	{
		OutCommand = FHyperTwistClassicCubeVoiceCommand();
		OutCommand.CommandKind = CommandKind;
		OutCommand.TranscriptText = TranscriptText;
		OutCommand.FeedbackLine = FeedbackLine;
	}
}

FString UHyperTwistClassicCubeCommandLibrary::NormalizeTranscriptText(const FString& TranscriptText)
{
	FString Normalized = TranscriptText.ToLower();
	Normalized.ReplaceInline(TEXT("\r"), TEXT(" "));
	Normalized.ReplaceInline(TEXT("\n"), TEXT(" "));
	Normalized.ReplaceInline(TEXT(","), TEXT(" "));
	Normalized.ReplaceInline(TEXT("."), TEXT(" "));
	Normalized.ReplaceInline(TEXT("!"), TEXT(" "));
	Normalized.ReplaceInline(TEXT("?"), TEXT(" "));
	Normalized.ReplaceInline(TEXT(":"), TEXT(" "));
	Normalized.ReplaceInline(TEXT(";"), TEXT(" "));
	Normalized.ReplaceInline(TEXT("-"), TEXT(" "));
	Normalized.ReplaceInline(TEXT("_"), TEXT(" "));

	TArray<FString> Tokens;
	Normalized.ParseIntoArrayWS(Tokens);
	return FString::Join(Tokens, TEXT(" "));
}

bool UHyperTwistClassicCubeCommandLibrary::TryParseMoveNotation(
	const FString& MoveNotation,
	FHyperTwistClassicCubeMoveDescriptor& OutMove
)
{
	const FString TrimmedNotation = MoveNotation.TrimStartAndEnd().ToUpper();
	if (TrimmedNotation.IsEmpty() || TrimmedNotation.Len() > 2)
	{
		return false;
	}

	EHyperTwistClassicCubeFace Face = EHyperTwistClassicCubeFace::Up;
	switch (TrimmedNotation[0])
	{
	case TEXT('U'):
		Face = EHyperTwistClassicCubeFace::Up;
		break;
	case TEXT('D'):
		Face = EHyperTwistClassicCubeFace::Down;
		break;
	case TEXT('F'):
		Face = EHyperTwistClassicCubeFace::Front;
		break;
	case TEXT('B'):
		Face = EHyperTwistClassicCubeFace::Back;
		break;
	case TEXT('L'):
		Face = EHyperTwistClassicCubeFace::Left;
		break;
	case TEXT('R'):
		Face = EHyperTwistClassicCubeFace::Right;
		break;
	default:
		return false;
	}

	EHyperTwistRotationDirection Direction = EHyperTwistRotationDirection::Clockwise;
	int32 QuarterTurns = 1;
	if (TrimmedNotation.Len() == 2)
	{
		if (TrimmedNotation[1] == TEXT('\''))
		{
			Direction = EHyperTwistRotationDirection::CounterClockwise;
		}
		else if (TrimmedNotation[1] == TEXT('2'))
		{
			QuarterTurns = 2;
		}
		else
		{
			return false;
		}
	}

	OutMove = FHyperTwistClassicCubeMoveDescriptor();
	OutMove.Face = Face;
	OutMove.Direction = Direction;
	OutMove.QuarterTurns = QuarterTurns;
	OutMove.Notation =
		HyperTwistClassicCubeCommandLibraryInternal::BuildNotation(Face, Direction, QuarterTurns);
	OutMove.bGameplayMove = true;
	return OutMove.IsStructurallyValid();
}

bool UHyperTwistClassicCubeCommandLibrary::TryParseVoiceCommand(
	const FString& TranscriptText,
	FHyperTwistClassicCubeVoiceCommand& OutCommand
)
{
	const FString NormalizedTranscript = NormalizeTranscriptText(TranscriptText);
	if (NormalizedTranscript.IsEmpty())
	{
		OutCommand = FHyperTwistClassicCubeVoiceCommand();
		return false;
	}

	if (HyperTwistClassicCubeCommandLibraryInternal::ContainsAnyPhrase(
			NormalizedTranscript,
			{
				TEXT("slice m"),
				TEXT("m slice"),
				TEXT("slice e"),
				TEXT("e slice"),
				TEXT("slice s"),
				TEXT("s slice")
			}))
	{
		HyperTwistClassicCubeCommandLibraryInternal::SetCommandResult(
			OutCommand,
			EHyperTwistClassicCubeVoiceCommandKind::Unsupported,
			NormalizedTranscript,
			TEXT("slice commands are not wired into the classic cube runtime yet")
		);
		return true;
	}

	if (HyperTwistClassicCubeCommandLibraryInternal::TryResolveControlCommand(
			NormalizedTranscript,
			OutCommand))
	{
		return true;
	}

	FHyperTwistClassicCubeMoveDescriptor ParsedMove;
	if (TryParseMoveNotation(NormalizedTranscript, ParsedMove))
	{
		OutCommand = FHyperTwistClassicCubeVoiceCommand();
		OutCommand.CommandKind = EHyperTwistClassicCubeVoiceCommandKind::Move;
		OutCommand.Move = ParsedMove;
		OutCommand.TranscriptText = NormalizedTranscript;
		OutCommand.FeedbackLine = FString::Printf(TEXT("executing move %s"), *ParsedMove.Notation);
		return true;
	}

	TArray<FString> Tokens;
	NormalizedTranscript.ParseIntoArrayWS(Tokens);
	if (HyperTwistClassicCubeCommandLibraryInternal::TryBuildMoveDescriptorFromTokens(Tokens, ParsedMove))
	{
		OutCommand = FHyperTwistClassicCubeVoiceCommand();
		OutCommand.CommandKind = EHyperTwistClassicCubeVoiceCommandKind::Move;
		OutCommand.Move = ParsedMove;
		OutCommand.TranscriptText = NormalizedTranscript;
		OutCommand.FeedbackLine = FString::Printf(TEXT("executing move %s"), *ParsedMove.Notation);
		return true;
	}

	OutCommand = FHyperTwistClassicCubeVoiceCommand();
	return false;
}

TArray<FHyperTwistClassicCubeMoveDescriptor> UHyperTwistClassicCubeCommandLibrary::ExpandMoveNotationSequence(
	const TArray<FString>& MoveNotationSequence
)
{
	TArray<FHyperTwistClassicCubeMoveDescriptor> ExpandedMoves;
	for (const FString& MoveNotation : MoveNotationSequence)
	{
		FHyperTwistClassicCubeMoveDescriptor ParsedMove;
		if (!TryParseMoveNotation(MoveNotation, ParsedMove))
		{
			continue;
		}

		const int32 QuarterTurnCount = FMath::Max(ParsedMove.QuarterTurns, 1);
		for (int32 TurnIndex = 0; TurnIndex < QuarterTurnCount; ++TurnIndex)
		{
			FHyperTwistClassicCubeMoveDescriptor ExpandedMove = ParsedMove;
			ExpandedMove.QuarterTurns = 1;
			ExpandedMove.Notation = HyperTwistClassicCubeCommandLibraryInternal::BuildNotation(
				ExpandedMove.Face,
				ExpandedMove.Direction,
				1
			);
			ExpandedMoves.Add(ExpandedMove);
		}
	}

	return ExpandedMoves;
}

FHyperTwistClassicCubeMoveDescriptor UHyperTwistClassicCubeCommandLibrary::InvertMoveDescriptor(
	const FHyperTwistClassicCubeMoveDescriptor& Move
)
{
	FHyperTwistClassicCubeMoveDescriptor InvertedMove = Move;
	if (Move.QuarterTurns % 2 == 1)
	{
		InvertedMove.Direction = Move.Direction == EHyperTwistRotationDirection::Clockwise
			? EHyperTwistRotationDirection::CounterClockwise
			: EHyperTwistRotationDirection::Clockwise;
	}
	InvertedMove.Notation = HyperTwistClassicCubeCommandLibraryInternal::BuildNotation(
		InvertedMove.Face,
		InvertedMove.Direction,
		FMath::Max(InvertedMove.QuarterTurns, 1)
	);
	return InvertedMove;
}
