// Copyright Epic Games, Inc. All Rights Reserved.


#include "LuaCallNode.h"

#include "BlueprintActionDatabaseRegistrar.h"
#include "BlueprintNodeSpawner.h"
#include "EdGraphSchema_K2.h"
#include "GameFramework/Actor.h"
#include "K2Node_CallFunction.h"
#include "KismetCompiler.h"
#include "LuaCall.h"

#define LOCTEXT_NAMESPACE "LuaCall"

// All argument pins will have internal Names that start with "A:"

const FName UK2Node_LuaInvoke::FunctionPinName(TEXT("Lua Function Prototype"));
const FName UK2Node_LuaInvoke::PlacePinName(TEXT("Place Tangible"));
const FName UK2Node_LuaInvoke::ExtraResultsPinName(TEXT("Extra Results"));
const FName UK2Node_LuaInvoke::ErrorPinName(TEXT("Error"));

FText UK2Node_LuaInvoke::GetTooltipText() const
{
	static FText Tooltip = FText::FromString(FString::Printf(TEXT(
		"Call a Lua function.\n"
		"\n"
		"The lua function prototype must be a hardwired string which must look like\n"
		"one of the following:\n"
		"\n"
		"  classname.funcname(int arg1, int arg2)\n"
		"  classname.funcname(int arg1, int arg2) : int ret1, int ret2\n"
		"  classname.funcname(int arg1, int arg2) : int ret1, int ret2, ...\n"
		"\n"
		"You must specify types for the argument and return value pins.  The\n"
		"types that you may specify are:\n"
		"\n"
		"Arguments: %s\n"
		"Return Values: %s\n"
		"\n"
		"The prototype is parsed to determine what lua function to call.\n"
		"The lua call node will automatically add pins for the arguments and\n"
		"return values.  If the function has no return values, you can omit those\n"
		"from the proto.  If you put an ellipsis at the end of the return values,\n"
		"then any additional return values will be collected into a\n"
		"dynamically typed array of values which you can iterate over later.\n"
		"\n"
		"Optionally, you may use the * wildcard for the classname.  In that\n"
		"case, the class of the 'place' tangible will be used.\n"
		"\n"), *UlxLuaCallLibrary::AllKnownArgumentTypes(), *UlxLuaCallLibrary::AllKnownReturnValueTypes()));
	return Tooltip;
}

void UK2Node_LuaInvoke::ReconstructNode()
{
	// Save the value of the Prototype Pin before it gets reconstructed.
	UEdGraphPin* FunctionPin = FindPin(FunctionPinName);
	if (FunctionPin != nullptr)
	{
		LuaFunctionPrototype = FunctionPin->DefaultValue;
	}

	Super::ReconstructNode();
}

void UK2Node_LuaInvoke::AllocateDefaultPins()
{
	Pins.Reset();
	Super::AllocateDefaultPins();

	// Parse the lua function prototype.
	// Note that we use the saved value of the function
	// prototype, because the function prototype pin
	// has been deleted at this point.
	FlxParsedProto ParsedProto = FlxParsedProto::ParsePrototype(LuaFunctionPrototype);
	if (!ParsedProto.ErrorMessage.IsEmpty())
	{
		SetErrorMsg(FString::Printf(TEXT("Syntax error in lua function prototype: %s"), *ParsedProto.ErrorMessage));
	}

	CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Exec, UEdGraphSchema_K2::PN_Execute);
	CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Exec, UEdGraphSchema_K2::PN_Then);

	if (!IsInvoke())
	{
		CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Exec, ErrorPinName);
	}

	UEdGraphPin *FunctionPin = CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_String, FunctionPinName);
	FunctionPin->DefaultValue = LuaFunctionPrototype;

	CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Object, AActor::StaticClass(), PlacePinName);

	// Create argument pins.
	for (const FlxParsedProto::Pin & Pin : ParsedProto.Arguments)
	{
		FName PrefixedName = AddPrefix(Pin.Name, 'A');
		UFunction *Accessor = UlxLuaCallLibrary::GetArgumentPacker(Pin.Type);
		if (Accessor == nullptr) {
			SetErrorMsg(FString::Printf(TEXT("Unknown argument type: %s"), *Pin.Type));
			continue;
		}
		CreatePin(EGPD_Input, PropertyToPinType(Accessor->FindPropertyByName(TEXT("Value"))), PrefixedName);
	}

	// Create return value pins.
	for (const FlxParsedProto::Pin & Pin : ParsedProto.ReturnValues)
	{
		FName PrefixedName = AddPrefix(Pin.Name, 'R');
		UFunction *Accessor = UlxLuaCallLibrary::GetReturnValueUnpacker(Pin.Type);
		if (Accessor == nullptr) {
			SetErrorMsg(FString::Printf(TEXT("Unknown return value type: %s"), *Pin.Type));
			continue;
		}
		CreatePin(EGPD_Output, PropertyToPinType(Accessor->FindPropertyByName(TEXT("Result"))), PrefixedName);
	}

	if (ParsedProto.ExtraReturnValues)
	{
		CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Object, UlxLuaValues::StaticClass(), ExtraResultsPinName);
	}
}


