// Copyright (c) Microsoft Corporation. 
// Licensed under the MIT license.

#include "StandinUtilitiesModule.h"
#include "StandinActor.h"
#include "StandinProxyContainer.h"

#include "SimplygonModule.h"
#include "ISimplygonPluginUtilitiesModule.h"
#include "ISimplygonPluginUtilities.h"
#include "SimplygonAssetUserData.h"
#include "SimplygonPluginUtilities/Private/SimplygonProcessor.h"
#include "SimplygonDataConverter.h"
#include "ISimplygonReduction.h"
#include "SimplygonPluginMeshReductionModule.h"

#include "Editor.h"
#include "Editor/UnrealEdEngine.h"
#include "UnrealEdGlobals.h"
#include "Engine/Selection.h"
#include "Engine/StaticMeshActor.h"
#include "Misc/ScopedSlowTask.h"
#include "ScopedTransaction.h"
#include "Materials/MaterialInstanceConstant.h"
#include "StaticMeshAttributes.h"
//#include "Toolkits/AssetEditorManager.h"
#include "Subsystems/AssetEditorSubsystem.h"
#include "AssetRegistryModule.h"
#include "ContentBrowserModule.h"
#include "IContentBrowserSingleton.h"
#include "Algo/Count.h"
#include "Algo/AnyOf.h"
#include "MaterialUtilities.h"
#include "MeshMergeUtilities/Private/MeshMergeHelpers.h"
//#include "Developer/MeshMergeUtilities/Private/ProxyMaterialUtilities.h"
#include "Telemetry.h"
#include "TelemetryManager.h"
#include "FileHelpers.h"
#include "StaticMeshOperations.h"

class FStandinUtilitiesModule : public IStandinUtilitiesModule
{

public:
	/** IModuleInterface implementation */
	virtual void StartupModule() override;
	virtual void ShutdownModule() override;
	
	virtual void GenerateStandinMeshes(
		const TArray<AStandInMeshActor*>& StandinActors, 
		const TMap<AStandInMeshActor*, FString>& OverridePackageNames = {}) override;

	virtual void BuildStandinMeshActors(bool bRebuild) override;

	virtual void GroupUnderStandinActor() override;

	virtual void AddActorsToStandinActor(AStandInMeshActor* InStandinActor, const TArray<AActor*>& InSelectedActors) override;
	virtual void RemoveActorsFromStandinActor(AStandInMeshActor* InStandinActor, const TArray<AActor*>& InSelectedActors) override;
	virtual bool IsActorValidForStandinInclusion(AActor* Actor) override;
	
	virtual AStandInMeshActor* CreateStandinMeshActor(const TArray<AActor*>& InActors) override;

	virtual const bool AreActorsInSamePersistingLevel(const TArray<AActor*>& InActors) override;
	virtual void MergeStandinActors(AStandInMeshActor* ToActor, AStandInMeshActor* FromActor) override;
	virtual void AddActorToStandinActor(AActor* InActor, AStandInMeshActor* InParentActor) override;
	virtual const bool RemoveActorFromStandinActor(AActor* InActor) override;
	virtual AStandInMeshActor* GetStandinActor(AActor* InActor) override;
	virtual bool DestroyStandinActor(AStandInMeshActor* InActor) override;
	virtual const bool AreActorsAlreadyBeingReplaced(const TArray<AActor*>& InActors) override;
	virtual void GetStandinActors(TArray<AStandInMeshActor*>& OutActors)  override;

	virtual void SavePackagesForStandins() override;
	virtual void GetListOfPackagesToSaveForStandins(const ULevel* Level, TSet<UPackage*>& PackagesToSave, const FString& PreviousLevelName = "") override;

	
private:
	AStandInMeshActor* CreateNewStandinMeshActor(UWorld* InWorld);
	UStandinProxyContainer* CreateOrRetrieveLevelStandinProxy(ULevel* InLevel);
	UPackage* CreateOrRetrieveLevelStandinPackage(const ULevel* InLevel);
	void CompleteGenerateStandinMesh(const FSimplygonStandinJobData* StandinJobData, Simplygon::spPipeline Pipeline, Simplygon::spScene Scene);
	void CompleteGenerateStandinMeshes(const FSimplygonJobData& InJobData, Simplygon::spPipelineBatch InPipelineBatch);
	UStandinProxyContainer* GetStandinProxyContainer(const ULevel* InLevel);
	UPackage* GetStandinProxyContainerPackage(const ULevel* InLevel);
};

IMPLEMENT_MODULE(FStandinUtilitiesModule, StandinUtilities)
DEFINE_LOG_CATEGORY_STATIC(LogStandinUtilitiesModule, Verbose, All);
#define LOCTEXT_NAMESPACE "StandinEditorModule"


static FString GetStandinProxyNameLevel(const FString& InLevelName)
{
	return FString::Printf(TEXT("%s_Standins"), *InLevelName);
}

static FString GetStandinProxyName(const ULevel* InLevel)
{
	UPackage* LevelOuterMost = InLevel->GetOutermost();

	const FString BaseName = FPackageName::GetShortName(LevelOuterMost->GetPathName());
	return FString::Printf(TEXT("%s_Standins"), *BaseName);
}

static FString GetStandinProxyName(const ULevel* InLevel, FString& InOutProxyName)
{
	UPackage* LevelOuterMost = InLevel->GetOutermost();

	//const FString PathName = FPackageName::GetLongPackagePath(LevelOuterMost->GetPathName());
	const FString BaseName = FPackageName::GetShortName(LevelOuterMost->GetPathName());
	InOutProxyName = GetStandinProxyName(InLevel);
	//return FString::Printf(TEXT("%s/Simplygon/Standin/%s"), *PathName, *InOutHLODProxyName);
	return FString::Printf(TEXT("/Game/Simplygon/Standins/%s"), *InOutProxyName);
}

UPackage* FStandinUtilitiesModule::CreateOrRetrieveLevelStandinPackage(const ULevel* InLevel)
{
	checkf(InLevel != nullptr, TEXT("Invalid Level supplied"));

	FString StandinProxyName;
	const FString StandinLevelPackageName = GetStandinProxyName(InLevel, StandinProxyName);

	// Find existing package
	bool bCreatedNewPackage = false;
	UPackage* StandinPackage = CreatePackage(*StandinLevelPackageName);
	StandinPackage->FullyLoad();

	// This is a hack to avoid save file dialog when we will be saving map package
	StandinPackage->SetLoadedPath(FPackagePath::FromPackageNameChecked(StandinLevelPackageName));
	return StandinPackage;
}

static void ClearFlagFromAssetsInPackage(UPackage* InPackage, EObjectFlags FlagToClear)
{
	TArray<UObject*> Objects;
	GetObjectsWithOuter(InPackage, Objects);
	for (UObject* PackageObject : Objects)
	{
		if (PackageObject->HasAnyFlags(FlagToClear))
		{
			if (PackageObject->IsA<UStaticMesh>() ||
				PackageObject->IsA<UTexture>() ||
				PackageObject->IsA<UMaterialInterface>())
			{
				PackageObject->ClearFlags(FlagToClear);
			}
		}
	}
}

UStandinProxyContainer* FStandinUtilitiesModule::CreateOrRetrieveLevelStandinProxy(ULevel* InLevel)
{
	UPackage* StandinProxyPackage = CreateOrRetrieveLevelStandinPackage(InLevel);

	// Check if our asset exists
	const FString StandinProxyName = GetStandinProxyName(InLevel);//GetHLODProxyName(InLevel);
	UStandinProxyContainer* Proxy = FindObject<UStandinProxyContainer>(StandinProxyPackage, *StandinProxyName);

	// Get the world associated with this level
	UWorld* LevelWorld = UWorld::FindWorldInPackage(InLevel->GetOutermost());

	// If proxy doesn't exist or is pointing to another world (could happen if package is duplicated)
	if (Proxy == nullptr || Proxy->GetMap() != LevelWorld)
	{
		// Make sure that the package doesn't have any standalone meshes etc. (i.e. this is an old style package)
		ClearFlagFromAssetsInPackage(StandinProxyPackage, RF_Standalone );

		// Create the new asset
		Proxy = NewObject<UStandinProxyContainer>(StandinProxyPackage, *StandinProxyName, RF_Public | RF_Standalone);
		Proxy->SetMap(LevelWorld);
	}

	return Proxy;
}

