/***************************************************************************
*                                                                          *
*  Copyright (C) Golaem S.A.  All Rights Reserved.                         *
*                                                                          *
***************************************************************************/

#pragma once

#include "glmSDKAPI.h"
#include "glmMotionDefinitions.h"

#include "glmEntity.h"
#include "glmAbstractMotionBehavior.h"
#include "glmBlendTree.h"

namespace glm
{
	class Simulation;
	class MotionEntityComponent;
	class MotionContext;
	class AimAtBN;
	class IKBN;
	class GroundAdaptationBN;
	class AdditiveBlendBN;
	class SimpleBlendBN;
	class MassSpringBN;

	/**
	* \brief Class handling motion behaviors control upon blend tree (used by of MotionEntityComponent only)
	*
	* The MotionBehaviorsHandler is responsible for building the blend tree used by MotionEntityComponent, with the following default structure:
	*
	*     ----------------
	*     | MassSpringBN |
	*     ----------------
	*           ^                                                                                               / ...
	*     -----------     --------     ----------------------     -------------------      -----------------  /
	*     | AimAtBN | <-- | IKBN | <-- | GroundAdaptationBN | <-- | AdditiveBlendBN | <-+- | SimpleBlendBN | <-- ...
	*     -----------     --------     ----------------------     -------------------   |  -----------------  \
	*                                                                                   |                      \ ...
	*                                                                                   +- ...
	*                                                                                   |
	*                                                                                   +- ...
	*
	* The MotionBehaviorsHandler registers only motion behaviors managing a custom blend tree sub-part that need to be connected to a dedicated
	* node in blend tree (PlayAnimationBehavior using standard blend or LocomotionBehavior for instance). The blend nodes handled by those behaviors
	* are connected to the blend tree through a simple blend node: the blend weights applied then vary according to the priorities of the registered
	* motion behaviors that currently running (evaluation is made each time one of those behaviors starts or stops).
	*
	* The same process is applied to motion behaviors that needs to be connected to blend tree through an additive blend node (PlayAnimationBehavior
	* using additive blend for instance). The blend weights applied to  then vary according to the priorities of the registered
	* motion behaviors that currently running (evaluation is made each time one of those behaviors starts or stops).
	*
	* Other motion behaviors (such as AimAtBehavior) do not affect the blend tree structure and directly controls the default blend nodes that
	* can be directly accessed.
	*/
	class GLM_SDK_API MotionBehaviorsHandler
	{

	public:

		GLM_CLASS_STAT_ID( glm::MemoryStatIds::Simulation);


		//************************************************************
		//! @name Blend Weights Configuration Interpolator
		//************************************************************
		//@{
		class BlendWeightsConfigInterpolatorArray
		{
		public:

			GLM_CLASS_STAT_ID( glm::MemoryStatIds::Simulation);

			glm::PODArray<float> _currentConfig; // was behaviorCount sized, now weightCount * behaviorCount sized. use getCurrentConfig(weightIndex)
			glm::PODArray<float> _targetConfig; // was behaviorCount sized, now weightCount * behaviorCount sized. use getTargetConfig(weightIndex)
			glm::PODArray<float> _targetConfigIssueDate; // access via _targetConfigIssueDate[weightIndex];
#ifndef NDEBUG
			int _weightCount; // only for checking
			int _behaviorCount; // only for checking
#endif

			BlendWeightsConfigInterpolatorArray();
			void setup(const int weightCount, const int behaviorCount);
			void clear();

		};

		class MotionBehaviorsHandlerDataPerEntity
		{
		public:

			GLM_CLASS_STAT_ID( glm::MemoryStatIds::Simulation);

			MotionBehaviorsHandlerDataPerEntity();
			~MotionBehaviorsHandlerDataPerEntity();

			//************************************************************
			//! @name Behaviors Events
			//************************************************************
			//@{
			glm::PODArray<AbstractMotionBehavior*> _standardBlendStartingBehaviors;
			glm::PODArray<AbstractMotionBehavior*> _standardBlendStoppingBehaviors;

			glm::PODArray<AbstractMotionBehavior*> _additiveBlendStartingBehaviors;
			glm::PODArray<AbstractMotionBehavior*> _additiveBlendStoppingBehaviors;
			//@}

			//************************************************************
			//! @name Blend Configuration
			//************************************************************
			//@{
			BlendWeightsConfigInterpolatorArray _boneStandardBlendWeightsConfigInterpolator;
			BlendWeightsConfigInterpolatorArray _boneAdditiveBlendWeightsConfigInterpolator;
			BlendWeightsConfigInterpolatorArray _blindDataStandardBlendWeightsConfigInterpolator;
			BlendWeightsConfigInterpolatorArray _blindDataAdditiveBlendWeightsConfigInterpolator;

