#include "WingFetcher.h"
#include "WingServer.h"
#include "WingUtils.h"
#include "WingActorComponent.h"
#include "Engine/Blueprint.h"
#include "EdGraph/EdGraph.h"
#include "EdGraph/EdGraphNode.h"
#include "EdGraph/EdGraphPin.h"
#include "Engine/SimpleConstructionScript.h"
#include "Engine/SCS_Node.h"
#include "Engine/World.h"
#include "Materials/Material.h"
#include "MaterialGraph/MaterialGraph.h"
#include "MaterialGraph/MaterialGraphNode.h"
#include "IMaterialEditor.h"
#include "Engine/LevelScriptBlueprint.h"
#include "WidgetBlueprint.h"
#include "Blueprint/WidgetTree.h"
#include "Components/Widget.h"
#include "Subsystems/AssetEditorSubsystem.h"
#include "WingServer.h"
#include "WingManual.h"

WingFetcher::WalkFunc WingFetcher::GetWalker(const FString& Step)
{
	if (Step.Equals(TEXT("graph"),          ESearchCase::IgnoreCase)) return &WingFetcher::Graph;
	if (Step.Equals(TEXT("node"),           ESearchCase::IgnoreCase)) return &WingFetcher::Node;
	if (Step.Equals(TEXT("pin"),            ESearchCase::IgnoreCase)) return &WingFetcher::Pin;
	if (Step.Equals(TEXT("component"),      ESearchCase::IgnoreCase)) return &WingFetcher::Component;
	if (Step.Equals(TEXT("widget"),         ESearchCase::IgnoreCase)) return &WingFetcher::Widget;
	if (Step.Equals(TEXT("levelblueprint"), ESearchCase::IgnoreCase)) return &WingFetcher::LevelBlueprint;
	return nullptr;
}

void WingFetcher::SetObj(UObject* InObj)
{
	if (!bSkipNotify)
		UWingServer::AddTouchedObject(InObj);
	Obj = InObj;
	ResultPin = nullptr;
}

void WingFetcher::SetPin(UEdGraphPin* InPin)
{
	ResultPin = InPin;
	Obj = nullptr;
}

WingFetcher& WingFetcher::SetError()
{
	bError = true;
	Obj = nullptr;
	ResultPin = nullptr;
	OriginalAsset = nullptr;
	Editor = nullptr;
	return *this;
}

void WingFetcher::PathFailed(const TCHAR* Expected)
{
	if (ResultPin)
		UWingServer::Printf(TEXT("ERROR: Path specifies a pin, but expected %s\n"), Expected);
	else if (Obj)
		UWingServer::Printf(TEXT("ERROR: Path specifies a %s, but expected %s\n"), *Obj->GetClass()->GetName(), Expected);
	else
		UWingServer::Printf(TEXT("ERROR: Path led to a null pointer\n"));
	UWingServer::SuggestManual(WingManual::Section::Paths);
	SetError();
}

WingFetcher& WingFetcher::TypeMismatch(const TCHAR* Walker, const TCHAR* Expected)
{
	if (ResultPin)
		UWingServer::Printf(TEXT("ERROR: Input to '%s' is a pin, but expected %s\n"), Walker, Expected);
	else if (Obj)
		UWingServer::Printf(TEXT("ERROR: Input to '%s' is %s, but expected %s\n"), Walker, *Obj->GetClass()->GetName(), Expected);
	else
		UWingServer::Printf(TEXT("ERROR: Path led to a null pointer\n"));
	UWingServer::SuggestManual(WingManual::Section::Paths);
	SetError();
	return *this;
}

WingFetcher& WingFetcher::Walk(const FString& Path)
{
	if (bError) return *this;

	if (Path.Contains(TEXT(" ")))
	{
		UWingServer::Printf(TEXT("ERROR: Paths may not contain whitespace."));
		UWingServer::SuggestManual(WingManual::Section::Paths);
		UWingServer::SuggestManual(WingManual::Section::EscapeSequences);
		return SetError();
	}
	TArray<FString> Segments;
	Path.ParseIntoArray(Segments, TEXT(","));
	if (Segments.Num() == 0)
	{
		UWingServer::Print(TEXT("ERROR: Empty path\n"));
		UWingServer::SuggestManual(WingManual::Section::Paths);
		return SetError();
	}

	for (int32 i = 0; i < Segments.Num(); i++)
	{
		if (!Obj && !ResultPin)
		{
			Asset(Segments[i]);
			if (bError) return *this;
			continue;
		}

		FString Key, Value;
		if (!Segments[i].Split(TEXT(":"), &Key, &Value))
			Key = Segments[i];

		WalkFunc Func = GetWalker(Key);
		if (!Func)
		{
			UWingServer::Printf(TEXT("ERROR: Unknown path step '%s'\n"), *Key);
			UWingServer::SuggestManual(WingManual::Section::Paths);
			return SetError();
		}
		(this->*Func)(Value);
		if (bError) return *this;
	}

	return *this;
}

