#include "WingVariables.h"
#include "WingTypes.h"
#include "WingUtils.h"
#include "WingProperty.h"
#include "WingTokenizer.h"
#include "EdGraphSchema_K2.h"
#include "K2Node_FunctionEntry.h"
#include "K2Node_FunctionResult.h"
#include "K2Node_CustomEvent.h"
#include "K2Node_EditablePinBase.h"
#include "Kismet2/BlueprintEditorUtils.h"
#include "Kismet2/KismetEditorUtilities.h"

// Flag names used for blueprint variables.
static const FName Flag_InstanceEditable(TEXT("InstanceEditable"));
static const FName Flag_BlueprintReadOnly(TEXT("BlueprintReadOnly"));
static const FName Flag_ExposeOnSpawn(TEXT("ExposeOnSpawn"));
static const FName Flag_Private(TEXT("Private"));
static const FName Flag_ExposeToCinematics(TEXT("ExposeToCinematics"));


static TSet<FName> Flags_None = { };
static TSet<FName> Flags_BlueprintVariables = { Flag_InstanceEditable, Flag_BlueprintReadOnly, Flag_ExposeOnSpawn, Flag_Private, Flag_ExposeToCinematics };



void WingVariableList::Print(WingOut Out)
{
	if (Variables.IsEmpty()) return;
	Out.Print(ListName);
	Out.PrintChar(':');
	Out.PrintChar('\n');
	for (const Var& V : Variables)
	{
		FString TypeStr = UWingTypes::TypeToText(V.Type);
		FString NameStr = WingUtils::ExternalizeID(V.Name);

		// Build flags string.
		FString FlagsStr;
		if (V.Flags.Num() > 0)
		{
			TArray<FName> Sorted = V.Flags.Array();
			Sorted.Sort(FNameLexicalLess());
			for (const FName& Flag : Sorted)
			{
				if (!FlagsStr.IsEmpty()) FlagsStr += TEXT(", ");
				FlagsStr += Flag.ToString();
			}
		}

		// Print: type name (flags) = defaultvalue
		Out.Printf(TEXT("    %s %s"), *TypeStr, *NameStr);
		if (!FlagsStr.IsEmpty())
			Out.Printf(TEXT(" (%s)"), *FlagsStr);
		if (!V.DefaultValue.IsEmpty())
			Out.Printf(TEXT(" = %s"), *V.DefaultValue);
		Out.Print(TEXT("\n"));
	}
}

void WingVariableList::PrintCompact(WingOut Out)
{
	bool First = true;
	for (const Var& V : Variables)
	{
		if (!First) Out.PrintChar(',');
		First = false;
		Out.Printf(TEXT("%s %s"), *UWingTypes::TypeToText(V.Type), *WingUtils::ExternalizeID(V.Name));
	}
}

void WingVariableList::ClearLinks()
{
	for (Var &V : Variables)
	{
		V.BPVar = nullptr;
		V.Pin = nullptr;
	}
}

bool WingVariableList::CheckSanity(const TSet<FName> &GoodFlags, bool Allow, WingOut Errors)
{
	if ((!Allow) && (!Variables.IsEmpty()))
	{
		Errors.Printf(TEXT("This object does not support %s.\n"), ListName);
		return false;
	}
	for (const Var &Variable : Variables)
	{
		FString VarName = WingUtils::ExternalizeID(Variable.Name);
		FString TypeText = UWingTypes::TypeToText(Variable.Type);
		if (TypeText.IsEmpty())
		{
			Errors.Printf(TEXT("Type of variable %s is not valid for unknown reasons\n"), *VarName);
			return false;
		}
		if (!UWingTypes::IsBlueprintType(Variable.Type))
		{
			Errors.Printf(TEXT("Type is not a valid BlueprintType: %s %s\n"), *TypeText, *VarName);
			return false;
		}
		for (FName Flag : Variable.Flags)
		{
			if (!GoodFlags.Contains(Flag))
			{
				Errors.Printf(TEXT("Flag %s is not valid here.  Valid flags are:"), *Flag.ToString());
				for (FName Rel : GoodFlags) Errors.Printf(TEXT("  %s\n"), *Rel.ToString());
				return false;
			}
		}
	}
	return true;
}

