#pragma once

#include "CoreMinimal.h"
#include "UObject/StrongObjectPtr.h"
#include "WingBasics.h"

// WingFetcher: Load an Asset and find an object within it.
// To find an object, you use a path.  This is typical:
//
//  F.Walk(TEXT("/Game/Mat/M_Test,graph,node:Param_1"))
//
// A path always starts from an asset name.  The path above
// starts at a material asset, then it walks to the material
// graph, then from there to a specific graph node.
//
// Instead of specifying the path as a string, you can also
// specify it using a sequence of procedural steps, like
// this:
//
//   F.Asset(TEXT("/Game/Materials/M_Test"));
//   F.Graph();
//   F.Node(TEXT("Param_1"));
//
// When you're finally at the object you want, you usually
// use the Cast method to get a pointer to the object.
//
// If any step fails, the WingFetcher will print an error
// message that can be seen by the MCP's caller.  It will
// also set an error flag.  Once the error flag is set, all
// further ops become no-ops.  After that point, fetching
// any data will return nullptr.
//

class WingFetcher
{
public:
	// Walk a path from an asset to an object
	// within that asset.  If you call walk a
	// second time, it will walk additional steps.
	//
	WingFetcher& Walk(const FString& Path);

	// Walk a path using individual path
	// steps instead of a path.  All these steps generate
	// errors if they cannot find the desired element.
	//
	WingFetcher& Asset(const FString& PackagePath);
	WingFetcher& Graph(const FString& Value);
	WingFetcher& Node(const FString& Value);
	WingFetcher& Pin(const FString& Value);
	WingFetcher& Component(const FString& Value);
	WingFetcher& Widget(const FString& Value);
	WingFetcher& LevelBlueprint(const FString& Value);
	WingFetcher& StructProp(const FString& Value);

	// Return true if there haven't been any errors.
	// Note that errors always automatically generate
	// output to WingServer::Printf.
	//
	bool Ok() const { return !bError; }

	// Try to fetch the current object as a UObject of
	// the specified type.  If it isn't one, generates an
	// error and returns nullptr.
	//
	template<class T> T *Cast()
	{
		if (bError) return nullptr;
		T* Result = ::Cast<T>(Obj.Get());
		if (Result == nullptr) PathFailed(*T::StaticClass()->GetName());
		return Result;
	}

	// Get the current object as a UObject if it is one,
	// otherwise nullptr.  Does not generate errors.
	//
	UObject* GetObj() const { return Obj.Get(); }

	// Get the asset from where it all began: the first
	// step in the walk path.  If the asset couldn't be
	// loaded, returns nullptr.  Does not generate errors.
	//
	UObject* GetAsset() const { return OriginalAsset.Get(); }

	// Get the asset from where it all began: the first
	// step in the walk path, as a specified type.  Errors
	// if it cannot cast to the specified type.
	//
	template<class T> T* CastAsset()
	{
		if (!CheckAssetIsA(T::StaticClass())) return nullptr;
		return ::Cast<T>(OriginalAsset.Get());
	}

	// Calling SkipNotify disables change notifications
	// for objects visited by this fetcher.  Useful for
	// read-only operations that don't modify anything.
	//
	WingFetcher& SkipNotify() { bSkipNotify = true; return *this; }

	// Initialize empty.  You need to call Asset, or walk
	// a path that starts with an asset.
	//
	WingFetcher(WingOut InErrors) : Errors(InErrors) {}

	// Initialize with an object.  From there, you can walk
	// to sub-objects.
	//
	WingFetcher(UObject* O, WingOut InErrors) : Obj(O), Errors(InErrors) {}


private:

	// The Current Object.  Only one of these can be non-null.
	TStrongObjectPtr<UObject> Obj;

	// The Starting Asset.
	TStrongObjectPtr<UObject> OriginalAsset;

	// True if an error has occurred.
	bool bError = false;
	bool bSkipNotify = false;

	// Error reporting destination.
	WingOut Errors;

	// Internal methods.
	using WalkFunc = WingFetcher& (WingFetcher::*)(const FString&);
	void SetObj(UObject* InObj);
	WingFetcher& SetError();
	void PathFailed(const TCHAR *Kind);
	WingFetcher& TypeMismatch(const TCHAR* Walker, const TCHAR* Expected);
	bool CheckAssetIsA(UClass* StaticClass);
	WalkFunc GetWalker(const FString &Step);
};
