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

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Components/StaticMeshComponent.h"
#include "SimplygonCustomPipelines.h"
#include "StandinActor.generated.h"

class UStandinProxyContainer;

/** Standin Mesh Actor are used to act as standin to exsting actors in your level
 *  they are meant to reduce drawcalls, tris and help with kitbashing workflows. 
 */
UCLASS(MinimalAPI, hidecategories = (Object, Collision, Display, Input, Blueprint, Physics, Actor, Transform))
class AStandInMeshActor : public AActor
{
	GENERATED_UCLASS_BODY()

	friend class UStandinProxyContainer;

protected:
	/** StaticMeshComponent where the generated standin geomerty will be palced.*/
	UPROPERTY(Category = Standin, VisibleAnywhere)
	UStaticMeshComponent* StaticMeshComponent;

#if	WITH_EDITORONLY_DATA
	/** Source actors used to generate StaticMeshComponent. */
	UPROPERTY(Category = Standin, VisibleAnywhere)
	TArray<AActor*> SubActors;

	/** Assets that were created for this, so that we can delete them. */
	//UPROPERTY(Category = Standin, VisibleAnywhere)
	//TArray<UObject*> SubObjects;

	/** The Simplygon pipeline used to build the standin. */
	UPROPERTY( EditInstanceOnly, BlueprintReadWrite, Instanced, Category = StandinSettings )
	USimplygonStandinPipeline* Pipeline;

	/** Build key to check if actor is invalidated and a build is requied. */
	UPROPERTY(Category = StandinActor, VisibleAnywhere)
	FName BuildKey;

	/** Unique ID */
	UPROPERTY()
	FGuid Id;

	/** Flag to store if actor is dirty and requires a rebuild. */
	UPROPERTY()
	bool bDirty;

	/** Fllag if actor is GameStandin. */
	UPROPERTY()
	bool bIsGameStandin;

	/** Package path where standin is saved. */
	UPROPERTY()
	FString PackagePath;

private:
	/** The standin mesh proxy used to display this LOD */
	UPROPERTY(Category = Standin, VisibleAnywhere)
	UStandinProxyContainer* Proxy;
#endif

public:
	/** BEGIN UObject Interface */
	virtual FString GetDetailedInfoInternal() const override;
	virtual bool NeedsLoadForClient() const override;
	virtual void Tick(float DeltaTime) override;
	virtual void PostLoad() override;
#if WITH_EDITOR
	virtual bool Modify(bool bAlwaysMarkDirty = true) override;
	virtual void PreEditChange(FProperty* PropertyThatWillChange) override;
	virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override;
	/** END UObject Interface */

	/** BEGIN AActor Interface */
	virtual bool GetReferencedContentObjects(TArray<UObject*>& Objects) const override;
#endif
	virtual FBox GetComponentsBoundingBox(bool bNonColliding = false, bool bIncludeFromChildActors = false) const override;
	virtual void PostRegisterAllComponents() override;
	/** END AActor Interface */

	STANDINACTOR_API FTransform GetComponentTransform()
	{ 
		return StaticMeshComponent->GetComponentTransform();
	}
#if WITH_EDITOR
	STANDINACTOR_API FBox GetSourceBounds()
	{
		FBox Result;
		Result.Init();
		for (auto Actor : SubActors)
		{
			FVector Origin ,BoxExtent;
			FBox Bounds;
			Actor->GetActorBounds(false, Origin, BoxExtent);
			Bounds = Bounds.BuildAABB(Origin, BoxExtent);
			Result += Bounds;
		}
		return Result;
	}

	/**
	 * @brief Get Static Mesh Component.
	 * @return Returns the static mesh component pointer or nullptr.
	*/
	UStaticMeshComponent* GetStaticMeshComponent() const { return StaticMeshComponent; }

