#include "WingFetcher.h"
#include "WingServer.h"
#include "WingBasics.h"
#include "WingProperty.h"
#include "WingUtils.h"
#include "WingComponent.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 "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;
	if (Step.Equals(TEXT("structprop"),    ESearchCase::IgnoreCase)) return &WingFetcher::StructProp;
	return nullptr;
}

void WingFetcher::SetObj(UObject* InObj)
{
	if (!bSkipNotify)
		UWingServer::AddTouchedObject(InObj);
	Obj.Reset(InObj);
}

WingFetcher& WingFetcher::SetError()
{
	bError = true;
	Obj.Reset();
	OriginalAsset.Reset();
	return *this;
}

void WingFetcher::PathFailed(const TCHAR* Expected)
{
	if (Obj)
		Errors.Printf(TEXT("ERROR: Path specifies a %s, but expected %s\n"), *Obj.Get()->GetClass()->GetName(), Expected);
	else
		Errors.Printf(TEXT("ERROR: Path led to a null pointer\n"));
	UWingServer::SuggestManual(GET_FUNCTION_NAME_CHECKED(UWingManualSections, FetcherPaths));
	SetError();
}

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

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

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

	for (int32 i = 0; i < Segments.Num(); i++)
	{
		if (!Obj)
		{
			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)
		{
			Errors.Printf(TEXT("ERROR: Unknown path step '%s'\n"), *Key);
			UWingServer::SuggestManual(GET_FUNCTION_NAME_CHECKED(UWingManualSections, FetcherPaths));
			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("/")))
	{
		Errors.Printf(TEXT("ERROR: Path must start with '/', got '%s'\n"), *PackagePath);
		UWingServer::SuggestManual(GET_FUNCTION_NAME_CHECKED(UWingManualSections, FetcherPaths));
		return SetError();
	}

	UPackage* Package = LoadPackage(nullptr, *PackagePath, LOAD_NoWarn);
	if (!Package)
	{
		Errors.Printf(TEXT("ERROR: Asset '%s' does not exist.\n"), *PackagePath);
		return SetError();
	}
	SetObj(Package->FindAssetInPackage());
	if (!Obj)
	{
		Errors.Printf(TEXT("ERROR: Could not find asset in package '%s'\n"), *PackagePath);
		return SetError();
	}

	OriginalAsset.Reset(Obj.Get());

	// If this is a material, open the editor and get the transient copy.
	// If it's not a material, try to open the editor, but it's okay if we can't.
	if (UMaterial* Mat = ::Cast<UMaterial>(Obj.Get()))
	{
		IAssetEditorInstance* Editor = WingUtils::CheckOpenEditorForAsset(Obj.Get(), Errors);
		if (!Editor) return SetError();
		IMaterialEditor *MatEditor = static_cast<IMaterialEditor*>(Editor);
		SetObj(MatEditor->GetMaterialInterface()->GetBaseMaterial());
	}
	else
	{
		WingUtils::CheckOpenEditorForAsset(OriginalAsset.Get(), nullptr);
	}

	return *this;
}

bool WingFetcher::CheckAssetIsA(UClass* StaticClass)
{
	if (bError) return false;
	if (!OriginalAsset || !OriginalAsset.Get()->IsA(StaticClass))
	{
		Errors.Printf(TEXT("ERROR: Asset is %s, expected %s\n"),
			OriginalAsset ? *OriginalAsset.Get()->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.Get()))
	{
		if (!Value.IsEmpty())
		{
			Errors.Printf(TEXT("ERROR: Materials have only one graph, with a blank name.\n\n"));
			UWingServer::SuggestManual(GET_FUNCTION_NAME_CHECKED(UWingManualSections, FetcherPaths));
			return SetError();
		}
		WingUtils::EnsureMaterialGraph(Mat);
		if (!Mat->MaterialGraph)
		{
			Errors.Printf(TEXT("ERROR: Material '%s' has no material graph\n"), *Mat->GetName());
			return SetError();
		}
		SetObj(Mat->MaterialGraph);
		return *this;
	}

	UBlueprint* BP = ::Cast<UBlueprint>(Obj.Get());
	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"), Errors);
	if (!Found)
	{
		Errors.Printf(TEXT("Graphs that exist in blueprint:\n"));
		for (const UEdGraph *G : Graphs)
		{
			Errors.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.Get());
	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"), Errors);
	if (Node == nullptr)
	{
		Errors.Printf(TEXT("Nodes that exist in graph:\n"));
		for (const UEdGraphNode *N : AllNodes)
		{
			Errors.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.Get());
	if (!N)
	{
		TypeMismatch(TEXT("pin"), TEXT("node"));
		return SetError();
	}
	UEdGraphPin *Found = WingUtils::FindOneWithExternalID(Value, N->Pins, TEXT("pin"), Errors);
	if (!Found)
	{
		Errors.Printf(TEXT("Pins that exist in the node:\n"));
		for (const UEdGraphPin *P : N->Pins)
		{
			Errors.Printf(TEXT("  %s\n"), *WingUtils::FormatName(P));
		}
		return SetError();
	}
	UWingGraphPinRef* Ref = NewObject<UWingGraphPinRef>();
	Ref->Node = N;
	Ref->PinName = Found->GetFName();
	SetObj(Ref);
	return *this;
}

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

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

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

	SetObj(Found.Get());
	return *this;
}

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

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

	TArray<UWidget*> AllWidgets;
	WidgetBP->WidgetTree->GetAllWidgets(AllWidgets);
	UWidget* Found = WingUtils::FindOneWithExternalID(Value, AllWidgets, TEXT("widget"), Errors);
	if (!Found)
	{
		Errors.Printf(TEXT("Widgets that exist in the blueprint:\n"));
		for (const UWidget *W : AllWidgets)
		{
			Errors.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.Get());
	if (!World)
	{
		TypeMismatch(TEXT("levelblueprint"), TEXT("world"));
		return SetError();
	}

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

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

	SetObj(LevelBP);
	return *this;
}

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

	FName InternalID = WingUtils::CheckInternalizeID(Value, Errors);
	if (InternalID.IsNone()) return SetError();

	if (!Obj)
	{
		TypeMismatch(TEXT("structprop"), TEXT("UObject"));
		return SetError();
	}

	TArray<FWingProperty> Details =
		FWingProperty::GetDetails(Obj.Get(), true);

	FWingProperty *WProp = WingUtils::FindOneWithInternalID(
		InternalID, Details, TEXT("Property"), WingOut::Stdout);
	if (!WProp) return SetError();

	if (FStructProperty *FSProp = CastField<FStructProperty>(WProp->Prop))
	{
		UWingStructRef* Ptr = NewObject<UWingStructRef>();
		Ptr->Object = WProp->Object.Get();
		Ptr->StructType = FSProp->Struct;
		Ptr->StructBase = FSProp->ContainerPtrToValuePtr<void>(WProp->Container);
		Ptr->Editable = WProp->Editable;
		SetObj(Ptr);
		return *this;
	}
	else
	{
		Errors.Printf(TEXT("Property %s is not a struct property.\n"),
			*WProp->Prop->GetName());
		return SetError();
	}
}