void WingVariables::Empty()
{
	BlueprintVariables.Empty();
	LocalVariables.Empty();
	InputVariables.Empty();
	OutputVariables.Empty();
}

bool WingVariables::IsEmpty()
{
	return (BlueprintVariables.IsEmpty() &&
			LocalVariables.IsEmpty() &&
			InputVariables.IsEmpty() &&
			OutputVariables.IsEmpty());
}

void WingVariables::ClearLinks()
{
	BlueprintVariables.ClearLinks();
	LocalVariables.ClearLinks();
	InputVariables.ClearLinks();
	OutputVariables.ClearLinks();
}

void WingVariables::Print(WingOut Out)
{
	BlueprintVariables.Print(Out);
	LocalVariables.Print(Out);
	InputVariables.Print(Out);
	OutputVariables.Print(Out);
}

WingVariableList *WingVariables::GetList(FName Name)
{
	if (Name == TEXT("blueprint")) return &BlueprintVariables;
	if (Name == TEXT("input")) return &InputVariables;
	if (Name == TEXT("output")) return &OutputVariables;
	if (Name == TEXT("local")) return &LocalVariables;
	return nullptr;
}

bool WingVariables::ParseOneVariable(WingTokenizer &Tok, FName &Kind, Var &V, bool NameOnly, WingOut Errors)
{
	// Parse Kind.
	if (GetList(Tok.NextName()) == nullptr)
	{
		Errors.Print(TEXT("ERROR: Variable description should start with 'blueprint', 'input', 'output', or 'local'"));
		return false;
	}
	Kind = Tok.NextName();
	Tok.Advance();
	
	// Parse type.
	if (!NameOnly)
	{
		UWingTypes::Requirements Req;
		Req.BlueprintType = true;
		Req.Blueprintable = false;
		Req.AllowContainer = true;
		if (!UWingTypes::TextToType(Tok, V.Type, Req, false, Errors))
			return false;
	}

	// Parse name.
	if (Tok.NextType() != Tok.Identifier)
	{
		Errors.Print(TEXT("ERROR: Expected variable name after type\n"));
		return false;
	}
	V.Name = Tok.NextName();
	Tok.Advance();

	// Parse optional flags: (flag1, flag2, ...)
	if ((!NameOnly) && Tok.TokenIs('('))
	{
		if (!ParseVariableFlags(Tok, V.Flags, Errors)) return false;
	}

	// Parse optional default value: = rest-of-line
	if (!NameOnly && (Tok.NextType() == Tok.RestOfLine))
	{
		V.DefaultSpecified = true;
		V.DefaultValue = FString(Tok.NextRest().TrimStartAndEnd());
		Tok.Advance();
	}

	// Should be at end of line.
	if (Tok.NextType() != 0)
	{
		Tok.SaveCursor(NAME_None);
		Errors.Printf(TEXT("ERROR: Unexpected token after variable declaration: '%s'\n"),
			*FString(Tok.GetRange(NAME_None, 1)));
		return false;
	}

	return true;
}

bool WingVariables::ParseVariableFlags(WingTokenizer &Tok, TSet<FName> &Out, WingOut Errors)
{
	Tok.Advance(); // Step over open-paren
	while (Tok.TokenIs(Tok.Identifier))
	{
		Out.Add(Tok.NextName());
		Tok.Advance();
		// Commas are optional.
		if (Tok.TokenIs(',')) Tok.Advance();
	}
	if (!Tok.TokenIs(')'))
	{
		Tok.SaveCursor(NAME_None);
		Errors.Printf(TEXT("ERROR: flag list contains invalid token '%s'\n"),
				*FString(Tok.GetRange(NAME_None, 1)));
		return false;
	}
	Tok.Advance(); // Step over close-paren
	return true;
}