			//glm::Array<BlendWeightsConfigInterpolator> _boneStandardBlendWeightsConfigInterpolator ;
			//glm::Array<BlendWeightsConfigInterpolator> _boneAdditiveBlendWeightsConfigInterpolator ;
			//glm::Array<BlendWeightsConfigInterpolator> _blindDataStandardBlendWeightsConfigInterpolator ;
			//glm::Array<BlendWeightsConfigInterpolator> _blindDataAdditiveBlendWeightsConfigInterpolator ;
			//@}

			//************************************************************
			//! @name Behaviors Index
			//************************************************************
			//@{
			glm::PODArray<bool> _behaviorStandardBlendIndexEnabled;
			glm::PODArray<bool> _behaviorAdditiveBlendIndexEnabled;
			//@}

			bool _hasSomeBoneBlendWeight;
		};

	public:
		MotionBehaviorsHandler();
		~MotionBehaviorsHandler();

		const BlendTree* getBlendTree() const { return _blendTree; }
		AimAtBN* touchAimAtBlendNode() const;
		IKBN *touchIKBlendNode() const;
        MassSpringBN *touchMassSpringBlendNodePostIK() const;
		MassSpringBN *touchMassSpringBlendNodePreIK() const;
		GroundAdaptationBN* touchGroundAdaptationBlendNode() const;
		AdditiveBlendBN* touchSubBlendTreeAdditiveConnectionNode() const;
		SimpleBlendBN* touchSubBlendTreeStandardConnectionNode() const;

		float getBehaviorCurrentWeight(const AbstractMotionBehavior* motionBehavior, int64_t indexInEntityType, int bone) const;

		bool hasRegisteredBehaviors() const;
		void registerStandardBlendBehavior(AbstractMotionBehavior* motionBehavior);
		void registerAdditiveBlendBehavior(AbstractMotionBehavior* motionBehavior);
		inline const glm::PODArray<AbstractMotionBehavior*>& getStandardBlendBehaviors() const;
		inline const glm::PODArray<AbstractMotionBehavior*>& getAdditiveBlendBehaviors() const;

		void onBehaviorStartStandardBlend(AbstractMotionBehavior* behavior, int64_t indexInEntityType);
		void onBehaviorStopStandardBlend(AbstractMotionBehavior* behavior, int64_t indexInEntityType);
		void onBehaviorStartAdditiveBlend(AbstractMotionBehavior* behavior, int64_t indexInEntityType);
		void onBehaviorStopAdditiveBlend(AbstractMotionBehavior* behavior, int64_t indexInEntityType);

		int getBoneBlendWeightCount() const;
		int getBlindDataBlendWeightCount() const;

		// Fill blend tree according to declared behaviors (requires an empty unitialized blend tree with an initialzed skeleton set)
		void init(int entityTypeId, BlendTree& blendTree, Simulation* simulation);
		bool isInitialized() const { return _initialized; }

		// Clear blend tree (requires a finished blend tree)
		void finish();

		// Initialize MotionBehaviorsHandlerDataPerEntity
		void initEntity(int64_t indexInEntityType);

		// Compute and update blend weights according to starting/stopping behaviors priorities
		void updateEntity(Time dt, int64_t indexInEntityType);

		// Clear MotionBehaviorsHandlerDataPerEntity
		void finishEntity(int64_t indexInEntityType);

		bool hasBoneBlendWeight(int64_t entityIndex);

	private:
		//int _entityIndexInBehaviorGraph ;
		bool _initialized;

		Simulation* _simulation;
		int _entityTypeIndex;

		//************************************************************
		//! @name Blend Tree
		//************************************************************
		//@{
		BlendTree* _blendTree;
		int _boneBlendWeightsCount;
		int _blindDataBlendWeightsCount;
		AimAtBN* _aimAtBlendNode;
		IKBN* _IKBlendNode;
		MassSpringBN* _massSpringBlendNodePostIK;
        MassSpringBN* _massSpringBlendNodePreIK;
		GroundAdaptationBN* _groundAdaptationBlendNode;
		AdditiveBlendBN* _subBlendTreeAdditiveConnectionNode;
		SimpleBlendBN * _subBlendTreeStandardConnectionNode;
		//@}

		glm::PODArray<AbstractMotionBehavior*> _standardBlendBehaviors;
		glm::PODArray<AbstractMotionBehavior*> _additiveBlendBehaviors;

		//************************************************************
		//! @name Blend Configuration Update
		//************************************************************
		//@{
		void setStandardBlendBoneWeight(int64_t entityIndex, const AbstractBlendNode* subNode, float weight, int bone);
		void setAdditiveBlendBoneWeight(int64_t entityIndex, const AbstractBlendNode* subNode, float weight, int bone);
		float getStandardBlendBoneWeight(int64_t entityIndex, const AbstractBlendNode* subNode, int bone) const;
		float getAdditiveBlendBoneWeight(int64_t entityIndex, const AbstractBlendNode* subNode, int bone) const;