static bool GetSelectedActors(TArray<AActor*>& OutActors)
{
	bool Sucess = false;
	TArray<AActor*> SelectedActors;

	GUnrealEd->GetSelectedActors()->GetSelectedObjects<AActor>(SelectedActors);

	for (auto SelActIt = SelectedActors.CreateConstIterator(); SelActIt; ++SelActIt)
	{
		if ((*SelActIt) != nullptr)
		{
			TInlineComponentArray<UActorComponent*> Components;
			(*SelActIt)->GetComponents<UActorComponent>(Components);
			if (Components.Num() > 0)
			{
				OutActors.Add(*SelActIt);
			}
		}
	}

	if (OutActors.Num() == 0)
	{
		UE_LOG(LogStandinUtilitiesModule, Error, TEXT("No Actors Of type found."));
	}

	Sucess = OutActors.Num() > 0 ? true : false;

	return Sucess;

}

static FBox GetComponentsBoundingBox(const AActor* Actor)
{
	FBox Box(EForceInit::ForceInitToZero);
	TArray<UActorComponent*> ActorComponents;
	Actor->GetComponents(ActorComponents);
	for (const UActorComponent* ActorComponent : ActorComponents)
	{
		const UPrimitiveComponent* PrimComp = Cast<const UPrimitiveComponent>(ActorComponent);
		const UChildActorComponent* ChildActorComp = Cast<const UChildActorComponent>(ActorComponent);
		if (PrimComp)
		{
			Box += PrimComp->Bounds.GetBox();
		}
		else if (ChildActorComp && ChildActorComp->GetChildActor())
		{
			AActor* ChildActor = ChildActorComp->GetChildActor();

			//Recursively add all child actor bounding boxes
			Box += GetComponentsBoundingBox(ChildActor);
		}
	}

	return Box;
}

/*
The following method is tempalted method to fetch actors of type from levels
*/
template<class ActorType>
static bool FindActorsOfTypeInLevel(TArray<ActorType*>& OutActors)
{
	bool Sucess = false;
	for (auto Level : GWorld->GetLevels())
	{
		if (Level->IsValidLowLevelFast() && Level->bIsVisible)
		{

			for (auto ActorIt = Level->Actors.CreateIterator(); ActorIt; ++ActorIt)
			{
				AActor* Actor = *ActorIt;
				if (Actor->IsValidLowLevelFast() && Actor->IsA(ActorType::StaticClass()))
				{
					OutActors.Add(CastChecked<ActorType>(Actor));
				}
			}
		}
	}

	if (OutActors.Num() == 0)
	{
		UE_LOG(LogStandinUtilitiesModule, Warning, TEXT("FindActorsOfTypeInLevel No Actors Of type %s found."), *ActorType::StaticClass()->GetName());
	}

	Sucess = OutActors.Num() > 0 ? true : false;

	return Sucess;
}

template<class ComponentType>
static bool GetSelectedActorsByComponent(TArray<AActor*>& OutActors)
{
	bool Sucess = false;
	TArray<AActor*> SelectedActors;

	GUnrealEd->GetSelectedActors()->GetSelectedObjects<AActor>(SelectedActors);

	for (auto SelActIt = SelectedActors.CreateConstIterator(); SelActIt; ++SelActIt)
	{
		if ((*SelActIt) != nullptr)
		{
			TInlineComponentArray<ComponentType*> Components;
			(*SelActIt)->GetComponents<ComponentType>(Components);
			if (Components.Num() > 0)
			{
				OutActors.Add(*SelActIt);
			}
		}
	}

	if (OutActors.Num() == 0)
	{
		UE_LOG(LogStandinUtilitiesModule, Warning, TEXT("GetSelectedActorsByComponent No Actors Of type %s found."), *ComponentType::StaticClass()->GetName());
	}

	Sucess = OutActors.Num() > 0 ? true : false;

	return Sucess;

}

void FStandinUtilitiesModule::StartupModule()
{
}


void FStandinUtilitiesModule::ShutdownModule()
{
}