bool WingVariables::Parse(const TArray<FString> &Vars, bool NameOnly, WingOut Errors)
{
	for (const FString& Onevar : Vars)
	{
		WingTokenizer Tok(Onevar);
		FName Kind;
		Var V;
		if (!ParseOneVariable(Tok, Kind, V, NameOnly, Errors)) return false;
		WingVariableList *List = GetList(Kind);
		List->Add(V);
	}
	return true;
}

void WingVariables::Load(WingOut Errors)
{
	Empty();
	if (Blueprint) return LoadBlueprint();
	if (Graph) return LoadGraph();
	if (CustomEvent) return LoadCustomEvent();
	ErrorNoBackingStore(Errors);
}

void WingVariables::LoadBlueprint()
{
	UObject *CDO = WingUtils::GetGeneratedCDO(Blueprint.Get());

	for (FBPVariableDescription& Desc : Blueprint->NewVariables)
	{
		// Skip event dispatchers.
		if (Desc.VarType.PinCategory == UEdGraphSchema_K2::PC_MCDelegate) continue;
		// Load up the variable.
		Var V = LoadBlueprintVariableDescription(Desc, CDO);
		BlueprintVariables.Variables.Add(MoveTemp(V));
	}
}

void WingVariables::LoadGraph()
{
	TWeakObjectPtr<UK2Node_EditablePinBase> EntryNode;
	TWeakObjectPtr<UK2Node_EditablePinBase> ResultNode;
	FBlueprintEditorUtils::GetEntryAndResultNodes(Graph.Get(), EntryNode, ResultNode);
	if (EntryNode.IsValid()) LoadEditablePinBase(EntryNode.Get(), InputVariables);
	if (ResultNode.IsValid()) LoadEditablePinBase(ResultNode.Get(), OutputVariables);
	LoadLocalVariables(EntryNode.Get());
}

void WingVariables::LoadLocalVariables(UK2Node_EditablePinBase *Node)
{
	UK2Node_FunctionEntry *Func = Cast<UK2Node_FunctionEntry>(Node);
	if (Func == nullptr) return;
	for (FBPVariableDescription& Desc : Func->LocalVariables)
	{
		Var V = LoadLocalVariableDescription(Desc);
		LocalVariables.Variables.Add(MoveTemp(V));
	}
}

WingVariables::Var WingVariables::LoadBlueprintVariableDescription(FBPVariableDescription &Desc, UObject *CDO)
{
	Var Result;
	Result.Name = Desc.VarName;
	Result.Type = Desc.VarType;
	
	if (!(Desc.PropertyFlags & CPF_DisableEditOnInstance))
		Result.Flags.Add(Flag_InstanceEditable);
	
	if (Desc.PropertyFlags & CPF_BlueprintReadOnly)
		Result.Flags.Add(Flag_BlueprintReadOnly);

	if (Desc.PropertyFlags & CPF_Interp)
		Result.Flags.Add(Flag_ExposeToCinematics);

	if (Desc.HasMetaData(FBlueprintMetadata::MD_ExposeOnSpawn))
		Result.Flags.Add(Flag_ExposeOnSpawn);

	if (Desc.HasMetaData(FBlueprintMetadata::MD_Private))
		Result.Flags.Add(Flag_Private);

	// Read default value from CDO if available.
	if (CDO)
	{
		FProperty* Prop = CDO->GetClass()->FindPropertyByName(Desc.VarName);
		if (Prop)
		{
			Result.DefaultValue = FWingProperty(CDO, CDO, Prop, false).GetText();
			Result.DefaultSpecified = true;
		}
	}

	return Result;
}

WingVariables::Var WingVariables::LoadLocalVariableDescription(FBPVariableDescription &Desc)
{
	Var Result;
	Result.Name = Desc.VarName;
	Result.Type = Desc.VarType;
	Result.DefaultValue = Desc.DefaultValue;
	Result.DefaultSpecified = true;
	return Result;
}

