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


#include "FormatMessage.h"

#include "Internationalization/TextFormatter.h"
#include "BlueprintActionDatabaseRegistrar.h"
#include "BlueprintNodeSpawner.h"
#include "Containers/EnumAsByte.h"
#include "Containers/UnrealString.h"
#include "EdGraph/EdGraph.h"
#include "EdGraph/EdGraphSchema.h"
#include "EdGraphSchema_K2.h"
#include "EdGraphSchema_K2_Actions.h"
#include "EditorCategoryUtils.h"
#include "Engine/Blueprint.h"
#include "HAL/PlatformCrt.h"
#include "Internationalization/Internationalization.h"
#include "K2Node_CallFunction.h"
#include "K2Node_MakeArray.h"
#include "K2Node_MakeStruct.h"
#include "Kismet/KismetMathLibrary.h"
#include "Kismet/KismetTextLibrary.h"
#include "Kismet2/BlueprintEditorUtils.h"
#include "Kismet2/CompilerResultsLog.h"
#include "KismetCompiler.h"
#include "Math/Vector2D.h"
#include "Misc/AssertionMacros.h"
#include "Misc/CString.h"
#include "ScopedTransaction.h"
#include "Templates/Casts.h"
#include "Templates/SubclassOf.h"
#include "UObject/Class.h"
#include "UObject/ObjectPtr.h"
#include "UObject/Package.h"
#include "UObject/UnrealNames.h"
#include "UObject/UnrealType.h"
#include "UObject/WeakObjectPtr.h"
#include "UObject/WeakObjectPtrTemplates.h"

#define LOCTEXT_NAMESPACE "FormatMessage"

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


const FName UK2Node_FormatMessage::VerbosityPinName(TEXT("Verbosity"));
const FName UK2Node_FormatMessage::FormatPinName(TEXT("Format"));
const FName UK2Node_FormatMessage::ResultPinName(TEXT("Result"));


void UK2Node_FormatMessage::ReconstructNode()
{
	// Save the value of the Format Pin before it gets reconstructed.
	UEdGraphPin* FormatPin = FindPin(FormatPinName);
	if (FormatPin != nullptr)
	{
		FormatPattern = FormatPin->DefaultValue;
	}

	Super::ReconstructNode();
}

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

	// Parse the format pattern to extract argument names.
	// Note that we use the saved value of the format pattern,
	// because the format pin itself has been deleted at this point.
	TArray<FString> PinNames;
	FText::GetFormatPatternParameters(FText::FromString(FormatPattern), PinNames);

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

	UEdGraphPin *FormatPin = CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_String, FormatPinName);
	FormatPin->DefaultValue = FormatPattern;

	if (!IsFormatErrorMessage())
	{
		CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Text, ResultPinName);
	}

	if (IsFormatErrorMessage())
	{
		UEdGraphPin *P = CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Byte, StaticEnum<ElxFormatLogVerbosity>(), VerbosityPinName);
		P->DefaultValue = TEXT("Error");
		P->AutogeneratedDefaultValue = P->DefaultValue;
	}

	// Create argument pins.
	for (const FString& Name : PinNames)
	{
		CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Wildcard, AddPrefix(Name, 'A'));
	}
}

void UK2Node_FormatMessage::SynchronizeArgumentPinType(UEdGraphPin* Pin)
{
	if (HasPrefix(Pin->PinName, 'A'))
	{
		FEdGraphPinType DesiredType;
		DesiredType.PinCategory = UEdGraphSchema_K2::PC_Wildcard;
		if (Pin->LinkedTo.Num() > 0)
		{
			DesiredType = Pin->LinkedTo[0]->PinType;
		}

		if (Pin->PinType != DesiredType)
		{
			Pin->PinType = DesiredType;

			GetGraph()->NotifyNodeChanged(this);
			UBlueprint* Blueprint = GetBlueprint();
			if (!Blueprint->bBeingCompiled)
			{
				FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint);
			}
		}
	}
}

FText UK2Node_FormatMessage::GetNodeTitle(ENodeTitleType::Type TitleType) const
{
	if (IsFormatErrorMessage())
	{
		return LOCTEXT("FormatErrorMessage_Title", "Format Log Message");
	}
	else
	{
		return LOCTEXT("FormatMessage_Title", "Format Message");
	}
}

