#include "WingTypes.h"
#include "WingUtils.h"
#include "WingServer.h"
#include "WingManual.h"
#include "WingTokenizer.h"
#include "Editor.h"
#include "EdGraphSchema_K2.h"
#include "Engine/Blueprint.h"
#include "StructUtils/UserDefinedStruct.h"
#include "Engine/UserDefinedEnum.h"
#include "UObject/UObjectIterator.h"
#include "AssetRegistry/AssetRegistryModule.h"
#include "Blueprint/BlueprintSupport.h"
#include "Kismet2/KismetEditorUtilities.h"
#include "GameFramework/Actor.h"
#include "Components/ActorComponent.h"
#include "Blueprint/UserWidget.h"
#include "GameFramework/Pawn.h"
#include "Engine/DataAsset.h"


static const FName NAME_TypeArray(TEXT("Array"));
static const FName NAME_TypeSet(TEXT("Set"));
static const FName NAME_TypeMap(TEXT("Map"));
static const FName NAME_TypeSoft(TEXT("Soft"));
static const FName NAME_TypeClass(TEXT("Class"));
static const FName NAME_TypeSoftClass(TEXT("SoftClass"));
static const FName NAME_StartOfType(TEXT("Start-of-Type"));
static const FName NAME_NoneMeaningNullptr(TEXT("None"));

// ---------------------------------------------------------------------------
// Simple Accessors
// ---------------------------------------------------------------------------

const TMap<FString, UWingTypes::Info>& UWingTypes::GetAllTypes()
{
	UWingTypes* Types = GEditor->GetEditorSubsystem<UWingTypes>();
	check(Types);
	return Types->ShortToInfo;
}

// ---------------------------------------------------------------------------
// Simple Utility Functions.
// ---------------------------------------------------------------------------

bool UWingTypes::IsBlueprintType(const UObject *Obj)
{
	if (const UEnum* Enum = Cast<UEnum>(Obj))
		return UEdGraphSchema_K2::IsAllowableBlueprintVariableType(Enum);
	if (const UScriptStruct* Struct = Cast<UScriptStruct>(Obj))
		return UEdGraphSchema_K2::IsAllowableBlueprintVariableType(Struct);
	if (const UClass* Class = Cast<UClass>(Obj))
		return UEdGraphSchema_K2::IsAllowableBlueprintVariableType(Class);
	return false;
}

bool UWingTypes::IsBlueprintType(const FEdGraphPinType &Type)
{
	if ((Type.PinCategory == UEdGraphSchema_K2::PC_Delegate) ||
		(Type.PinCategory == UEdGraphSchema_K2::PC_MCDelegate) ||
		(Type.PinValueType.TerminalCategory == UEdGraphSchema_K2::PC_Delegate) ||
		(Type.PinValueType.TerminalCategory == UEdGraphSchema_K2::PC_MCDelegate))
		return false;

	if ((Type.PinSubCategoryObject != nullptr) &&
		!IsBlueprintType(Type.PinSubCategoryObject.Get()))
		return false;

	if ((Type.PinValueType.TerminalSubCategoryObject != nullptr) &&
		!IsBlueprintType(Type.PinValueType.TerminalSubCategoryObject.Get()))
		return false;

	return true;
}

bool UWingTypes::IsBlueprintable(const FEdGraphPinType &Type)
{
	if (Type.IsContainer()) return false;

	if ((Type.PinCategory != UEdGraphSchema_K2::PC_Object) &&
		(Type.PinCategory != UEdGraphSchema_K2::PC_Interface))
		return false;

	UClass *Class = Cast<UClass>(Type.PinSubCategoryObject.Get());
	if (!Class) return false;

	return FKismetEditorUtilities::CanCreateBlueprintOfClass(Class);
}

