////////////////////////////////////////////////////////////
//
// LuaCall.h
//
// Low-level functions for calling Lua from blueprints.
// The "Call Lua" K2Node macroexpands into sequences
// of these functions.
//
// Also contains UlxLuaValues, which stores return
// values from Lua calls, and FlxParsedProto, which
// parses Lua function prototype strings.
//
////////////////////////////////////////////////////////////

#pragma once

#include "CoreMinimal.h"
#include "EdGraph/EdGraphPin.h"
#include <string_view>
#include <string>
#include "Kismet/BlueprintFunctionLibrary.h"
#include "Common.h"
#include "LuaCall.generated.h"

class UlxLuaValues;

////////////////////////////////////////////////////////////
//
// FlxParsedProto
//
// The first argument to LuaCallNode is a function
// prototype of the form:
//
//   class.name(int arg1, int arg2) : int ret1
//
// Return values can be omitted. The last return
// value can be an ellipsis. The class name can be
// asterisk.
//
////////////////////////////////////////////////////////////

class FlxParsedProto
{
public:
	struct Pin
	{
		FString Type;
		FString Name;
		Pin(const FString &T, const FString &N) : Type(T), Name(N) {}
	};

	// If parsing generated an error, the error
	// message is stored here.
	//
	FString ErrorMessage;

	// The results of parsing the prototype.
	//
	FString ClassName;
	FString FunctionName;
	TArray<Pin> Arguments;
	TArray<Pin> ReturnValues;
	bool ExtraReturnValues;

	// Used internally to help generate the error
	// message.
	//
	TArray<FString> Tokens;
	int NextToken;

private:
	// Check the next token to see if it's exactly
	// equal to text.
	//
	bool IsLiteral(const TCHAR *text);

	// Check the next token to see if it's an
	// identifier.
	//
	bool IsIdent();

	// Make a syntax error message, using the tokens.
	//
	void Syntax();

	// Empty out the FlxParsedProto.
	//
	void Empty();

	// Parse comma-separated "type name" pairs into ReturnValues.
	//
	void ParseReturnValues();

	// Parse a full function prototype (assumes already tokenized).
	//
	void Parse();

	// Tokenize the input string.
	//
	bool Tokenize(const FString &str);

public:
	// Parse a full function prototype.
	//
	static FlxParsedProto ParsePrototype(const FString &str);

	// Parse just a comma-separated list of "type name" pairs.
	//
	static FlxParsedProto ParseReturnValuesOnly(const FString &str);

private:
	FlxParsedProto() { Empty(); }
};

////////////////////////////////////////////////////////////
//
// UlxLuaCallLibrary
//
// Low-level functions that LuaCallNode macroexpands
// into.  Every LuaCallNode expands into the following
// sequence of functions:
//
// * LuaCallBegin to set class/function name.
// * LuaCallArgument_XXX to pack arguments.
// * LuaCallInvoke or LuaCallProbe to call.
// * LuaCallReturnValue_XXX to fetch results.
//
// The buffers used by these functions are global
// variables in LuprexGameModeBase.  It is ok to use
// globals, because blueprint is single-threaded.
//
////////////////////////////////////////////////////////////

UCLASS()
class INTEGRATION_API UlxLuaCallLibrary : public UBlueprintFunctionLibrary
{
	GENERATED_BODY()

public:
	// Get an argument packing function or a return
	// value unpacking function.
	//
	static UFunction *GetArgumentPacker(const FString &Type);
	static UFunction *GetReturnValueUnpacker(const FString &Type);

	// Get the types supported for arguments and
	// return values, as a documentation string.
	//
	static FString AllFunctionsWithPrefix(const TCHAR *Prefix);
	static FString AllKnownArgumentTypes() { return AllFunctionsWithPrefix(TEXT("LuaCallArgument_")); }
	static FString AllKnownReturnValueTypes() { return AllFunctionsWithPrefix(TEXT("LuaCallReturnValue_")); }

public:
	////////////////////////////////////////////////////////
	//
	// Functions that initiate and complete the lua call.
	//
	////////////////////////////////////////////////////////
	