void FStandinUtilitiesModule::GenerateStandinMeshes(const TArray<AStandInMeshActor*>& StandinActors, const TMap<AStandInMeshActor*,FString>& OverridePackageNames)
{
	ISimplygonPluginUtilitiesModule& SimplygonUtilities = FModuleManager::Get().LoadModuleChecked<ISimplygonPluginUtilitiesModule>("SimplygonPluginUtilities");
	Simplygon::ISimplygon* SimplygonAPI = ISimplygonModule::Get().GetSDK(true);
	if (SimplygonAPI == nullptr)
		return;

	ISimplygonPluginMeshReductionModule& ReductionModule = FModuleManager::LoadModuleChecked<ISimplygonPluginMeshReductionModule>("SimplygonPluginMeshReduction");
	ISimplygonReduction* SimplygonProcessingInterface = ReductionModule.GetSimplygonReduction();

	bool bIsSavingStandAlonePackage = OverridePackageNames.Num() > 0;
	if(bIsSavingStandAlonePackage)
		check(StandinActors.Num() == OverridePackageNames.Num())

	// Pick standin actors which has a valid pipeline
	TArray<AStandInMeshActor*> ValidStandinActors = StandinActors.FilterByPredicate([](const AStandInMeshActor* StandinActorItem)
		{
			bool bValidPipeline = StandinActorItem && StandinActorItem->GetPipeline() != nullptr;
			if(!bValidPipeline)
				UE_LOG(LogStandinUtilitiesModule, Warning, TEXT("No pipeline selected for standin actor %s"), *StandinActorItem->GetFName().ToString()) ;

			return bValidPipeline;
		});

	int32 NumValidStandinActors = ValidStandinActors.Num();
	if (NumValidStandinActors  == 0)
	{
		UE_LOG(LogStandinUtilitiesModule, Warning, TEXT("No pipline selected for standin actors."));
		return;
	}

	// Game thread ticker
	ISimplygonPluginUtilities& PluginUtils = SimplygonUtilities.GetUtilities();
	FSimplygonProcessor* SimplygonProcessor = PluginUtils.GetSimplygonProcessor();

	// Progress
	int32 SimplygonAmountOfWork = NumValidStandinActors;
	volatile int32 SimplygonWorkCompleted = 0;
	volatile int32 SimplygonWorkProgress = 0;

	FText DataConvertionProgressText = LOCTEXT("GenerateStandinMeshes_ConvertData", "Converting mesh data ({0}/{1})");
	int32 DataConvertionWorkComplete = 0;
	float DataConvertionAmountOfWork = (float)NumValidStandinActors * 0.2f;

	bool bUseIncrediBuild = SimplygonProcessingInterface->IsUsingIncrediBuild();

	Simplygon::spPipelineBatch PipelineBatch;
	if( bUseIncrediBuild )
	{
		PipelineBatch = SimplygonAPI->CreatePipelineBatch();
		SimplygonAmountOfWork = 1;
	}
	
	FText InitialProgressText = LOCTEXT("GenerateStandinMeshes_Initial", "Preparing to generate standin meshes...");
	FScopedSlowTask SlowTask(SimplygonAmountOfWork + DataConvertionAmountOfWork, InitialProgressText);
	SlowTask.MakeDialog();

	//Delegates
	FSimplygonJobDelegates JobDelegates;
	JobDelegates.Progress.BindLambda([&SimplygonWorkProgress](const FSimplygonJobData& InJobData, int32 ProgressPercentage, int32 ProgressPercentageDelta)
		{
			FPlatformAtomics::InterlockedAdd(&SimplygonWorkProgress, ProgressPercentageDelta);
		});

	JobDelegates.Complete.BindLambda([&SimplygonWorkCompleted, &SimplygonProcessor,this](const FSimplygonJobData& InJobData, const Simplygon::spPipeline& Pipeline)
		{
			FSimplygonProcessGameThreadDelegate CompleteJobOnGameThread;
			CompleteJobOnGameThread.BindLambda([=]()
				{
					FSimplygonStandinJobData* JobData = (FSimplygonStandinJobData*)InJobData.UserData;
					UE_LOG(LogStandinUtilitiesModule, Log, TEXT("Processed Standin actor %s"), *JobData->StandinMeshActor->GetFName().ToString());
					CompleteGenerateStandinMesh(JobData, Pipeline, Pipeline->GetProcessedScene());
					delete JobData;
				});

			SimplygonProcessor->Add(CompleteJobOnGameThread);

			FTelemetry::Record(TEXT("JobCompleted"), SGTC::Standin);
			UE_LOG(LogStandinUtilitiesModule, Log, TEXT("StandinJobCompleted"));
			FPlatformAtomics::InterlockedAdd(&SimplygonWorkCompleted, 1);
		});

	JobDelegates.BatchComplete.BindLambda([&SimplygonWorkCompleted, &SimplygonProcessor, SimplygonAPI, this](const FSimplygonJobData& InJobData, const Simplygon::spPipelineBatch& PipelineBatch)
		{
			FSimplygonProcessGameThreadDelegate CompleteJobOnGameThread;
			CompleteJobOnGameThread.BindLambda([=]()
				{
					UE_LOG(LogStandinUtilitiesModule, Log, TEXT("Processed Standin actors batch"));
					CompleteGenerateStandinMeshes(InJobData, PipelineBatch);
					TArray<FSimplygonStandinJobData>* JobDataArray = (TArray<FSimplygonStandinJobData>*)InJobData.UserData;
					delete JobDataArray;
				});

			SimplygonProcessor->Add(CompleteJobOnGameThread);

			FTelemetry::Record(TEXT("JobCompleted"), SGTC::Standin);
			UE_LOG(LogStandinUtilitiesModule, Log, TEXT("StandinJobCompleted"));
			FPlatformAtomics::InterlockedAdd(&SimplygonWorkCompleted, 1);
		});

	JobDelegates.Failed.BindLambda([&SimplygonWorkCompleted, bUseIncrediBuild](const FSimplygonJobData& InJobData, const FString& InError)
		{
			FPlatformAtomics::InterlockedAdd(&SimplygonWorkCompleted, 1);
			if( bUseIncrediBuild )
			{
				TArray<FSimplygonStandinJobData>* JobDataArray = (TArray<FSimplygonStandinJobData>*)InJobData.UserData;
				delete JobDataArray;
			}
		    else
		    {
				FSimplygonStandinJobData* JobData = (FSimplygonStandinJobData*)InJobData.UserData;
				delete JobData;
		    }

			FTelemetryBuilder Builder;
			Builder.SetProperties({ 
				FTelemetry::Prop(TEXT("error_message"),InError)
				});
			
			UE_LOG(LogStandinUtilitiesModule, Error, TEXT("%s"), *FString::Printf(TEXT("StandinJobFailed : %s"), *InError));

			FTelemetry::Record(TEXT("JobFailed"), SGTC::Standin, MoveTemp(Builder));
		});

	FTelemetryBuilder Builder;
	Builder.SetProperties({
		FTelemetry::Prop(TEXT("num_meshes"),ValidStandinActors.Num())
		});

	FTelemetry::Record(TEXT("GeneterateStandinMeshes"), SGTC::Standin, MoveTemp(Builder));

	TArray<FSimplygonStandinJobData>* JobDataArray = nullptr;
	if( bUseIncrediBuild )
		JobDataArray = new TArray<FSimplygonStandinJobData>();
	FSimplygonJobData JobData;
	JobData.JobGuid = FGuid::NewGuid();
	JobData.UserData = JobDataArray;

	for (AStandInMeshActor* StandinActor : ValidStandinActors)
	{
		SlowTask.EnterProgressFrame(
			DataConvertionAmountOfWork / (float)NumValidStandinActors, 
			FText::Format(DataConvertionProgressText, FText::AsNumber(DataConvertionWorkComplete + 1), FText::AsNumber(NumValidStandinActors)));

		UE_LOG(LogStandinUtilitiesModule, Log, TEXT("Preparing Standin actor %s"), *StandinActor->GetFName().ToString());

		UStandinProxyContainer* StandinProxy = nullptr;
		UPackage* AssetsOuter = nullptr;
		FString AssetName = TEXT("");
		FString BasePackageName = TEXT("");
		if (bIsSavingStandAlonePackage)
		{
			BasePackageName = OverridePackageNames[StandinActor];
		}
		else
		{
			StandinProxy = CreateOrRetrieveLevelStandinProxy(StandinActor->GetLevel());
			StandinActor->Modify(false);
			StandinProxy->Modify();
			StandinProxy->Clean();
			AssetsOuter = StandinProxy->GetOutermost();

			AssetName = StandinActor->GetName();
			BasePackageName = StandinProxy->GetOutermost()->GetName();
		}

		// StaninJobData is released in Complete lambda
		FSimplygonStandinJobData StandinJobData;
		StandinJobData.Outer = AssetsOuter;
		StandinJobData.AssetName = AssetName;
		StandinJobData.BasePackageName = BasePackageName;
		StandinJobData.StandinMeshActor = StandinActor;
		StandinJobData.Proxy = StandinProxy;

		USimplygonStandinPipeline* PipelineUObject = StandinActor->GetPipeline();
		Simplygon::spPipeline Pipeline = PipelineUObject->CreatePipeline(SimplygonAPI);
		FSimplygonFlattenMaterialSettings FlattenMaterialSettings(Pipeline);
		FlattenMaterialSettings.ChartAggregatorSettings = PipelineUObject->ChartAggregatorSettings;
		FSimplygonMeshMaterialInfo MeshMaterialInfo(true);

		// 1. Add meshes to Simplygon scene and build up a mesh-material mapping
		Simplygon::spScene SceneToProcess = SimplygonAPI->CreateScene();
		for (AActor* Actor : StandinActor->GetSourceActors())
		{
			TInlineComponentArray<UStaticMeshComponent*> Components;
			Actor->GetComponents<UStaticMeshComponent>(Components, true);

			// if the actor is a custom actor with attached static mesh components i.e (Prefabricator)
			TArray<AActor*> AttachedActors;
			Actor->GetAttachedActors(AttachedActors);

			for (AActor* AttachedActor : AttachedActors)
			{
				AttachedActor->GetComponents<UStaticMeshComponent>(Components, true);
			}

			for (UStaticMeshComponent* Component : Components)
			{
				FSimplygonDataConverter::AddToScene(SimplygonAPI, SceneToProcess, FlattenMaterialSettings, Component, MeshMaterialInfo);
			}
		}

		// 2a. Use mesh-material info to generate flatten materials and add them to Simplygon scene
		if (MeshMaterialInfo.bBakeMaterials)
		{
			FSimplygonDataConverter::AddToScene(SimplygonAPI, SceneToProcess, MeshMaterialInfo);
		}
		// 2b. ...or use source materials on the processed scene
		else
		{
			for (const FMeshMaterialSectionId& Id : MeshMaterialInfo.MaterialOutputOrder)
			{
				StandinJobData.OriginalMaterials.Add(MeshMaterialInfo.SourceMaterials[Id]);
			}
		}

		// 3. [Optional] Use clipping planes
		const TArray<FPlane>& ClippingPlanes = SimplygonProcessingInterface->GetClippingPlanes();
		FSimplygonDataConverter::AddToScene(SimplygonAPI, SceneToProcess, Pipeline, ClippingPlanes);

		// 4. [Optional] Use nav mesh data for visibility
		const TArray<FVector>& VisibilitySamples = SimplygonProcessingInterface->GetNavMeshVisibilitySamples();
		FSimplygonDataConverter::AddToScene(SimplygonAPI, SceneToProcess, Pipeline, VisibilitySamples);

		// 5. [Optional] Use Landscpae Culling These are temporary copies of the culling data and should be deleted after use.
		FSimplygonDataConverter::PopulateLandscapeCullingGeometry(SimplygonAPI, SceneToProcess, Pipeline, StandinActor->GetWorld(), StandinActor->GetSourceBounds());
	
		if( bUseIncrediBuild )
		{
			// 6. Add to pipeline batch
			PipelineBatch->Queue(Pipeline, SceneToProcess);
			JobDataArray->Add(StandinJobData);
		}
		else
		{
			// 6. Execute Simplygon using the scene we just built
			UE_LOG(LogStandinUtilitiesModule, Log, TEXT("Processing Standin actor %s"), *StandinActor->GetFName().ToString());
			JobData.UserData = new FSimplygonStandinJobData(StandinJobData);
			SimplygonProcessingInterface->RunSceneAsync(SceneToProcess, Pipeline, JobData, JobDelegates.Progress, JobDelegates.Complete, JobDelegates.Failed);
		}

		++DataConvertionWorkComplete;
	}

	if( bUseIncrediBuild )
	{
		// Launch the batch processing
		UE_LOG(LogStandinUtilitiesModule, Log, TEXT("Processing Standin actors in batch"));
		SimplygonProcessingInterface->RunBatchAsync(PipelineBatch, JobData, JobDelegates.Progress, JobDelegates.BatchComplete, JobDelegates.Failed);
	}

	//Wait for complete
	FText SimplygonProgressText = LOCTEXT("StandinUtilities_GenerateStandinMeshes", "Generating standin meshes");
	int32 LastWorkProgress = 0;
	while (SimplygonWorkCompleted < SimplygonAmountOfWork)
	{
		if (SimplygonWorkProgress > LastWorkProgress)
		{
			int32 CurrentProgress = SimplygonWorkProgress;
			int32 Delta = CurrentProgress - LastWorkProgress;
			SlowTask.EnterProgressFrame((float)Delta / 100.0f, SimplygonProgressText);
			LastWorkProgress = CurrentProgress;
		}
		SimplygonProcessor->Tick(0.0f);
		FPlatformProcess::Sleep(0.1f);
	}
}

