
#include "AssetLookup.h"
#include "AssetRegistry/IAssetRegistry.h"
#include "AssetRegistry/AssetData.h"
#include "AssetRegistry/AssetRegistryState.h"
#include "Kismet/GameplayStatics.h"
#include "Engine/GameInstance.h"
#include "Components/Widget.h"
#include "WidgetBlueprint.h"
#include "Blueprint/UserWidget.h"
#include "Animation/AnimSequence.h"
#include "Engine/StaticMesh.h"
#include "Engine/SkeletalMesh.h"


const ElxValidOrNotValid NotValid = ElxValidOrNotValid::NotValid;
const ElxValidOrNotValid Valid    = ElxValidOrNotValid::Valid;

void UlxAssetLookup::Initialize(FSubsystemCollectionBase& Collection)
{
	Super::Initialize(Collection);
	RebuildIndex();
}

void UlxAssetLookup::RebuildIndex()
{
    IAssetRegistry::GetChecked().WaitForCompletion();
    AssetPaths.Empty();
    AddAssets(TEXT("/Game/StaticMeshes"), UStaticMesh::StaticClass(), TEXT("SM_"));
    AddAssets(TEXT("/Game/SkeletalMeshes"), USkeletalMesh::StaticClass(), TEXT("SKM_"));
    AddAssets(TEXT("/Game/AnimSequences"), UAnimSequence::StaticClass(), TEXT("SEQ_"));
    AddAssets(TEXT("/Game/Tangibles"), UBlueprint::StaticClass(), TEXT("TAN_"));
    AddAssets(TEXT("/Game/Widgets"), UWidgetBlueprint::StaticClass(), TEXT("WB_"));
}

void UlxAssetLookup::AddAssets(const TCHAR *Path, UClass *Class, const TCHAR *NamePrefix)
{
	TArray<FAssetData> FoundData;
    TMap<FName, FString> Result;
	FARFilter AssetFilter;
	AssetFilter.PackagePaths.Add(FName(Path));
    AssetFilter.ClassPaths.Add(Class->GetClassPathName());
	AssetFilter.bIncludeOnlyOnDiskAssets = true;
    AssetFilter.bRecursivePaths = true;
	IAssetRegistry::GetChecked().GetAssets(AssetFilter, FoundData);
    FString FSNamePrefix(NamePrefix);

    for (const FAssetData &Data : FoundData)
    {
        FString AssetName = Data.AssetName.ToString();
        if (AssetName.StartsWith(FSNamePrefix))
        {
            FName ShortName(AssetName.RightChop(FSNamePrefix.Len()));
            AssetPaths.Add(MakeTuple(Class->GetName(), ShortName), Data.GetObjectPathString());
        }
    }

    UE_LOG(LogLuprexIntegration, Display, TEXT("Found %d assets of type %s in %s"),
        FoundData.Num(), *Class->GetName(), Path);
}

void UlxAssetLookup::ReportFailedLoad(const FString &ClassName, const FString &Name, const FString &Reason)
{
    static TSet<FString> Reported;
    FString Key = ClassName + TEXT(":") + Name + TEXT(":") + Reason;
    if (Reported.Contains(Key)) return;
    Reported.Add(Key);
    UE_LOG(LogLuprexIntegration, Display, TEXT("Loading %s %s: %s"), *ClassName, *Name, *Reason);
}

UObject *UlxAssetLookup::LoadAsset(const UObject *Context, UClass *Class, UClass *ChildOf, const FString &Name)
{
    const UlxAssetLookup *Lookup = UGameplayStatics::GetGameInstance(Context)->GetSubsystem<UlxAssetLookup>();
    const FString *Path = Lookup->AssetPaths.Find(MakeTuple(Class->GetName(), FName(Name)));
    if (Path == nullptr)
    {
        ReportFailedLoad(Class->GetName(), Name, TEXT("asset not found"));
        return nullptr;
    }

    UObject *Result;
    if (ChildOf == nullptr)
    {
        Result = StaticLoadObject(Class, nullptr, **Path);
    }
    else
    {
        Result = StaticLoadObject(UClass::StaticClass(), nullptr, *((*Path) + FString(TEXT("_C"))));
    }

    if (Result == nullptr)
    {
        ReportFailedLoad(Class->GetName(), Name, TEXT("unknown load failure"));
        return nullptr;
    }

    if (ChildOf != nullptr)
    {
        UClass *ResClass = (UClass *)Result;
        if (!ResClass->IsChildOf(ChildOf))
        {
            ReportFailedLoad(Class->GetName(), Name, FString::Printf(TEXT("blueprint not a child of %s"), *ChildOf->GetName()));
            return nullptr;
        }
    }

    return Result;
}

ElxValidOrNotValid UlxAssetLookup::LoadStaticMeshAsset(
    UStaticMesh *&Result, const UObject *Context, const FString &Name)
{
    Result = (UStaticMesh *)LoadAsset(Context, UStaticMesh::StaticClass(), nullptr, Name);
    return Result ? Valid : NotValid;
}

ElxValidOrNotValid UlxAssetLookup::LoadSkeletalMeshAsset(
    USkeletalMesh *&Result, const UObject *Context, const FString &Name)
{
    Result = (USkeletalMesh *)LoadAsset(Context, USkeletalMesh::StaticClass(), nullptr, Name);
    return Result ? Valid : NotValid;
}

ElxValidOrNotValid UlxAssetLookup::LoadAnimSequenceAsset(
    UAnimSequence *&Result, const UObject *Context, const FString &Name)
{
    Result = (UAnimSequence *)LoadAsset(Context, UAnimSequence::StaticClass(), nullptr, Name);
    return Result ? Valid : NotValid;
}

ElxValidOrNotValid UlxAssetLookup::LoadTangibleBlueprintAsset(
    TSubclassOf<AActor> &Result, const UObject *Context, const FString &Name)
{
    Result = (UClass*)LoadAsset(Context, UBlueprint::StaticClass(), AActor::StaticClass(), Name);
    if (Result == nullptr) return NotValid;
    UFunction *aqchanged = Result->FindFunctionByName(FName(TEXT("Animation Queue Changed")));
	if ((aqchanged == nullptr)||(aqchanged->ParmsSize != 0))
    {
		ReportFailedLoad(TEXT("Blueprint"), Name, TEXT("tangible does not have 'Animation Queue Changed' function"));
        Result = nullptr; return NotValid;
	}
	return Valid;
}

ElxValidOrNotValid UlxAssetLookup::LoadUserWidgetAsset(
    TSubclassOf<UUserWidget> &Result, const UObject *Context, const FString &Name)
{
    Result = (UClass *)LoadAsset(Context, UWidgetBlueprint::StaticClass(), UUserWidget::StaticClass(), Name);
    return Result ? Valid : NotValid;
}