void WingVariables::LoadEditablePinBase(UK2Node_EditablePinBase* Node, WingVariableList &List)
{
	if (Node == nullptr) return;
	for (const TSharedPtr<FUserPinInfo>& PinInfo : Node->UserDefinedPins)
	{
		Var V;
		V.Name = PinInfo->PinName;
		V.Type = PinInfo->PinType;
		V.DefaultValue = PinInfo->PinDefaultValue;
		V.DefaultSpecified = true;
		List.Variables.Add(MoveTemp(V));
	}
}

void WingVariables::LoadCustomEvent()
{
	LoadEditablePinBase(CustomEvent.Get(), InputVariables);
}

bool WingVariables::Check(WingOut Errors)
{
	if (Blueprint) return CheckBlueprint(Errors);
	if (Graph) return CheckGraph(Errors);
	if (CustomEvent) return CheckCustomEvent(Errors);
	return ErrorNoBackingStore(Errors);
}

bool WingVariables::CheckBlueprint(WingOut Errors)
{
	bool OK = true;
	if (!BlueprintVariables.CheckSanity(Flags_BlueprintVariables, true, Errors)) OK = false;
	if (!LocalVariables.CheckSanity(Flags_None, false, Errors)) OK = false;
	if (!InputVariables.CheckSanity(Flags_None, false, Errors)) OK = false;
	if (!OutputVariables.CheckSanity(Flags_None, false, Errors)) OK = false;
	return OK;
}

bool WingVariables::CheckGraph(WingOut Errors)
{
	EGraphType T = Graph->GetSchema()->GetGraphType(Graph.Get());
	bool AllowLocal = (T == EGraphType::GT_Function);
	bool AllowInput = (T == EGraphType::GT_Function) || (T == EGraphType::GT_Macro);
	bool AllowOutput = (T == EGraphType::GT_Function) || (T == EGraphType::GT_Macro);
	if ((!AllowLocal) && (!AllowInput) && (!AllowOutput))
	{
		Errors.Printf(TEXT("This graph type has no variables."));
		return false;
	}

	bool OK = true;
	if (!BlueprintVariables.CheckSanity(Flags_None, false, Errors)) OK = false;
	if (!LocalVariables.CheckSanity(Flags_None, AllowLocal, Errors)) OK = false;
	if (!InputVariables.CheckSanity(Flags_None, AllowInput, Errors)) OK = false;
	if (!OutputVariables.CheckSanity(Flags_None, AllowOutput, Errors)) OK = false;
	return OK;
}

bool WingVariables::CheckCustomEvent(WingOut Errors)
{
	bool OK = true;
	if (!BlueprintVariables.CheckSanity(Flags_None, false, Errors)) OK = false;
	if (!LocalVariables.CheckSanity(Flags_None, false, Errors)) OK = false;
	if (!InputVariables.CheckSanity(Flags_None, true, Errors)) OK = false;
	if (!OutputVariables.CheckSanity(Flags_None, false, Errors)) OK = false;
	return OK;
}

bool WingVariables::Modify(WingOut Errors)
{
	if (Blueprint) return ModifyBlueprint(Errors);
	if (Graph) return ModifyGraph(Errors);
	if (CustomEvent) return ModifyCustomEvent(Errors);
	return ErrorNoBackingStore(Errors);
}

bool WingVariables::ModifyBlueprint(WingOut Errors)
{
	if (!CheckBlueprint(Errors)) return false;
	if (!LinkBlueprintVariables(Errors)) return false;
	for (Var &V : BlueprintVariables.Variables)
	{
		V.BPVar->VarType = V.Type;
		ModifyBlueprintVariableFlags(V);
	}
	if (!ModifyBlueprintDefaults(Errors)) return false;
	return true;
}

bool WingVariables::LinkBlueprintVariables(WingOut Errors)
{
	ClearLinks();
	for (Var &V : BlueprintVariables.Variables)
	{
		V.BPVar = WingUtils::FindOneWithInternalID(
			V.Name, Blueprint->NewVariables, TEXT("non-inherited variable"), Errors);
		if (V.BPVar == nullptr) return false;
	}
	return true;
}