bool UWingTypes::IsChildOf(const FEdGraphPinType &Type, UClass *Parent)
{
	if (Type.IsContainer()) return false;

	if ((Type.PinCategory != UEdGraphSchema_K2::PC_Object) &&
		(Type.PinCategory != UEdGraphSchema_K2::PC_Interface))
		return false;

	UClass *Class = Cast<UClass>(Type.PinSubCategoryObject.Get());
	if (!Class) return false;

	return Class->IsChildOf(Parent);
}

const UClass *UWingTypes::FindNativeParent(const UClass *Obj)
{
	const UClass* Native = Obj;
	while (Native && Native->ClassGeneratedBy != nullptr)
		Native = Native->GetSuperClass();
	return Native;
}

FName UWingTypes::BroadCategory(const UClass* Class)
{
	// The nullptr case can really only happen if metadata is missing.
	// 
	if (Class == nullptr) return("Unknown");

	// Classify the object type.
	//
	if (Class->IsChildOf(UUserWidget::StaticClass())) return TEXT("Widget");
	if (Class->IsChildOf(UActorComponent::StaticClass())) return TEXT("ActorComponent");
	if (Class->IsChildOf(APawn::StaticClass())) return TEXT("Pawn");
	if (Class->IsChildOf(AActor::StaticClass())) return TEXT("Actor");
	if (Class->IsChildOf(UDataAsset::StaticClass())) return TEXT("DataAsset");
	return TEXT("Object");
}

// ---------------------------------------------------------------------------
// Choose Short Name
// ---------------------------------------------------------------------------

void UWingTypes::ReserveShortName(FName Name, FName PinCategory, FName PinSubCategory)
{
	FString NameStr = Name.ToString();
	Info Dummy;
	Dummy.Short = NameStr;
	Dummy.PinCategory = PinCategory;
	Dummy.PinSubCategory = PinSubCategory;
	Dummy.PinSubCategoryObject.Empty();
	Dummy.BroadCategory = TEXT("Primitive");
	Dummy.IsUserDefined = false;
	ShortToInfo.Add(NameStr.ToLower(), MoveTemp(Dummy));
}

void UWingTypes::ReserveShortName(FName Name)
{
	ReserveShortName(Name, Name, FName());
}

FString UWingTypes::GetShortName(const FString &Path)
{
	FString *Result = PathToShort.Find(Path);
	if (Result) return *Result;
	return FString();
}

FString UWingTypes::NewShortName(const FString &Path, FName PinCategory, FName BroadCategory, bool IsUserDefined)
{
	// Verify that the path is not already associated.
	check(!PathToShort.Find(Path));

	// Derive the name proposal from the path.  Names used by
	// the type system are always simple ids that require no
	// escaping when emitted.
	int32 DotIndex;
	check(Path.FindLastChar('.', DotIndex));
	FString Proposal = Path.Mid(DotIndex + 1);
	if ((PinCategory == UEdGraphSchema_K2::PC_Object || PinCategory == UEdGraphSchema_K2::PC_Interface)
		 && IsUserDefined && Proposal.EndsWith(TEXT("_C")))
		Proposal.LeftChopInline(2);
	Proposal = WingTokenizer::SimplifyID(Proposal);
	if (Proposal.IsEmpty()) Proposal = "Unknown";

	// Construct the Info struct.
	Info TypeInfo;
	TypeInfo.PinCategory = PinCategory;
	TypeInfo.PinSubCategoryObject = Path;
	TypeInfo.BroadCategory = BroadCategory;
	TypeInfo.IsUserDefined = IsUserDefined;

	// Check if the proposed name is available.
	FString Lower = Proposal.ToLower();
	if (!ShortToInfo.Contains(Lower))
	{
		TypeInfo.Short = Proposal;
		PathToShort.Add(Path, Proposal);
		ShortToInfo.Add(Lower, TypeInfo);
		return Proposal;
	}

	// The proposed name is not available.  Add numbers until we
	// find a name that is available.
	for (int32 i = 2; ; ++i)
	{
		FString NumberedLower = FString::Printf(TEXT("%s%d"), *Lower, i);
		if (!ShortToInfo.Contains(NumberedLower))
		{
			FString NumberedProposal = FString::Printf(TEXT("%s_%d"), *Proposal, i);
			TypeInfo.Short = NumberedProposal;
			PathToShort.Add(Path, NumberedProposal);
			ShortToInfo.Add(NumberedLower, TypeInfo);
			return NumberedProposal;
		}
	}
}