	UFUNCTION(BlueprintCallable, meta = (WorldContext = "context"), Category = "Luprex|Call Lua Function")
	static void InvokeLuaExpr(UObject *context, const FString &Code);

	UFUNCTION(BlueprintCallable, meta = (WorldContext = "context", BlueprintInternalUseOnly = "true"), Category = "Luprex|Call Lua Function")
	static void LuaCallBegin(UObject *context, const FString &ClassName, const FString &FunctionName);

	UFUNCTION(BlueprintCallable, meta = (WorldContext = "context", BlueprintInternalUseOnly = "true"), Category = "Luprex|Call Lua Function")
	static void LuaCallInvoke(UObject *context, AActor *Place);

	UFUNCTION(BlueprintCallable, meta = (WorldContext = "context", ExpandBoolAsExecs="ReturnValue", BlueprintInternalUseOnly = "true"), Category = "Luprex|Call Lua Function")
	static bool LuaCallProbe(UObject *context, AActor *Place, UlxLuaValues *&ReturnArray);

	////////////////////////////////////////////////////////
	//
	// Functions that pack arguments into the call buffer.
	//
	////////////////////////////////////////////////////////

	UFUNCTION(BlueprintCallable, meta = (WorldContext = "context", BlueprintInternalUseOnly = "true"), Category = "Luprex|Call Lua Function")
	static void LuaCallArgument_string(UObject *context, const FString &Value);

	UFUNCTION(BlueprintCallable, meta = (WorldContext = "context", BlueprintInternalUseOnly = "true"), Category = "Luprex|Call Lua Function")
	static void LuaCallArgument_name(UObject *context, const FName &Value);

	UFUNCTION(BlueprintCallable, meta = (WorldContext = "context", BlueprintInternalUseOnly = "true"), Category = "Luprex|Call Lua Function")
	static void LuaCallArgument_float(UObject *context, double Value);

	UFUNCTION(BlueprintCallable, meta = (WorldContext = "context", BlueprintInternalUseOnly = "true"), Category = "Luprex|Call Lua Function")
	static void LuaCallArgument_int(UObject *context, int Value);

	UFUNCTION(BlueprintCallable, meta = (WorldContext = "context", BlueprintInternalUseOnly = "true"), Category = "Luprex|Call Lua Function")
	static void LuaCallArgument_vector(UObject *context, const FVector &Value);

	UFUNCTION(BlueprintCallable, meta = (WorldContext = "context", BlueprintInternalUseOnly = "true"), Category = "Luprex|Call Lua Function")
	static void LuaCallArgument_vector2d(UObject *context, const FVector2D &Value);

	UFUNCTION(BlueprintCallable, meta = (WorldContext = "context", BlueprintInternalUseOnly = "true"), Category = "Luprex|Call Lua Function")
	static void LuaCallArgument_boolean(UObject *context, bool Value);
};

////////////////////////////////////////////////////////////
//
// UlxLuaValues
//
// Stores an array of values returned by Lua.
// Provides a cursor-based API for reading values
// one at a time with type checking.
//
////////////////////////////////////////////////////////////

UCLASS(BlueprintType)
class INTEGRATION_API UlxLuaValues : public UObject
{
    GENERATED_BODY()

private:
    // The deserialized values.
    //
    TArray<LuaValue> Values;

    // The current cursor.
    //
    int Cursor = 0;

    // Saved cursor for rollback on error.
    //
    int SavedCursor = 0;

    static const ElxSuccessOrWrongType WrongType = ElxSuccessOrWrongType::WrongType;
    static const ElxSuccessOrWrongType Success = ElxSuccessOrWrongType::Success;

public:
    UlxLuaValues() {}

    // Copies the data, then preprocesses it to make
    // sure it's all valid. Returns false if invalid.
    //
    bool Initialize(std::string_view data);

	// Remove all elements before the cursor.
	//
    void DiscardBeforeCursor();

	// Convert a lua values array into a string.
	// The string shows all the values, including those
	// before the cursor, and the cursor as well.
	//
    UFUNCTION(BlueprintPure, Category = "Luprex|Lua Value Array")
    FString DebugString() const;

