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

#pragma once

#include "glmAbstractBlendNode.h"
#include "glmIKBN.h"

namespace glm
{
	/**
 * \brief MassSpring Request
 *
 * \author dev@golaem.com
 *
 */
	class GLM_SDK_API MassSpringRequest : public IKRequest
	{
	public:

		GLM_CLASS_STAT_ID( glm::MemoryStatIds::Simulation);

		//************************************************************
		//! @name Constructors and destructor
		//************************************************************
		//@{
		MassSpringRequest();

		virtual ~MassSpringRequest();
		//@}

		float _spring;
		float _damping;

	protected:

		int _frame;
		Vector3 _currentPosition;
		Vector3 _currentSpeed;

		friend class MassSpringBN;
	};

	/**
	* \brief Blend node handling Mass Spring on limbs
	*
	* \author dev@golaem.com
	*
	*/
	class GLM_SDK_API MassSpringBN : public AbstractBlendNode
	{
	public:

		GLM_CLASS_STAT_ID( glm::MemoryStatIds::Simulation);

		//************************************************************
		//! @name Constructors and destructor
		//************************************************************
		//@{
		MassSpringBN();

		~MassSpringBN();
		//@}


		//************************************************************
		//! @name Execution control
		//************************************************************
		//@{
		void init();
		void init(int64_t entityIndex); // once per entity
		void finish(int64_t /*entityIndex*/) {} // once per entity, unused
		void finish();

		class MassSpringBNPerEntityData
		{
		public:

			GLM_CLASS_STAT_ID( glm::MemoryStatIds::Simulation);

			MassSpringBNPerEntityData();

			glm::Array< MassSpringRequest * > _requests;
		};

		void allocatePerEntityData(int64_t entityCount) override;
		void deallocatePerEntityData() override;

		void connect() override;

		void disconnect() override;

		/**
		 * \brief Update blend node (synchronization)
		 * \warning Node must have been initialized
		 */
		void updateSync(int64_t entityIndex, glm::Time dt, SyncTime syncData[]) override;
		/**
		 * \warning Node must have been initialized
		 */
		void updateTime(int64_t entityIndex, glm::Time dt, SyncTime syncData[]) override;

		virtual void updateActiveBones(int64_t entityIndex, glm::Time dt) override;
		virtual void updateFullPostureNeededFlag(int64_t entityIndex, glm::Time dt) override;

		/**
		 * \brief Update blend node (posture)
		 * \warning Node must have been initialized
		 */
		void updatePosture(int64_t entityIndex, glm::Time dt) override;
		//@}

		//************************************************************
		//! @name Position/orientation accessors
		//************************************************************
		//@{
		virtual void setZupPelvisWorldPositionAndOrientation(int64_t entityIndex, const glm::Vector3& position, const glm::Quaternion& orientation, bool forcePosXY = false, bool forcePosZ = false, bool forceOri = false) override;

		virtual glm::Vector3 getZupPelvisWorldPosition(int64_t entityIndex) const override;
		virtual void setZupPelvisWorldPosition(int64_t entityIndex, const glm::Vector3& position, bool forcePosXY = false, bool forcePosZ = false) override;

		virtual glm::Quaternion getZupPelvisWorldOrientation(int64_t entityIndex) const override;
		virtual void setZupPelvisWorldOrientation(int64_t entityIndex, const glm::Quaternion& orientation, bool forceOri = false) override;

		virtual float getSpeed(int64_t entityIndex) const override;
		virtual glm::Vector3 getMovingDirection(int64_t entityIndex) const override;
		//@}

		//************************************************************
		//! @name Accessors
		//************************************************************
		//@{
		MassSpringRequest * addRequest(int64_t entityIndex);
		void removeRequest(int64_t entityIndex, MassSpringRequest * request);
		//@}


		//************************************************************
		//! @name Constraint system
		//************************************************************
		//@{
		/**
		* \brief Get an array of cosntraint channels
		* \note size of the array is _tree->_genericSpecificConverter->_converterMapping->getLimbBones().size()
		*/
		virtual const ConstraintChannel *const getConstraintChannels(int64_t entityIndex) const override;
		//@}


		//************************************************************
		//! @name Synchronization system
		//************************************************************
		//@{
		void setChannelSyncTime(int64_t entityIndex, int channelIndex, bool externallySynchronized, float externalCycleTime) override;
		//@}

		//************************************************************
		//! @name runtime properties
		//************************************************************
		//@{
		int _maxIterationsCount;
		float _distanceThreshold;
		//@}

	protected:
		/**
		 * \warning Must be called only if initialized
		 */
		void computeActiveBones();

		/**
		* \warning Must be called only if initialized
		*/
		void computeActiveBlindData();

		/**
		 * \brief do the job updating the posture
		 */
		void computeMassSpringPosture(MassSpringBNPerEntityData& entityData, BlendTreePosture& posture, glm::Time dt);

		/**
		 * \brief sort requests
		 */
		static bool sortMassSpringRequests(MassSpringRequest *rA, MassSpringRequest* rB);

	private:
		//************************************************************
		//! @name 
		//************************************************************
		//{@

		//@}

		// effective targets
		struct EffectiveTargets
		{
			Vector3 _worldTarget;
			MassSpringRequest *_request;
		};

		glm::Array< MassSpringBNPerEntityData > _perEntityData;
	};



	//------------------------------------------------------------
	// getConstraintChannels
	//------------------------------------------------------------
	inline const AbstractBlendNode::ConstraintChannel *const
		MassSpringBN::getConstraintChannels(int64_t entityIndex) const
	{
		GLM_DEBUG_ASSERT_MSG(_tree->getSubNodesCount(_id), "You need a blend node linked after this node.");

		const AbstractBlendNode *sourceBN = _tree->getNode(_tree->getSubNode(_tree->getFirstSubNodePos(_id)));
		return sourceBN->getConstraintChannels(entityIndex);
	}
}