FString UWingTypes::ChooseShortName(const UObject* Obj)
{
	// If it's a blueprint, register its generated class instead.
	if (const UBlueprint* BP = Cast<UBlueprint>(Obj))
		Obj = BP->GeneratedClass;

	// Don't register null pointer.
	if (Obj == nullptr) return FString();

	FString Path = Obj->GetPathName();
	FString Existing = GetShortName(Path);
	if (!Existing.IsEmpty()) return Existing;

	if (Cast<UEnum>(Obj))
	{
		return NewShortName(Path, UEdGraphSchema_K2::PC_Enum, TEXT("Enum"),
			Cast<UUserDefinedEnum>(Obj) != nullptr);
	}

	if (Cast<UScriptStruct>(Obj))
	{
		return NewShortName(Path, UEdGraphSchema_K2::PC_Struct, TEXT("Struct"),
			Cast<UUserDefinedStruct>(Obj) != nullptr);
	}

	if (const UClass* Class = Cast<UClass>(Obj))
	{
		bool IsInterface = Class->IsChildOf(UInterface::StaticClass());
		bool IsUserDefined = (Class->ClassGeneratedBy != nullptr);
		FName PinCategory = IsInterface ? UEdGraphSchema_K2::PC_Interface : UEdGraphSchema_K2::PC_Object;
		return NewShortName(Path, PinCategory, BroadCategory(Class), IsUserDefined);
	}

	return FString();
}

void UWingTypes::ChooseShortName(const FAssetData &Data)
{
	FString AssetName = Data.AssetName.ToString();
	FString PackageName = Data.PackageName.ToString();
	FTopLevelAssetPath ClassPath = Data.AssetClassPath;

	// Handle structs.
	if (ClassPath == UUserDefinedStruct::StaticClass()->GetClassPathName())
	{
		FString Path = FString::Printf(TEXT("%s.%s"), *PackageName, *AssetName);
		if (!GetShortName(Path).IsEmpty()) return;
		NewShortName(Path, UEdGraphSchema_K2::PC_Struct, TEXT("Struct"), true);
		return;
	}

	// Handle enums.
	if (ClassPath == UUserDefinedEnum::StaticClass()->GetClassPathName())
	{
		FString Path = FString::Printf(TEXT("%s.%s"), *PackageName, *AssetName);
		if (!GetShortName(Path).IsEmpty()) return;
		NewShortName(Path, UEdGraphSchema_K2::PC_Enum, TEXT("Enum"), true);
		return;
	}
	
	// Handle Blueprints.  Get the generated class path.
	FString Path;
	if (!Data.GetTagValue(FBlueprintTags::GeneratedClassPath, Path)) return;
	Path = FPackageName::ExportTextPathToObjectPath(Path);
	if (!GetShortName(Path).IsEmpty()) return;

	// Get the Native Parent.
	FString NativeParentPath;
	if (!Data.GetTagValue(FBlueprintTags::NativeParentClassPath, NativeParentPath)) return;
	NativeParentPath = FPackageName::ExportTextPathToObjectPath(NativeParentPath);
	UClass *NativeParent = FindObject<UClass>(nullptr, *NativeParentPath);
	if (NativeParent == nullptr) return;
	
	// Get the blueprint type.
	FString BPType;
	if (!Data.GetTagValue(FBlueprintTags::BlueprintType, BPType)) return;

	if (BPType == TEXT("BPTYPE_Interface"))
	{
		check(NativeParent->IsChildOf(UInterface::StaticClass()));
		NewShortName(Path, UEdGraphSchema_K2::PC_Interface, TEXT("Interface"), true);
		return;
	}
	else if (BPType == TEXT("BPTYPE_Normal") || BPType == TEXT("BPTYPE_Const"))
	{
		NewShortName(Path, UEdGraphSchema_K2::PC_Object, BroadCategory(NativeParent), true);
		return;
	}
}

