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

#pragma once

#include "Containers/Array.h"
#include "Containers/Set.h"
#include "Containers/UnrealString.h"
#include "Containers/StringFwd.h"
#include "Common.h"
#include "SlashCommand.generated.h"

////////////////////////////////////////////////////////////
//
// UlxSlashCommand
//
// Holds a command line (a string the user has typed) together
// with a cursor marking the current position within it.  This
// is the object that wraps Unreal's FParse facilities so that
// blueprint can parse a typed command piece by piece, with the
// cursor advancing as each token is consumed.
//
////////////////////////////////////////////////////////////

UCLASS(BlueprintType)
class UlxSlashCommand : public UObject
{
	GENERATED_BODY()

private:
	// The full command line that we are parsing.
	//
	FString CommandLine;

	// The current parse position: an index into CommandLine.
	//
	int32 Cursor = 0;

	// Known command names that have been registered while exploring
	// possible parses for this line.
	//
	TSet<FString> KnownCommands;

	// Prototypes whose command name matches this line.
	//
	TArray<FString> MatchingPrototypes;

	// Skip leading whitespace at the cursor, then read characters up
	// to (but not including) the next whitespace.  The token is
	// returned as a view into CommandLine, and the cursor is advanced
	// past it.
	//
	// Returns an empty view if there is no nonwhitespace input left.
	//
	FStringView FetchToken();

public:
	// Construct a slash command from a command line string.
	// The cursor starts at the beginning.
	//
	UFUNCTION(BlueprintCallable)
	static UlxSlashCommand* MakeSlashCommand(const FString& CommandLine);

	// Return true if the string is a slash followed by one or more
	// alphanumeric characters, and nothing else (e.g. "/foo").
	//
	static bool IsSlashCommand(const FString& Command);

	// Reset the cursor to the start, then read the first token and
	// check whether it matches the given literal, case-insensitively.
	// The command name is recorded in KnownCommands either way.  If it
	// matched, the prototype is added to MatchingPrototypes.  The token
	// is consumed either way.  Returns Success if it matched, Error
	// otherwise.
	//
	UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", ExpandEnumAsExecs = "ReturnValue"))
	ElxSuccessOrError CheckCommand(const FString& Literal, const FString& Prototype);

	// Read the next whitespace-delimited word from the command line.
	//
	// This is the blueprint-callable form of FetchToken: it returns
	// the token as an FString.  Returns Error (with an empty Result)
	// if there is no nonwhitespace input left, Success otherwise.
	//
	UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", ExpandEnumAsExecs = "ReturnValue"))
	ElxSuccessOrError ReadToken(FString& Result);

	// Read the next token and interpret it as an integer, using C++
	// number syntax (optional sign; decimal, 0x hex, or leading-0
	// octal).  The whole token must be valid; "12abc" is rejected.
	// Returns Error (with Result 0) on failure, Success otherwise.
	//
	UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", ExpandEnumAsExecs = "ReturnValue"))
	ElxSuccessOrError ReadInteger(int32& Result);

	// Read the next token and interpret it as a floating-point number,
	// using C++ number syntax (optional sign, digits, dot, e/E
	// exponent, trailing f).  The whole token must be valid; "12abc"
	// is rejected.  Returns Error (with Result 0) on failure, Success
	// otherwise.
	//
	UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", ExpandEnumAsExecs = "ReturnValue"))
	ElxSuccessOrError ReadFloat(double& Result);

	// Read the rest of the command line, from the cursor to the end,
	// trimmed of leading and trailing whitespace.  The cursor is
	// advanced to the end.  Always returns Success.
	// 
	UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", ExpandEnumAsExecs = "ReturnValue"))
	ElxSuccessOrError ReadRest(FString& Result);

	// Return a user-facing error message describing why parsing failed.
	//
	UFUNCTION(BlueprintCallable)
	FString GetErrorMessage() const;
};
