#pragma once

#include "CoreMinimal.h"
#include "WingServer.h"
#include "WingBasics.h"
#include "WingFetcher.h"
#include "WingUtils.h"
#include "Engine/Blueprint.h"
#include "Animation/AnimBlueprint.h"
#include "Animation/Skeleton.h"
#include "WingComponent.h"
#include "AnimationGraph.h"
#include "AnimationGraphSchema.h"
#include "AnimationStateMachineSchema.h"
#include "WingVariables.h"
#include "WingWidgets.h"
#include "WidgetBlueprint.h"
#include "Blueprint/WidgetTree.h"
#include "Blueprint_Dump.generated.h"


// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------

UCLASS()
class UWing_Blueprint_Dump : public UWingHandler
{
	GENERATED_BODY()

public:
	UPROPERTY(EditAnywhere, meta=(Description="Blueprint path"))
	FString BlueprintPath;

	virtual void Register() override
	{
		UWingServer::AddHandler(this,
			TEXT("Dump a Blueprint's structure: variables, interfaces, components, "
			"and graph names. Does not include graph contents (use Graph_Dump for that)."));
	}
	virtual void Handle() override
	{
		WingFetcher F(WingOut::Stdout);
		UBlueprint* BP = F.Walk(BlueprintPath).Cast<UBlueprint>();
		if (!BP) return;

		// Header
		WingOut::Stdout.Printf(TEXT("Blueprint: %s\n"), *WingUtils::FormatName(BP));
		WingOut::Stdout.Printf(TEXT("Parent: %s\n"), BP->ParentClass ? *WingUtils::FormatName(BP->ParentClass) : TEXT("None"));
		WingOut::Stdout.Printf(TEXT("Type: %s\n"), *WingUtils::EnumToString(BP->BlueprintType));

		// Animation Blueprint
		if (UAnimBlueprint* AnimBP = Cast<UAnimBlueprint>(BP))
		{
			if (AnimBP->TargetSkeleton)
				WingOut::Stdout.Printf(TEXT("TargetSkeleton: %s\n"), *AnimBP->TargetSkeleton->GetPathName());
		}

		// Interfaces
		for (const FBPInterfaceDescription& I : BP->ImplementedInterfaces)
		{
			if (I.Interface) WingOut::Stdout.Printf(TEXT("Interface: %s\n"), *WingUtils::FormatName(I));
		}

		// Variables
		WingVariables BlueprintVars;
		BlueprintVars.SetBackingStore(BP, WingOut::Stdout);
		BlueprintVars.Load(WingOut::Stdout);
		BlueprintVars.Print(WingOut::StdoutBuffer);

		// Components
		TArray<TStrongObjectPtr<UWingComponentRef>> Components = UWingComponent::GetAll(BP);
		if (!Components.IsEmpty()) WingOut::Stdout.Print(TEXT("\nComponents:\n"));
		UWingComponent::PrintAll(BP, WingOut::Stdout);

		// Widget Tree
		if (UWidgetBlueprint* WidgetBP = Cast<UWidgetBlueprint>(BP))
		{
			WingOut::Stdout.Print(TEXT("\nWidget Tree:\n"));
			WingWidgets::PrintWidgetTree(WidgetBP->WidgetTree->RootWidget, 1);
		}

		// Event Dispatchers
		if (!BP->DelegateSignatureGraphs.IsEmpty())
		{
			WingOut::Stdout.Print(TEXT("\nEvent Dispatchers:\n"));
			for (UEdGraph* Graph : BP->DelegateSignatureGraphs)
				PrintEventDispatcher(Graph);
		}

		// Graphs
		TSet<UEdGraph*> Printed;

		WingOut::Stdout.Print(TEXT("\nGraphs:\n"));
		for (UEdGraph* Graph : BP->UbergraphPages)
		{
			WingOut::Stdout.Printf(TEXT("  EventGraph %s\n"), *WingUtils::FormatName(Graph));
			Printed.Add(Graph);
		}
		for (UEdGraph* Graph : BP->FunctionGraphs)
		{
			if (Graph->IsA<UAnimationGraph>()) continue;
			PrintGraph(Graph, TEXT("Function"));
			Printed.Add(Graph);
		}
		for (UEdGraph* Graph : BP->MacroGraphs)
		{
			PrintGraph(Graph, TEXT("Macro"));
			Printed.Add(Graph);
		}
		for (const FBPInterfaceDescription& I : BP->ImplementedInterfaces)
			for (UEdGraph* Graph : I.Graphs)
			{
				PrintGraph(Graph, TEXT("Interface"), I.Interface);
				Printed.Add(Graph);
			}

		// Delegate signature graphs are not real graphs, but mark them as printed.
		for (UEdGraph* Graph : BP->DelegateSignatureGraphs)
			Printed.Add(Graph);

		// Animation graphs and state machines.
		TArray<UEdGraph*> AllGraphs = WingUtils::AllGraphs(BP);

		FString AnimGraphNames;
		for (UEdGraph* Graph : AllGraphs)
		{
			if (Printed.Contains(Graph)) continue;
			if (!Graph->Schema || !Graph->Schema->IsChildOf(UAnimationGraphSchema::StaticClass())) continue;
			if (!AnimGraphNames.IsEmpty()) AnimGraphNames += TEXT(", ");
			AnimGraphNames += WingUtils::FormatName(Graph);
			Printed.Add(Graph);
		}
		if (!AnimGraphNames.IsEmpty())
			WingOut::Stdout.Printf(TEXT("\nAnimation Graphs: %s\n"), *AnimGraphNames);

		FString StateMachineNames;
		for (UEdGraph* Graph : AllGraphs)
		{
			if (Printed.Contains(Graph)) continue;
			if (!Graph->Schema || !Graph->Schema->IsChildOf(UAnimationStateMachineSchema::StaticClass())) continue;
			if (!StateMachineNames.IsEmpty()) StateMachineNames += TEXT(", ");
			StateMachineNames += WingUtils::FormatName(Graph);
			Printed.Add(Graph);
		}
		if (!StateMachineNames.IsEmpty())
			WingOut::Stdout.Printf(TEXT("\nAnimation State Machines: %s\n"), *StateMachineNames);

		// Catch any graphs we missed.
		for (UEdGraph* Graph : AllGraphs)
		{
			if (Printed.Contains(Graph)) continue;
			WingOut::Stdout.Printf(TEXT("WARNING: unlisted graph: %s (%s)\n"),
				*WingUtils::FormatName(Graph),
				*WingUtils::FormatName(Graph->GetSchema()->GetClass()));
		}
	}

private:
	void PrintEventDispatcher(UEdGraph* Graph)
	{
		WingVariables Vars;
		Vars.SetBackingStore(Graph, WingOut::Stdout);
		Vars.Load(WingOut::Stdout);

		WingOut::Stdout.Printf(TEXT("  %s("), *WingUtils::FormatName(Graph));
		Vars.InputVariables.PrintCompact(WingOut::Stdout);
		WingOut::Stdout.Printf(TEXT(")\n"));
	}

	void PrintGraph(UEdGraph* Graph, const TCHAR* Type, UClass* Interface = nullptr)
	{
		WingVariables Vars;
		Vars.SetBackingStore(Graph, WingOut::Stdout);
		Vars.Load(WingOut::Stdout);

		FStringBuilderBase &Out = WingOut::StdoutBuffer;
		Out.Appendf(TEXT("  %s %s"), Type, *WingUtils::FormatName(Graph));
		Out.AppendChar('(');
		Vars.InputVariables.PrintCompact(Out);
		Out.Append(TEXT(") -> ("));
		Vars.OutputVariables.PrintCompact(Out);
		Out.AppendChar(')');
		if (Interface)
			Out.Appendf(TEXT("  [%s]"), *WingUtils::FormatName(Interface));
		Out.AppendChar('\n');
	}

};