AStandInMeshActor* FStandinUtilitiesModule::GetStandinActor(AActor* InActor)
{
	UWorld* CurrentWorld = nullptr;
	for (const FWorldContext& Context : GEngine->GetWorldContexts())
	{
		if (Context.WorldType == EWorldType::PIE)
		{
			CurrentWorld = Context.World();
			break;
		}
		else if (Context.WorldType == EWorldType::Editor)
		{
			CurrentWorld = Context.World();
		}
	}

	if (CurrentWorld)
	{
		for (ULevel* Level : CurrentWorld->GetLevels())
		{
			// Only handling visible levels (this is to allow filtering the HLOD outliner per level, should change when adding new sortable-column)
			if (Level->bIsVisible)
			{
				for (AActor* Actor : Level->Actors)
				{
					if (Actor)
					{
						AStandInMeshActor* ReplacementActor = Cast<AStandInMeshActor>(Actor);

						if (ReplacementActor)
						{
							if (ReplacementActor->GetSourceActors().Contains(InActor))
							{
								return ReplacementActor;
							}
						}
					}
				}
			}
		}
	}

	return nullptr;
}

const bool FStandinUtilitiesModule::AreActorsInSamePersistingLevel(const TArray<AActor*>& InActors)
{
	ULevel* Level = nullptr;
	bool bSameLevelInstance = true;
	for (AActor* Actor : InActors)
	{
		if (Level == nullptr)
		{
			Level = Actor->GetLevel();
		}

		bSameLevelInstance &= (Level == Actor->GetLevel());

		if (!bSameLevelInstance)
		{
			break;
		}
	}

	return bSameLevelInstance;
}

const bool FStandinUtilitiesModule::AreActorsAlreadyBeingReplaced(const TArray<AActor*>& InActors)
{
	ULevel* Level = nullptr;
	bool bAlreadyBeingReplaced = true;
	for (AActor* Actor : InActors)
	{
		bAlreadyBeingReplaced &= (GetStandinActor(Actor) != nullptr);

		if (!bAlreadyBeingReplaced)
		{
			break;
		}
	}

	return bAlreadyBeingReplaced;
}

void FStandinUtilitiesModule::BuildStandinMeshActors(bool bRebuild)
{
	UWorld* CurrentWorld = nullptr;
	for (const FWorldContext& Context : GEngine->GetWorldContexts())
	{
		if (Context.WorldType == EWorldType::PIE)
		{
			CurrentWorld = Context.World();
			break;
		}
		else if (Context.WorldType == EWorldType::Editor)
		{
			CurrentWorld = Context.World();
		}
	}

	if (CurrentWorld)
	{
		ISimplygonPluginUtilitiesModule& SimplygonPluginUtilities = FModuleManager::Get().LoadModuleChecked<ISimplygonPluginUtilitiesModule>("SimplygonPluginUtilities");
		TArray<AStandInMeshActor*> StandinActorsToProcess;

		for (ULevel* Level : CurrentWorld->GetLevels())
		{
			// Only handling visible levels (this is to allow filtering the HLOD outliner per level, should change when adding new sortable-column)
			if (Level->bIsVisible)
			{
				for (AActor* Actor : Level->Actors)
				{
					if (Actor)
					{
						AStandInMeshActor* StandinActor = Cast<AStandInMeshActor>(Actor);

						if (StandinActor)
						{
							if (StandinActor->IsDirty() || bRebuild)
							{
								StandinActorsToProcess.Add(StandinActor);
								//SimplygonPluginUtilities.GetUtilities().GenerateStandinMesh(StandinActor);
							}
						}
					}
				}
			}
		}

		GenerateStandinMeshes(StandinActorsToProcess);
	}
}

