#include "WingGraphExport.h"
#include "WingProperty.h"
#include "WingTypes.h"
#include "WingUtils.h"
#include "EdGraph/EdGraph.h"
#include "EdGraph/EdGraphNode.h"
#include "EdGraph/EdGraphPin.h"
#include "EdGraphSchema_K2.h"
#include "EdGraphNode_Comment.h"
#include "K2Node_CallFunction.h"
#include "WingVariables.h"
#include "MaterialGraph/MaterialGraphNode.h"

WingGraphExport::WingGraphExport(UEdGraph* InGraph, bool Locals, bool Details)
	: Graph(InGraph), ShowLocals(Locals), ShowDetails(Details)
{
	SortNodes();
	EmitLocalVariables();
	EmitGraph();
	EmitComments();
	EmitDetailsSuggestion();
}

WingGraphExport::WingGraphExport(UEdGraphNode* InNode, bool Locals, bool Details)
	: Graph(InNode->GetGraph()), ShowLocals(Locals), ShowDetails(Details)
{
	SortedNodes.Add(InNode);
	Visited.Add(InNode);
	EmitLocalVariables();
	EmitGraph();
	EmitComments();
	EmitDetailsSuggestion();
}

////////////////////////////////////////////////////////
//
// General utilities for manipulating UEdGraph nodes.
//
////////////////////////////////////////////////////////

bool WingGraphExport::IsLinked(UEdGraphPin* Pin)
{
	if (Pin == nullptr) return false;
	if (Pin->LinkedTo.IsEmpty()) return false;
	return true;
}

UEdGraphPin* WingGraphExport::GetLinkedTo(UEdGraphPin* Pin)
{
	if (Pin == nullptr) return nullptr;
	if (Pin->LinkedTo.IsEmpty()) return nullptr;
	return Pin->LinkedTo[0];
}

bool WingGraphExport::IsDefaultToSelf(UEdGraphPin* Pin)
{
	// Only valid call-function nodes can have default-to-self.
	UK2Node_CallFunction* CallNode = Cast<UK2Node_CallFunction>(Pin->GetOwningNode());
	if (!CallNode) return false;
	UFunction* Function = CallNode->GetTargetFunction();
	if (!Function) return false;

	// In a call function node, the pin name 'self' is reserved
	// for the C++ 'this' pointer.  This always has 'DefaultToSelf'
	// behavior.  Note that 'self' in other nodes is not special.
	if (Pin->PinName == UEdGraphSchema_K2::PN_Self) return true;

	// For any other pin name, we have to check for
	// the presence of meta = (DefaultToSelf = "Pin")
	const FString& DefaultToSelfPinName = Function->GetMetaData(FBlueprintMetadata::MD_DefaultToSelf);
	return Pin->PinName.ToString() == DefaultToSelfPinName;
}

TArray<UEdGraphPin*> WingGraphExport::GetPins(
	UEdGraphNode* Node, EEdGraphPinDirection Direction, PinKind Kind)
{
	TArray<UEdGraphPin*> Result;
	for (UEdGraphPin* Pin : Node->Pins)
	{
		if (Pin->bHidden) continue;
		if (Pin->Direction != Direction) continue;
		if ((Kind == PK_VALUE) && (Pin->PinType.PinCategory == UEdGraphSchema_K2::PC_Exec)) continue;
		if ((Kind == PK_EXEC) && (Pin->PinType.PinCategory != UEdGraphSchema_K2::PC_Exec)) continue;
		Result.Add(Pin);
	}
	return Result;
}

bool WingGraphExport::HasExecPin(UEdGraphNode* Node, EEdGraphPinDirection Direction)
{
	return GetPins(Node, Direction, PK_EXEC).Num() > 0;
}


////////////////////////////////////////////////////////
//
// Traversal
//
////////////////////////////////////////////////////////

void WingGraphExport::Traverse(UEdGraphNode* Node)
{
	if (Visited.Contains(Node)) return;
	Visited.Add(Node);

	// First, traverse input nodes
	for (UEdGraphPin* Pin : GetPins(Node, EGPD_Input, PK_BOTH))
		for (UEdGraphPin* LinkedPin : Pin->LinkedTo)
			Traverse(LinkedPin->GetOwningNode());

	// Add this node to the sorted list.
	SortedNodes.Add(Node);

	// Then, traverse exec output nodes only.
	// Data outputs are not followed — data nodes get pulled in
	// through their consumers' input traversal.
	for (UEdGraphPin* Pin : GetPins(Node, EGPD_Output, PK_EXEC))
		for (UEdGraphPin* LinkedPin : Pin->LinkedTo)
			Traverse(LinkedPin->GetOwningNode());
}