void UWingTypes::ChooseShortNames(UPackage* Package)
{
	if (Package == nullptr) return;
	ForEachObjectWithPackage(Package, [&](UObject* Obj)
	{
		ChooseShortName(Obj);
		return true;
	}, false);
}

// ---------------------------------------------------------------------------
// TypeToText
// ---------------------------------------------------------------------------

FString UWingTypes::TypeToText(FName Category, FName SubCategory, UObject* SubCategoryObject)
{
	if ((Category == UEdGraphSchema_K2::PC_Delegate) ||
		(Category == UEdGraphSchema_K2::PC_MCDelegate) ||
		(Category == UEdGraphSchema_K2::PC_Boolean) ||
		(Category == UEdGraphSchema_K2::PC_Int) ||
		(Category == UEdGraphSchema_K2::PC_Int64) ||
		(Category == UEdGraphSchema_K2::PC_Name) ||
		(Category == UEdGraphSchema_K2::PC_String) ||
		(Category == UEdGraphSchema_K2::PC_Text) ||
		(Category == NAME_NoneMeaningNullptr))
	{
		return Category.ToString();
	}

	if (Category == UEdGraphSchema_K2::PC_Real)
	{
		return SubCategory.ToString();
	}

	if (Category == UEdGraphSchema_K2::PC_Byte)
	{
		if (SubCategoryObject)
			return ChooseShortName(SubCategoryObject);
		return Category.ToString();
	}

	if (Category == UEdGraphSchema_K2::PC_Enum)
	{
		if (SubCategoryObject)
			return ChooseShortName(SubCategoryObject);
		return FString();
	}

	if (SubCategoryObject)
	{
		FString Short = ChooseShortName(SubCategoryObject);
		if (Short.IsEmpty()) return FString();
		
		if (Category == UEdGraphSchema_K2::PC_Struct)
			return Short;
		if (Category == UEdGraphSchema_K2::PC_Object)
			return Short;
		if (Category == UEdGraphSchema_K2::PC_Class)
			return FString::Printf(TEXT("Class<%s>"), *Short);
		if (Category == UEdGraphSchema_K2::PC_SoftObject)
			return FString::Printf(TEXT("Soft<%s>"), *Short);
		if (Category == UEdGraphSchema_K2::PC_SoftClass)
			return FString::Printf(TEXT("SoftClass<%s>"), *Short);
		if (Category == UEdGraphSchema_K2::PC_Interface)
			return Short;
	}

	return FString();
}

FString UWingTypes::TypeToText(const FEdGraphPinType& PinType)
{
	UWingTypes* Types = GEditor->GetEditorSubsystem<UWingTypes>();
	if (!Types) return FString();

	FString Inner = Types->TypeToText(PinType.PinCategory, PinType.PinSubCategory, PinType.PinSubCategoryObject.Get());
	if (Inner.IsEmpty())
		return FString();

	if (PinType.IsArray())
		return FString::Printf(TEXT("Array<%s>"), *Inner);
	if (PinType.IsSet())
		return FString::Printf(TEXT("Set<%s>"), *Inner);
	if (PinType.IsMap())
	{
		FString ValueInner = Types->TypeToText(
			PinType.PinValueType.TerminalCategory,
			PinType.PinValueType.TerminalSubCategory,
			PinType.PinValueType.TerminalSubCategoryObject.Get());
		if (ValueInner.IsEmpty())
			return FString();
		return FString::Printf(TEXT("Map<%s,%s>"), *Inner, *ValueInner);
	}

	return Inner;
}