void WingVariables::ModifyBlueprintVariableFlags(Var &Input)
{
	FBPVariableDescription &Out = *Input.BPVar;

	// InstanceEditable: absence means CPF_DisableEditOnInstance is set
	if (Input.Flags.Contains(Flag_InstanceEditable))
		Out.PropertyFlags &= ~CPF_DisableEditOnInstance;
	else
		Out.PropertyFlags |= CPF_DisableEditOnInstance;

	if (Input.Flags.Contains(Flag_BlueprintReadOnly))
		Out.PropertyFlags |= CPF_BlueprintReadOnly;
	else
		Out.PropertyFlags &= ~CPF_BlueprintReadOnly;

	if (Input.Flags.Contains(Flag_ExposeToCinematics))
		Out.PropertyFlags |= CPF_Interp;
	else
		Out.PropertyFlags &= ~CPF_Interp;

	if (Input.Flags.Contains(Flag_ExposeOnSpawn))
		Out.SetMetaData(FBlueprintMetadata::MD_ExposeOnSpawn, TEXT("true"));
	else
		Out.RemoveMetaData(FBlueprintMetadata::MD_ExposeOnSpawn);

	if (Input.Flags.Contains(Flag_Private))
		Out.SetMetaData(FBlueprintMetadata::MD_Private, TEXT("true"));
	else
		Out.RemoveMetaData(FBlueprintMetadata::MD_Private);
}

bool WingVariables::ModifyBlueprintDefaults(WingOut Errors)
{
	bool AnySpecified = false;
	for (Var &Input : BlueprintVariables.Variables)
		if (Input.DefaultSpecified) AnySpecified = true;
	if (!AnySpecified) return true;

	FKismetEditorUtilities::CompileBlueprint(Blueprint.Get());
	UObject *CDO = WingUtils::GetGeneratedCDO(Blueprint.Get());
	if (!CDO)
	{
		Errors.Printf(TEXT("Blueprint didn't compile, cannot store default values"));
		return false;
	}

	for (Var &Input : BlueprintVariables.Variables)
	{
		if (Input.DefaultSpecified)
		{
			FProperty* Prop = CDO->GetClass()->FindPropertyByName(Input.Name);
			if (!Prop)
			{
				Errors.Printf(TEXT("Variable exists in blueprint, but not the generated class, which is weird: %s."),
					*WingTokenizer::ExternalizeID(Input.Name));
				return false;
			}
			if (!FWingProperty(CDO, CDO, Prop, true).SetText(Input.DefaultValue, Errors)) return false;
		}
	}
	return true;
}

bool WingVariables::ModifyGraph(WingOut Errors)
{
	if (!CheckGraph(Errors)) return false;
	ClearLinks();

	UK2Node_EditablePinBase *InputNode, *OutputNode;
	UK2Node_FunctionEntry *LocalNode;
	bool CreateOutputNode = !OutputVariables.Variables.IsEmpty();
	if (!GetGraphNodes(InputNode, OutputNode, LocalNode, CreateOutputNode, Errors)) return false;

	for (Var &V : LocalVariables.Variables)
	{
		FBPVariableDescription *Desc =
			WingUtils::FindOneWithInternalID(V.Name, LocalNode->LocalVariables, TEXT("local variable"), Errors);
		if (Desc == nullptr) return false;
		Desc->VarType = V.Type;
		if (V.DefaultSpecified) Desc->DefaultValue = V.DefaultValue;
	}
	if (!ModifyEditablePinBase(InputVariables, InputNode, Errors)) return false;
	if (OutputNode)
		if (!ModifyEditablePinBase(OutputVariables, OutputNode, Errors)) return false;

	if (InputNode) InputNode->ReconstructNode();
	if (OutputNode) OutputNode->ReconstructNode();
	return true;
}

bool WingVariables::ModifyCustomEvent(WingOut Errors)
{
	if (!CheckCustomEvent(Errors)) return false;
	ClearLinks();
	if (!ModifyEditablePinBase(InputVariables, CustomEvent.Get(), Errors)) return false;
	CustomEvent->ReconstructNode();
	return true;
}

