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

#pragma once

#include "glmSDKAPI.h"
#include "glmMotionDefinitions.h"
#include "glmBlendTreePosture.h"
#include "glmGenericSkeleton.h"
#include "glmGenericPosture.h"
#include "glmCachePerThread.h"
#include "glmAbstractGround.h"
#include <glmEvent.h>

class BlendTreeTest ;

namespace glm
{
	class AbstractBlendNode ;
	class MotionEntityComponent;
	
	/**
	 * \brief Blend tree handling blend nodes to produce a Specific posture
	 *
	 * \author dev@golaem.com
	 *
	 */
	class GLM_SDK_API BlendTree 
	{
		friend class MotionEntityComponent;

	public:	//methods

		GLM_CLASS_STAT_ID( glm::MemoryStatIds::Simulation)

		//************************************************************
		//! @name Constructors & destructor
		//************************************************************
		//@{
		BlendTree();
		/**
		 * \warning Destructor removes all nodes that are currently handled by tree
		 */
		~BlendTree();
		//@}

		//************************************************************
		//! @name Execution control
		//************************************************************
		//@{
		bool isInitialized() const ;

		void init() ;

		void finish() ;
		/**
		 * \brief Update blend tree by updating handled blend nodes
		 * \param dt 
		 * \param posture Pool of postures where are stored postures produced by blend nodes
		 * \warning Tree must have been initialized
		 * \warning Pool size must be sufficient (see below)
		 *
		 * This method updates the blend nodes in the required order. Each blend produces a posture that is then
		 * used by its parent blend node, and this posture is stored in the postures pool. This implies that the
		 * postures pool has a sufficient size to contain all the produced postures, e.g. its size is at least the
		 * count of blend nodes contained in the blend tree (no check is perfomed, if needed use the 
		 * "computeTreeSize" method to get the pool required size). Notice that:
		 * - The posture produced by the root node is always stored in first place in the pool (index 0): this is 
		 *   the final posture produced by the blend tree. This posture can be used just after calling this method,
		 *   so if it has to be kept for further use, it has to be saved (indeed, the pool is used to compute
		 *   postures produced by other blend trees).
		 * - If the blend tree is empty (tree size is 0), no root node is available and no posture is produced.
		 *   However, the pool size must be at least 1 in order to store a NULL pointer in the first position.
		 */
		void init(int64_t entityIndexInEntityType, float scale, float rootHeightUnscaled, const HierarchicalSkeletonDescription::Override* skeletonOverride);
		void update(int64_t entityIndexInEntityType, glm::Time dt, BlendTreePosture* postures);
		void update(int64_t entityIndexInEntityType, glm::Time dt, BlendTreePosture* postures, const Vector3& pelvisWorldPos, const Quaternion& pelvisWorldOri);
		void finish(int64_t entityIndexInEntityType) ;
		//@}

		
		//************************************************************
		//! @name Nodes connection
		//************************************************************
		//@{
		int getNodesCount() const ;

		int getConnectionsCount() const ;
		/*
		 * \warning Node index must be valid (no check performed)
		 */
		const AbstractBlendNode* getNode( int nodeId ) const ;
		/*
		 * \warning Node index must be valid (no check performed)
		 */
		AbstractBlendNode* touchNode( int nodeId ) const ;
		/*
		 * \warning Node index must be valid (no check performed)
		 */
		int getSubNodesCount( int nodeId ) const ;
		/*
		 * \warning Node index must be valid (no check performed)
		 */
		int getSubNode( int pos ) const ;
		/*
		 * \warning Node index must be valid (no check performed)
		 */
		int getFirstSubNodePos( int nodeId ) const ;
		/**
		 * \param deletNodes Flag inidicating if the current nodes have to be deleted or just removed from tree
		 * \warning Node must not already belong to current tree or another
		 * \warning The previous root node (and all its sub-nodes) is removed from tree
		 */
		void setRootNode( AbstractBlendNode* rootNode, bool deleteNodes = true ) ;

		const AbstractBlendNode* getRootNode() const ;

		AbstractBlendNode* touchRootNode() ;
		
		/**
		 * \brief Add sub-nodes to tree by connecting them to a node of the tree
		 * \param parentNode The parent node to which connect the sub-nodes
		 * \param nodesToConnect Array of sub-nodes to add to tree
		 * \parma nodesToConnectSize The count of sub-nodes to add (e.g. the size of sudNodes array)
		 * \warning Parent node must already belong to current tree
		 * \warning Sub-nodes array must not be NULL or empty (e.g. size equals 0)
		 * \warning Sub-nodes must not already belong to another tree
		 */
		void addSubNodes( const AbstractBlendNode* parentNode, AbstractBlendNode** nodesToConnect, int nodesToConnectSize ) ;
		/**
		 * \brief Remove a node and all its sub-nodes from tree
		 * \param node The node to remove
		 * \param deleteNodes Flag inidcating if the node all of this sub-nodes must be destroyed
		 */
		void removeNode( AbstractBlendNode* node, bool deleteNodes ) ;
		//@}
		
		//************************************************************
		//! @name Postures
		//************************************************************
		//@{
		/*
		 * \warning The postures pool is only accessible during blend tree update (otherwise, it is set to NULL)
		 */
		BlendTreePosture* getPostures(int64_t entityIndex) const ;
		/*
		 * \warning The postures pool is only accessible during blend tree update (otherwise, it is set to NULL)
		 */
		BlendTreePosture* getIntermediatePostures(int64_t entityIndex) const ;

