#include "SlashCommand.h"
#include "CoreMinimal.h"
#include "Containers/StringView.h"
#include "Misc/Char.h"
#include "Misc/DefaultValueHelper.h"

//////////////////////////////////////////////////////////////
//
// SlashCommand
//
// A command line plus a cursor, exposed to blueprint.
//
//////////////////////////////////////////////////////////////


UlxSlashCommand* UlxSlashCommand::MakeSlashCommand(const FString& CommandLine)
{
	UlxSlashCommand* Result = NewObject<UlxSlashCommand>();
	Result->CommandLine = CommandLine;
	return Result;
}


bool UlxSlashCommand::IsSlashCommand(const FString& Command)
{
	if (Command.Len() < 2 || Command[0] != TEXT('/'))
	{
		return false;
	}

	// Every character after the slash must be alphanumeric.
	for (int32 i = 1; i < Command.Len(); i++)
	{
		if (!FChar::IsAlnum(Command[i]))
		{
			return false;
		}
	}

	return true;
}


FStringView UlxSlashCommand::FetchToken()
{
	const TCHAR* Data = *CommandLine;
	int32 Len = CommandLine.Len();

	// Skip leading whitespace.
	while (Cursor < Len && FChar::IsWhitespace(Data[Cursor]))
	{
		Cursor++;
	}

	// Read characters up to the next whitespace.
	int32 Start = Cursor;
	while (Cursor < Len && !FChar::IsWhitespace(Data[Cursor]))
	{
		Cursor++;
	}

	return FStringView(Data + Start, Cursor - Start);
}

ElxSuccessOrError UlxSlashCommand::CheckCommand(const FString& Literal, const FString& Prototype)
{
	KnownCommands.Add(Literal);

	// Checking the command always starts a fresh parse.
	Cursor = 0;
	FStringView Token = FetchToken();
	if (Token.Equals(Literal, ESearchCase::IgnoreCase))
	{
		MatchingPrototypes.Add(Prototype);
		return ElxSuccessOrError::Success;
	}

	return ElxSuccessOrError::Error;
}

ElxSuccessOrError UlxSlashCommand::ReadToken(FString& Result)
{
	FStringView Token = FetchToken();
	if (Token.IsEmpty())
	{
		Result.Empty();
		return ElxSuccessOrError::Error;
	}

	Result = FString(Token);
	return ElxSuccessOrError::Success;
}


ElxSuccessOrError UlxSlashCommand::ReadInteger(int32& Result)
{
	// ParseInt validates the whole token and converts it, so "12abc"
	// is rejected rather than read as a partial number.
	FStringView Token = FetchToken();
	if (!FDefaultValueHelper::ParseInt(FString(Token), Result))
	{
		Result = 0;
		return ElxSuccessOrError::Error;
	}

	return ElxSuccessOrError::Success;
}


ElxSuccessOrError UlxSlashCommand::ReadFloat(double& Result)
{
	// ParseDouble validates the whole token and converts it, so
	// "12abc" is rejected rather than read as a partial number.
	FStringView Token = FetchToken();
	if (!FDefaultValueHelper::ParseDouble(FString(Token), Result))
	{
		Result = 0.0;
		return ElxSuccessOrError::Error;
	}

	return ElxSuccessOrError::Success;
}


ElxSuccessOrError UlxSlashCommand::ReadRest(FString& Result)
{
	const TCHAR* Data = *CommandLine;
	int32 Len = CommandLine.Len();

	// Everything from the cursor to the end of the line, trimmed.
	Result = FString(FStringView(Data + Cursor, Len - Cursor));
	Result.TrimStartAndEndInline();
	Cursor = Len;
	return ElxSuccessOrError::Success;
}

FString UlxSlashCommand::GetErrorMessage() const
{
	if (MatchingPrototypes.Num() == 0)
	{
		TArray<FString> Commands;
		for (const FString& Command : KnownCommands)
		{
			Commands.Add(Command);
		}
		Commands.Sort();
		return FString(TEXT("No such slash command.  Valid slash commands: ")) + FString::Join(Commands, TEXT(", "));
	}

	FString Result = TEXT("Invalid parameters.  Valid parameters:\n");
	for (const FString& Prototype : MatchingPrototypes)
	{
		Result += Prototype;
		Result += TEXT("\n");
	}
	return Result;
}