FString UWingTypes::TypeToText(const FProperty *Property)
{
	FEdGraphPinType PinType;
	if (!GetDefault<UEdGraphSchema_K2>()->ConvertPropertyToPinType(Property, PinType))
	{
		return TEXT("void");
	}
	else
	{
		return TypeToText(PinType);
	}
}

FString UWingTypes::TypeToText(const UObject* Obj)
{
	UWingTypes* Types = GEditor->GetEditorSubsystem<UWingTypes>();
	if (!Types) return FString();
	if (Obj == nullptr) return TEXT("None");
	return Types->ChooseShortName(Obj);
}

FString UWingTypes::TypeToTextOrDie(const UObject* Obj)
{
	UWingTypes* Types = GEditor->GetEditorSubsystem<UWingTypes>();
	if (!Types) return FString();
	if (Obj == nullptr) return TEXT("None");
	FString Result = Types->ChooseShortName(Obj);
	check(!Result.IsEmpty());
	return Result;
}

// ---------------------------------------------------------------------------
// Parser and Resolve Short Name
// ---------------------------------------------------------------------------

void UWingTypes::PrintParseError(WingTokenizer& Tok, const TCHAR* Message, WingOut Errors)
{
	FString TypeText(Tok.GetRange(NAME_StartOfType, 1));
	Errors.Printf(TEXT("ERROR parsing type '%s' — %s\n"), *TypeText, Message);
	UWingServer::SuggestManual(GET_FUNCTION_NAME_CHECKED(UWingManualSections, ExpressingTypes));
}



bool UWingTypes::ParseChar(WingTokenizer& Tok, TCHAR c, WingOut Errors)
{
	if (!Tok.TokenIs(c))
	{
		PrintParseError(Tok, *FString::Printf(TEXT("expected '%c'"), c), Errors);
		return false;
	}
	Tok.Advance();
	return true;
}

bool UWingTypes::ParsePlainIdentifier(WingTokenizer& Tok, FEdGraphPinType& OutType, WingOut Errors)
{
	if (!Tok.TokenIs(Tok.Identifier))
	{
		PrintParseError(Tok, TEXT("expected identifier"), Errors);
		return false;
	}
	FString Name = Tok.NextName().ToString();
	Tok.Advance();
	return ResolveShortName(Tok, Name, OutType, Errors);
}

bool UWingTypes::ParseWrapped(WingTokenizer& Tok, FName Wrapper, FEdGraphPinType& OutType, WingOut Errors)
{
	Tok.Advance();
	if (!ParseChar(Tok, '<', Errors)) return false;
	if (!ParsePlainIdentifier(Tok, OutType, Errors)) return false;
	if (OutType.PinCategory != UEdGraphSchema_K2::PC_Object)
	{
		FString Message = FString::Printf(TEXT("'%s' is not an object type"), *OutType.PinCategory.ToString());
		PrintParseError(Tok, *Message, Errors);
		return false;
	}
	if (!ParseChar(Tok, '>', Errors)) return false;
	OutType.PinCategory = Wrapper;
	return true;
}

bool UWingTypes::ParseMaybeWrapped(WingTokenizer& Tok, FEdGraphPinType& OutType, WingOut Errors)
{
	if (Tok.TokenIs(NAME_TypeSoft, '<'))
	{
		return ParseWrapped(Tok, UEdGraphSchema_K2::PC_SoftObject, OutType, Errors);
	}
	else if (Tok.TokenIs(NAME_TypeClass, '<'))
	{
		return ParseWrapped(Tok, UEdGraphSchema_K2::PC_Class, OutType, Errors);
	}
	else if (Tok.TokenIs(NAME_TypeSoftClass, '<'))
	{
		return ParseWrapped(Tok, UEdGraphSchema_K2::PC_SoftClass, OutType, Errors);
	}
	else return ParsePlainIdentifier(Tok, OutType, Errors);
}