AStandInMeshActor* FStandinUtilitiesModule::CreateStandinMeshActor(const TArray<AActor*>& InActors)
{

	if( InActors.Num() == 0 )
	{
		UE_LOG( LogStandinUtilitiesModule, Warning, TEXT(" Number of actors need to be greater than 0" ) );

		return nullptr;
	}

	

	FBox SummedBoundingBoxes(EForceInit::ForceInitToZero);
	TArray<ULevel*> UniqueLevels;

	UWorld* World = NULL;
	for (auto Actor : InActors)
	{
		if (!World)
		{
			World = Actor->GetWorld();
		}
		SummedBoundingBoxes += Actor->GetComponentsBoundingBox(true);
		UniqueLevels.AddUnique(Actor->GetLevel());
	}

	if (!World)
	{
		UE_LOG(LogStandinUtilitiesModule, Log, TEXT("Could not create Standin actor"));
		return nullptr;
	}
	bool bCanCreateStandin = Algo::AnyOf(InActors, [&](AActor* InActor) { return IsActorValidForStandinInclusion(InActor); });

	if (!bCanCreateStandin)
	{
		UE_LOG(LogStandinUtilitiesModule, Log, TEXT("Standin can not be created from actor selected. Only StaticMeshActors allowed."));
		return nullptr;
	}

	AStandInMeshActor* StandinActor = CreateNewStandinMeshActor(World);
	StandinActor->PreEditChange(NULL);

	for (auto Actor : InActors)
	{
		if (!IsActorValidForStandinInclusion(Actor))
			continue;

		TInlineComponentArray<UStaticMeshComponent*> Components;
		Actor->GetComponents(Components);
		bool ShouldRemoveActor = true;
		for (auto& Component : Components)
		{
			USimplygonAssetUserData* SimplygonUserData = Component->GetAssetUserData<USimplygonAssetUserData>();
			if (SimplygonUserData)
			{
				ShouldRemoveActor = !(SimplygonUserData->MetaTag == ESimplygonMetaTagType::Occluder
					|| SimplygonUserData->MetaTag == ESimplygonMetaTagType::ClippingGeometry
					|| SimplygonUserData->MetaTag == ESimplygonMetaTagType::GeometryCameraPath);
				break;
			}
		}

		if(ShouldRemoveActor)
			RemoveActorFromStandinActor(Actor);

		StandinActor->AddSubActor(Actor);
	}

	FTelemetryBuilder Builder;
	Builder.SetProperties({
		FTelemetry::Prop(TEXT("num_sourceactors"),StandinActor->GetSourceActors().Num())
		});
	FTelemetry::Record(TEXT("CreateStandinMeshActor"), SGTC::Standin, MoveTemp(Builder));


	StandinActor->PostEditChange();

	return  StandinActor;
}

AStandInMeshActor* FStandinUtilitiesModule::CreateNewStandinMeshActor(UWorld* InWorld)
{
	// Check incoming data
	check(InWorld != nullptr);

	/*FActorSpawnParameters Params;
	Params.OverrideLevel = UniqueLevels[0];
	Params.ObjectFlags = RF_Transactional | RF_Standalone | RF_Public;*/

	// Spawn and setup actor
	AStandInMeshActor* NewActor = nullptr;
	NewActor = InWorld->SpawnActor<AStandInMeshActor>(AStandInMeshActor::StaticClass(), FTransform());
	NewActor->SetStaticMesh(nullptr);
	NewActor->PostEditChange();
	return NewActor;
}

