#include "WingVariables.h"
#include "WingServer.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_Tunnel.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(FStringBuilderBase &Out)
{
	if (Variables.IsEmpty()) return;
	Out.Append(ListName);
	Out.AppendChar(':');
	Out.AppendChar('\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.Appendf(TEXT("    %s %s"), *TypeStr, *NameStr);
		if (!FlagsStr.IsEmpty())
			Out.Appendf(TEXT(" (%s)"), *FlagsStr);
		if (!V.DefaultValue.IsEmpty())
			Out.Appendf(TEXT(" = %s"), *V.DefaultValue);
		Out.Append(TEXT("\n"));
	}
}

void WingVariableList::PrintCompact(FStringBuilderBase &Out)
{
	bool First = true;
	for (const Var& V : Variables)
	{
		if (!First) Out << TEXT(",");
		First = false;
		Out << UWingTypes::TypeToText(V.Type) << TEXT(" ") << 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)
{
	if ((!Allow) && (!Variables.IsEmpty()))
	{
		UWingServer::Printf(TEXT("In this context, %s must be empty."), ListName);
		return false;
	}
	for (const Var &Variable : Variables)
	{
		FString VarName = WingUtils::ExternalizeID(Variable.Name);
		FString TypeText = UWingTypes::TypeToText(Variable.Type);
		if (TypeText.IsEmpty())
		{
			UWingServer::Printf(TEXT("Type of variable %s is not valid for unknown reasons\n"), *VarName);
			return false;
		}
		if (!UWingTypes::IsBlueprintType(Variable.Type))
		{
			UWingServer::Printf(TEXT("Type is not a valid BlueprintType: %s %s\n"), *TypeText, *VarName);
			return false;
		}
		for (FName Flag : Variable.Flags)
		{
			if (!GoodFlags.Contains(Flag))
			{
				UWingServer::Printf(TEXT("Flag %s is not valid here.  Valid flags are:"), *Flag.ToString());
				for (FName Rel : GoodFlags) UWingServer::Printf(TEXT("  %s\n"), *Rel.ToString());
				return false;
			}
		}
	}
	return true;
}

bool WingVariableList::ParseString(const FString &Input)
{
	Variables.Empty();

	TArray<FString> Lines;
	Input.ParseIntoArrayLines(Lines);

	for (const FString& Line : Lines)
	{
		WingTokenizer Tok(Line);
		if (Tok.NextType() == 0) continue;
		Var V;
		V.DefaultSpecified = false;
		if (!ParseOneVariable(Tok, V)) return false;
		Variables.Add(MoveTemp(V));
	}

	return true;
}

bool WingVariableList::ParseNamesString(const FString &Input)
{
	Variables.Empty();
	WingTokenizer Tok(Input);
	while (Tok.TokenIs(Tok.Identifier))
	{
		FName Name = Tok.NextName();
		Var V;
		V.Name = Name;
		Variables.Add(V);
		V.DefaultSpecified = false;
		Tok.Advance();
		if (Tok.TokenIs(',')) Tok.Advance();
	}
	if (!Tok.TokenIs(0))
	{
		Tok.SaveCursor(NAME_None);
		UWingServer::Printf(TEXT("Unexpected token %s in variable list"),
			*FString(Tok.GetRange(NAME_None, 1)));
		return false;
	}
	return true;
}

bool WingVariableList::ParseOneVariable(WingTokenizer &Tok, Var &V)
{
	// Parse type.
	UWingTypes::Requirements Req;
	Req.BlueprintType = true;
	Req.Blueprintable = false;
	Req.AllowContainer = true;
	if (!UWingTypes::TextToType(Tok, V.Type, Req, false))
		return false;

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

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

	// Parse optional default value: = rest-of-line
	if (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);
		UWingServer::Printf(TEXT("ERROR: Unexpected token after variable declaration: '%s'\n"),
			*FString(Tok.GetRange(NAME_None, 1)));
		return false;
	}

	return true;
}

bool WingVariableList::ParseVariableFlags(WingTokenizer &Tok, TSet<FName> &Out)
{
	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);
		UWingServer::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;
}

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

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

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

void WingVariables::Load()
{
	Empty();
	if (Blueprint != nullptr) return LoadBlueprint();
	if (Graph != nullptr) return LoadGraph();
	if (CustomEvent != nullptr) return LoadCustomEvent();
	ErrorNoBackingStore();
}

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

	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, 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(Prop, CDO).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, InputVariables);
}

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

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

bool WingVariables::CheckGraph()
{
	EGraphType T = Graph->GetSchema()->GetGraphType(Graph);
	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))
	{
		UWingServer::Printf(TEXT("This graph type has no variables."));
		return false;
	}

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

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

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

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