	// Return the number of elements remaining after the cursor.
	//
    UFUNCTION(BlueprintPure, Category = "Luprex|Lua Value Array")
    int Length() const { return Values.Num() - Cursor; }

	// Return the total number of elements, including both those
	// before and after the cursor.
	//
    UFUNCTION(BlueprintPure, Category = "Luprex|Lua Value Array")
    int TotalLength() const { return Values.Num(); }

	// Get the current position of the cursor.
	//
    UFUNCTION(BlueprintPure, Category = "Luprex|Lua Value Array")
    int GetCursor() const { return Cursor; }

	// Set the position of the cursor. The value is clamped to
	// be within the array.
	//
    UFUNCTION(BlueprintCallable, Category = "Luprex|Lua Value Array")
    void SetCursor(int n) { Cursor = FMath::Clamp(n, 0, Values.Num()); }

	// Get the type of the next element.
	//
    UFUNCTION(BlueprintPure, Category = "Luprex|Lua Value Array")
    ElxLuaValueType NextType() const;

	// Return true if the type of the next element is what you say.
	//
    UFUNCTION(BlueprintPure, Category = "Luprex|Lua Value Array")
    bool IsNextType(ElxLuaValueType Type) const { return NextType() == Type; }

	// Switch-statement that depends on the type of the next element.
	//
    UFUNCTION(BlueprintCallable, meta = (ExpandEnumAsExecs = "ReturnValue"), Category = "Luprex|Lua Value Array")
    ElxLuaValueType SwitchNextType() { return NextType(); }

	// Allows you to pass a LuaValues to Format Message.
	//
	UFUNCTION(BlueprintPure, meta = (BlueprintInternalUseOnly = "true"), Category = "Luprex|Lua Value Array")
	static FFormatArgumentData FormatArgumentDataLuaValues(const UlxLuaValues *AutoConvertedValue, const FString &Name);

	////////////////////////////////////////////////////////
	//
	// Routines that are used by the ReadLuaValues K2Node
	// to read values out.
	//
	////////////////////////////////////////////////////////
	
    UFUNCTION(BlueprintCallable, meta = (ExpandEnumAsExecs = "Status"), Category = "Luprex|Lua Value Array")
    void ReadString(ElxSuccessOrWrongType &Status, FString &Result);

    UFUNCTION(BlueprintCallable, meta = (ExpandEnumAsExecs = "Status"), Category = "Luprex|Lua Value Array")
	void ReadName(ElxSuccessOrWrongType &Status, FName &Result);

    UFUNCTION(BlueprintCallable, meta = (ExpandEnumAsExecs = "Status"), Category = "Luprex|Lua Value Array")
	void ReadFloat(ElxSuccessOrWrongType &Status, double &Result);

    UFUNCTION(BlueprintCallable, meta = (ExpandEnumAsExecs = "Status"), Category = "Luprex|Lua Value Array")
	void ReadInt(ElxSuccessOrWrongType &Status, int &Result);

    UFUNCTION(BlueprintCallable, meta = (ExpandEnumAsExecs = "Status"), Category = "Luprex|Lua Value Array")
	void ReadVector(ElxSuccessOrWrongType &Status, FVector &Result);

    UFUNCTION(BlueprintCallable, meta = (ExpandEnumAsExecs = "Status"), Category = "Luprex|Lua Value Array")
	void ReadVector2D(ElxSuccessOrWrongType &Status, FVector2D &Result);

    UFUNCTION(BlueprintCallable, meta = (ExpandEnumAsExecs = "Status"), Category = "Luprex|Lua Value Array")
	void ReadBoolean(ElxSuccessOrWrongType &Status, bool &Result);

	////////////////////////////////////////////////////////
	//
	// Other routines used by the ReadLuaValues K2Node
	//
	////////////////////////////////////////////////////////

    UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true"), Category = "Luprex|Lua Value Array")
    UlxLuaValues* SaveCursor() { SavedCursor = Cursor; return this; }
};
