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

#pragma once

#include "CoreMinimal.h"
#include "UObject/ObjectMacros.h"
#include "UObject/Object.h"
#include "PerPlatformProperties.h"
#include "SimplygonUE4UI.h"
#include "LODRecipeMiscSettings.h"
#include "PerPlatformProperties.h"

#if WITH_EDITOR
#if PLATFORM_WINDOWS
THIRD_PARTY_INCLUDES_START
#include "Windows\AllowWindowsPlatformTypes.h"
#include "Simplygon.h"
#include "Windows\HideWindowsPlatformTypes.h"
THIRD_PARTY_INCLUDES_END
#endif // PLATFORM_WINDOWS
#endif

#include "LODRecipe.generated.h"

class UStaticMesh;
class USkeletalMesh;
struct FSimplygonJobData;
struct FSimplygonLODRecipeJobData;
struct FFlattenMaterial;

/**
 * A collection of Static & Skeletal mesh along with processing settings for each LOD Level
*/
UCLASS(Blueprintable, BlueprintType, hidecategories = (Object))
class LODRECIPE_API ULODRecipe : public UObject
{
	GENERATED_UCLASS_BODY()

protected:
	/* Static Meshes that will use this LODRecipe.*/
	UPROPERTY( VisibleAnywhere, BlueprintReadWrite, Category = Assets )
	TArray<TSoftObjectPtr<UStaticMesh>> StaticMeshes;
	
	/* Skeletal Meshes that will use this LODRecipe.*/
	UPROPERTY( VisibleAnywhere, BlueprintReadWrite, Category = Assets )
	TArray<TSoftObjectPtr<USkeletalMesh>> SkeletalMeshes;

	/* Number of LODs this Recipe will generate.*/
	UPROPERTY( EditAnywhere, BlueprintReadWrite, Category = LevelOfDetail, meta = ( DisplayName = "Number of LODs", ClampMin = "1", ClampMax = "8", UIMin = "1", UIMax = "8" ) )
	int32 NumLODs;

	/* Per LOD settings which maps to Simplygon API pipeline settings */
	UPROPERTY( EditAnywhere, Instanced, BlueprintReadWrite, Category = LevelOfDetail, meta = ( DisplayName = "Simplygon Settings", EditFixedOrder ) )
	TArray<USimplygonPipeline*> PerLODPipelineSettings;

	/* Per LOD settings which are unique for the UE4 plugin*/
	UPROPERTY( EditAnywhere, BlueprintReadWrite, Category = LevelOfDetail, meta = ( DisplayName = "Miscellaneous  Settings", EditFixedOrder ) )
	TArray<FLODRecipeMiscSettings> PerLODMiscSettings;

	/* If non-negative, specify the maximum number of streamed LODs.
	* Only has effect if mesh LOD streaming is enabled for the target platform. */
	UPROPERTY()
	FPerPlatformInt NumStreamedLODs;

	/* Key */
	UPROPERTY()
	FGuid Key;

public:

	/**
	 * @brief Serialize object.
	 * @param Ar The serialization backend.
	*/
	virtual void Serialize(FArchive& Ar) override;

#if WITH_EDITORONLY_DATA
	virtual void PostDuplicate(bool bDuplicateForPIE) override;
#endif

#if WITH_EDITOR
	virtual void PostLoad() override;
	
	/**
	 * Constructor, initializing counter to passed in value.
	 *
	 * @param Mesh Value to initialize counter to
	 */
	void AddMesh(TSoftObjectPtr<UObject> Mesh);

	/**
	 * Removes a UStaticMesh or USkeletalMesh from the recipe.
	 * @param Mesh a TSoftObjectPtr<UObject> to static or skeletal mesh to remove.
	 */
	void RemoveMesh(TSoftObjectPtr<UObject> Mesh);
	
	/**
	 *  Removes all UStaticMeshes from the recipe.
	 */
	void RemoveAllStaticMeshes();
	
	/**
	 *  Removes all USkeletalMeshes from the recipe.
	 */
	void RemoveAllSkeletalMeshes();
	
	/**
	 *  Removes all USkeletalMeshes & UStaticMeshes from the recipe.
	 */
	void RemoveAllMeshes();

	/**
	 *  Get Unique ID.
	 */
	const FGuid& GetKey() const { return Key; }

	/**
	 *  Gets all Static Meshes
	 *  @return an TArray of TSoftObjectPtr<UStaticMesh>
	 */
	TArray<TSoftObjectPtr<UStaticMesh>> GetStaticMeshes() const;
	
	/**
	 *  Gets all SKeletal Meshes
	 *  @return an TArray of TSoftObjectPtr<USkeletalMesh>
	 */
	TArray<TSoftObjectPtr<USkeletalMesh>> GetSkeletalMeshes() const;

	/**
	 *  Get number of LODs
	 *  @return Number of LODs
	 */
	int32 GetNumLODs() const { return NumLODs; };

	/**
	 *  Set number of LODs
	 *  @param NewNumLODs - Number of LODs that LODRecipe should generate.
	 *  Note: 1 corresponds to no LODs i.e only LOD0
	 */
	void SetNumLODs(const int32 NewNumLODs) { NumLODs = FMath::Clamp(NewNumLODs, 1, (int32)MAX_MESH_LOD_COUNT); }
	
	/**
	 *  Gets all Meshes
	 *  @return  an TArray of TSoftObjectPtr<UObject>
	 */
	TArray<TSoftObjectPtr<UObject>> GetAllMeshes() const;

	const TArray<USimplygonPipeline*>& GetPipelines() const;