bool WingVariables::LinkBlueprintVariables()
{
	ClearLinks();
	for (Var &V : BlueprintVariables.Variables)
	{
		V.BPVar = WingUtils::FindOneWithInternalID(
			V.Name, Blueprint->NewVariables, TEXT("non-inherited variable"));
		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()
{
	bool AnySpecified = false;
	for (Var &Input : BlueprintVariables.Variables)
		if (Input.DefaultSpecified) AnySpecified = true;
	if (!AnySpecified) return true;

	FKismetEditorUtilities::CompileBlueprint(Blueprint);
	UObject *CDO = WingUtils::GetGeneratedCDO(Blueprint);
	if (!CDO)
	{
		UWingServer::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)
			{
				UWingServer::Printf(TEXT("Variable exists in blueprint, but not the generated class, which is weird: %s."),
					*WingTokenizer::ExternalizeID(Input.Name));
				return false;
			}
			if (!FWingProperty(Prop, CDO).SetText(Input.DefaultValue)) return false;
		}
	}
	return true;
}

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

	UK2Node_EditablePinBase *InputNode, *OutputNode;
	UK2Node_FunctionEntry *LocalNode;
	if (!GetGraphNodes(InputNode, OutputNode, LocalNode)) return false;

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

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

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

bool WingVariables::ModifyEditablePinBase(WingVariableList &List, UK2Node_EditablePinBase *Node)
{
	for (Var &V : List.Variables)
	{
		TSharedPtr<FUserPinInfo> *Found =
			WingUtils::FindOneWithInternalID(V.Name, Node->UserDefinedPins, List.ListName);
		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)
{
	// 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, Inputs, Outputs);
	if (!Inputs.IsValid())
	{
		UWingServer::Printf(TEXT("ERROR: no function entry node for graph."));
		return false;
	} 
	if (!Outputs.IsValid() && (!OutputVariables.Variables.IsEmpty()))
	{
		Outputs = FBlueprintEditorUtils::FindOrCreateFunctionResultNode(Inputs.Get());
		if (!Outputs.IsValid())
		{
			UWingServer::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()))
	{
		UWingServer::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()
{
	if (Blueprint) return CreateBlueprint();
	if (Graph) return CreateGraph();
	if (CustomEvent) return CreateCustomEvent();
	return ErrorNoBackingStore();
}

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

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

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

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

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

	UK2Node_EditablePinBase *InputNode, *OutputNode;
	UK2Node_FunctionEntry *LocalNode;
	if (!GetGraphNodes(InputNode, OutputNode, LocalNode)) 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)) return false;
	if (OutputNode && !WingUtils::FindNoDuplicateNames(
			Names, OutputNode->UserDefinedPins, ctx)) return false;
	if (LocalNode && !WingUtils::FindNoDuplicateNames(
			Names, LocalNode->LocalVariables, ctx)) return false;
	if (!WingUtils::FindNoDuplicateNames(
			Names, InputVariables.Variables, ctx)) return false;
	if (!WingUtils::FindNoDuplicateNames(
			Names, OutputVariables.Variables, ctx)) return false;
	if (!WingUtils::FindNoDuplicateNames(
			Names, LocalVariables.Variables, ctx)) 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);
	for (const Var& V : LocalVariables.Variables)
	{
		if (!FBlueprintEditorUtils::AddLocalVariable(BP, Graph, V.Name, V.Type, V.DefaultValue))
		{
			UWingServer::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()
{
	if (!CheckCustomEvent()) return false;
	ClearLinks();

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

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

	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()
{
	if (Blueprint) return RemoveBlueprint();
	if (Graph) return RemoveGraph();
	if (CustomEvent) return RemoveCustomEvent();
	return ErrorNoBackingStore();
}

bool WingVariables::RemoveBlueprint()
{
	// 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"));
		if (!Found) return false;
		Names.Add(V.Name);
	}

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

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

	// Verify that all named variables exist before removing anything.
	for (const Var& V : InputVariables.Variables)
	{
		TSharedPtr<FUserPinInfo>* Found =
			WingUtils::FindOneWithInternalID(V.Name, InputNode->UserDefinedPins, TEXT("input variable"));
		if (!Found) return false;
	}
	for (const Var& V : OutputVariables.Variables)
	{
		TSharedPtr<FUserPinInfo>* Found =
			WingUtils::FindOneWithInternalID(V.Name, OutputNode->UserDefinedPins, TEXT("output variable"));
		if (!Found) return false;
	}
	for (const Var& V : LocalVariables.Variables)
	{
		FBPVariableDescription* Found =
			WingUtils::FindOneWithInternalID(V.Name, LocalNode->LocalVariables, TEXT("local variable"));
		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);
	for (const Var& V : LocalVariables.Variables)
	{
		LocalNode->LocalVariables.RemoveAll(
			[&](const FBPVariableDescription& Desc) { return Desc.VarName == V.Name; });
		FBlueprintEditorUtils::RemoveVariableNodes(BP, V.Name, true, Graph);
	}

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

bool WingVariables::RemoveCustomEvent()
{
	// 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"));
		if (!Found) return false;
	}

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

	CustomEvent->ReconstructNode();
	return true;
}

bool WingVariables::SetBackingStore(UObject *Obj)
{
	Blueprint = nullptr;
	Graph = nullptr;
	CustomEvent = nullptr;
	if (UBlueprint *BP = Cast<UBlueprint>(Obj))
	{
		Blueprint = BP;
		return true;
	}
	if (UEdGraph *G = Cast<UEdGraph>(Obj))
	{
		Graph = G;
		return true;
	}
	if (UK2Node_CustomEvent *E = Cast<UK2Node_CustomEvent>(Obj))
	{
		CustomEvent = E;
		return true;
	}
	UWingServer::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()
{
	UWingServer::Printf(TEXT(
		"ERROR: The variable editor was not successfully "
		"set up with an object to edit."));
	return false;
}