bool UWingTypes::ParseArrayOrSet(WingTokenizer& Tok, FEdGraphPinType& OutType, WingOut Errors)
{
	Tok.Advance();
	if (!ParseChar(Tok, '<', Errors)) return false;
	if (!ParseMaybeWrapped(Tok, OutType, Errors)) return false;
	if (!ParseChar(Tok, '>', Errors)) return false;
	return true;
}

bool UWingTypes::ParseMap(WingTokenizer& Tok, FEdGraphPinType& OutType, WingOut Errors)
{
	Tok.Advance();
	if (!ParseChar(Tok, '<', Errors)) return false;
	if (!ParsePlainIdentifier(Tok, OutType, Errors)) return false;
	if (!ParseChar(Tok, ',', Errors)) return false;
	FEdGraphPinType ValueType;
	if (!ParseMaybeWrapped(Tok, ValueType, Errors)) return false;
	OutType.PinValueType.TerminalCategory = ValueType.PinCategory;
	OutType.PinValueType.TerminalSubCategory = ValueType.PinSubCategory;
	OutType.PinValueType.TerminalSubCategoryObject = ValueType.PinSubCategoryObject;
	if (!ParseChar(Tok, '>', Errors)) return false;
	return true;
}

bool UWingTypes::ParseType(WingTokenizer& Tok, FEdGraphPinType& OutType, WingOut Errors)
{
	if (Tok.TokenIs(NAME_TypeArray, '<'))
	{
		OutType.ContainerType = EPinContainerType::Array;
		if (!ParseArrayOrSet(Tok, OutType, Errors)) return false;
	}
	else if (Tok.TokenIs(NAME_TypeSet, '<'))
	{
		OutType.ContainerType = EPinContainerType::Set;
		if (!ParseArrayOrSet(Tok, OutType, Errors)) return false;
	}
	else if (Tok.TokenIs(NAME_TypeMap, '<'))
	{
		OutType.ContainerType = EPinContainerType::Map;
		if (!ParseMap(Tok, OutType, Errors)) return false;
	}
	else
	{
		if (!ParseMaybeWrapped(Tok, OutType, Errors)) return false;
	}
	return true;
}

bool UWingTypes::ResolveShortName(WingTokenizer& Tok, const FString &Name, FEdGraphPinType &OutType, WingOut Errors)
{
	Info* TypeInfo = ShortToInfo.Find(Name.ToLower());
	if (!TypeInfo)
	{
		PrintParseError(Tok, *FString::Printf(TEXT("unrecognized type '%s'"), *Name), Errors);
		return false;
	}

	// Handle primitives.
	if (TypeInfo->PinSubCategoryObject.IsEmpty())
	{
		OutType.PinCategory = TypeInfo->PinCategory;
		OutType.PinSubCategory = TypeInfo->PinSubCategory;
		return true;
	}

	// Load the PinSubCategoryObject.
	UObject* Obj = LoadObject<UObject>(nullptr, *TypeInfo->PinSubCategoryObject);
	if (!Obj)
	{
		PrintParseError(Tok, *FString::Printf(TEXT("failed to load type '%s' at path '%s'"), *Name, *TypeInfo->PinSubCategoryObject), Errors);
		return false;
	}

	// Sanity check the result.
	if (UClass *Class = Cast<UClass>(Obj))
	{
		if (Class->IsChildOf(UInterface::StaticClass()))
		{
			check(TypeInfo->PinCategory == UEdGraphSchema_K2::PC_Interface);
		}
		else
		{
			check(TypeInfo->PinCategory == UEdGraphSchema_K2::PC_Object);
		}
	}
	else if (Cast<UScriptStruct>(Obj))
	{
		check(TypeInfo->PinCategory == UEdGraphSchema_K2::PC_Struct);
	}
	else if (Cast<UEnum>(Obj))
	{
		check(TypeInfo->PinCategory == UEdGraphSchema_K2::PC_Enum);
	}
	else
	{
		UE_LOG(LogTemp, Fatal, TEXT("PinSubCategoryObject '%s' doesn't belong in type registry"), *Obj->GetClass()->GetName());
	}

	OutType.PinCategory = TypeInfo->PinCategory;
	OutType.PinSubCategoryObject = Obj;
	return true;
}