bool WingVariables::ModifyEditablePinBase(WingVariableList &List, UK2Node_EditablePinBase *Node, WingOut Errors)
{
	for (Var &V : List.Variables)
	{
		TSharedPtr<FUserPinInfo> Found =
			WingUtils::FindOneWithInternalID(V.Name, Node->UserDefinedPins, List.ListName, Errors);
		if (!Found) return false;
		Found->PinType = V.Type;
		if (V.DefaultSpecified) Found->PinDefaultValue = V.DefaultValue;
	}
	return true;
}

bool WingVariables::GetGraphNodes(
        UK2Node_EditablePinBase *&InputNode,
        UK2Node_EditablePinBase *&OutputNode,
        UK2Node_FunctionEntry *&LocalNode,
		bool CreateOutputNode,
        WingOut Errors)
{
	// In theory, none of these errors should trigger, because
	// we call CheckSanity before calling this function.  But
	// you never know.
	InputNode = nullptr;
	OutputNode = nullptr;
	LocalNode = nullptr;

	TWeakObjectPtr<UK2Node_EditablePinBase> Inputs, Outputs;
	FBlueprintEditorUtils::GetEntryAndResultNodes(Graph.Get(), Inputs, Outputs);
	if (!Inputs.IsValid())
	{
		Errors.Printf(TEXT("ERROR: no function entry node for graph."));
		return false;
	}
	if (!Outputs.IsValid() && CreateOutputNode)
	{
		Outputs = FBlueprintEditorUtils::FindOrCreateFunctionResultNode(Inputs.Get());
		if (!Outputs.IsValid())
		{
			Errors.Printf(TEXT("ERROR: couldn't create result node for graph."));
			return false;
		}
	}
	UK2Node_FunctionEntry *Locals = Cast<UK2Node_FunctionEntry>(Inputs.Get());
	if (!Locals && (!LocalVariables.Variables.IsEmpty()))
	{
		Errors.Printf(TEXT("ERROR: function doesn't have a proper entry node for local variables"));
		return false;
	}
	InputNode = Inputs.Get();
	OutputNode = Outputs.Get();
	LocalNode = Locals;
	return true;
}

bool WingVariables::Create(WingOut Errors)
{
	if (Blueprint) return CreateBlueprint(Errors);
	if (Graph) return CreateGraph(Errors);
	if (CustomEvent) return CreateCustomEvent(Errors);
	return ErrorNoBackingStore(Errors);
}

bool WingVariables::CreateBlueprint(WingOut Errors)
{
	if (!CheckBlueprint(Errors)) return false;
	ClearLinks();

	// Check for name collisions against existing variables, components, and the like.
	TSet<FName> InUse = WingUtils::GetAllInUseNames(Blueprint.Get());
	if (!WingUtils::FindNoDuplicateNames(InUse, BlueprintVariables.Variables, TEXT("variable or component"), Errors)) return false;

	// Create the variables.
	for (const WingVariables::Var& V : BlueprintVariables.Variables)
	{
		if (!FBlueprintEditorUtils::AddMemberVariable(Blueprint.Get(), V.Name, V.Type))
		{
			Errors.Printf(TEXT("ERROR: Failed to add variable '%s'\n"),
				*WingUtils::ExternalizeID(V.Name));
			return false;
		}
	}

	if (!LinkBlueprintVariables(Errors)) return false;
	for (Var &V : BlueprintVariables.Variables) ModifyBlueprintVariableFlags(V);
	if (!ModifyBlueprintDefaults(Errors)) return false;
	return true;
}

