#ifdef IMPOSTER_BAKER
#include "ImpostorGeneratorHelperActor.h"
#include "Camera/CameraComponent.h"
#include "Components/ArrowComponent.h"
#include "Components/DrawFrustumComponent.h"
#include "Components/StaticMeshComponent.h"
#include "Engine/World.h"
#include "Engine/StaticMeshActor.h"
#include "Editor.h"
#include "Editor/UnrealEdEngine.h"
#include "UnrealEdGlobals.h"
#include "Algo/Copy.h"
#include "Algo/AnyOf.h"
#include "Kismet/GameplayStatics.h"
#include "SimplygonAssetUserData.h"
#include "SimplygonDataConverter.h"
#include "SimplygonEditorModule.h"

DEFINE_LOG_CATEGORY_STATIC(LogImposterGeneratorHelperActor, Verbose, All);


//////////////////////////////////////////////////////////////////////////
// AImpostorGeneratorHelperActor

AImpostorGeneratorHelperActor::AImpostorGeneratorHelperActor(const FObjectInitializer& ObjectInitializer)
	: Super(ObjectInitializer)
{
	SceneComponent = CreateDefaultSubobject<USceneComponent>(TEXT("SceneComponent"));

	// Make the scene component the root component
	RootComponent = SceneComponent;
	
	// Setup camera defaults
	CameraComponent = CreateDefaultSubobject<UCameraComponent>(TEXT("CameraComponent"));
	CameraComponent->FieldOfView = 90.0f;
	CameraComponent->bConstrainAspectRatio = true;
	CameraComponent->AspectRatio = 1.0f;
	CameraComponent->PostProcessBlendWeight = 1.0f;
	CameraComponent->SetIsVisualizationComponent(true);
	CameraComponent->SetHiddenInGame(true);
	CameraComponent->SetFieldOfView(90.f);
	CameraComponent->SetAspectRatio(1.f);
	CameraComponent->SetupAttachment(SceneComponent);
	
	
	// Setup Direction
	CameraDirection = CreateDefaultSubobject<UArrowComponent>(TEXT("ArrowComponent"));
	CameraDirection->SetupAttachment(CameraComponent);
	CameraDirection->SetIsVisualizationComponent(true);
	CameraDirection->SetHiddenInGame(true);
		
	ImpostorPipeline = CreateDefaultSubobject<UImpostorFromSingleViewPipeline>(TEXT("DefaultImporterPipeline"));
	
	
	MaterialPropertyCasters.Empty();
	Size.X = ImpostorPipeline->Settings.MappingImageSettings.OutputMaterialSettings[0].TextureWidth;
	Size.Y = ImpostorPipeline->Settings.MappingImageSettings.OutputMaterialSettings[0].TextureHeight;
	Algo::Copy(ImpostorPipeline->Settings.MaterialPropertyCasters, MaterialPropertyCasters);


	FString DefaultPath = FPaths::ProjectContentDir() + TEXT("Simplygon/Assets");
	PackagePath = FPackageName::FilenameToLongPackageName(DefaultPath + TEXT("/") + GetName());

}



USceneComponent* AImpostorGeneratorHelperActor::GetDefaultAttachComponent() const
{
	return CameraComponent;
}

void AImpostorGeneratorHelperActor::PostLoadSubobjects(FObjectInstancingGraph* OuterInstanceGraph)
{
	/*OuterInstanceGraph->AddNewInstance()
	if(!ImpostorPipeline)
		ImpostorPipeline = CreateDefaultSubobject<UImpostorFromSingleViewPipeline>(TEXT("DefaultImporterPipeline"));*/
}

#if WITH_EDITOR
void AImpostorGeneratorHelperActor::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
{
	Super::PostEditChangeProperty(PropertyChangedEvent);
}