void FStandinUtilitiesModule::CompleteGenerateStandinMesh(const FSimplygonStandinJobData* StandinJobData, Simplygon::spPipeline Pipeline, Simplygon::spScene Scene)
{
	bool bIsSavingStandalonePackage = StandinJobData->Proxy == nullptr;
	if( !Scene )
		return;

	auto GetGeneratedGeometry = [&Scene]() -> Simplygon::spGeometryData
	{
		if (Scene.IsNull())
			return Simplygon::NullPtr;

		Simplygon::spSceneMesh ProxyMesh;
		int32 SelectionSetId = Scene->SelectNodes("ISceneMesh");
		Simplygon::spSelectionSet SelectedMeshNodes =
			Scene->GetSelectionSetTable()->GetSelectionSet(SelectionSetId);
		if (SelectedMeshNodes->GetItemCount() > 0)
			ProxyMesh = Simplygon::spSceneMesh::SafeCast(
				Scene->GetNodeByGUID(SelectedMeshNodes->GetItem(0)));

		if (ProxyMesh.IsNull())
			return Simplygon::NullPtr;

		return ProxyMesh->GetGeometry();

	};

	FBox Box( EForceInit::ForceInit );
	for (const auto Actor : StandinJobData->StandinMeshActor->GetSourceActors()) {
		
		Box += Actor->GetComponentsBoundingBox();
	}

	FVector ObjectCenter = Box.GetCenter(); //Worldspace
	FTransform ObjectCenterTransfrom = FTransform::Identity;
	ObjectCenterTransfrom.SetTranslation( ObjectCenter );

	Simplygon::spGeometryData GeneratedGeometry = GetGeneratedGeometry();
	if (GeneratedGeometry.IsNull())
	{
		UE_LOG(LogStandinUtilitiesModule, Error, TEXT("Standin mesh generation failed for %s"), *StandinJobData->StandinMeshActor->GetFName().ToString());
		return;
	}

	bool bHasBakedMaterials = false;

	Simplygon::spObjectCollection MaterialCasters = Pipeline ? Pipeline->GetMaterialCasters() : Simplygon::NullPtr;
	bHasBakedMaterials = !MaterialCasters.IsNull() && MaterialCasters->GetItemCount() > 0;

	// Convert simplygon geometry data to mesh description
	FMeshDescription OutProxyMesh;
	FStaticMeshAttributes(OutProxyMesh).Register();
	auto CreateMeshDescription =
	    [ &OutProxyMesh, &GeneratedGeometry, &StandinJobData, &bHasBakedMaterials, &bIsSavingStandalonePackage, &ObjectCenterTransfrom ]()
	{
		TMap<int32, FName> EmptyMaterialMap;
		FSimplygonDataConverter::CreateMeshDescriptionFromGeometry(GeneratedGeometry, EmptyMaterialMap, OutProxyMesh, bHasBakedMaterials);

		//Reset pivot if we are saving to standalone package
		if (bIsSavingStandalonePackage)
		{
			//Get bounds
			const FBox& Bounds = StandinJobData->StandinMeshActor->GetSourceBounds();

			// Create a transform from bounds min as to adjust the pivot
			FStaticMeshOperations::ApplyTransform(OutProxyMesh, ObjectCenterTransfrom.Inverse());
		}
		return true;
	}();

	FString AssetBaseName = FPackageName::GetShortName(StandinJobData->AssetName);
	FString AssetBasePath = StandinJobData->Outer ? TEXT("") : FPackageName::GetLongPackagePath(StandinJobData->BasePackageName) + TEXT("/");

	if (bIsSavingStandalonePackage)
	{
		AssetBaseName = FPackageName::GetShortName(StandinJobData->BasePackageName);
		AssetBasePath = FPackageName::GetLongPackagePath(StandinJobData->BasePackageName) + TEXT("/");
	}

	TArray<UObject*> AssetsToSync;
	UMaterialInstanceConstant* ProxyMaterial = nullptr;
	if (bHasBakedMaterials)
	{
		FFlattenMaterial BakedMaterial;
		FSimplygonDataConverter::CreateFlattenMaterial(Scene, BakedMaterial);
		
		// Get material template based on pipeline.
		FString BaseMaterialTemplate = FSimplygonDataConverter::GetPipelineBakedMaterialBaseTemplatePath( Pipeline );

		// Create a new proxy material instance
		UMaterialInterface* BaseMaterial = LoadObject<UMaterialInterface>(NULL, *BaseMaterialTemplate, NULL, LOAD_None, NULL);
		
		FSimplygonFlattenMaterialSettings FlattenMaterialSettings(Pipeline);
		FlattenMaterialSettings.BlendMode = BakedMaterial.BlendMode;
		//ProxyMaterial = ProxyMaterialUtilities::CreateProxyMaterialInstance(StandinJobData->Outer, FlattenMaterialSettings, BaseMaterial, BakedMaterial, AssetBasePath, AssetBaseName, AssetsToSync);
		ProxyMaterial = FSimplygonDataConverter::CreateProxyMaterialInstance(StandinJobData->Outer, FlattenMaterialSettings, BaseMaterial, BakedMaterial, AssetBasePath, AssetBaseName, AssetsToSync);

		// Set material static lighting usage flag if project has static lighting enabled
		static const auto AllowStaticLightingVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.AllowStaticLighting"));
		const bool bAllowStaticLighting = (!AllowStaticLightingVar || AllowStaticLightingVar->GetValueOnGameThread() != 0);
		if (bAllowStaticLighting)
		{
			ProxyMaterial->CheckMaterialUsage(MATUSAGE_StaticLighting);
		}
	}

	// Construct proxy static mesh
	UPackage* MeshPackage = StandinJobData->Outer;
	FString MeshAssetName = TEXT("SM_") + AssetBaseName;
	if (MeshPackage == nullptr)
	{
		MeshPackage = CreatePackage(*(AssetBasePath + MeshAssetName));
		MeshPackage->FullyLoad();
		MeshPackage->Modify();
	}

	FStaticMeshComponentRecreateRenderStateContext RecreateRenderStateContext(FindObject<UStaticMesh>(MeshPackage, *MeshAssetName));

	UStaticMesh* StaticMesh = NewObject<UStaticMesh>(MeshPackage, FName(*MeshAssetName), RF_Public | RF_Standalone);
	StaticMesh->InitResources();

	// make sure it has a new lighting guid
	StaticMesh->SetLightingGuid();

	// Set it to use textured lightmaps. Note that Build Lighting will do the error-checking (texcoordindex exists for all LODs, etc).
	StaticMesh->SetLightMapResolution(64);//Data->Data->InProxySettings.LightMapResolution;
	StaticMesh->SetLightMapCoordinateIndex(1);
	
	//avoid nav mesh update
	StaticMesh->MarkAsNotHavingNavigationData();

	FStaticMeshSourceModel& SrcModel = StaticMesh->AddSourceModel();
	/*Don't allow the engine to recalculate normals*/
	SrcModel.BuildSettings.bRecomputeNormals = false;
	SrcModel.BuildSettings.bRecomputeTangents = false;
	SrcModel.BuildSettings.bRemoveDegenerates = true;
	SrcModel.BuildSettings.bUseHighPrecisionTangentBasis = false;
	SrcModel.BuildSettings.bUseFullPrecisionUVs = false;
	SrcModel.BuildSettings.bGenerateLightmapUVs = false;
	SrcModel.BuildSettings.bBuildReversedIndexBuffer = false;
	//SrcModel.BuildSettings.bBuildAdjacencyBuffer = false;
	SrcModel.BuildSettings.DistanceFieldResolutionScale = 0.0f;	//set to not compute distance field by default.

	TPolygonGroupAttributesConstRef<FName> PolygonGroupMaterialSlotName = OutProxyMesh.PolygonGroupAttributes().GetAttributesRef<FName>(MeshAttribute::PolygonGroup::ImportedMaterialSlotName);

	//Commit the FMeshDescription to the source model we just created
	int32 SourceModelIndex = StaticMesh->GetNumSourceModels() - 1;
	FMeshDescription* MeshDescription = StaticMesh->CreateMeshDescription(SourceModelIndex);
	if (ensure(MeshDescription))
	{
		*MeshDescription = OutProxyMesh;

		// Make sure the Proxy material have a valid ImportedMaterialSlotName
		//The proxy material must be add only once and is always the first slot of the HLOD mesh
		if (!bHasBakedMaterials)
		{
			for (const FPolygonGroupID PolygonGroupID : MeshDescription->PolygonGroups().GetElementIDs())
			{
				UMaterialInterface* MaterialInterface = StandinJobData->OriginalMaterials[PolygonGroupID.GetValue()];
				FStaticMaterial NewMaterial(MaterialInterface);
				NewMaterial.ImportedMaterialSlotName = PolygonGroupMaterialSlotName[PolygonGroupID];
				StaticMesh->GetStaticMaterials().Add(NewMaterial);
			}
		}
		else
		{
			FStaticMaterial NewMaterial(ProxyMaterial);
			if (MeshDescription->PolygonGroups().Num() > 0)
			{
				NewMaterial.ImportedMaterialSlotName = PolygonGroupMaterialSlotName[MeshDescription->PolygonGroups().GetFirstValidID()];
			}
			StaticMesh->GetStaticMaterials().Add(NewMaterial);
		}
		StaticMesh->CommitMeshDescription(SourceModelIndex);
	}

	//Set the Imported version before calling the build
	StaticMesh->ImportVersion = EImportStaticMeshVersion::LastVersion;

	// setup section info map
	TPolygonGroupAttributesConstRef<FName> PolygonGroupImportedMaterialSlotNames = OutProxyMesh.PolygonGroupAttributes().GetAttributesRef<FName>(MeshAttribute::PolygonGroup::ImportedMaterialSlotName);
	TArray<int32> UniqueMaterialIndices;
	for (const FPolygonGroupID& PolygonGroupID : OutProxyMesh.PolygonGroups().GetElementIDs())
	{
		int32 PolygonGroupMaterialIndex = PolygonGroupID.GetValue();
		FName PolygonGroupName = PolygonGroupImportedMaterialSlotNames[PolygonGroupID];
		if (PolygonGroupName != NAME_None)
		{
			for (int32 MaterialIndex = 0; MaterialIndex < StaticMesh->GetStaticMaterials().Num(); ++MaterialIndex)
			{
				if (StaticMesh->GetStaticMaterials()[MaterialIndex].ImportedMaterialSlotName == PolygonGroupName)
				{
					PolygonGroupMaterialIndex = MaterialIndex;
					break;
				}
			}
		}
		if (!StaticMesh->GetStaticMaterials().IsValidIndex(PolygonGroupMaterialIndex))
		{
			PolygonGroupMaterialIndex = 0;
		}
		UniqueMaterialIndices.AddUnique(PolygonGroupMaterialIndex);
	}

	int32 SectionIndex = 0;
	for (int32 UniqueMaterialIndex : UniqueMaterialIndices)
	{
		FMeshSectionInfo MeshSectionInfo(UniqueMaterialIndex);

		// enable/disable section collision according to settings
		MeshSectionInfo.bEnableCollision = true;

		StaticMesh->GetSectionInfoMap().Set(0, SectionIndex, MeshSectionInfo);
		SectionIndex++;
	}

	StaticMesh->Build();
	StaticMesh->PostEditChange();
	
	if (!bIsSavingStandalonePackage)
	{
		bool bDirtyPackage = false;
		UStaticMesh* PreviousStaticMesh = StandinJobData->StandinMeshActor->GetStaticMeshComponent()->GetStaticMesh();
		bDirtyPackage |= (PreviousStaticMesh != StaticMesh);

		UStandinProxyContainer* PreviousProxy = StandinJobData->StandinMeshActor->GetProxyContainer();

		//Note: update pipeline has after standin is generated. 
		StandinJobData->StandinMeshActor->UpdateBuildKey(true);
		StandinJobData->StandinMeshActor->SetStaticMesh(StaticMesh);
		const FName BuildKey = StandinJobData->StandinMeshActor->GetBuildKey();
		TArray<UTexture2D*> BakedTextureList;
		for (auto& Asset : AssetsToSync)
		{
			UTexture2D* Texture = Cast<UTexture2D>(Asset);
			if (Texture)
				BakedTextureList.Add(Texture);
		}
		StandinJobData->Proxy->AddStandinProxy(StandinJobData->StandinMeshActor, StaticMesh, ProxyMaterial, BakedTextureList, BuildKey);

		bDirtyPackage = (StandinJobData->StandinMeshActor->GetProxyContainer() != PreviousProxy);

		if (bDirtyPackage)
		{
			StandinJobData->StandinMeshActor->MarkPackageDirty();
		}
		ClearFlagFromAssetsInPackage(StandinJobData->Proxy->GetOutermost(), RF_Standalone);
	}
	
	CollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS);
}

