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

#pragma once

#include "glmIContext.h"
#include "glmAbstractGround.h"
#include "glmMotionDefinitions.h"
#include "glmGenericPosture.h"
#include "glmAnimationCache.h"
#include "glmBlendTreePostureCache.h"
#include "glmAnimationPlayBNCache.h"
#include "glmGenericSpecificConverterPool.h"
#include "glmBlendTree.h"

#include "glmMotionEntityComponent.h"

//FIXME: because of nested classes, can't forward AbstractBlendNode::ConstraintChannel, thus including header file
#include "glmAbstractBlendNode.h"

//golaem core includes
#include <glmMutex.h>

namespace glm
{
	class AbstractBlendNode;
	class MotionBehaviorsHandler;
	class MotionContext;

	class IEntityInitBlendTreeCB
	{
	public:
		virtual void doCall(int64_t index) = 0;
	};

	class GLM_SDK_API MotionEntityTypeData
	{

	public:

		GLM_CLASS_STAT_ID( glm::MemoryStatIds::Simulation);

		MotionEntityTypeData();
		~MotionEntityTypeData();

		/// \name Configure skeleton
		//@{
		/**
		* \brief loads a skeleton setup (deferred to init)
		* \param characterFile full path to to the golaem character file (.gch)
		*/
		void setCharacterToLoad(const char* characterFile);

		/**
		* \brief loads a skeleton setup from an existing converter
		* \param genericToSpecificConverter the already build converter, must already be scaled
		* \param scale the same scale as converter, just used to inform LOD
		* \param skeletonLOD the LOD on which setting the skeleton. If negative, or superior to the number of LOD already present, the skeleton will be set the the next available LOD (LOD 0, then LOD 1, then LOD 2, and so on)
		*/
		void loadCharacter(GenericSpecificConverter* genericToSpecificConverter);
		//@}

		/**
		* \brief alternative way to build a blend tree without using motion behavior handler
		*/
		BlendTree& buildBlendTreeManually(AbstractBlendNode* rootNode);

		void init(MotionContext* motionContext);

		void initEntityBlendTree(int64_t indexInEntityType, float entityScale, float rootHeightUnscaled, const HierarchicalSkeletonDescription::Override* skeletonOverride);

		void updateEntityBlendTree(glm::Time dt, int64_t indexInEntityType, const PODArray<int>& blindDataOverrideIndices, const PODArray<float>& blindDataOverrideValues, const Vector3& worldUpVector, AbstractGround::GroundPinMode::Value pinMode, LockedThreadData<BlendTreePostureCache::Data>& blendTreePostureCache, BlendTreePosture*& entityBlendTreePosture);
		void updateEntityBlendTree(glm::Time dt, int64_t indexInEntityType, const PODArray<int>& blindDataOverrideIndices, const PODArray<float>& blindDataOverrideValues, const Vector3& worldUpVector, AbstractGround::GroundPinMode::Value pinMode, const Vector3& pelvisPosition, const Quaternion& pelvisOrientation, LockedThreadData<BlendTreePostureCache::Data>& blendTreePostureCache, BlendTreePosture*& entityBlendTreePosture);
		void finishEntityBlendTree(int64_t indexInEntityType);

		void getZupPelvisTransformationFromBlendTree(Vector3& zupPelvisPosition, Quaternion& zupPelvisOrientation, const int64_t indexInEntityType) const;
		float getSpeedFromBlendTree(const int64_t indexInEntityType) const;
		Vector3 getMovingDirectionFromBlendTree(const int64_t indexInEntityType) const;

		/**
		* \brief returns the MotionBehaviorsHandler to be used by motion behaviors using a sub-blend tree
		*/
		MotionBehaviorsHandler* touchMotionBehaviorsHandler() { return _motionBehaviorsHandler; }

		const GlmString& getCharacterFileName() const { return _characterFile; }
		const GenericSpecificConverter* getGenericSpecificConverter() const { return _genericToSpecificConverter; }

		const AbstractBlendNode::ConstraintChannel* debugGetConstraintChannels(int64_t indexInEntityType) const;

		float getMaxBonesHierarchyLength() const;

		void addEntityBlendTreeInitCB(IEntityInitBlendTreeCB* callback);
		void removeEntityBlendTreeInitCB(IEntityInitBlendTreeCB* callback);

	public:
		BlendTree				_blendTree;
		MotionBehaviorsHandler* _motionBehaviorsHandler;

		GolaemCharacter* _golaemCharacter;
		GenericSpecificConverter* _genericToSpecificConverter;
		float _rootHeigthRatio;	//!< height of the animated pelvis relative to the entity's height, in % of skeleton's root height

		glm::GlmString	_characterFile;
		GenericSpecificConverter::SpecificPostureBlindData::Value _specificAnimationBlindDataMode;
		float _rollExtractionBaseOffset;

		PODArray<IEntityInitBlendTreeCB*> _initEntityBlendTreeCBs;

		friend class MotionContext;
	};