void AImpostorGeneratorHelperActor::CopySettingsToPipeline()
{
	ImpostorPipeline->Settings.MaterialPropertyCasters.Empty();
	ImpostorPipeline->Settings.MappingImageSettings.OutputMaterialSettings[0].TextureWidth = Size.X;
	ImpostorPipeline->Settings.MappingImageSettings.OutputMaterialSettings[0].TextureHeight = Size.Y;
	Algo::Copy(MaterialPropertyCasters, ImpostorPipeline->Settings.MaterialPropertyCasters);
}

bool AImpostorGeneratorHelperActor::IsValidPipeline()
{
	CopySettingsToPipeline();

	return ImpostorPipeline != nullptr 
		&& ImpostorPipeline->Settings.MaterialPropertyCasters.Num() > 0 
		&& ImpostorPipeline->Settings.MappingImageSettings.OutputMaterialSettings[0].TextureWidth > 16
		&& ImpostorPipeline->Settings.MappingImageSettings.OutputMaterialSettings[0].TextureHeight > 16;
}

void AImpostorGeneratorHelperActor::Update()
{

	if (!ImpostorPipeline)
		return;
	
	FTransform Transform = FTransform::Identity;// GetTransform();
	FMinimalViewInfo ViewInfo;
	CameraComponent->GetCameraView(FApp::GetDeltaTime(), ViewInfo);

	
	
	FMatrix ProjectionMatrix = ViewInfo.CalculateProjectionMatrix();
		
	FMatrix ViewProjectMatrix;
	FMatrix ProjMatrix;
	FMatrix ViewMatrix;
	
	UGameplayStatics::CalculateViewProjectionMatricesFromMinimalView(ViewInfo, ProjectionMatrix, ViewMatrix, ProjMatrix, ViewProjectMatrix);

	FConvexVolume FrustumVolume;
	FPlane FarPlane(FPlane::BackwardVector, 10.0f);
	GetViewFrustumBounds(FrustumVolume, ViewProjectMatrix, FarPlane, false, false);

	FRotator Rotation = ViewMatrix.Rotator();
	FVector ForwardVectorWorldSpace = CameraComponent->GetForwardVector();
	FVector ForwardVectorViewSpace = Rotation.RotateVector(ForwardVectorWorldSpace);
	ForwardVectorWorldSpace = FSimplygonDataConverter::VectorToSimplygon(ForwardVectorWorldSpace);
	ImpostorPipeline->Settings.ImpostorFromSingleViewSettings.ViewDirection = ForwardVectorWorldSpace;
	

	ActorsToBake.Empty();
	
	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)
	{
		TArray<AActor*> Actors;
		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)
			{
				Algo::CopyIf(Level->Actors, Actors, [](const AActor* InActor) { return InActor != nullptr && InActor->IsA<AStaticMeshActor>(); });
				Actors.RemoveAll([](const AActor* InActor) { return !InActor->IsRootComponentStatic() || InActor->bIsEditorOnlyActor || InActor->bIsEditorPreviewActor; });
				TInlineComponentArray<UStaticMeshComponent*> OutComponents;
				
				for (AActor* Actor : Actors)
				{
					Actor->GetComponents(OutComponents);
					bool ExcludeActor = Algo::AnyOf(OutComponents, [](UStaticMeshComponent* InComponent) {				
						USimplygonAssetUserData* SimplygonUserData = InComponent->GetAssetUserData<USimplygonAssetUserData>();
						return SimplygonUserData && SimplygonUserData->MetaTag == ESimplygonMetaTagType::ExcludeFromImposterGeneration;
						});

					if (ExcludeActor)
						continue;

					if (FrustumVolume.IntersectBox(Actor->GetRootComponent()->Bounds.Origin, Actor->GetRootComponent()->Bounds.BoxExtent))
					{
						ActorsToBake.Add( Cast<AStaticMeshActor>(Actor));
					}

					
				}
			}

		}
	}

	CopySettingsToPipeline();
}
#endif
#endif