void FStandinUtilitiesModule::CompleteGenerateStandinMeshes(const FSimplygonJobData& InJobData, Simplygon::spPipelineBatch InPipelineBatch)
{
	TArray<FSimplygonStandinJobData>* JobDataArray = (TArray<FSimplygonStandinJobData>*)InJobData.UserData;
	Simplygon::ISimplygon* SimplygonAPI = ISimplygonModule::Get().GetSDK(true);
	if (SimplygonAPI == nullptr)
		return;

	for(unsigned int ipipeline = 0; ipipeline < InPipelineBatch->GetBatchCount(); ++ipipeline)
	{
		FSimplygonStandinJobData* StandinJobData = &((*JobDataArray)[ipipeline]);

		Simplygon::spSceneImporter SceneImporter = SimplygonAPI->CreateSceneImporter();
		SceneImporter->SetImportFilePath(InPipelineBatch->GetOutputPath(ipipeline));
		SceneImporter->RunImport();

		Simplygon::spScene Scene = SceneImporter->GetScene();
		if(Scene.IsNull())
		{
			UE_LOG(LogStandinUtilitiesModule, Error, TEXT("Standin generation failed for %s"), *StandinJobData->StandinMeshActor->GetFName().ToString());
			continue;
		}

		Simplygon::spPipelineSerializer PipelineSerializer = SimplygonAPI->CreatePipelineSerializer();
		Simplygon::spPipeline Pipeline = PipelineSerializer->LoadPipelineFromFile(InPipelineBatch->GetPipelinePath(ipipeline));

		CompleteGenerateStandinMesh(StandinJobData, Pipeline, Scene);
	}
}

const bool FStandinUtilitiesModule::RemoveActorFromStandinActor(AActor* InActor)
{
	checkf(InActor != nullptr, TEXT("Invalid InActor"));

	bool bSucces = false;
	
	AStandInMeshActor* StandinActor = GetStandinActor(InActor);
	
	if (StandinActor != nullptr)
	{
		bSucces = StandinActor->RemoveSubActor(InActor);

		if (!StandinActor->HasAnySubActors())
		{
			DestroyStandinActor(StandinActor);
		}

		//TODO: 
		//Update Editor window
		//StandInsOutlinerWindow->HandleForceRefresh();
	}
	
	return bSucces;
}

void FStandinUtilitiesModule::GroupUnderStandinActor()
{
	const FScopedTransaction Transaction(LOCTEXT("StandinVolume_EncapsualteActors", "Encapsualte Actors"));

	TArray<AActor*> ActorsContainingStaticMeshComponents;

	TArray<ULevel*> UniqueLevels;

	if (GetSelectedActorsByComponent<UStaticMeshComponent>(ActorsContainingStaticMeshComponents))
	{
		FBox SummedBoundingBoxes(EForceInit::ForceInitToZero);
		UWorld* World = NULL;
		for (auto Actor : ActorsContainingStaticMeshComponents)
		{
			if (!World)
			{
				World = Actor->GetWorld();
			}
			SummedBoundingBoxes += Actor->GetComponentsBoundingBox(true);
			UniqueLevels.AddUnique(Actor->GetLevel());
		}

		if (!World)
		{
			UE_LOG(LogStandinUtilitiesModule, Log, TEXT("Could not create volume: No world was found"));
			return;
		}

		FActorSpawnParameters Params;
		Params.OverrideLevel = UniqueLevels[0];
		Params.ObjectFlags = RF_Transactional | RF_Standalone | RF_Public;
		TWeakObjectPtr<AStandInMeshActor> StandinActor = World->SpawnActor<AStandInMeshActor>(AStandInMeshActor::StaticClass(), FTransform(SummedBoundingBoxes.GetCenter()), Params);
		StandinActor->GetStaticMeshComponent()->SetStaticMesh(nullptr);

		StandinActor->PreEditChange(NULL);

		for (auto Actor : ActorsContainingStaticMeshComponents)
		{
			StandinActor->AddSubActor(Actor);
		}

		StandinActor->PostEditChange();

		//Select the new Standin volume
		GEditor->SelectNone(false, true, false);
		GEditor->SelectActor(StandinActor.Get(), true, true);

	}
}

bool FStandinUtilitiesModule::IsActorValidForStandinInclusion(AActor* Actor)
{
	if (!Actor)
		return false;

	if (Actor->IsHidden())
		return false;

	if (!Actor->bEnableAutoLODGeneration)
		return false;
	   
	FVector Origin, Extent;
	Actor->GetActorBounds(false, Origin, Extent);
	if (Extent.SizeSquared() <= 0.1)
	{
		return false;
	}

	// fetch static mesh components
	TArray<UStaticMeshComponent*> Components;
	Actor->GetComponents<UStaticMeshComponent>(Components);

	Components.RemoveAll([](UStaticMeshComponent* Val) 
		{ 
			return 
				Val->GetStaticMesh() == nullptr
				|| Val->GetLODParentPrimitive()
				|| Val->bHiddenInGame;
		});
	

	int32 ValidComponentCount = 0;
	bool bHasValidStaticMeshCompoent = Algo::AnyOf(Components, [](UStaticMeshComponent* Val) { return Val->ShouldGenerateAutoLOD(0); });
		
	return bHasValidStaticMeshCompoent;
}

void FStandinUtilitiesModule::AddActorsToStandinActor(AStandInMeshActor* InReplacementActor, const TArray<AActor*>& InSelectedActors)
{
	for (AActor* ActorToAdd : InSelectedActors)
	{
		InReplacementActor->AddSubActor(ActorToAdd);
	}
}

void FStandinUtilitiesModule::AddActorToStandinActor(AActor* InActor, AStandInMeshActor* InParentActor)
{
	checkf(InActor != nullptr, TEXT("Invalid InActor"));
	checkf(InParentActor != nullptr, TEXT("Invalid InParentActor"));

	TInlineComponentArray<UStaticMeshComponent*> Components;
	InActor->GetComponents(Components);
	bool ShouldRemoveActor = true;
	for (auto& Component : Components)
	{
		USimplygonAssetUserData* SimplygonUserData = Component->GetAssetUserData<USimplygonAssetUserData>();
		if (SimplygonUserData)
		{
			ShouldRemoveActor = !(SimplygonUserData->MetaTag == ESimplygonMetaTagType::Occluder
				|| SimplygonUserData->MetaTag == ESimplygonMetaTagType::ClippingGeometry
				|| SimplygonUserData->MetaTag == ESimplygonMetaTagType::GeometryCameraPath);
			break;
		}
	}

	// First, if it is the case remove the actor from it's current cluster
	if(ShouldRemoveActor)
		RemoveActorFromStandinActor(InActor);

	// Now add it to the new one
	const FScopedTransaction Transaction(LOCTEXT("UndoAction_AddActorToCluster", "Add Actor To Cluster"));
	InParentActor->Modify();
	InActor->Modify();

	// Add InActor to InParentActor cluster
	InParentActor->AddSubActor(InActor);

}

void FStandinUtilitiesModule::MergeStandinActors(AStandInMeshActor* ToActor, AStandInMeshActor* FromActor)
{
	checkf(ToActor != nullptr && ToActor->GetSourceActors().Num() > 0, TEXT("Invalid ToActor"));
	checkf(FromActor != nullptr && FromActor->GetSourceActors().Num() > 0, TEXT("Invalid FromActor"));

	const FScopedTransaction Transaction(LOCTEXT("UndoAction_MergeReplacmeentActors", "Merge ReplacementActors"));
	ToActor->Modify();
	FromActor->Modify();
	FromActor->GetProxyContainer()->RemoveActorMesh(FromActor);

	while (FromActor->GetSourceActors().Num())
	{
		AActor* SubActor = FromActor->GetSourceActors().Last();
		AddActorToStandinActor(SubActor, ToActor);
	}

	if (!FromActor->HasAnySubActors())
	{
		DestroyStandinActor(FromActor);
	}

}

