// © 2024 Long Ly. All rights reserved. Any unauthorized use, reproduction, or distribution of this trademark is strictly prohibited and may result in legal action.

#pragma once

#include "CoreMinimal.h"
#include "AnimNode_SlotCustom.h"
#include "Data/OLSMaskAnimationData.h"
#include "AnimNode_OLSMask.generated.h"

/**
 * 
 */

USTRUCT(BlueprintInternalUseOnly)
struct FOLSWeightCurves
{
	GENERATED_BODY()

public:
	
	UPROPERTY(BlueprintReadWrite, EditAnywhere, Category=Config)
	FName OverrideCurve = NAME_None;

	UPROPERTY(BlueprintReadWrite, EditAnywhere, Category=Config)
	FName AdditiveCurve = NAME_None;

	UPROPERTY(BlueprintReadWrite, EditAnywhere, Category=Config)
	FName LocalSpaceBlendCurve = NAME_None;

	UPROPERTY(BlueprintReadWrite, EditAnywhere, Category=Config)
	FName MeshSpaceBlendCurve = NAME_None;
};

USTRUCT(BlueprintInternalUseOnly)
struct FOLSMaskSettings
{
	GENERATED_BODY()

	UPROPERTY(BlueprintReadWrite, EditAnywhere, Category=Config)
	FName BlendProfileName = NAME_None;

	UPROPERTY(BlueprintReadWrite, EditAnywhere, Category=Config)
	bool bUseMeshSpaceAdditive = false;

	UPROPERTY(BlueprintReadWrite, EditAnywhere, Category=Config)
	bool bShouldAddSlot = false;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=Config)
	TEnumAsByte<ECurveBlendOption::Type> CurveBlendOption = ECurveBlendOption::Override;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=Config)
	FOLSWeightCurves WeightCurves;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=Config)
	FName SlotName = FAnimSlotGroup::DefaultSlotName;

	// Transient properties should be declared at the end
	UPROPERTY(Transient)
	TObjectPtr<class UBlendProfile> BlendProfile = nullptr;

	// Slot To Add If Needed
	FAnimNode_SlotCustom Slot;
};

USTRUCT(BlueprintInternalUseOnly)
struct FOLSPerBoneWeights
{
	GENERATED_BODY()

public:
	
	// Constructor to reserve memory for the arrays.
	// Assuming using Unreal's mannequin.
	FOLSPerBoneWeights()
	{
		LocalSpaceDesiredBoneBlendWeights.Reserve(32);
		MeshSpaceDesiredBoneBlendWeights.Reserve(32);
		LocalSpaceCurrentBoneBlendWeights.Reserve(32);
		MeshSpaceCurrentBoneBlendWeights.Reserve(32);
		LocalCurvePoseSourceIndices.Reserve(32);
		MeshCurvePoseSourceIndices.Reserve(32);
	}

public:
	
	UPROPERTY()
	TArray<FPerBoneBlendWeight> LocalSpaceDesiredBoneBlendWeights;

	UPROPERTY()
	TArray<FPerBoneBlendWeight> MeshSpaceDesiredBoneBlendWeights;

	UPROPERTY()
	TArray<FPerBoneBlendWeight> LocalSpaceCurrentBoneBlendWeights;

	UPROPERTY()
	TArray<FPerBoneBlendWeight> MeshSpaceCurrentBoneBlendWeights;

	// Using a more memory-efficient representation for the curves
	TBaseBlendedCurve<FDefaultAllocator, UE::Anim::FCurveElementIndexed> LocalCurvePoseSourceIndices;
	TBaseBlendedCurve<FDefaultAllocator, UE::Anim::FCurveElementIndexed> MeshCurvePoseSourceIndices;
};

USTRUCT(BlueprintInternalUseOnly)
struct FOLSPerBoneBlendWeights
{
	GENERATED_BODY()

public:
	
	UPROPERTY()
	TArray<FPerBoneBlendWeight> PerBoneBlendWeights;
};

USTRUCT(BlueprintInternalUseOnly)
struct OLSANIMATION_API FAnimNode_OLSMask : public FAnimNode_Base
{
	GENERATED_BODY()
	
public:
	FAnimNode_OLSMask()
		: bHasRelevantPoses(false)
		, LODThreshold(INDEX_NONE)
		, RequiredBonesSerialNumber(0)
	{
	}

	// FAnimNode_Base interface
	virtual void Initialize_AnyThread(const FAnimationInitializeContext& context) override;
	virtual void CacheBones_AnyThread(const FAnimationCacheBonesContext& context) override;
	virtual void Update_AnyThread(const FAnimationUpdateContext& Context) override;
	virtual void Evaluate_AnyThread(FPoseContext& output) override;
	virtual int32 GetLODThreshold() const override { return LODThreshold; }

	// Invalidate the cached per-bone blend weights from the skeleton
	void InvalidatePerBoneBlendWeights();
	
	// Invalidates the cached bone data so it is recalculated the next time this node is updated
	void InvalidateCachedBoneData();
	
	// Rebuild cache per bone blend weights from the skeleton
	void RebuildPerBoneBlendWeights(const USkeleton* skeleton);

	// Check whether per-bone blend weights are valid according to the skeleton (GUID check)
	bool ArePerBoneBlendWeightsValid(const USkeleton* skeleton) const;

	// Update cached data if required
	void UpdateCachedBoneData(const FBoneContainer& requiredBones, const USkeleton* skeleton);

	void UpdateBodyPartCachedBoneData(const FBoneContainer& requiredBones, const USkeleton* skeleton, const int32 maskIndex);

	void UpdateWeightsFromCurves(const USkeleton* skeleton, const FPoseContext& desiredCurvesPose, const int32 index);

	static void MakeAdditivePose(FPoseContext& outAdditivePose,FPoseContext& outBasePose, const bool shouldUseMeshSpaceAdditive);

public:
	
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=Links)
	FPoseLink AnimationPose;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=Links)
	FPoseLink MaskPose;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=Links)
	FPoseLink BaseAdditivePose;
	
	UPROPERTY(EditDefaultsOnly,BlueprintReadWrite, Category=Config)
	TArray<FOLSMaskSettings> BlendMasks;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=Runtime, meta=(PinShownByDefault))
	TArray<FOLSNodeMaskWeights> MaskBlendWeights;

	uint8 bHasRelevantPoses : 1;

	/*
	 * Max LOD that this node is allowed to run
	 * For example if you have LODThreadhold to be 2, it will run until LOD 2 (based on 0 index)
	 * when the component LOD becomes 3, it will stop update/evaluate
	 * currently transition would be issue and that has to be re-visited
	 */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Performance, meta = (DisplayName = "LOD Threshold"))
	int32 LODThreshold = 0;

	// transient data to handle weight and target weight
	// this array changes based on required bones
	UPROPERTY()
	TArray<FOLSPerBoneWeights> AllBoneBlendWeights;

protected:
	
	// Per-bone weights for the skeleton. Serialized as these are only relative to the skeleton, but can potentially
	// be regenerated at runtime if the GUIDs dont match.
	UPROPERTY()
	TArray<FOLSPerBoneBlendWeights>	PerBoneBlendWeights;

	// Guids for skeleton used to determine whether the PerBoneBlendWeights need rebuilding
	UPROPERTY()
	FGuid SkeletonGuid;

	// Guid for virtual bones used to determine whether the PerBoneBlendWeights need rebuilding
	UPROPERTY()
	FGuid VirtualBoneGuid;
	
	// Serial number of the required bones container
	uint16 RequiredBonesSerialNumber : 1;

private:
	
	friend class UAnimGraphNode_KLSMask;
};