	/**
	 * @brief Sets StaticMesh and IsPreviewActor to true if InStaticMesh equals nullptr.
	 * @param InStaticMesh StaticMesh to set.
	*/
	STANDINACTOR_API void SetStaticMesh(UStaticMesh* InStaticMesh);

	/**
	 * @brief Get Static Mesh.
	*/
	STANDINACTOR_API UStaticMesh* GetStaticMesh() const;

	/**
	 * @brief Get source actors that will be replaced by the standin.
	 * @return Returns and array of AActor pointers.
	*/
	TArray<AActor*> GetSourceActors() const { return SubActors; }
	
	STANDINACTOR_API const int GetSourceStaticMeshComponentCount() const;

	/**
	* Adds InAcor to the SubActors array and set its LODParent to this.
	* @param InActor - Actor to add
	*/
	STANDINACTOR_API void AddSubActor(AActor* InActor);

	/**
	* Removes InActor from the SubActors array and sets its LODParent to nullptr.
	* @param InActor - Actor to remove.
	*/
	STANDINACTOR_API const bool RemoveSubActor(AActor* InActor);

	USimplygonStandinPipeline* GetPipeline() const { return Pipeline; }

	/**
	* Returns whether or not this ReplacementActor is dirty.
	* @return Returns true if dirty.
	*/
	STANDINACTOR_API const bool IsDirty();

	/**
	* Sets whether or not this ReplacementActor is dirty and should have its LODMesh (re)build.
	* @param bNewState - New dirty state.
	*/
	STANDINACTOR_API void SetIsDirty(const bool bNewState);

	/**
	 * @brief Get the build key.
	 * @return Returns and FName.
	*/
	const FName& GetBuildKey() const { return BuildKey; }

	/**
	 * @brief Generates a BuildKey.
	 * @return Returns FName.
	*/
	FName GenerateBuildKey(bool bRegeneratePipelineHash = false) const;

	/**
	 * @brief UpdateBuildKey.
	*/
	STANDINACTOR_API void UpdateBuildKey(bool bRegeneratePipelineHash = false);

	/**
	* Determines whether or not this ReplacementActor has valid SubActors and can be built
	* @return true if the subactor(s) contain at least two static mesh components
	*/
	STANDINACTOR_API const bool HasValidSubActors() const;

	/**
	* Determines whether or not this ReplacementActor has any SubActors
	* @return true if it contains any subactors
	*/
	STANDINACTOR_API const bool HasAnySubActors() const;

	/** Cleans the SubActor array (removes all NULL entries) */
	STANDINACTOR_API void CleanupInvalidSourceActors();

	/** Cleans the SubObject array (removes all NULL entries) */
	void CleanSubObjectsArray();

	/**
	 * @brief Force a recache. 
	*/
	STANDINACTOR_API void ForceRecache();

	/**
	 * @brief Get Num Triangles for Standin.
	 * @return Number of triangles.
	*/
	STANDINACTOR_API int32 GetNumTriangles() const;

	/**
	 * @brief Get Num Triangles for SubActors.
	 * @return Number of triangles.
	*/
	STANDINACTOR_API int32 GetNumChildrenTriangles() const;

	/**
	 * @brief Gets package path.
	 * @return The path to package.
	*/
	const FString& GetPackgePath() { return PackagePath; }

	/**
	 * @brief Gets unique Id.
	 * @return Returns the guid.
	*/
	FGuid GetID() const { return Id; }


	/** Get the proxy mesh we use to render */
	UStandinProxyContainer* GetProxyContainer() const { return Proxy; }
#endif
	
	/**
	 * @brief A delegate which handles setup post mesh generation.
	*/
	DECLARE_EVENT(AStandInMeshActor, FStandInMeshGeneratedEvent);

#if WITH_EDITOR

	/**
	 * @brief Get the delegate to MeshGenerated event.
	 * @return Delegate to FStandInMeshGeneratedEvent.
	*/
	FStandInMeshGeneratedEvent& OnStanInMeshGenerated() { return StandInGeneratedEvent; }