FText UK2Node_FormatMessage::GetPinDisplayName(const UEdGraphPin* Pin) const
{
	// The exec pins don't need labels.
	if (Pin->PinType.PinCategory == UEdGraphSchema_K2::PC_Exec)
	{
		return FText::GetEmpty();
	}
	
	// Many pins can go unlabeled if they have default values.
	if ((Pin->PinName == FormatPinName) || (Pin->PinName == VerbosityPinName))
	{
		if (Pin->LinkedTo.Num() == 0)
		{
			return FText::GetEmpty();
		}
	}

	// Just return the pin name, stripping prefix if present.
	return FText::FromName(RemovePrefix(Pin->PinName));
}

void UK2Node_FormatMessage::PinConnectionListChanged(UEdGraphPin* Pin)
{
	Super::PinConnectionListChanged(Pin);
	SynchronizeArgumentPinType(Pin);
}

void UK2Node_FormatMessage::PinDefaultValueChanged(UEdGraphPin* Pin)
{
	if ((Pin->PinName == FormatPinName) && (Pin->DefaultValue != FormatPattern))
	{
		ReconstructNode();
	}
}

FText UK2Node_FormatMessage::GetTooltipText() const
{
	if (IsFormatErrorMessage())
	{
		static FText Tooltip = LOCTEXT("LogTooltip",
			"Output an error, warning, or informational message to the log file.\n"
			"\n"
			"  \u2022 Use {ArgName} to denote format arguments, giving each argument a different ArgName.\n"
			"\n"
			"It is often desirable to use this in conjunction with a separate utility that\n"
			"pauses the execution of the blueprint whenever an error is logged.");
		return Tooltip;
	}
	else
	{
		static FText Tooltip = LOCTEXT("FormatTooltip",
			"Format a message, and output it as Text.\n"
			"\n"
			"  \u2022 Use {ArgName} to denote format arguments, giving each argument a different ArgName.\n"
			"\n");
		return Tooltip;
	}
}


void UK2Node_FormatMessage::PostReconstructNode()
{
	Super::PostReconstructNode();
	if (IsTemplate() || (GetGraph() == nullptr)) return;
	for (UEdGraphPin* Pin : Pins)
	{
		SynchronizeArgumentPinType(Pin);
	}
}


UEdGraphPin* UK2Node_FormatMessage::ExpandArgumentPin(UEdGraphPin* ArgumentPin, FKismetCompilerContext& CompilerContext, UEdGraph* SourceGraph)
{
	UFunction *Converter = UlxFormatDataLibrary::GetConverterForPinType(CompilerContext.GetSchema(), ArgumentPin->PinType, true);
	if (Converter == nullptr)
	{
		CompilerContext.MessageLog.Error(TEXT("Cannot convert Pin to a Format Argument"));
		return nullptr;
	}

	FName OriginalName = RemovePrefix(ArgumentPin->PinName);
	UK2Node_CallFunction* ConvertNode = MakeCallFunctionNode(CompilerContext, SourceGraph, Converter);
	ConvertNode->GetSchema()->TrySetDefaultValue(*ConvertNode->FindPinChecked(TEXT("Name")), OriginalName.ToString());

	UEdGraphPin *ValuePin = ConvertNode->FindPin(TEXT("AutoConvertedValue"));
	if (ValuePin != nullptr)
	{
		CompilerContext.MovePinLinksToIntermediate(*ArgumentPin, *ValuePin);
	}

	UEdGraphPin *SubCategoryObjectPin = ConvertNode->FindPin(TEXT("PinSubCategoryObject"));
	if (SubCategoryObjectPin != nullptr)
	{
		SubCategoryObjectPin->DefaultObject = Cast<UObject>(ArgumentPin->PinType.PinSubCategoryObject);
	}

	return ConvertNode->GetReturnValuePin();
}