// ---------------------------------------------------------------------------
// Text to Type
// ---------------------------------------------------------------------------


bool UWingTypes::TextToType(WingTokenizer &Tok, FEdGraphPinType& OutPinType, const Requirements &Require, bool CheckEOF, WingOut Errors)
{
	UWingTypes* Types = GEditor->GetEditorSubsystem<UWingTypes>();
	check(Types);
	Tok.SaveCursor(NAME_StartOfType);

	OutPinType = FEdGraphPinType();
	if (!Types->ParseType(Tok, OutPinType, Errors))
	{
		OutPinType = FEdGraphPinType(); return false;
	}

	if (CheckEOF && (Tok.NextType() != 0))
	{
		PrintParseError(Tok, TEXT("Extra tokens at end of Type name"), Errors);
		OutPinType = FEdGraphPinType(); return false;
	}

	// Check for none.  If the type is none, and allownone is specified,
	// then that overrides a lot of other requirements.  We set a flag for this.
	bool IsNone = OutPinType.PinCategory == NAME_NoneMeaningNullptr;
	bool AllowNoneIsOverriding = (Require.AllowNone && IsNone);

	// None is never allowed inside a container.
	if (IsNone && OutPinType.IsContainer())
	{
		Errors.Printf(TEXT("ERROR: 'None' is not allowed in an array/set/map\n"));
		UWingServer::SuggestHandlerHelp();
		OutPinType = FEdGraphPinType(); return false;
	}

	// Don't allow none unless AllowNone is set.
	if (!Require.AllowNone && IsNone)
	{
		Errors.Printf(TEXT("ERROR: 'None' is not allowed here\n"));
		UWingServer::SuggestHandlerHelp();
		OutPinType = FEdGraphPinType(); return false;
	}

	if (!Require.AllowContainer)
	{
		if (OutPinType.IsContainer())
		{
			FString Text(Tok.GetRange(NAME_StartOfType, 0));
			Errors.Printf(TEXT("ERROR: Type '%s' is a container, not allowed here\n"), *Text);
			UWingServer::SuggestHandlerHelp();
			OutPinType = FEdGraphPinType(); return false;
		}
	}

	if (!Require.PinCategory.IsNone() && (!AllowNoneIsOverriding))
	{
		if (OutPinType.PinCategory != Require.PinCategory)
		{
			Errors.Printf(TEXT("ERROR: Need a type which is an %s, got a %s instead.\n"),
					*Require.PinCategory.ToString(), *OutPinType.PinCategory.ToString());
			UWingServer::SuggestHandlerHelp();
			OutPinType = FEdGraphPinType(); return false;
		}
	}

	if (Require.IsChildOf && (!AllowNoneIsOverriding))
	{
		// If the base class is not an interface, don't allow interfaces.
		if (!Require.IsChildOf->IsChildOf(UInterface::StaticClass())
			&& OutPinType.PinCategory == UEdGraphSchema_K2::PC_Interface)
		{
			FString Text(Tok.GetRange(NAME_StartOfType, 0));
			Errors.Printf(TEXT("ERROR: '%s' is an interface, not a class\n"), *Text);
			UWingServer::SuggestHandlerHelp();
			OutPinType = FEdGraphPinType(); return false;
		}
		if (!IsChildOf(OutPinType, Require.IsChildOf))
		{
			Errors.Printf(TEXT("ERROR: Type must derive from %s\n"), *WingUtils::FormatName(Require.IsChildOf));
			UWingServer::SuggestHandlerHelp();
			OutPinType = FEdGraphPinType(); return false;
		}
	}

	if (Require.BlueprintType && (!AllowNoneIsOverriding))
	{
		if (!IsBlueprintType(OutPinType))
		{
			FString Text(Tok.GetRange(NAME_StartOfType, 0));
			Errors.Printf(TEXT("ERROR: Not a blueprint type: %s\n"), *Text);
			UWingServer::SuggestHandlerHelp();
			OutPinType = FEdGraphPinType(); return false;
		}
	}

	if (Require.Blueprintable && (!AllowNoneIsOverriding))
	{
		if (!IsBlueprintable(OutPinType))
		{
			FString Text(Tok.GetRange(NAME_StartOfType, 0));
			Errors.Printf(TEXT("ERROR: Not a blueprintable type: %s\n"), *Text);
			UWingServer::SuggestHandlerHelp();
			OutPinType = FEdGraphPinType(); return false;
		}
	}

	return true;
}