		int getPosturesCount() const ;
		//@}

	public:	//data
		//************************************************************
		//! @name Skeleton
		//************************************************************
		//@{
		const GenericSpecificConverter* _genericSpecificConverter;
		Array<float> _entityScales;
		Array<float> _entityRootHeightUnscaled;
		Array<const HierarchicalSkeletonDescription::Override*> _skeletonOverrides;	//per entity overrides. May be NULL
		Array<GenericSkeleton::Override*> _genericSkeletonOverrides;	//per entity overrides. Not NULL
		CachePerThread<Array<GenericPosture*> > _genericPosturesCache;  //generic posture cache is used by animationPlayBN when remaping skeletons
		//@}

		//************************************************************
		//! @name Configuration data
		//************************************************************
		//@{
		bool        _usePreciseIKNormalPlaneBlend;									//only used for blending several animations together
		bool        _usePreciseIKNormalPlaneBlend_onInterpolationBetweenFrames;		//only used for blending between frames of a single 
		int64_t     _maxInstanceCount;
		//@}

		//************************************************************
		//! @name Runtime data
		//************************************************************
		//@{
		glm::Array<Vector3> _worldUpVector;
		glm::Array<Quaternion> _upToZRotation; // used if mirroring is active
		glm::Array<Quaternion> _zToUpRotation; // used if mirroring is active
		glm::Array<AbstractGround::GroundPinMode::Value> _pinMode;
		//@}

	protected:

	private:	//methods
		
		//************************************************************
		//! @name Nodes connection
		//************************************************************
		//@{
		void instantiateTree() ;
		
		void clearTree( bool deleteNodes ) ;

		/*
		 * \warning Node id must be valid
		 * \warning Node id must be different from 0 (no parent)
		 */
		int retrieveParentNodeIndex( int nodeId ) const ;
		//@}

	private:	//data
		//************************************************************
		//! @name Execution control
		//************************************************************
		//@{
		bool _isInitialized ;
		//@}

		//************************************************************
		//! @name Postures
		//************************************************************
		//@{
		glm::Array<BlendTreePosture*> _postures ;
		int _posturesCount ;
		//@}

		//************************************************************
		//! @name Nodes connection
		//************************************************************
		//@{
		glm::PODArray<AbstractBlendNode*> _nodes ;
		glm::PODArray<int> _subNodesCount ;
		glm::PODArray<int> _subNodes ;
		glm::PODArray<int> _firstSubNodePos ;
		//@}

		friend class ::BlendTreeTest ;
	};

	//------------------------------------------------------------
	// isInitialized
	//------------------------------------------------------------
	inline bool 
	BlendTree::isInitialized() const
	{
		return _isInitialized ;
	}
	
	//------------------------------------------------------------
	// getNodesCount
	//------------------------------------------------------------
	inline int  
	BlendTree::getNodesCount() const
	{
		return static_cast<int>(_nodes.size());
	}
	

	//------------------------------------------------------------
	// getConnectionsCount
	//------------------------------------------------------------
	inline int
	BlendTree::getConnectionsCount() const
	{
		return static_cast<int>(_subNodes.size());
	}
	
	//------------------------------------------------------------
	// getNode
	//------------------------------------------------------------
	inline const AbstractBlendNode*
	BlendTree::getNode( int nodeId ) const
	{
		return _nodes[nodeId] ;
	}

	//------------------------------------------------------------
	// touchNode
	//------------------------------------------------------------
	inline AbstractBlendNode*
	BlendTree::touchNode( int nodeId ) const
	{
		return _nodes[nodeId] ;
	}

	//------------------------------------------------------------
	// getSubNodesCount
	//------------------------------------------------------------
	inline int
	BlendTree::getSubNodesCount( int nodeId ) const
	{
		return _subNodesCount[nodeId] ;
	}
	
	//------------------------------------------------------------
	// getSubNode
	//------------------------------------------------------------
	inline int 
	BlendTree::getSubNode( int pos ) const
	{
		return _subNodes[pos] ;
	}

	//------------------------------------------------------------
	// getFirstSubNodePos
	//------------------------------------------------------------
	inline int
	BlendTree::getFirstSubNodePos( int nodeId ) const
	{
		return _firstSubNodePos[nodeId] ;
	}

	//------------------------------------------------------------
	// getPostures
	//------------------------------------------------------------
	inline BlendTreePosture*
	BlendTree::getPostures(int64_t entityIndex) const
	{
		GLM_DEBUG_ASSERT_MSG(_postures[entityIndex], "_postures is NULL right now, it can be get only during a blend tree update, not before, not after...");
		return _postures[entityIndex] ;
	}

	//------------------------------------------------------------
	// getIntermediatePostures
	//------------------------------------------------------------
	inline BlendTreePosture*
	BlendTree::getIntermediatePostures(int64_t entityIndex) const
	{
		return &( getPostures(entityIndex)[_nodes.size()] ) ;
	}
	
	//------------------------------------------------------------
	// getPosturesCount
	//------------------------------------------------------------
	inline int
	BlendTree::getPosturesCount() const
	{
		return _posturesCount ;
	}
}