		void setStandardBlindDataWeight(int64_t entityIndex, const AbstractBlendNode* subNode, float weight, int blindData);
		void setAdditiveBlindDataWeight(int64_t entityIndex, const AbstractBlendNode* subNode, float weight, int blindData);
		float getStandardBlindDataWeight(int64_t entityIndex, const AbstractBlendNode* subNode, int blindData) const;
		float getAdditiveBlindDataWeight(int64_t entityIndex, const AbstractBlendNode* subNode, int blindData) const;

		bool isBoneActive(int64_t entityIndex, const AbstractMotionBehavior* behavior, int bone) const;
		bool isBlindDataActive(int64_t entityIndex, const AbstractMotionBehavior* behavior, int blendShape) const;

		void computeTargetBlendConfig(
			const PODArray<AbstractMotionBehavior*>& behaviors,
			BlendWeightsConfigInterpolatorArray& configInterpolators,
			int weightsCount,
			bool (MotionBehaviorsHandler::*isActivePointer)(int64_t entityIndex, const AbstractMotionBehavior*, int) const,
			float (MotionBehaviorsHandler::*weightGetterPointer)(int64_t entityIndex, const AbstractBlendNode*, int) const,
			const glm::PODArray<int>& startingBehaviorsIDs,
			const glm::PODArray<AbstractMotionBehavior*>& startingBehaviors,
			const glm::PODArray<AbstractMotionBehavior*>& stoppingBehaviors,
			const glm::PODArray<bool>& enabled,
			int64_t indexInEntityType
		);

		void updateCurrentBlendConfig(
			const PODArray<AbstractMotionBehavior*>& behaviors,
			BlendWeightsConfigInterpolatorArray& configInterpolators,
			int weightsCount,
			void (MotionBehaviorsHandler::*weightSetterPointer)(int64_t entityIndex, const AbstractBlendNode*, float, int),
			float dt,
			int64_t indexInEntityType
		);

		static void collectIDs(
			const PODArray<AbstractMotionBehavior*>& subsetBehaviors,
			const PODArray<AbstractMotionBehavior*>& fullsetBehaviors,
			glm::PODArray<int>& subsetIndices);

		static void setEnables(const glm::PODArray<int>& indexSet, glm::PODArray<bool>& enables, const bool enable);
		//@}

		glm::Array<MotionBehaviorsHandlerDataPerEntity> _dataPerEntity;

		friend class AbstractMotionBehavior;
	};

	//-------------------------------------------------------------------------
	inline AimAtBN* MotionBehaviorsHandler::touchAimAtBlendNode() const
	{
		return _aimAtBlendNode;
	}

	//-------------------------------------------------------------------------
	inline IKBN* MotionBehaviorsHandler::touchIKBlendNode() const
	{
		return _IKBlendNode;
	}

	//-------------------------------------------------------------------------
	inline MassSpringBN* MotionBehaviorsHandler::touchMassSpringBlendNodePostIK() const
	{
		return _massSpringBlendNodePostIK;
	}

	//-------------------------------------------------------------------------
    inline MassSpringBN* MotionBehaviorsHandler::touchMassSpringBlendNodePreIK() const
    {
        return _massSpringBlendNodePreIK;
    }


	//-------------------------------------------------------------------------
	inline GroundAdaptationBN* MotionBehaviorsHandler::touchGroundAdaptationBlendNode() const
	{
		return _groundAdaptationBlendNode;
	}

	//-------------------------------------------------------------------------
	inline AdditiveBlendBN* MotionBehaviorsHandler::touchSubBlendTreeAdditiveConnectionNode() const
	{
		return _subBlendTreeAdditiveConnectionNode;
	}

	//-------------------------------------------------------------------------
	inline SimpleBlendBN* MotionBehaviorsHandler::touchSubBlendTreeStandardConnectionNode() const
	{
		return _subBlendTreeStandardConnectionNode;
	}

	//-------------------------------------------------------------------------
	inline bool MotionBehaviorsHandler::hasRegisteredBehaviors() const
	{
		return (_standardBlendBehaviors.size() > 0 || _additiveBlendBehaviors.size() > 0);
	}

	//-------------------------------------------------------------------------
	inline const glm::PODArray<AbstractMotionBehavior*>& MotionBehaviorsHandler::getStandardBlendBehaviors() const
	{
		return _standardBlendBehaviors;
	}

	//-------------------------------------------------------------------------
	inline const glm::PODArray<AbstractMotionBehavior*>& MotionBehaviorsHandler::getAdditiveBlendBehaviors() const
	{
		return _additiveBlendBehaviors;
	}
}