WingFetcher& WingFetcher::Asset(const FString& PackagePath)
{
	if (bError) return *this;

	if (!PackagePath.StartsWith(TEXT("/")))
	{
		UWingServer::Printf(TEXT("ERROR: Path must start with '/', got '%s'\n"), *PackagePath);
		UWingServer::SuggestManual(WingManual::Section::Paths);
		return SetError();
	}

	// Check if the package exists before calling LoadObject, because
	// LoadObject logs its own errors when the package doesn't exist.
	FString PackageName = FPackageName::ObjectPathToPackageName(PackagePath);
	if (!FPackageName::DoesPackageExist(PackageName))
	{
		UWingServer::Printf(TEXT("ERROR: Asset '%s' does not exist.\n"), *PackagePath);
		return SetError();
	}
	SetObj(LoadObject<UObject>(nullptr, *PackagePath));
	if (!Obj)
	{
		UWingServer::Printf(TEXT("ERROR: Could not load asset '%s'\n"), *PackagePath);
		return SetError();
	}

	OriginalAsset = Obj;

	// Open the editor for this asset (or bring it to front if already open).
	UAssetEditorSubsystem* Sub = GEditor->GetEditorSubsystem<UAssetEditorSubsystem>();
	if (!Sub || !Sub->OpenEditorForAsset(Obj))
	{
		UWingServer::Printf(TEXT("ERROR: Could not open editor for '%s'\n"), *PackagePath);
		return SetError();
	}
	Editor = Sub->FindEditorForAsset(OriginalAsset, false);
	if (!Editor)
	{
		UWingServer::Printf(TEXT("ERROR: Could not find editor instance for '%s'\n"), *PackagePath);
		return SetError();
	}

	// If this is a material, use the editor's transient copy.
	if (UMaterial* Mat = ::Cast<UMaterial>(Obj))
	{
		IMaterialEditor *MatEditor = static_cast<IMaterialEditor*>(Editor);
		SetObj(MatEditor->GetMaterialInterface()->GetBaseMaterial());
	}
	return *this;
}

bool WingFetcher::CheckAssetIsA(UClass* StaticClass)
{
	if (bError) return false;
	if (!OriginalAsset || !OriginalAsset->IsA(StaticClass))
	{
		UWingServer::Printf(TEXT("ERROR: Asset is %s, expected %s\n"),
			OriginalAsset ? *OriginalAsset->GetClass()->GetName() : TEXT("null"),
			*StaticClass->GetName());
		SetError();
		return false;
	}
	return true;
}

WingFetcher& WingFetcher::Graph(const FString& Value)
{
	if (bError) return *this;

	// Material with blank graph name
	if (UMaterial* Mat = ::Cast<UMaterial>(Obj))
	{
		if (!Value.IsEmpty())
		{
			UWingServer::Printf(TEXT("ERROR: Materials have only one graph, with a blank name.\n\n"));
			UWingServer::SuggestManual(WingManual::Section::Paths);
			return SetError();
		}
		WingUtils::EnsureMaterialGraph(Mat);
		if (!Mat->MaterialGraph)
		{
			UWingServer::Printf(TEXT("ERROR: Material '%s' has no material graph\n"), *Mat->GetName());
			return SetError();
		}
		SetObj(Mat->MaterialGraph);
		return *this;
	}

	UBlueprint* BP = ::Cast<UBlueprint>(Obj);
	if (!BP)
	{
		TypeMismatch(TEXT("graph"), TEXT("Blueprint or Material"));
		return SetError();
	}

	TArray<UEdGraph*> Graphs = WingUtils::AllGraphs(BP);
	UEdGraph* Found = WingUtils::FindOneWithExternalID(Value, Graphs, TEXT("graph"));
	if (!Found)
	{
		UWingServer::Printf(TEXT("Graphs that exist in blueprint:\n"));
		for (const UEdGraph *G : Graphs)
		{
			UWingServer::Printf(TEXT("  %s\n"), *WingUtils::FormatName(G));
		}
		return SetError();
	}

	SetObj(Found);
	return *this;
}