void FStandinUtilitiesModule::RemoveActorsFromStandinActor(AStandInMeshActor* InReplacementActor,const TArray<AActor*>& InSelectedActors)
{
	for (AActor* ActorToRemove : InSelectedActors)
	{
		InReplacementActor->RemoveSubActor(ActorToRemove);
	}

	if (!InReplacementActor->HasAnySubActors())
	{
		DestroyStandinActor(InReplacementActor);
	}
}

bool FStandinUtilitiesModule::DestroyStandinActor(AStandInMeshActor* InActor)
{
	checkf(InActor != nullptr, TEXT("Invalid InActor"));


	AActor* Actor = CastChecked<AActor>(InActor);
	UWorld* World = Actor->GetWorld();

	const FScopedTransaction Transaction(LOCTEXT("UndoAction_DeleteCluster", "Deleting a (invalid) Cluster"));
	Actor->Modify();
	World->Modify();

	//Reset visibility
	InActor->SetIsVisibleInEditor(true, true);

	// Clean out sub actors and update their LODParent
	while (InActor->GetSourceActors().Num())
	{
		AActor* SubActor = InActor->GetSourceActors()[0];
		SubActor->Modify();
		InActor->RemoveSubActor(SubActor);
	}

	{
		
#if WITH_EDITOR
		// Close possible open editors using this asset	
		auto StaticMesh = InActor->GetStaticMesh();
		auto Proxy = InActor->GetProxyContainer();
		if(StaticMesh)
			GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->CloseAllEditorsForAsset(StaticMesh->GetOuter());
#endif // WITH_EDITOR
		InActor->GetStaticMeshComponent()->SetStaticMesh(nullptr);
		if(Proxy)
			Proxy->RemoveActorMesh(InActor);
		
	}

	FTelemetry::Record(TEXT("DestroyStandinActor"), SGTC::Standin);


	//All flags has been clear, so lets force the garbage collector 
	CollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS);
	return World->DestroyActor(InActor);
}

void FStandinUtilitiesModule::GetStandinActors(TArray<AStandInMeshActor*>& OutActors)
{
	UWorld* CurrentWorld = nullptr;
	for (const FWorldContext& Context : GEngine->GetWorldContexts())
	{
		if (Context.WorldType == EWorldType::PIE)
		{
			CurrentWorld = Context.World();
			break;
		}
		else if (Context.WorldType == EWorldType::Editor)
		{
			CurrentWorld = Context.World();
		}
	}

	if (CurrentWorld)
	{
		for (ULevel* Level : CurrentWorld->GetLevels())
		{
			// Only handling visible levels (this is to allow filtering the HLOD outliner per level, should change when adding new sortable-column)
			if (Level->bIsVisible)
			{
				for (AActor* Actor : Level->Actors)
				{
					if (Actor)
					{
						AStandInMeshActor* StandinActor = Cast<AStandInMeshActor>(Actor);

						if (StandinActor)
						{
							OutActors.Add(StandinActor);
						}
					}
				}
			}
		}
	}
}


UStandinProxyContainer* FStandinUtilitiesModule::GetStandinProxyContainer(const ULevel* InLevel)
{
	checkf(InLevel != nullptr, TEXT("Invalid Level supplied"));
	FString StandinProxyName;
	const FString StandinProxyPackageName = GetStandinProxyName(InLevel, StandinProxyName);
	UPackage* StandinProxyPackage = FindPackage(nullptr, *StandinProxyPackageName);
	if (StandinProxyPackage)
	{
		StandinProxyPackage->FullyLoad();
		return FindObject<UStandinProxyContainer>(StandinProxyPackage, *StandinProxyName);
	}

	return nullptr;
}

UPackage* FStandinUtilitiesModule::GetStandinProxyContainerPackage(const ULevel* InLevel)
{
	UStandinProxyContainer* Proxy = GetStandinProxyContainer(InLevel);
	if (Proxy)
	{
		return Proxy->GetOutermost();
	}
	return nullptr;
}

void FStandinUtilitiesModule::SavePackagesForStandins() 
{
	TArray<UPackage*> PackagesToSave;
	TArray<FString> OldLevelNames;

	bool bUnsavedLevel = false;
	const TArray<ULevel*>& Levels = GWorld->GetLevels();
	for (const ULevel* Level : Levels)
	{
		// Levels might also need a resave, or levels might not have been saved yet
		PackagesToSave.Add(Level->GetOutermost());
		OldLevelNames.Add(Level->GetOutermost()->GetName());
		bUnsavedLevel |= Level->GetOutermost()->GetName().StartsWith("/Temp/");
	}

	bool bSuccess = true;

	// Save levels first if they are in the /Temp/ mount point
	if (bUnsavedLevel)
	{
		bSuccess = UEditorLoadingAndSavingUtils::SavePackagesWithDialog(PackagesToSave, true);
	}

	if(bSuccess)
	{
		check(PackagesToSave.Num() == PackagesToSave.Num() && PackagesToSave.Num() == Levels.Num());

		TSet<UPackage*> StandinPackagesToSave;
		for (int32 PackageIndex = 0; PackageIndex < PackagesToSave.Num(); ++PackageIndex)
		{
			FString PreviousLevelName;
			bool bLevelRenamed = bUnsavedLevel && PackagesToSave[PackageIndex]->GetName() != OldLevelNames[PackageIndex];
			if (bLevelRenamed)
			{
				PreviousLevelName = OldLevelNames[PackageIndex];
			}

			ULevel* Level = Levels[PackageIndex];
			StandinPackagesToSave.Add(Level->GetOutermost());
			GetListOfPackagesToSaveForStandins(Level, StandinPackagesToSave, PreviousLevelName);
		}

		TArray<UPackage*> PackagesToSaveArray = StandinPackagesToSave.Array();
		UEditorLoadingAndSavingUtils::SavePackagesWithDialog(PackagesToSaveArray, true);
	}
}

void FStandinUtilitiesModule::GetListOfPackagesToSaveForStandins(const ULevel* Level, TSet<UPackage*>& PackagesToSave, const FString& PreviousLevelName)
{
	TArray<AStandInMeshActor*> OutActors;
	GetStandinActors(OutActors);

	OutActors.RemoveAll([Level](const AStandInMeshActor* InActor) { return InActor->GetLevel() != Level; });

	for (auto Actor : OutActors)
	{
		UPackage* PackageOuter = GetStandinProxyContainerPackage(Level);
		if (PackageOuter)
		{
			PackagesToSave.Add(PackageOuter);
		}
		else if (PreviousLevelName.Len() != 0)
		{
			// If we couldn't find the package, the level may have been renamed, 
			// so we need to relocate our old package before saving it.
			FString NewLevelName = Level->GetOutermost()->GetName();

			FString OldProxyName = GetStandinProxyNameLevel(PreviousLevelName);
			UStandinProxyContainer* OldProxyContainer = FindObject<UStandinProxyContainer>(nullptr, *OldProxyName);
			if (OldProxyContainer)
			{
				FString NewProxyName = GetStandinProxyNameLevel(NewLevelName);
				FString OldPackageName = FPackageName::ObjectPathToPackageName(OldProxyName);
				FString NewPackageName = FPackageName::ObjectPathToPackageName(NewProxyName);
				UPackage* OldPackage = FindObject<UPackage>(nullptr, *OldPackageName);
				if (OldPackage)
				{
					OldProxyContainer->Rename(*FPackageName::ObjectPathToObjectName(NewProxyName), OldPackage, REN_NonTransactional | REN_DontCreateRedirectors);
					OldPackage->Rename(*NewPackageName, nullptr, REN_NonTransactional | REN_DontCreateRedirectors);

					PackagesToSave.Add(OldPackage);

					// Mark the level package as dirty as we have changed export locations, and without a resave we will not pick up
					// packages when reloaded.
					Level->GetOutermost()->MarkPackageDirty();
				}
			}
		}
	}
}


#undef LOCTEXT_NAMESPACE