	/**
	 * Creates a pipeline ready to be processed by RunSceneAsync()
	 * @param SDK - Simplygon API pointer fetched from ISimplygonModule.
	 * @param OutFlattenMaterialSettings - Returns flatten material settings needed 
	 * for material baking. This is only valid if the pipeline contains casters and 
	 * valid mapping image settings.
	 * @return A Passthrough Pipeline where each children is a pipeline corresponding to a LOD.
	 */
	Simplygon::spPipeline CreatePipeline(Simplygon::ISimplygon* SDK, struct FSimplygonFlattenMaterialSettings& OutFlattenMaterialSettings) const;

	/**
	 * Specify whether Morph Targets are allowed in LOD or not.
	 * Morph Targets are remapped based on LOD 0.
	 * @param bAllowMorphTargets - Set to true if LOD should be remapped based on LOD 0
	 * @param LODIndex - Which LOD should be allowed to have morph targets
	*/
	void AllowMorphTargetsInLOD(const bool bAllowMorphTargets, const int32 LODIndex);

	/**
	 * Check if Morph Targets are allowed for a specific LOD.
	 * @param LODIndex - Specify which LOD to check
	*/
	bool IsMorphTargetsAllowedInLOD(const int32 LODIndex) const;

	/**
	 * Check if any of the LODs are allowed to have morph targets.
	 * @return True, if any of the LODs are allowed to have Morph Targets.
	*/
	bool IsMorphTargetsAllowed() const;

	/**
	 * Check if a Morph Target is already removed from LOD
	 * @param MorphTarget - Specify Morph Target to check
	 * @param LODIndex - Specify which LOD to check
	 * @return True, if morph target is already removed.
	*/
	bool IsMorphTargetRemovedInLOD(const UMorphTarget* MorphTarget, const int32 LODIndex) const;

	/**
	 * Specify morph target that should be removed when remapping morph target to LOD.
	 * By default all morph targets are included when LOD is allowed to have morph targets.
	 * See AllowMorphTargetsInLOD()
	 * @param MorphTarget - Morph target to remove when remapping to LOD
	 * @param LODIndex - Specify for which LOD the morph target should be removed from.
	*/
	void RemoveMorphTargetInLOD(const UMorphTarget* MorphTarget, const int32 LODIndex);

	/**
	 * Specify morph target that should be included when remapping morph target to LOD.
	 * By default all morph targets are included when LOD is allowed to have morph targets.
	 * @param MorphTarget - Morph target to include when remapping to LOD
	 * @param LODIndex - Specify for which LOD the morph target should be included in.
	*/
	void IncludeMorphTargetInLOD(const UMorphTarget* MorphTarget, const int32 LODIndex);

	/**
	 *  Process Completed Callback
	 * 
	 *  @param StaticMesh The mesh that was processed.
	 *  @param Pipeline the Simplygon pipeline object used to process. 
	 *  @param CascadedScene the cascaded output scene from the processing.
	*/
	void ProcessCompleted(UStaticMesh* StaticMesh, const FSimplygonLODRecipeJobData& LODRecipeJobData, const Simplygon::spPipeline& Pipeline, const Simplygon::spScene& CascadedScene);
	
	/**
	 *  Process Completed Callback
	 *  @param LODRecipe The LOD recipe used to process.
	 *  @param SkeletalMesh Skeletal Mesh after LOD process is completed.
	 *  @param Pipeline the Simplygon pipeline object used to process. 
	*/
	void ProcessSkeletalMeshCompleted(USkeletalMesh* SkeletalMesh, const FSimplygonLODRecipeJobData& LODRecipeJobData, const Simplygon::spPipeline& Pipeline, const Simplygon::spScene& CascadedScene);
			

	DECLARE_EVENT_OneParam(ULODRecipe, FNewAssetsCreatedEvent, TArray<UObject*>*);
	static FNewAssetsCreatedEvent& OnNewAssetsCreated() { return NewAssetsCreatedEvent; }

	void ApplyStreamedLODsToMeshes(bool bPostEditChange);
	void ApplyStreamedLODs(UStaticMesh* StaticMesh, bool bPostEditChange);
	void ApplyStreamedLODs(USkeletalMesh* SkeletalMesh, bool bPostEditChange);

	void
	SetupPerLODMaterialIds( Simplygon::spScene& Scene, TMap<int32, FName>& MaterialNameMap, Simplygon::spSceneMesh& ReducedMesh, TArray<int32>& OutSectionIndices );

	void SetupPerLODMaterialIds( Simplygon::spScene& Scene,
	                         TMap<int32, FName>& MaterialNameMap,
	                         Simplygon::spGeometryData& ProcessedGeometry,
	                         TArray<int32>& OutSectionIndices );
	
#endif
	friend class FLODRecipeDetailsCustomization;
	friend class SAllowMorphTargetsList;
	friend class SAllowMorphTargetsPerLOD;
	friend class SAllowMorphTargetsMeshView;

private:
#if WITH_EDITOR
	void ApplyBakedMaterials(UStaticMesh* StaticMesh, const int32 LODIndex, FFlattenMaterial& BakedMaterial, const Simplygon::spPipeline& Pipeline, const TArray<int32>& SectionIndices);
	void ApplyBakedMaterials(USkeletalMesh* SkeletalMesh, const int32 LODIndex, FFlattenMaterial& BakedMaterial, const Simplygon::spPipeline& Pipeline);

	void TriggerPropertyChanged(const FName PropertyName, EPropertyChangeType::Type PropertyChangeType);

	static FNewAssetsCreatedEvent NewAssetsCreatedEvent;
#endif
};