WingFetcher& WingFetcher::Node(const FString& Value)
{
	if (bError) return *this;

	// If current object is not a graph, refuse.
	UEdGraph *Graph = ::Cast<UEdGraph>(Obj);
	if (Graph == nullptr)
	{
		TypeMismatch(TEXT("node"), TEXT("Graph"));
		return SetError();
	}

	// Get the nodes from the graph.
	TArray<UEdGraphNode *> AllNodes = WingUtils::AllNodes(Graph);
	UEdGraphNode *Node = WingUtils::FindOneWithExternalID(Value, AllNodes, TEXT("node"));
	if (Node == nullptr)
	{
		UWingServer::Printf(TEXT("Nodes that exist in graph:\n"));
		for (const UEdGraphNode *N : AllNodes)
		{
			UWingServer::Printf(TEXT("  %s\n"), *WingUtils::FormatName(N));
		}
		return SetError();
	}
	SetObj(Node);
	return *this;
}

WingFetcher& WingFetcher::Pin(const FString& Value)
{
	if (bError) return *this;

	UEdGraphNode* N = ::Cast<UEdGraphNode>(Obj);
	if (!N)
	{
		TypeMismatch(TEXT("pin"), TEXT("node"));
		return SetError();
	}
	UEdGraphPin *Found = WingUtils::FindOneWithExternalID(Value, N->Pins, TEXT("pin"));
	if (!Found)
	{
		UWingServer::Printf(TEXT("Pins that exist in the node:\n"));
		for (const UEdGraphPin *P : N->Pins)
		{
			UWingServer::Printf(TEXT("  %s\n"), *WingUtils::FormatName(P));
		}
		return SetError();
	}
	SetPin(Found);
	return *this;
}

WingFetcher& WingFetcher::Component(const FString& Value)
{
	if (bError) return *this;

	UBlueprint* BP = ::Cast<UBlueprint>(Obj);
	if (!BP)
	{
		TypeMismatch(TEXT("component"), TEXT("Blueprint"));
		return SetError();
	}

	TArray<UWingComponentReference*> AllComponents = UWingComponentReference::GetAll(BP);
	UWingComponentReference* Found = WingUtils::FindOneWithExternalID(Value, AllComponents, TEXT("component"));
	if (!Found)
	{
		UWingServer::Printf(TEXT("Components that exist in the blueprint:\n"));
		for (const UWingComponentReference* C : AllComponents)
		{
			UWingServer::Printf(TEXT("  %s\n"), *WingUtils::FormatName(C));
		}
		return SetError();
	}

	SetObj(Found);
	return *this;
}

WingFetcher& WingFetcher::Widget(const FString& Value)
{
	if (bError) return *this;

	UWidgetBlueprint* WidgetBP = ::Cast<UWidgetBlueprint>(Obj);
	if (!WidgetBP)
	{
		TypeMismatch(TEXT("widget"), TEXT("WidgetBlueprint"));
		return SetError();
	}

	TArray<UWidget*> AllWidgets;
	WidgetBP->WidgetTree->GetAllWidgets(AllWidgets);
	UWidget* Found = WingUtils::FindOneWithExternalID(Value, AllWidgets, TEXT("widget"));
	if (!Found)
	{
		UWingServer::Printf(TEXT("Widgets that exist in the blueprint:\n"));
		for (const UWidget *W : AllWidgets)
		{
			UWingServer::Printf(TEXT("  %s\n"), *WingUtils::FormatName(W));
		}
		return SetError();
	}
	SetObj(Found);
	return *this;
}

WingFetcher& WingFetcher::LevelBlueprint(const FString& Value)
{
	if (bError) return *this;

	UWorld* World = ::Cast<UWorld>(Obj);
	if (!World)
	{
		TypeMismatch(TEXT("levelblueprint"), TEXT("world"));
		return SetError();
	}

	if (!World->PersistentLevel)
	{
		UWingServer::Print(TEXT("ERROR: World has no PersistentLevel\n"));
		return SetError();
	}

	ULevelScriptBlueprint* LevelBP = World->PersistentLevel->GetLevelScriptBlueprint(true);
	if (!LevelBP)
	{
		UWingServer::Print(TEXT("ERROR: World has no level blueprint\n"));
		return SetError();
	}

	SetObj(LevelBP);
	return *this;
}

