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

#pragma once

#include "glmAbstractBlendNode.h"
#include "glmGenericSpecificConverter.h"

namespace glm
{
	class Delaunay2;

	/**
	* \brief Blend node mixing postures produced by blend nodes
	*
	* \author dev@golaem.com
	*
	*/
	class GLM_SDK_API SimpleBlendBN : public AbstractBlendNode
	{

	public:

		GLM_CLASS_STAT_ID( glm::MemoryStatIds::Simulation);

		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 weight, int id) : _subNode(subNode), _weight(weight), _id(id) {};
		};

		class ParamsConfig
		{

		public:
			GLM_CLASS_STAT_ID( glm::MemoryStatIds::Simulation);

			enum ConfigCommand
			{
				SET_PARAMS,
				COMPUTE_DELAUNAY_PARAMS,
				COMPUTE_BOTH_PARAMS,
				COMPUTE_PARAM1_ONLY,
				COMPUTE_PARAM2_ONLY,
				END
			};

			int _command;
			const AbstractBlendNode* _subNode;
			float _param1;
			float _param2;

			ParamsConfig() : _command(SimpleBlendBN::ParamsConfig::END), _subNode(NULL), _param1(0.0f), _param2(0.0f) {};
			ParamsConfig(ConfigCommand command, const AbstractBlendNode* subNode, float param1, float param2) : _command(command), _subNode(subNode), _param1(param1), _param2(param2) {};

		};

		struct ComputeBlendWeightsReturnCode
		{
			enum Value
			{
				OK,
				OK_TRIANGULATION_IS_1D,
				OK_TRIANGULATION_IS_2D,
				OK_PARAM_IS_OUT_OF_TRIANGULATION2D,	//the param was reprojected on the triangulation
				WARNING_ONLY_ONE_SUBNODE,
				WARNING_TRIANGULATION_IS_0D,
				COMMAND_QUEUED,
				ERROR_INVALID_PARAM,
				END
			};
		};

		class SimpleBlendBNPerEntityData
		{

		public:

			GLM_CLASS_STAT_ID( glm::MemoryStatIds::Simulation);

			SimpleBlendBNPerEntityData();
			~SimpleBlendBNPerEntityData();

			//************************************************************
			//! @name Per Entity Data
			//************************************************************
			//{@
			Time _currentTime;

			Vector3 _pelvisWorldPosition;
			Quaternion _pelvisWorldOrientation;

			float _speed;
			Vector3 _movingDirection;

			AbstractBlendNode::ConstraintChannel* _constraintChannels;

			glm::PODArray<float> _userBonesWeightsArray;
			glm::Array<WeightsConfig> _userBonesWeightConfigQueue;

			glm::PODArray<float> _userBlindDatasWeightsArray;
			glm::Array<WeightsConfig> _userBlindDatasWeightConfigQueue;

			glm::PODArray<float> _paramsArray;
			glm::Array<ParamsConfig> _paramsConfigQueue;

			Delaunay2* _paramsTriangulation;
			bool _paramsTriangulationRequired;
			//}@

#if CSV_LOG_ALL
			mutable glm::Array< Vector3 >	_setPelvisPosition;
			mutable glm::Array< Vector3 >	_getPelvisPosition;
			mutable glm::Array< Quaternion >	_setPelvisOrientation;
			mutable glm::Array< Quaternion >	_getPelvisOrientation;
#endif
		};

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

		virtual ~SimpleBlendBN();
		//@}

		//************************************************************
		//! @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);

		/**
		* \brief Blend Shape weight configuration
		*/
		void setBlindDataBlendWeight(int64_t entityIndex, const AbstractBlendNode* subNode, float weight, int blindData = -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[]);

		/**
		* \brief Blend Shape weight configuration
		*/
		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 bone id is -1, weight is set for all bones
		*/
		void setBoneBlendWeight(int64_t entityIndex, int subNode, float weight, int bone = -1);

		/**
		* \brief Blend Shape weight configuration
		*/
		void setBlindDataBlendWeight(int64_t entityIndex, int subNode, float weight, int blindData = -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;

		/**
		* \brief Blend Shape weight configuration
		*/
		float getBlindDataBlendWeight(int64_t entityIndex, const AbstractBlendNode* subNode, int blindData) 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;

		/**
		* \brief Blend Shape weight configuration
		*/
		const float* getBlindDataBlendWeight(int64_t entityIndex, const AbstractBlendNode* subNode) const;
		/*
		* \warning Direct read accessor on _userBonesWeightsArray for inheritance, no check performed & node must be initialized
		*/
		float getBoneBlendWeight(int64_t entityIndex, int node, int bone) const;

		/**
		* \brief Blend Shape weight configuration
		*/
		float getBlindDataBlendWeight(int64_t entityIndex, int node, int blindData) const;

		//void computeBlendWeight(float desiredP1, float desiredP2, const AbstractBlendNode* subNode1, float m1p1, float m1p2, const AbstractBlendNode* subNode2, float m2p1, float m2p2);

		void setNodeParameters(int64_t entityIndex, const AbstractBlendNode* subNode, float param1, float param2);
		/*
		* \warning Node must have been initialized
		*/
		void setNodeParameters(int64_t entityIndex, int subNode, float param1, float param2);
		/*
		* \warning Node must have been initialized
		*/
		void getNodeParameters(int64_t entityIndex, const int subNode, float& param1, float& param2) const;
		/*
		* \brief Compute blend weights only by using a linear interpolation between values set as parameter 1 for all nodes
		*/
		SimpleBlendBN::ComputeBlendWeightsReturnCode::Value computeBlendWeightsForParameter1only_noApply(int64_t entityIndex, float* outputWeights, float param1) const;
		SimpleBlendBN::ComputeBlendWeightsReturnCode::Value computeBlendWeightsForParameter1only(int64_t entityIndex, float param1);
		/*
		* \brief Compute blend weights only by using a linear interpolation between values set as parameter 2 for all nodes
		*/
		SimpleBlendBN::ComputeBlendWeightsReturnCode::Value computeBlendWeightsForParameter2only_noApply(int64_t entityIndex, float* outputWeights, float param2) const;
		SimpleBlendBN::ComputeBlendWeightsReturnCode::Value computeBlendWeightsForParameter2only(int64_t entityIndex, float param2);
		/*
		* \brief Compute blend weights only by using a 2D Delaunay triangulation according to parameters 1 and 2 set for all nodes
		* \warning The 2D Delaunay triangulation must have been computed once (by calling the 'computeTriangulationFor2ParametersBlend' method)
		*/
		SimpleBlendBN::ComputeBlendWeightsReturnCode::Value computeBlendWeightsFor2Parameters_noApply(int64_t entityIndex, float* outputWeights, float param1, float param2) const;
		SimpleBlendBN::ComputeBlendWeightsReturnCode::Value computeBlendWeightsFor2Parameters(int64_t entityIndex, float param1, float param2);

		/*
		* \brief Compute 2D Delaunay triangulation for 2-Parameter blend weights computation (according to parameters set for all nodes)
		* \warning Computing 2D Delaunay triangulation is costly
		* \warning The 2D Delaunay triangulation must have been computed before any call to the 'computeBlendWeightsFor2Parameters' method
		* \warning Each time a new parameter value is set changed on a node, the 2D Delaunay triangulation must be computed again before calling the 'computeBlendWeightsFor2Parameters' method
		*/
		void computeTriangulationFor2ParametersBlend(int64_t entityIndex, const float delaunayDataBaseEpsilon = GLM_EPSILON);

		bool isComputeTriangulationFor2ParametersBlendRequired(int64_t entityIndex) const;
		//@}

		//************************************************************
		//! @name Estimations
		//************************************************************
		//@{
		/*
		* \brief Estimate the moving direction that would result from given blending weights, but without applying them
		*/
		Vector3 estimateMovingDirectionForBlendingWeights(int64_t entityIndex, float* blendingWeights) const;
		/*
		* \brief Estimate the moving direction that would result from given blend parameters, but without applying them
		*/
		Vector3 estimateMovingDirectionFor2Parameters(int64_t entityIndex, float param1, float param2) 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 Trajectory extrapolation system
		//************************************************************
		//@{
		/**
		* \brief Get an array of expected root positions in the future
		*/
		virtual void computeTrajectoryNormalizedDeltasPrediction(Vector3* retDeltaPositions, Quaternion* retDeltaOrientations, int64_t entityIndex, unsigned int predictionSampleCount, glm::Time predictionSampleDt) const override;
		//@}

		//************************************************************
		//! @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;
		//@}

		const Delaunay2* getParamsTriangulation(int64_t entityIndex) const;

	private:

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

		void setBlindDataBlendWeight(int64_t entityIndex, const WeightsConfig& config);

		/*
		* \warning Node must have been initialized
		*/
		void setNodeParameters(int64_t entityIndex, const ParamsConfig& config);
		/*
		* \warning Node must not have been initialized
		*/
		void enqueueParamsConfig(int64_t entityIndex, const ParamsConfig& config);

	protected:

		//@}

		glm::Array< SimpleBlendBNPerEntityData > _perEntityData;
	};

	//-------------------------------------------------------------------------		
	inline float SimpleBlendBN::getBoneBlendWeight(int64_t entityIndex, int node, int bone) const
	{
		const size_t specificSkeletonBonesCount = _tree->_genericSpecificConverter->_converterMapping->_skeletonDescription->getBones().size();
		return _perEntityData[entityIndex]._userBonesWeightsArray[(node * specificSkeletonBonesCount) + bone];
	}

	//-------------------------------------------------------------------------		
	inline float SimpleBlendBN::getBlindDataBlendWeight(int64_t entityIndex, int node, int blindData) const
	{
		const size_t blindDataFloatCount = _tree->_genericSpecificConverter->_converterMapping->getBlindDataTotalFloatCount();
		return _perEntityData[entityIndex]._userBlindDatasWeightsArray[(node * blindDataFloatCount) + blindData];
	}

	//-------------------------------------------------------------------------
	inline bool
		SimpleBlendBN::isComputeTriangulationFor2ParametersBlendRequired(int64_t entityIndex) const
	{
		return _perEntityData[entityIndex]._paramsTriangulationRequired;
	}

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