	/**
	* \brief abstract interface realization for the motion entity
	*/
	class GLM_SDK_API MotionContext : public IContext
	{
	public:	//static functions
		static GenericSkeleton::Override* createGenericSkeletonOverride(const GenericSpecificConverterMapping* converterMapping, const HierarchicalSkeletonDescription::Override* skeletonOverride);

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

		MotionContext();
		virtual ~MotionContext();

		MotionEntityComponent& getEntityComponent(int typeIndex, int64_t entityIndex);
		MotionEntityComponent& getEntityComponent(SimuEntityHandle handle);

		//************************************************************
		//! @name Generic Animation Cache requests
		//************************************************************
		//@{
		AnimationCache& getAnimationCache() const;
		void setAnimationCache(AnimationCache& animationCache);
		//@}

		//************************************************************
		//! @name Generic Postures Pool(s) requests
		//************************************************************
		//@{
		GenericSpecificConverterPool& getConvertersPool() const;
		void setConvertersPool(GenericSpecificConverterPool& converterPool);
		//@}

		BlendTreePostureCache& getBlendTreePostureCache()
		{
			return _blendTreePostureCache;
		}
		BlendTreePostureCache& getBlendTreePostureCacheForAnimInit()
		{
			return _blendTreePostureCacheForAnimInit;
		} // flushed each frame to avoid keeping big buffers !

		AnimationPlayBNCache& getAnimationPlayBNCache()
		{
			return _animationPlayBNCache;
		}

		inline MotionEntityTypeData& getEntityTypeData(int64_t index) const
		{
			return *_entityTypeData[index];
		}

		static ContextType::Value getStaticContextType() { return IContext::ContextType::ANIMATION; }

	protected:

		/**
		* \brief path context initialization
		*/
		virtual void doInit() override;

		/**
		* \brief called by update
		*/
		virtual void doUpdate(Time dt) override;

		/**
		* \brief called by finish
		* \return true if successfully finished, false otherwise
		*/
		virtual void doFinish() override;

		/**
		* \brief called by init, derived classes must implement it to set their component type
		*/
		virtual void setContextType() override;

		virtual bool registeredOnCreateEntityType() override { return true; }
		virtual void onCreateEntityType(int64_t maxCount) override;

		virtual bool registeredOnCreateEntityComponent() override { return true; }
		virtual void onCreateEntityComponent(int entityTypeIndex, int64_t entityIndex) override;

		virtual bool registeredOnInitEntityComponent() override { return true; }
		virtual void onInitEntityComponent(int entityTypeIndex, int64_t entityIndex) override;

		virtual bool registeredOnUpdateEntityComponent() override { return true; }
		virtual void onUpdateEntityComponent(Time dt, int entityTypeIndex, int64_t entityIndex) override;

		virtual bool registeredOnFinishEntityComponent() override { return true; }
		virtual void onFinishEntityComponent(int entityTypeIndex, int64_t entityIndex) override;

		virtual bool registeredOnDestroyEntityComponent() override { return true; }
		virtual void onDestroyEntityComponent(int entityTypeIndex, int64_t entityIndex) override;

	private:

		GLM_DISALLOW_COPY_AND_ASSIGN(MotionContext);

	private:

		ComponentsArray<MotionEntityComponent> _components;

		//************************************************************
		//! @name Cache data
		//************************************************************
		//@{
		bool _ownAnimationCache;
		AnimationCache* _animationCache;

		GenericSpecificConverterPool* _converterPool;
		GenericSpecificConverterPool _defaultConverterPool;

		BlendTreePostureCache _blendTreePostureCache;
		BlendTreePostureCache _blendTreePostureCacheForAnimInit;

		AnimationPlayBNCache _animationPlayBNCache;
		//@}


		glm::PODArray<MotionEntityTypeData*> _entityTypeData; // stored as pointers to avoid data movement when resizing (POD)Array. Anyway it can only be allocated instance per instance at callback
	};

	//-------------------------------------------------------------------------
	inline MotionEntityComponent& MotionContext::getEntityComponent(int typeIndex, int64_t entityIndex)
	{
		return _components.getComponent(typeIndex, entityIndex);
	}

	//-------------------------------------------------------------------------
	inline MotionEntityComponent& MotionContext::getEntityComponent(SimuEntityHandle handle)
	{
		return _components.getComponent(handle._typeIndex, handle._index);
	}

	//-------------------------------------------------------------------------
	inline AnimationCache& MotionContext::getAnimationCache() const
	{
		return *_animationCache;
	}

	//-------------------------------------------------------------------------
	inline void MotionContext::setAnimationCache(AnimationCache& animationCache)
	{
		if (_ownAnimationCache)
		{
			GLM_SAFE_DELETE(_animationCache)
		}

		_ownAnimationCache = false;
		_animationCache = &animationCache;
	}

	//-------------------------------------------------------------------------
	inline GenericSpecificConverterPool&
		MotionContext::getConvertersPool() const
	{
		// avoid requesting a non-const for caches and pools
		return const_cast<GenericSpecificConverterPool&>(*_converterPool);
	}

	//-------------------------------------------------------------------------
	inline void MotionContext::setConvertersPool(GenericSpecificConverterPool& converterPool)
	{
		_converterPool = &converterPool;
	}

}