FText UK2Node_LuaInvoke::GetNodeTitle(ENodeTitleType::Type TitleType) const
{
	if (IsInvoke())
	{
		return LOCTEXT("LuaInvoke_Title", "Invoke a Lua Function");
	}
	else
	{
		return LOCTEXT("LuaProbe_Title", "Probe a Lua Function");
	}
}

FText UK2Node_LuaInvoke::GetPinDisplayName(const UEdGraphPin* Pin) const
{
	// These pins don't need labels.
	if ((Pin->PinName == UEdGraphSchema_K2::PN_Execute) ||
		(Pin->PinName == UEdGraphSchema_K2::PN_Then) ||
		(Pin->PinName == FunctionPinName))
	{
		return FText::GetEmpty();
	}

	// Return the pin name, removing A: or R: if present.
	return FText::FromName(RemovePrefix(Pin->PinName));
}

void UK2Node_LuaInvoke::PinDefaultValueChanged(UEdGraphPin* Pin)
{
	if ((Pin->PinName == FunctionPinName) && (Pin->DefaultValue != LuaFunctionPrototype))
	{
		ReconstructNode();
	}
}


#define LuaCallLibraryFunction(name) (UlxLuaCallLibrary::StaticClass()->FindFunctionByName(GET_MEMBER_NAME_CHECKED(UlxLuaCallLibrary, name)))