bool UWingTypes::TextToType(const FString &Text, FEdGraphPinType& OutPinType, const Requirements &Require, WingOut Errors)
{
	WingTokenizer Tok(Text);
	return TextToType(Tok, OutPinType, Require, true, Errors);
}

// ---------------------------------------------------------------------------
// Subsystem lifecycle
// ---------------------------------------------------------------------------

void UWingTypes::Initialize(FSubsystemCollectionBase& Collection)
{
	Super::Initialize(Collection);

	// Collect all packages and sort by name for stable short-name assignment.
	TArray<UPackage*> Packages;
	for (TObjectIterator<UPackage> It; It; ++It)
		Packages.Add(*It);
	Packages.Sort([](const UPackage& A, const UPackage& B) { return A.GetName() < B.GetName(); });

	// Reserve the short names of the primitives.
	ReserveShortName(UEdGraphSchema_K2::PC_Delegate);
	ReserveShortName(UEdGraphSchema_K2::PC_MCDelegate);
	ReserveShortName(UEdGraphSchema_K2::PC_Boolean);
	ReserveShortName(UEdGraphSchema_K2::PC_Int);
	ReserveShortName(UEdGraphSchema_K2::PC_Int64);
	ReserveShortName(UEdGraphSchema_K2::PC_Float,  UEdGraphSchema_K2::PC_Real, UEdGraphSchema_K2::PC_Float);
	ReserveShortName(UEdGraphSchema_K2::PC_Double, UEdGraphSchema_K2::PC_Real, UEdGraphSchema_K2::PC_Double);
	ReserveShortName(UEdGraphSchema_K2::PC_Byte);
	ReserveShortName(UEdGraphSchema_K2::PC_Name);
	ReserveShortName(UEdGraphSchema_K2::PC_String);
	ReserveShortName(UEdGraphSchema_K2::PC_Text);
	ReserveShortName(NAME_NoneMeaningNullptr);

	// Scan priority packages first, then everything else in sorted order.
	ChooseShortNames(FindPackage(nullptr, TEXT("/Script/CoreUObject")));
	ChooseShortNames(FindPackage(nullptr, TEXT("/Script/Engine")));
	ChooseShortNames(FindPackage(nullptr, TEXT("/Script/Integration")));

	// Choose short names for everything else.
	for (UPackage* Pkg : Packages)
		ChooseShortNames(Pkg);

	// Scan the asset registry for unloaded assets.
	IAssetRegistry& AssetRegistry = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry")).Get();
	TArray<FAssetData> AllAssets;
	AssetRegistry.GetAllAssets(AllAssets, true);
	for (const FAssetData& Data : AllAssets)
		ChooseShortName(Data);

	// Register for future asset discoveries.
	AssetRegistry.OnAssetAdded().AddUObject(this, &UWingTypes::ChooseShortName);
}

void UWingTypes::Deinitialize()
{
	if (FModuleManager::Get().IsModuleLoaded(TEXT("AssetRegistry")))
	{
		IAssetRegistry& AssetRegistry = FModuleManager::GetModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry")).Get();
		AssetRegistry.OnAssetAdded().RemoveAll(this);
	}
	Super::Deinitialize();
}
