#include "WingFactories.h"
#include "WingServer.h"
#include "PackageTools.h"
#include "WingProperty.h"
#include "AssetRegistry/AssetRegistryModule.h"
#include "Editor.h"
#include "Kismet2/EnumEditorUtils.h"
#include "Subsystems/AssetEditorSubsystem.h"

bool WingFactories::CheckNewAssetPath(const FString& Path, WingOut Errors)
{
	if (UPackageTools::SanitizePackageName(Path) != Path)
	{
		Errors.Printf(TEXT("ERROR: Package path '%s' is not a valid package name\n"), *Path);
		return false;
	}
	if (!FPackageName::IsValidTextForLongPackageName(Path))
	{
		Errors.Printf(TEXT("ERROR: Package path '%s' is not a valid package name\n"), *Path);
		return false;
	}
	if (!Path.StartsWith(TEXT("/Game/")))
	{
		Errors.Printf(TEXT("ERROR: Package path '%s' must start with '/Game'\n"), *Path);
		return false;
	}
	if (FindObject<UPackage>(nullptr, *Path) || FPackageName::DoesPackageExist(Path))
	{
		Errors.Printf(TEXT("ERROR: An asset already exists at '%s'\n"), *Path);
		return false;
	}
	return true;
}

bool WingFactories::IsBlacklisted(TSubclassOf<UFactory> FactoryClass)
{
	FName Name = FactoryClass->GetFName();
	if (Name == TEXT("PhysicsAssetFactory")) return true;  // Pops a modal dialog
	if (Name == TEXT("EnumFactory")) return true;           // Pops a modal dialog
	return false;
}

bool WingFactories::IsSpecialCase(TSubclassOf<UFactory> FactoryClass)
{
	if (FactoryClass == nullptr) return false;
	FName Name = FactoryClass->GetFName();
	if (Name == TEXT("MaterialInstanceConstantFactoryNew")) return true;
	return false;
}

bool WingFactories::CanCreate(TSubclassOf<UFactory> FactoryClass)
{
	if (FactoryClass == nullptr) return false;
	if (FactoryClass->HasAnyClassFlags(CLASS_Abstract)) return false;
	if (IsBlacklisted(FactoryClass)) return false;
	UFactory* CDO = FactoryClass->GetDefaultObject<UFactory>();
	return CDO->CanCreateNew() && CDO->ShouldShowInNewMenu();
}

TArray<FName> WingFactories::GetParameterNames(TSubclassOf<UFactory> FactoryClass)
{
	return FWingProperty::GetVisibleNames(FactoryClass);
}

UObject* WingFactories::CreateAsset(
	const FString& Path, UFactory* Factory, UClass *CheckIs, WingOut Errors)
{
	// Check the blacklist.
	if (IsBlacklisted(Factory->GetClass()))
	{
		Errors.Printf(TEXT("ERROR: Factory '%s' is blacklisted\n"), *Factory->GetClass()->GetName());
		return nullptr;
	}

	// Validate the path, and that there's not already something there.
	if (!CheckNewAssetPath(Path, Errors)) return nullptr;
	FName Name = FName(FPackageName::GetShortName(Path));

	// Create the package.
	UPackage *Package = CreatePackage(*Path);
	if (!Package)
	{
		Errors.Printf(TEXT("ERROR: Failed to create package at '%s'\n"), *Path);
		return nullptr;
	}

	// Create the asset.
	UObject* NewAsset = Factory->FactoryCreateNew(
		Factory->GetSupportedClass(),
		Package,
		Name,
		RF_Public | RF_Standalone | RF_Transactional,
		nullptr,
		GWarn
	);

	// If the factory created nothing, report the error.
	if (NewAsset == nullptr)
	{
		Errors.Printf(TEXT("ERROR: Factory '%s' failed to create an object\n"),
			*Factory->GetClass()->GetName());
	}

	// Verify the class of the created object.
	if ((NewAsset != nullptr) && (CheckIs != nullptr))
	{
		if (!NewAsset->IsA(CheckIs))
		{
			Errors.Printf(TEXT("ERROR: factory did not create a '%s'\n"),
				*CheckIs->GetName());
			NewAsset = nullptr;
		}
	}

	// If the asset was created, turn the package into a
	// permanent package and encourge the editor to save it
	// to disk.  Also, open the asset in the editor if possible.
	// Otherwise, mark the package for deletion.
	if (NewAsset)
	{
		Package->ClearFlags(RF_Transient);
		Package->SetIsExternallyReferenceable(true);
		Package->MarkPackageDirty();
		FAssetRegistryModule::AssetCreated(NewAsset);
		UWingServer::AddTouchedObject(NewAsset);
		UAssetEditorSubsystem* Sub = GEditor->GetEditorSubsystem<UAssetEditorSubsystem>();
		if (Sub) Sub->OpenEditorForAsset(NewAsset);
	}
	else
	{
		Package->ClearDirtyFlag();
		Package->MarkAsGarbage();
		return nullptr;
	}
	return NewAsset;
}

FString WingFactories::DeriveFactoryName(TSubclassOf<UFactory> FactoryClass)
{
	FString Name = FactoryClass->GetName();
	int32 Index = Name.Find(TEXT("Factory"));
	if (Index != INDEX_NONE)
	{
		Name.LeftInline(Index);
	}
	if (Name.EndsWith(TEXT("_")))
	{
		Name.LeftChopInline(1);
	}
	return Name;
}

UObject* UEnumFactoryWing::FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn)
{
	if (!FEnumEditorUtils::IsNameAvailebleForUserDefinedEnum(Name))
	{
		WingOut::Stdout.Printf(TEXT("ERROR: Enum name is already taken: %s\n"), *Name.ToString());
		return nullptr;
	}
	return FEnumEditorUtils::CreateUserDefinedEnum(InParent, Name, Flags);
}