void WingGraphExport::SortNodes()
{
	// Find starter nodes: have exec output but no exec input.
	TArray<UEdGraphNode*> Starters;
	for (UEdGraphNode* Node : Graph->Nodes)
	{
		if (HasExecPin(Node, EGPD_Output) && !HasExecPin(Node, EGPD_Input))
		{
			Starters.Add(Node);
		}
	}

	// Sort starters by Y position.
	Starters.Sort([](const UEdGraphNode& A, const UEdGraphNode& B)
	{
		return A.NodePosY < B.NodePosY;
	});

	// Traverse from each starter.
	for (UEdGraphNode* Starter : Starters)
	{
		Traverse(Starter);
	}

	// Traverse all nodes.
	for (UEdGraphNode* Node : Graph->Nodes)
	{
		Traverse(Node);
	}
}

////////////////////////////////////////////////////////
//
// Text Output
//
////////////////////////////////////////////////////////

void WingGraphExport::EmitLinks(UEdGraphPin* Pin)
{
	bool comma = false;
	for (const UEdGraphPin *Other : Pin->LinkedTo)
	{
		if (comma) Output.Append(TEXT(", "));
		comma = true;
		Output.Appendf(TEXT("node:%s,pin:%s"),
			*WingUtils::FormatName(Other->GetOwningNode()), *WingUtils::FormatName(Other));
	}
}

void WingGraphExport::EmitInputPinValue(UEdGraphPin* Pin)
{
	// If connected, show source node and pin
	if (IsLinked(Pin)) return EmitLinks(Pin);

	// String pins: always show in quotes (even if empty).
	FName Category = Pin->PinType.PinCategory;
	if (Category == UEdGraphSchema_K2::PC_String ||
		Category == UEdGraphSchema_K2::PC_Name ||
		Category == UEdGraphSchema_K2::PC_Text)
	{
		Output.Appendf(TEXT("\"%s\""), *Pin->DefaultValue);
		return;
	}

	// If has a non-empty default, show it.
	if (!Pin->DefaultValue.IsEmpty())
	{
		Output.Append(Pin->DefaultValue);
		return;
	}

	if (Pin->DefaultObject)
	{
		Output.Append(Pin->DefaultObject->GetName());
		return;
	}

	if (!Pin->DefaultTextValue.IsEmpty())
	{
		Output.Appendf(TEXT("\"%s\""), *Pin->DefaultTextValue.ToString());
		return;
	}

	Output.Appendf(TEXT("<default>"));
}


void WingGraphExport::EmitNode(UEdGraphNode* Node)
{
	if (Node->IsA<UEdGraphNode_Comment>()) return;

	Output.Appendf(TEXT("node %s: %s\n"), *WingUtils::FormatName(Node), *WingUtils::FormatNodeTitle(Node));
	Output.Appendf(TEXT("    pos %d, %d\n"), Node->NodePosX, Node->NodePosY);

	// Emit node properties.
	TArray<FWingProperty> Primary, Secondary;
	GetProperties(Node, Primary, Secondary);
	for (const FWingProperty &Prop : Primary) Prop.Print(Output);

	// Emit secondary node properties.
	if (ShowDetails || Secondary.Num() < 2)
	{
		for (const FWingProperty &Prop : Secondary) Prop.Print(Output);
	}
	else if (Secondary.Num() > 0)
	{
		Output.Appendf(TEXT("    minor properties: %d\n"), Secondary.Num());
		SuppressedDetails = true;
	}
	
	// Emit the input exec pin(s).  Do not show what they're connected to.
	TArray<UEdGraphPin*> ExecIns = GetPins(Node, EGPD_Input, PK_EXEC);
	for (UEdGraphPin* Pin : ExecIns)
	{
		if (IsLinked(Pin))
		{
			Output.Appendf(TEXT("    exec-in %s from "), *WingUtils::FormatName(Pin));
			EmitLinks(Pin);
			Output.AppendChar('\n');
		}
		else
		{
			Output.Appendf(TEXT("    exec-in %s (unconnected)\n"), *WingUtils::FormatName(Pin));
		}
	}

	// Emit input data pins.
	for (UEdGraphPin* Pin : GetPins(Node, EGPD_Input, PK_VALUE))
	{
		if (Pin->PinType.PinCategory == UEdGraphSchema_K2::PC_Exec) continue;

		Output.Appendf(TEXT("    input-pin %s %s = "),
			*UWingTypes::TypeToText(Pin->PinType),
			*WingUtils::FormatName(Pin));
		EmitInputPinValue(Pin);
		Output.AppendChar('\n');
	}

	// Emit output data pins as a single 'output-pins' line.
	TArray<UEdGraphPin*> OutputPins = GetPins(Node, EGPD_Output, PK_VALUE);
	if (!OutputPins.IsEmpty())
	{
		Output.Append(TEXT("    output-pins "));
		bool comma = false;
		for (UEdGraphPin *Pin : OutputPins)
		{
			if (comma) Output.Append(TEXT(", "));
			comma = true;
			Output.Append(UWingTypes::TypeToText(Pin->PinType));
			Output.AppendChar(' ');
			Output.Append(WingUtils::FormatName(Pin));
		}
		Output.AppendChar('\n');
	}

	// Emit output exec pins.
	TArray<UEdGraphPin*> ExecOuts = GetPins(Node, EGPD_Output, PK_EXEC);
	for (UEdGraphPin* Pin : ExecOuts)
	{
		if (IsLinked(Pin))
		{
			Output.Appendf(TEXT("    exec-out %s to "), *WingUtils::FormatName(Pin));
			EmitLinks(Pin);
			Output.AppendChar('\n');
		}
		else
		{
			Output.Appendf(TEXT("    exec-out %s (unconnected)\n"), *WingUtils::FormatName(Pin));
		}
	}

	Output.Append(TEXT("\n"));
}