void UK2Node_LuaInvoke::ExpandNode(class FKismetCompilerContext& CompilerContext, UEdGraph* SourceGraph)
{
	Super::ExpandNode(CompilerContext, SourceGraph);
	// The BeginNode function packs the class name and function name into the call buffer.
	FlxParsedProto ParsedProto = FlxParsedProto::ParsePrototype(LuaFunctionPrototype);
	const UEdGraphSchema_K2* CCSchema = CompilerContext.GetSchema();
	UK2Node_CallFunction* BeginNode = MakeCallFunctionNode(CompilerContext, SourceGraph, LuaCallLibraryFunction(LuaCallBegin));
	CCSchema->TrySetDefaultValue(*BeginNode->FindPinChecked(TEXT("ClassName")), ParsedProto.ClassName);
	CCSchema->TrySetDefaultValue(*BeginNode->FindPinChecked(TEXT("FunctionName")), ParsedProto.FunctionName);
	UEdGraphPin *ThenPin = BeginNode->GetThenPin();
	
	// Add Packing operations for all argument pins.
	for (const FlxParsedProto::Pin &PinInfo : ParsedProto.Arguments)
	{
		UEdGraphPin *Pin = FindPinChecked(AddPrefix(PinInfo.Name, 'A'));
		UFunction *PackingFunc = UlxLuaCallLibrary::GetArgumentPacker(PinInfo.Type);
		if (PackingFunc == nullptr)
		{
			// This codepath should be unreachable, but just in case.
			CompilerContext.MessageLog.Error(TEXT("All argument pins must have known types"));
			continue;
		}
		UK2Node_CallFunction *PackNode = MakeCallFunctionNode(CompilerContext, SourceGraph, PackingFunc);
		CompilerContext.MovePinLinksToIntermediate(*Pin, *PackNode->FindPinChecked(TEXT("Value")));
		ThenPin = ChainExecPin(ThenPin, PackNode, TEXT("Then"));
	}

	// Add the invoke or probe node.
	UEdGraphPin *ReturnArrayPin = nullptr;
	if (IsInvoke())
	{
		UK2Node_CallFunction* ActionNode = MakeCallFunctionNode(CompilerContext, SourceGraph, LuaCallLibraryFunction(LuaCallInvoke));
		CompilerContext.MovePinLinksToIntermediate(*FindPinChecked(PlacePinName), *ActionNode->FindPinChecked(TEXT("Place")));
		ThenPin = ChainExecPin(ThenPin, ActionNode, TEXT("Then"));
	}
	else
	{
		UK2Node_CallFunction* ActionNode = MakeCallFunctionNode(CompilerContext, SourceGraph, LuaCallLibraryFunction(LuaCallProbe));
		CompilerContext.MovePinLinksToIntermediate(*FindPinChecked(PlacePinName), *ActionNode->FindPinChecked(TEXT("Place")));
		CompilerContext.CopyPinLinksToIntermediate(*FindPinChecked(ErrorPinName), *ActionNode->FindPinChecked(TEXT("False")));
		ReturnArrayPin = ActionNode->FindPinChecked(TEXT("ReturnArray"));
		ThenPin = ChainExecPin(ThenPin, ActionNode, TEXT("True"));
	}
	
	if (IsInvoke())
	{
		// Make sure we didn't have return values for an invoke.
		if ((ParsedProto.ReturnValues.Num() > 0) || (ParsedProto.ExtraReturnValues))
		{
			CompilerContext.MessageLog.Error(TEXT("Lua Call in Invoke mode does not support return values"));
		}
	}
	else
	{
		// Add Unpacking operations for all return value pins.
		for (const FlxParsedProto::Pin &PinInfo : ParsedProto.ReturnValues)
		{
			UEdGraphPin *Pin = FindPinChecked(AddPrefix(PinInfo.Name, 'R'));
			UFunction *UnpackingFunc = UlxLuaCallLibrary::GetReturnValueUnpacker(PinInfo.Type);
			if (UnpackingFunc == nullptr)
			{
				// This codepath should be unreachable, but just in case.
				CompilerContext.MessageLog.Error(TEXT("All return value pins must have known types."));
				continue;
			}
			UK2Node_CallFunction *UnpackNode = MakeCallFunctionNode(CompilerContext, SourceGraph, UnpackingFunc);
			ReturnArrayPin->MakeLinkTo(UnpackNode->FindPinChecked(UEdGraphSchema_K2::PN_Self));
			CompilerContext.CopyPinLinksToIntermediate(*FindPinChecked(ErrorPinName), *UnpackNode->FindPinChecked(TEXT("WrongType")));
			CompilerContext.MovePinLinksToIntermediate(*Pin, *UnpackNode->FindPinChecked(TEXT("Result")));
			ThenPin = ChainExecPin(ThenPin, UnpackNode, TEXT("Success"));
		}

		// If there is an extra results pin, pass through the LuaValues object.
		if (ParsedProto.ExtraReturnValues)
		{
			UEdGraphPin *ExtraResultsPin = FindPinChecked(ExtraResultsPinName);
			CompilerContext.MovePinLinksToIntermediate(*ExtraResultsPin, *ReturnArrayPin);
		}
	}

	// Link up the Exec pins.
	CompilerContext.MovePinLinksToIntermediate(*GetExecPin(), *BeginNode->GetExecPin());
	CompilerContext.MovePinLinksToIntermediate(*GetThenPin(), *ThenPin);
	

	BreakAllNodeLinks();
}


UK2Node::ERedirectType UK2Node_LuaInvoke::DoPinsMatchForReconstruction(const UEdGraphPin* NewPin, int32 NewPinIndex, const UEdGraphPin* OldPin, int32 OldPinIndex) const
{
	if (IsTemplate() || (GetGraph() == nullptr)) return ERedirectType_None;
	if ((NewPin->PinName == OldPin->PinName) &&
		(NewPin->Direction == OldPin->Direction) &&
		(NewPin->PinType == OldPin->PinType))
	{
		return ERedirectType_Name;
	}
	return ERedirectType_None;
}

bool UK2Node_LuaInvoke::IsConnectionDisallowed(const UEdGraphPin* MyPin, const UEdGraphPin* OtherPin, FString& OutReason) const
{
	// The function pin cannot be connected.
	if (MyPin->PinName == FunctionPinName)
	{
		OutReason = LOCTEXT("Error_FunctionPrototypeMustBeHardwired", "Lua function prototype must be a hardwired constant.").ToString();
		return true;
	}

	return Super::IsConnectionDisallowed(MyPin, OtherPin, OutReason);
}

void UK2Node_LuaInvoke::GetMenuActions(FBlueprintActionDatabaseRegistrar& ActionRegistrar) const
{
	UClass* ActionKey = GetClass();
	if (ActionRegistrar.IsOpenForRegistration(ActionKey))
	{
		UBlueprintNodeSpawner* NodeSpawner = UBlueprintNodeSpawner::Create(GetClass());
		check(NodeSpawner != nullptr);
		ActionRegistrar.AddBlueprintAction(ActionKey, NodeSpawner);
	}
}

FText UK2Node_LuaInvoke::GetMenuCategory() const
{
	return FText::FromString(FString(TEXT("Luprex|Lua")));
}


#undef LOCTEXT_NAMESPACE