void UK2Node_FormatMessage::ExpandNode(class FKismetCompilerContext& CompilerContext, UEdGraph* SourceGraph)
{
	Super::ExpandNode(CompilerContext, SourceGraph);

	const UEdGraphSchema_K2* CCSchema = CompilerContext.GetSchema();

	// Parse the format pattern to extract argument names.
	TArray<FString> PinNames;
	FText::GetFormatPatternParameters(FText::FromString(FormatPattern), PinNames);

	// Decide which formatting function we're going to call.
	UFunction *FormatFunction;
	if (IsFormatErrorMessage())
	{
		FormatFunction = UlxFormatDataLibrary::StaticClass()->FindFunctionByName(GET_MEMBER_NAME_CHECKED(UlxFormatDataLibrary, FormatLogMessageInternal));
	}
	else
	{
		FormatFunction = UlxFormatDataLibrary::StaticClass()->FindFunctionByName(GET_MEMBER_NAME_CHECKED(UlxFormatDataLibrary, FormatMessageInternal));
	}

	// This is the node that does all the Format work and outputs the message.
	UK2Node_CallFunction* CallFormatFunction = MakeCallFunctionNode(CompilerContext, SourceGraph, FormatFunction);

	// Create a "Make Array" node to compile the list of arguments into an array for the Format function being called.
	UK2Node_MakeArray* MakeArrayNode = CompilerContext.SpawnIntermediateNode<UK2Node_MakeArray>(this, SourceGraph);
	MakeArrayNode->NumInputs = PinNames.Num();
	MakeArrayNode->AllocateDefaultPins();
	CompilerContext.MessageLog.NotifyIntermediateObjectCreation(MakeArrayNode, this);
	UEdGraphPin* ArrayOut = MakeArrayNode->GetOutputPin();
	ArrayOut->MakeLinkTo(CallFormatFunction->FindPinChecked(TEXT("InArgs")));

	// This will set the "Make Array" node's type, only works if one pin is connected.
	MakeArrayNode->PinConnectionListChanged(ArrayOut);

	// For each argument, expand it into a converter node and link to the array.
	for (int32 ArgIdx = 0; ArgIdx < PinNames.Num(); ++ArgIdx)
	{
		UEdGraphPin* ArgumentPin = FindPin(AddPrefix(PinNames[ArgIdx], 'A'), EGPD_Input);
		UEdGraphPin* ConvertedPin = ExpandArgumentPin(ArgumentPin, CompilerContext, SourceGraph);
		if (ConvertedPin == nullptr) continue;
		const FString PinName = FString::Printf(TEXT("[%d]"), ArgIdx);
		ConvertedPin->MakeLinkTo(MakeArrayNode->FindPinChecked(PinName));
	}

	// Connect up other pins to the Formatting node.
	CompilerContext.MovePinLinksToIntermediate(*FindPinChecked(FormatPinName), *CallFormatFunction->FindPinChecked(TEXT("InPattern")));
	if (IsFormatErrorMessage())
	{
		CompilerContext.MovePinLinksToIntermediate(*FindPinChecked(VerbosityPinName), *CallFormatFunction->FindPinChecked(TEXT("Verbosity")));
	}
	else
	{
		CompilerContext.MovePinLinksToIntermediate(*FindPinChecked(ResultPinName), *CallFormatFunction->GetReturnValuePin());
	}

	// Link up the Exec pins.
	CompilerContext.MovePinLinksToIntermediate(*GetExecPin(), *CallFormatFunction->GetExecPin());
	CompilerContext.MovePinLinksToIntermediate(*GetThenPin(), *CallFormatFunction->GetThenPin());
	
	BreakAllNodeLinks();
}


UK2Node::ERedirectType UK2Node_FormatMessage::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) || (NewPin->PinType.PinCategory == UEdGraphSchema_K2::PC_Wildcard)))
	{
		return ERedirectType_Name;
	}
	return ERedirectType_None;
}

bool UK2Node_FormatMessage::IsConnectionDisallowed(const UEdGraphPin* MyPin, const UEdGraphPin* OtherPin, FString& OutReason) const
{
	// The following pins cannot be connected.  They are meant to be hardwired constants.
	if (MyPin->PinName == FormatPinName)
	{
		OutReason = LOCTEXT("Error_FormatStringMustBeHardwired", "Format string must be a hardwired constant.").ToString();
		return true;
	}

	// Argument input pins may only be connected to convertible types.
	if (HasPrefix(MyPin->PinName, 'A'))
	{
		const UEdGraphSchema_K2* K2Schema = Cast<const UEdGraphSchema_K2>(GetSchema());
		UFunction *Converter = UlxFormatDataLibrary::GetConverterForPinType(K2Schema, OtherPin->PinType, false);
		if (Converter == nullptr)
		{
			OutReason = LOCTEXT("Error_InvalidArgumentType", "Data cannot be converted to text.").ToString();
			return true;
		}
	}

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


void UK2Node_FormatMessage::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_FormatMessage::GetMenuCategory() const
{
	return FEditorCategoryUtils::GetCommonCategory(FCommonEditorCategory::Text);
}


#undef LOCTEXT_NAMESPACE