void WingGraphExport::GetProperties(UEdGraphNode* Node,
	TArray<FWingProperty> &Primary, TArray<FWingProperty> &Secondary)
{
	Primary = FWingProperty::GetDetails(Node, false);
	Secondary.Empty();

	if (UMaterialGraphNode* MatNode = Cast<UMaterialGraphNode>(Node))
	{
		FString PrimaryCategory = MatNode->MaterialExpression->GetClass()->GetName();
		TArray<FWingProperty> NewPrimary;
		for (const FWingProperty& P : Primary)
		{
			if (P.GetCategory() == PrimaryCategory) NewPrimary.Add(P);
			else Secondary.Add(P);
		}
		Swap(NewPrimary, Primary);
	}
}

void WingGraphExport::EmitLocalVariables()
{
	if (!ShowLocals) return;
	WingVariables Vars;
	Vars.SetBackingStore(Graph, WingOut::Stdout);
	Vars.Load(WingOut::Stdout);
	Vars.Print(Output);
	if (!Vars.IsEmpty()) Output.Append(TEXT("\n"));
}

void WingGraphExport::EmitGraph()
{
	for (UEdGraphNode* Node : SortedNodes)
	{
		EmitNode(Node);
	}
}

void WingGraphExport::EmitComments()
{
	for (UEdGraphNode* CommentNode : SortedNodes)
	{
		if (!CommentNode->IsA<UEdGraphNode_Comment>()) continue;

		int32 CX = CommentNode->NodePosX;
		int32 CY = CommentNode->NodePosY;
		int32 CW = CommentNode->NodeWidth;
		int32 CH = CommentNode->NodeHeight;

		// Emit header.
		Output.Appendf(TEXT("comment %s:\n"), *WingUtils::FormatName(CommentNode));

		// Emit wrapped, indented body.
		Output.Append(WingUtils::WrapText(CommentNode->NodeComment, 70, TEXT("    - ")));

		// Find contained nodes.
		TArray<FString> ContainedNames;
		for (UEdGraphNode* Node : SortedNodes)
		{
			if (Node->IsA<UEdGraphNode_Comment>()) continue;
			int32 NX = Node->NodePosX;
			int32 NY = Node->NodePosY;
			if (NX >= CX && NY >= CY && NX <= CX + CW && NY <= CY + CH)
				ContainedNames.Add(WingUtils::FormatName(Node));
		}

		if (ContainedNames.Num() > 0)
		{
			Output.Appendf(TEXT("    applies to: %s\n"), *FString::Join(ContainedNames, TEXT(", ")));
		}
		Output.Append(TEXT("\n"));
	}
}

void WingGraphExport::EmitDetailsSuggestion()
{
	if (SuppressedDetails)
	{
		Output.Appendf(TEXT("Minor node properties were suppressed, use details=true to show.\n\n"));
	}
}