bool WingVariables::CreateGraph(WingOut Errors)
{
	if (!CheckGraph(Errors)) return false;
	ClearLinks();

	UK2Node_EditablePinBase *InputNode, *OutputNode;
	UK2Node_FunctionEntry *LocalNode;
	bool CreateOutputNode = (!OutputVariables.Variables.IsEmpty());
	if (!GetGraphNodes(InputNode, OutputNode, LocalNode, CreateOutputNode, Errors)) return false;

	// Check for name collisions against existing local variables.
	const TCHAR *ctx = TEXT("local, function input, and function output variables");
	TSet<FName> Names;
	if (InputNode && !WingUtils::FindNoDuplicateNames(
			Names, InputNode->UserDefinedPins, ctx, Errors)) return false;
	if (OutputNode && !WingUtils::FindNoDuplicateNames(
			Names, OutputNode->UserDefinedPins, ctx, Errors)) return false;
	if (LocalNode && !WingUtils::FindNoDuplicateNames(
			Names, LocalNode->LocalVariables, ctx, Errors)) return false;
	if (!WingUtils::FindNoDuplicateNames(
			Names, InputVariables.Variables, ctx, Errors)) return false;
	if (!WingUtils::FindNoDuplicateNames(
			Names, OutputVariables.Variables, ctx, Errors)) return false;
	if (!WingUtils::FindNoDuplicateNames(
			Names, LocalVariables.Variables, ctx, Errors)) return false;

	// Create input pins on the input node.
	for (const Var& V : InputVariables.Variables)
		AddUserPinInfo(V, EGPD_Output, InputNode);

	// Create output pins on the output node.
	for (const Var& V : OutputVariables.Variables)
		AddUserPinInfo(V, EGPD_Input, OutputNode);

	// Create local variables via the proper API.
	UBlueprint* BP = FBlueprintEditorUtils::FindBlueprintForGraph(Graph.Get());
	for (const Var& V : LocalVariables.Variables)
	{
		if (!FBlueprintEditorUtils::AddLocalVariable(BP, Graph.Get(), V.Name, V.Type, V.DefaultValue))
		{
			Errors.Printf(TEXT("ERROR: Failed to create local variable '%s'\n"),
				*WingUtils::ExternalizeID(V.Name));
			return false;
		}
	}

	if (InputNode) InputNode->ReconstructNode();
	if (OutputNode) OutputNode->ReconstructNode();
	return true;
}

bool WingVariables::CreateCustomEvent(WingOut Errors)
{
	if (!CheckCustomEvent(Errors)) return false;
	ClearLinks();

	// Check for name collisions against existing pins.
	TSet<FName> Names;
	if (!WingUtils::FindNoDuplicateNames(
			Names, CustomEvent->UserDefinedPins, TEXT("event parameter"), Errors)) return false;
	if (!WingUtils::FindNoDuplicateNames(
			Names, InputVariables.Variables, TEXT("event parameter"), Errors)) return false;

	for (const Var& V : InputVariables.Variables)
		AddUserPinInfo(V, EGPD_Output, CustomEvent.Get());

	CustomEvent->ReconstructNode();
	return true;
}

void WingVariables::AddUserPinInfo(const Var &V, EEdGraphPinDirection Dir, UK2Node_EditablePinBase *Node)
{
	TSharedPtr<FUserPinInfo> Result = MakeShareable( new FUserPinInfo() );
	Result->PinName = V.Name;
	Result->PinType = V.Type;
	Result->PinDefaultValue = V.DefaultValue;
	Result->DesiredPinDirection = Dir;
	Node->UserDefinedPins.Add(Result);
}

bool WingVariables::Remove(WingOut Errors)
{
	if (Blueprint) return RemoveBlueprint(Errors);
	if (Graph) return RemoveGraph(Errors);
	if (CustomEvent) return RemoveCustomEvent(Errors);
	return ErrorNoBackingStore(Errors);
}

bool WingVariables::RemoveBlueprint(WingOut Errors)
{
	// Verify that all named variables exist.
	TArray<FName> Names;
	for (const Var& V : BlueprintVariables.Variables)
	{
		FBPVariableDescription* Found =
			WingUtils::FindOneWithInternalID(V.Name, Blueprint->NewVariables, TEXT("non-inherited variable"), Errors);
		if (!Found) return false;
		Names.Add(V.Name);
	}

	// Remove them.
	FBlueprintEditorUtils::BulkRemoveMemberVariables(Blueprint.Get(), Names);
	return true;
}