	/**
	 * @brief Use as Game Standin
	 * @param bUseAsGameStandin If set to true will be a game standin.
	*/
	STANDINACTOR_API void UseAsGameStandin(bool bUseAsGameStandin);

	/**
	 * @brief Check if this standin is a game standin.
	 * @return Returns true if this is a game standin else false.
	*/
	bool IsGameStandin() { return bIsGameStandin; }

	/**
	 * @brief Sets standin as visible in editor.
	 * @param bStandinVisible If true standin is visible in editor.
	 * @param bStandinChildrenVisible If true standin children are visible in editor.
	*/
	STANDINACTOR_API void SetIsVisibleInEditor(bool bStandinVisible, bool bStandinChildrenVisible);

	/**
	 * @brief Sets standin as hideen in editor.
	 * @param bHide If true will hide in editor.
	*/
	STANDINACTOR_API void HiddenInEditor(bool bHide);
#endif

private:
	
	/**
	 * @brief This will determine the shadowing flags for the static mesh component according to all sub actors.
	*/
	void DetermineShadowingFlags();

	/**
	 * @brief Finalize Standin Mesh.
	*/
	void FinalizeStandInMesh();

#if WITH_EDITOR
	
	/**
	 * @brief Hide actor in game.
	 * @param Actor Actor to hide.
	 * @param Hide If ture actor would be hidden in game else visible.
	*/
	void HiddenInGame(AActor* Actor, bool Hide);

	/**
	 * @brief Hide Actor in editor
	 * @param Actor Actor to hide.
	 * @param Hide Hide If ture actor would be hidden in editor else visible.
	*/
	void HiddenInEditor(AActor* Actor, bool Hide);

	/**
	 * @brief Generate a hash (CRC) of the Simplygon pipeline settings. Can be used to identify if settings has changed.
	*/
	uint32 GeneratePipelineHash() const;
#endif
	
private:
#if WITH_EDITOR
	/**
	 * @brief Last time the build key was updated.
	*/
	double BuildKeyLastUpdateTime;

	/**
	 * @brief Current state of the build key. E.g True if settings has changed.
	*/
	bool bIsBuildKeyDirty;

	/**
	 * @brief Triangle count.
	*/
	int32 TriangleCount;

	/**
	 * @brief Get sub actor triangle count.
	*/
	int32 SubActorsTriangleCount;

	/**
	 * @brief Standin in generated event.
	*/
	FStandInMeshGeneratedEvent StandInGeneratedEvent;

	/**
	 * @brief Hashed Simplygon pipeline settings, utilized when generating a standin build key
	*/
	mutable uint32 PipelineHash;

	struct FMemoryUsageInfo
	{
		float SizeInBytes = 0.f;
		int32 NumTextures = 0;
		int32 NumMaterials = 0;
		float TexturesSizeInBytes = 0.f;
		float MaterialsSizeInBytes = 0.f;

		void Reset()
		{
			SizeInBytes = 0.f;
			NumTextures = 0;
			NumMaterials = 0;
			TexturesSizeInBytes = 0.f;
			MaterialsSizeInBytes = 0.f;
		}
	};

	/**
	 * @brief Tracks Memeoy Info for Standin
	*/
	FMemoryUsageInfo MemoryUsedByStandin;

	/**
	 * @brief Tracks Memeoy Info for Source Actors
	*/
	FMemoryUsageInfo MemoryUsedBySourceActors;

public:


	/**
	 * @brief Update memory usage estimates
	*/
	void UpdateMemoryInfo();

	/**
	 * @brief Get estimated memory use for standin excluding source actors
	*/
	const float GetSelfSize() const { return MemoryUsedByStandin.SizeInBytes; }

	/**
	 * @brief Get estimated memory used by source actors
	*/
	const float GetSourceSize() const { return MemoryUsedBySourceActors.SizeInBytes; }

#endif
};
