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

#pragma once

#include "glmAbstractBlendNode.h"

namespace glm
{
	class Delaunay2 ;

	/**
	 * \brief Blend node performing additive blend from postures to a reference posture
	 * 
	 * The AdditiveBlendBN is a blend node that performs an additive blend on a main posture produced by a reference child node, by adding the 
	 * postures produced by the other children nodes (these postures should have been produced by AnimationPlayBN nodes using subtrahend postures).
	 *
	 * \author dev@golaem.com
	 *
	 */
	class GLM_SDK_API AdditiveBlendBN : public AbstractBlendNode
	{
	public:

		class WeightsConfig
		{
		public:

			GLM_CLASS_STAT_ID( glm::MemoryStatIds::Simulation)

			const AbstractBlendNode* _subNode ;
			float _weight ;
			int _id ;

			WeightsConfig() : _subNode( NULL ), _weight( 0.0f ), _id( -1 ) {} ;
			WeightsConfig( const AbstractBlendNode* subNode, float boneWeight, int boneId ) : _subNode( subNode ), _weight( boneWeight ), _id( boneId ) {} ;
		};
		
		class AdditiveBlendBNPerEntityData
		{
		public:

			GLM_CLASS_STAT_ID( glm::MemoryStatIds::Simulation)

			AdditiveBlendBNPerEntityData();

			Time _currentTime;

			glm::PODArray<float> _bonesWeightsArray;
			glm::Array<WeightsConfig> _bonesWeightConfigQueue;			

			glm::PODArray<float> _blindDataWeightsArray;
			glm::Array<WeightsConfig> _blindDataWeightConfigQueue;

			Vector3 _pelvisWorldPosition;
			Quaternion _pelvisWorldOrientation;

			float _speed; 
			Vector3 _movingDirection;

			AbstractBlendNode::ConstraintChannel* _constraintChannels;
		};

		GLM_CLASS_STAT_ID( glm::MemoryStatIds::Simulation)

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

		~AdditiveBlendBN();
		//@}

		//************************************************************
		//! @name Configuration
		//************************************************************
		//@{
		/*
		 * \warning If bone id is -1, weight is set for all bones
		 */
		void setBoneBlendWeight(int64_t entityIndex, const AbstractBlendNode* subNode, float weight, int bone = -1 ) ;
		/*
		 * \warning Weights size must be exactly the bones count (meaning that node must be initialized)
		 */
		void setBoneBlendWeight(int64_t entityIndex, const AbstractBlendNode* subNode, float weights[] ) ;
		/*
		 * \warning Node must have been initialized
		 * \warning Sub-node index must be valid
		 * \warning If bone id is -1, weight is set for all bones
		 */
		void setBoneBlendWeight(int64_t entityIndex, int subNode, float weight, int bone = -1 ) ;
		/*
		 * \warning Returns 0.0f if sub-node was not found or if bone is invalid
		 */
		float getBoneBlendWeight(int64_t entityIndex, const AbstractBlendNode* subNode, int bone ) const ;
		/*
		 * \warning Returns an array of weights which size is the bones count (NULL if sub-node was not found)
		 * \warning Returns NULL if node is not initialized
		 */
		const float* getBoneBlendWeight(int64_t entityIndex, const AbstractBlendNode* subNode ) const ;
		/*
		 * \warning Direct read accessor on _bonesWeightsArray for inheritance, no check performed & node must be initialized
		 */
		float getBoneBlendWeight(int64_t entityIndex, int node, int bone ) const ;
		/*
		 * \warning If blindData id is -1, weight is set for all blindData
		 */
		void setBlindDataBlendWeight(int64_t entityIndex, const AbstractBlendNode* subNode, float weight, int blendShape = -1 ) ;
		/*
		 * \warning Weights size must be exactly the blindData count (meaning that node must be initialized)
		 */
		void setBlindDataBlendWeight(int64_t entityIndex, const AbstractBlendNode* subNode, float weights[] ) ;
		/*
		 * \warning Node must have been initialized
		 * \warning Sub-node index must be valid
		 * \warning If blindData id is -1, weight is set for all blindData
		 */
		void setBlindDataBlendWeight(int64_t entityIndex, int subNode, float weight, int blendShape = -1 ) ;
		/*
		 * \warning Returns 0.0f if sub-node was not found or if blendShape is invalid
		 */
		float getBlindDataBlendWeight(int64_t entityIndex, const AbstractBlendNode* subNode, int blendShape ) const ;
		/*
		 * \warning Returns an array of weights which size is the blindData count (NULL if sub-node was not found)
		 * \warning Returns NULL if node is not initialized
		 */
		const float* getBlindDataBlendWeight(int64_t entityIndex, const AbstractBlendNode* subNode ) const ;
		/*
		 * \warning Direct read accessor on _blindDataWeightsArray for inheritance, no check performed & node must be initialized
		 */
		float getBlindDataBlendWeight(int64_t entityIndex, int node, int blendShape ) const ;
		//@}

		//************************************************************
		//! @name Execution control
		//************************************************************
		//@{
		void init() override;
		void init(int64_t entityIndex ) override; // once per entity
		void finish(int64_t entityIndex ) override; // once per entity
		void finish() override;
		
		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
		 */
		virtual void updateSync(int64_t entityIndex, glm::Time dt, SyncTime syncData[]) override;
		/**
		 * \warning Node must have been initialized
		 */
		virtual 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 { return _perEntityData[entityIndex]._pelvisWorldPosition; }
		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 { return _perEntityData[entityIndex]._pelvisWorldOrientation; }
		virtual void setZupPelvisWorldOrientation(int64_t entityIndex, const glm::Quaternion& orientation, bool forceOri = false) override;

		virtual float getSpeed(int64_t entityIndex) const override { return _perEntityData[entityIndex]._speed; }
		virtual glm::Vector3 getMovingDirection(int64_t entityIndex) const override { return _perEntityData[entityIndex]._movingDirection; }
		//@}

		//************************************************************
		//! @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
		//************************************************************
		//@{
		Time getCurrentTime(int64_t entityIndex) const { return _perEntityData[entityIndex]._currentTime; }
		void setChannelSyncTime(int64_t entityIndex, int channelIndex, bool externallySynchronized, float externalCycleTime) override;
		//@}

		/*
		 * \warning Node must have been initialized
		 */
		void setBoneBlendWeight(int64_t entityIndex, const WeightsConfig& config ) ;
		/*
		 * \warning Node must not have been initialized
		 */
		void enqueueBonesWeightConfig(int64_t entityIndex, const WeightsConfig& config ) ;
		/*
		 * \warning Node must have been initialized
		 */
		void setBlindDataBlendWeight(int64_t entityIndex, const WeightsConfig& config ) ;
		/*
		 * \warning Node must not have been initialized
		 */
		void enqueueBlendShapeWeightConfig(int64_t entityIndex, const WeightsConfig& config ) ;

		//************************************************************
		//! @name Data
		//************************************************************
		//{@
	protected:

		glm::Array< AdditiveBlendBNPerEntityData > _perEntityData;
		//@}
	};

	//------------------------------------------------------------
	inline const AbstractBlendNode::ConstraintChannel *const 
	AdditiveBlendBN::getConstraintChannels(int64_t entityIndex) const
	{
		return _perEntityData[entityIndex]._constraintChannels;
	}
}