bool WingVariables::RemoveGraph(WingOut Errors)
{
	UK2Node_EditablePinBase *InputNode, *OutputNode;
	UK2Node_FunctionEntry *LocalNode;
	if (!GetGraphNodes(InputNode, OutputNode, LocalNode, false, Errors)) return false;

	// Verify that all named variables exist before removing anything.
	if (!OutputNode && (!OutputVariables.Variables.IsEmpty()))
	{
		Errors.Printf(TEXT("No output variables on this function to remove.\n"));
		return false;
	}
	for (const Var& V : InputVariables.Variables)
	{
		TSharedPtr<FUserPinInfo> Found =
			WingUtils::FindOneWithInternalID(V.Name, InputNode->UserDefinedPins, TEXT("input variable"), Errors);
		if (!Found) return false;
	}
	for (const Var& V : OutputVariables.Variables)
	{
		TSharedPtr<FUserPinInfo> Found =
			WingUtils::FindOneWithInternalID(V.Name, OutputNode->UserDefinedPins, TEXT("output variable"), Errors);
		if (!Found) return false;
	}
	for (const Var& V : LocalVariables.Variables)
	{
		FBPVariableDescription* Found =
			WingUtils::FindOneWithInternalID(V.Name, LocalNode->LocalVariables, TEXT("local variable"), Errors);
		if (!Found) return false;
	}

	// Remove input and output pins.
	for (const Var& V : InputVariables.Variables)
		InputNode->RemoveUserDefinedPinByName(V.Name);
	for (const Var& V : OutputVariables.Variables)
		OutputNode->RemoveUserDefinedPinByName(V.Name);

	// Remove local variables.
	UBlueprint* BP = FBlueprintEditorUtils::FindBlueprintForGraph(Graph.Get());
	for (const Var& V : LocalVariables.Variables)
	{
		LocalNode->LocalVariables.RemoveAll(
			[&](const FBPVariableDescription& Desc) { return Desc.VarName == V.Name; });
		FBlueprintEditorUtils::RemoveVariableNodes(BP, V.Name, true, Graph.Get());
	}

	if (InputNode) InputNode->ReconstructNode();
	if (OutputNode) OutputNode->ReconstructNode();
	return true;
}

bool WingVariables::RemoveCustomEvent(WingOut Errors)
{
	// Verify that all named parameters exist before removing anything.
	for (const Var& V : InputVariables.Variables)
	{
		TSharedPtr<FUserPinInfo> Found =
			WingUtils::FindOneWithInternalID(V.Name, CustomEvent->UserDefinedPins, TEXT("event parameter"), Errors);
		if (!Found) return false;
	}

	for (const Var& V : InputVariables.Variables)
		CustomEvent->RemoveUserDefinedPinByName(V.Name);

	CustomEvent->ReconstructNode();
	return true;
}

bool WingVariables::SetBackingStore(UObject *Obj, WingOut Errors)
{
	Blueprint.Reset();
	Graph.Reset();
	CustomEvent.Reset();
	if (UBlueprint *BP = Cast<UBlueprint>(Obj))
	{
		Blueprint.Reset(BP);
		return true;
	}
	if (UEdGraph *G = Cast<UEdGraph>(Obj))
	{
		Graph.Reset(G);
		return true;
	}
	if (UK2Node_CustomEvent *E = Cast<UK2Node_CustomEvent>(Obj))
	{
		CustomEvent.Reset(E);
		return true;
	}
	Errors.Printf(TEXT(
		"ERROR: The variable editor can only edit blueprints, "
		"graphs, and custom event nodes. Passed in: %s"),
		*WingUtils::FormatName(Obj->GetClass()));
	return false;
}

bool WingVariables::ErrorNoBackingStore(WingOut Errors)
{
	Errors.Printf(TEXT(
		"ERROR: The variable editor was not successfully "
		"set up with an object to edit."));
	return false;
}
