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

#pragma once

#include "glmAbstractBlendNode.h"
#include "glmAnimationInterface.h"
#include "glmAnimationReplayMapping.h"

#define GLM_REPLAY_MAPPING_INVALID_INDEX -1

class AnimationPlayBNTest;

namespace glm
{
    class AnimationCache;
    class BlendTreePostureCache;
    class AnimationPlayBNCache;
    class GenericSpecificConverter;
    class SyncActionBehavior;

    //-------------------------------------------------------------------------
    // LoopMode
    //-------------------------------------------------------------------------
    struct LoopMode
    {
    public:
        enum Value
        {
            SINGLE_PLAY = 0,
            LOOP,
            MANUAL,
            END
        };
    };

    //-------------------------------------------------------------------------
    // SequenceEvent
    //-------------------------------------------------------------------------
    struct SequenceEvent
    {
        SequenceEvent(int type, int frame, bool inSupport)
            : _eventFrame(static_cast<uint32_t>(frame))
            , _eventType(static_cast<int8_t>(type))
            , _inSupport(inSupport)
        {
        }

        uint32_t _eventFrame;
        int8_t _eventType; //0 for begin of animation, 1 for begin of ground support phase, 2 for begin of fly support phase, 3 for end of animation
        bool _inSupport;
    };

    //-------------------------------------------------------------------------
    // ReplayTimeChannel
    //-------------------------------------------------------------------------
    struct GLM_SDK_API ReplayTimeChannel
    {
    public:
        GLM_CLASS_STAT_ID(glm::MemoryStatIds::Simulation);

        ReplayTimeChannel()
            : _currentToNextValue(0)
        {
            _loopIndex[0] = 0;
            _loopIndex[1] = 0;
            _postureIndex[0] = 0;
            _postureIndex[1] = 0;
        }

        void setCurrentTime(float normalizedTime, int animationPostureCount, LoopMode::Value loopMode, bool playLastFrame = false);
        float getNormalizedTime(int animationPostureCount, LoopMode::Value loopMode) const;

        int _loopIndex[2];    //!< _loopIndex[0] is the current loop index, _loopIndex[1] is the next one
        int _postureIndex[2]; //!< _postureIndex[0] is the current posture index, _postureIndex[1] is the next one

        float _currentToNextValue;
    };

    /**
	* \brief Represent a channel of played constraints
	*
	* \author dev@golaem.com
	*
	*/
    struct GLM_SDK_API LimbConstraint
    {
    public:
        GLM_CLASS_STAT_ID( glm::MemoryStatIds::Simulation);

        LimbConstraint()
            : _limbConstraintPoints(getClassMemoryStatId())
            , _groundSupportEvents(getClassMemoryStatId())
            , _limbExtremityBoneIndex(-1)
            , _activationSequence(NULL)
        {
        }

        LimbConstraint(const GroundSupportSequence& activationSequence)
            : _limbConstraintPoints(getClassMemoryStatId())
            , _groundSupportEvents(getClassMemoryStatId())
            , _limbExtremityBoneIndex(-1)
            , _activationSequence(&activationSequence)
        {
        }

        glm::Array<Vector3> _limbConstraintPoints;  // 1 point per contraint, Vector3 is an EndOfBoneWorldPosition;
        glm::Array<Vector3> _groundNormal;          // 1 normal per contraint
        glm::Array<SequenceEvent> _groundSupportEvents;
        int _limbExtremityBoneIndex;
        const GroundSupportSequence* _activationSequence;
    };

    //-------------------------------------------------------------------------
    // PerAnimationData
    //-------------------------------------------------------------------------
    class GLM_SDK_API PerAnimationData
    {
        friend class AnimationPlayBN;

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

        PerAnimationData(const AnimationInterface* animation = NULL, const bool mirrored = false, const bool forceConstantGroundHeightInAnimation = false);
        ~PerAnimationData();

        void initAnimationReplayMapping(const GenericSpecificConverter* replaySkeleton, const glm::Array<ChannelInfo>& channelsInfo, bool displayMappingError);

        //************************************************************
        //! @name Configuration
        //************************************************************
        //@{
        inline bool isMirrored() const { return _mirror; }
        inline bool isIdentityReplay() const { return _identityReplay; }
        inline const AnimationReplayMapping* getAnimationReplayMapping() const { return _theAnimationReplayMapping; }
        inline void setAnimationReplayMapping(AnimationReplayMapping* animationReplayMapping) { _theAnimationReplayMapping= animationReplayMapping; }
        inline LoopMode::Value getLoopMode() const { return _loopMode; }
        inline bool getInterpolationBetweenFrames() const { return _interpolateBetweenFrames; }
        inline void setLoopMode(LoopMode::Value loopMode) { _loopMode = loopMode; }
        inline void setInterpolationBetweenFrames(bool interpolateBetweenFrames) { _interpolateBetweenFrames = interpolateBetweenFrames; }
        //@}

        //************************************************************
        //! @name Animation related accessors
        //************************************************************
        //@{
        const AnimationInterface* getAnimation() const { return _animation; }
        void setAnimation(const AnimationInterface* animation) { _animation = animation; }

        const GenericSpecificConverter* getAnimationGenericSpecificConverter() const { return _genericConverterForAnimation; }

        inline int getPosturesCount() const { return _animation->getPosturesCount(); }
        inline Time getDeltaTime() const { return _animation->getDeltaTime(); }

        void getTotalNormalizedDeltaPosition(Vector3& result) const;
        void getTotalDeltaOrientation(Quaternion& result) const;
        glm::Angle computeTotalDeltaHeading() const;

        Quaternion getPelvisWorldOrientationAtFrame(int frameIndex, const GenericSpecificConverter* blendTreeConverter) const;
        Vector3 getPelvisNormalizedPositionAtFrame(int frameIndex) const;
        Vector3 getMovingDirectionAtFrame(int frameIndex, const GenericSpecificConverter* blendTreeConverter) const;   //in local pelvis 
        void computePelvisTransformsAtTime(
            const GenericSpecificConverter* blendTreeConverter,
            const ReplayTimeChannel& desiredTime,
            Vector3& retPelvisNormalizedPosition, Quaternion& retPelvisOrientation, Vector3& retMovingDirection,
            int referenceLoopIndex[2], Vector3 referenceLoopOriginNormalizedPosition[2], Quaternion referenceLoopOriginOrientation[2], //references for both frames of interpolation if interpolateBetweenFrames is true
            const Quaternion& upToZRotation, const Quaternion& zToUpRotation) const;
        void computePelvisTransformsAtTime(
            const GenericSpecificConverter* blendTreeConverter,
            const ReplayTimeChannel& desiredTime,
            Vector3& retPelvisNormalizedPosition, Quaternion& retPelvisOrientation, Vector3& retMovingDirection,
            int referenceLoopIndex[2], Vector3 referenceLoopOriginNormalizedPosition[2], Quaternion referenceLoopOriginOrientation[2], //references for both frames of interpolation if interpolateBetweenFrames is true
            const Quaternion& upToZRotation, const Quaternion& zToUpRotation,
            Vector3* retPelvisNormalizedPositions, Quaternion* retPelvisOrientations, Vector3* retMovingDirections) const;

        void computeNextLoopOrigin(Vector3& loopOriginNormalizedPosition, Quaternion& loopOriginOrientation) const;
        void computeLastLoopOrigin(Vector3& loopOriginNormalizedPosition, Quaternion& loopOriginOrientation) const;
        //@}

        int getConstrainedChannelsCount() const { return _nbConstrainedChannels; }
        int* getConstrainedChannels() const { return _constrainedChannelsIndices; }

        //************************************************************
        //! @name Replay mapping related accessors
        //************************************************************
        //@{
        const glm::PODArray<int>& getBoneReplayMapping() const { return _boneReplayMapping; }
        const glm::PODArray<int>& getBoneReplayModes() const { return _boneReplayModes; }
        const glm::PODArray<int>& getBlindDataMapping() const { return _blindDataMapping; }
        const glm::PODArray<Angle>& getBoneAdditionalIkNormalOffset() const { return _boneAdditionalIkNormalOffset; }
        //@}

        // only for unit tests purpose
        const glm::Array<LimbConstraint*>& getLimbConstraint() const
        {
            return _limbConstraints;
        }

    protected:  //methods
        void allocateBoneReplayArrays(const size_t boneCount);
        void deallocateBoneReplayArrays();

        void allocateBlindDataMappingArray(const size_t blindDataInfoCount);
        void deallocateBlindDataMappingArray();

    protected:  //data
        const AnimationInterface* _animation = NULL;
        const GenericSpecificConverter* _genericConverterForAnimation = NULL;    //is not necessary the one from the animation
        bool _identityReplay = false;

        //************************************************************
        //! @name Constraint data
        //************************************************************
        //@{
        int _nbConstrainedChannels;          //!< number of channels that have constraints
        int* _constrainedChannelsIndices;    //!< Indices of the channels that have constraints (size is _nbConstrainedChannels)
        int* _nonConstrainedChannelsIndices; //!< Indices of the channels that have no constraints (size is _tree->_genericSpecificConverter->_converterMapping->getLimbBones().size() - _nbConstrainedChannels)

        glm::Array<LimbConstraint*> _limbConstraints;           //!< Cache of LimbConstraint, size of vector is _nbConstrainedChannels
        PODArray<float> _limbConstraintPointsWorldGroundHeight; // per animation world ground height, stored WGH are per active constraint channel, then per anim posture inside each channel (animPosture minor, constraintChannel major : [cstr0-frame0][cst0-frame1][cst0-frame2][cstr1-frame0][cstr1-frame1][cstr1-frame2]
        //@}

        //************************************************************
        //! @name Configuration data that can be changed directly
        //************************************************************
        //@{
        LoopMode::Value _loopMode;
        bool _interpolateBetweenFrames;
        bool _forceConstantGroundHeightInAnimation;
        bool _allowBoneToBoneIdentityMappingIfAvailable = true;
        //@}

        //************************************************************
        //! @name Configuration data
        //************************************************************
        //@{
        const AnimationReplayMapping* _theAnimationReplayMapping;
        bool _mirror;
        //@}

        //************************************************************
        //! @name Runtime data
        //************************************************************
        //@{
        glm::PODArray<int> _boneReplayMapping;
        glm::PODArray<int> _boneReplayModes;
        glm::PODArray<int> _blindDataMapping;
        glm::PODArray<Angle> _boneAdditionalIkNormalOffset;
        //@}
    };

    /**
	* \brief Blend node handling a generalized animation
	*
	* The AnimationPlayBN is a blend node that plays an animation on the skeleton set for blend tree.
	*
	* For additive blend purpose, a given posture (said 'subtrahend posture') can be set : the produced posture is the result of the difference
	* between the posture computed form input animation ('minuhend posture') and the subtrahend posture. Notice that in this case, the
	* AnimationPlayBN should be used only as an input of an AdditiveBlendBN (e.g. there should be an AdditiveBlendBN somewhere in its parent
	* hierarchy).
	*
	* \author dev@golaem.com
	*
	*/
    class GLM_SDK_API AnimationPlayBN : public AbstractBlendNode
    {
        friend class PlayAnimationBehavior;
        friend class SyncActionBehavior;
        friend class MotionContext;
        friend class ::AnimationPlayBNTest;

    public: //structures and enums

        //-------------------------------------------------------------------------
        struct IKPlaneOrientationMode
        {
            uint8_t _fullSourceOri : 1;       //if not, keep only the direction from source orientation
            uint8_t _fixIkOri : 1;            //if _fullSourceOri is set to false, this will try to fix the IK orientation another way but won't change the bone roll direction
            uint8_t _rollCompensateOnEOB : 1; //if _fullSourceOri is set to false, this will also fix the end of bone orientation that is usually uncorrect
        };

        //-------------------------------------------------------------------------
        struct SyncDurationMode
        {
        public:
            enum Value
            {
                EVENLY_SPREAD_ON_TIME = 0,
                EVENLY_SPREAD_ON_TRANSLATION = 1,
                END
            };
        };

        //-------------------------------------------------------------------------
        struct FootPrintRollTimes
        {
            GLM_CLASS_STAT_ID( glm::MemoryStatIds::Simulation);

            // pre/post roll times for the current playback phase
            Time _prePostRollTimes[4];
        };

        //-------------------------------------------------------------------------
        struct SlaveData
        {
            GLM_CLASS_STAT_ID( glm::MemoryStatIds::Simulation);

            AnimationPlayBN* _slaveAnimPlayBN;
            int64_t _slaveIndex;

            Vector3 _nullSyncPosition;
            Quaternion _nullSyncOrientation;

            Time _syncDuration;
            SyncDurationMode::Value _syncDurationMethod; //method to spread the transformation during the _syncDuration
            mutable Time _syncDurationSpent;
        };

        //-------------------------------------------------------------------------
        class GLM_SDK_API PerEntityData
        {
            friend class PlayAnimationBehavior;
            friend class SyncActionBehavior;
            friend class AnimationPlayBN;

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

            PerEntityData();
            ~PerEntityData();

            //************************************************************
            //! @name configuration data
            //************************************************************
            //@{
            int16_t _animationIndex; // index of animation to use
            //@}

            //************************************************************
            //! @name Runtime time data
            //************************************************************
            //@{
            LoopMode::Value _loopMode;

            Time _lastDeltaTime;
            Time _currentTime;
            float _replayRatio;

            ReplayTimeChannel _globalReplayTime;
            ReplayTimeChannel _pelvisReplayTime;
            ReplayTimeChannel* _channelReplayTime;
            //@}

            //************************************************************
            //! @name Runtime position data
            //************************************************************
            //@{
            Vector3 _zupPelvisWorldPosition;
            Quaternion _zupPelvisWorldOrientation;

            float _speed;
            Vector3 _movingDirection;

            Vector3 _loopOriginNormalizedPosition[2]; //!< _loopOriginNormalizedPosition[0] is the current loop position, _loopOriginNormalizedPosition[1] is the next one
            Quaternion _loopOriginOrientation[2];     //!< _loopOriginOrientation[0] is the current loop position, _loopOriginOrientation[1] is the next one
            //@}
            FootPrintRollTimes* _footprintRollTimes; //!< Cache of last footprints, size of array is _nbConstrainedChannels
            unsigned char* _lastFootPrintPhase;      //!< Cache of footprint phases, size of array is _nbConstrainedChannels
            int _nbFootprintsAllocated;
            //@}

            //************************************************************
            //! @name Constraint data
            //************************************************************
            //@{
            AbstractBlendNode::ConstraintChannel* _constraintChannels; //!< Constraint points for each channel
            //@}

            //************************************************************
            //! @name Synchronization data
            //************************************************************
            //@{
            AbstractBlendNode::SyncChannel* _syncChannels;
            SyncTime* _perChannelSyncTime;
            //@}

            BlendTreePosture* _subtrahendPosture;

            bool _isInitialized;
            bool _footprintDirty;

            //these options tells how the blend tree transform is applied back on the animation transforms (the blend node will use it's own transforms if locked, or the blend tree transform if not)
            bool _lockPosXY;
            bool _lockPosZ;
            bool _lockOri;

            //************************************************************
            //! @name Slave list to sync time and transforms
            //************************************************************
            //@{
            mutable glm::SpinLock _slavesToSyncLock;
            glm::Array<SlaveData> _slavesToSync; //slaves are sync during the update time function
                                                 //@}

#if CSV_LOG_ALL
            glm::Array<float> _currentlyPlayedNormalizedTime;
            glm::Array<float> _currentlyPlayedNormalizedTimeOnPelvis;
            glm::Array<Vector3> _currentPosition;
            glm::Array<Quaternion> _currentOrientation;
            glm::Array<glm::Array<float>> _currentlyPlayedNormalizedTimeOnChannel;
#endif

        private:
            //************************************************************
            //! @name These methods are for serialization purpose only
            //************************************************************
            //@{
            const Vector3& getLoopOriginNormalizedPosition(unsigned int idx) const
            {
                return _loopOriginNormalizedPosition[idx];
            }
            void setLoopOriginNormalizedPosition(unsigned int idx, const Vector3& npos)
            {
                _loopOriginNormalizedPosition[idx] = npos;
            }
            const Quaternion& getLoopOriginOrientation(unsigned int idx) const
            {
                return _loopOriginOrientation[idx];
            }
            void setLoopOriginOrientation(unsigned int idx, const Quaternion& ori)
            {
                _loopOriginOrientation[idx] = ori;
            }
            //@}
        };

    public: //static methods
        GLM_CLASS_STAT_ID(glm::MemoryStatIds::Simulation);

        static void computeTrajectoryNormalizedDeltasPrediction(Vector3* retDeltaPositions, Quaternion* retDeltaOrientations, float normalizedStartTime, float normalizedDeltaTimePerPoint, unsigned int predictionPointsCount, const GenericSpecificConverter* replayConverter, const PerAnimationData& animData, const Quaternion& upToZRotation, const Quaternion& zToUpRotation);
        static void computePosture(BlendTreePosture& outPosture, BlendTreePosture& intermediatePosture,
            Array<GenericPosture*>& genericPosturesCache,
            const GenericSkeleton* genericSkeleton, const GenericSkeleton::Override* genericSkeletonOverride,
            const GenericSpecificConverter* postureGenericSpecificConverter, const HierarchicalSkeletonDescription::Override* specificSkeletonOverride,
            float postureScale, float skeletonRootHeightUnscaled,
            const Quaternion& upToZRotation, const Quaternion& zToUpRotation,
            const PerAnimationData& animData, const AnimationPlayBN::PerEntityData& entityData,
            AnimationPlayBN::IKPlaneOrientationMode ikPlaneOrientationMode,
            const glm::PODArray<bool>& snsBoneEnabled, const glm::PODArray<bool>& snsEndOfBoneEnabled,
            MirrorMethod::Value mirrorMethod, bool usePreciseIKNormalPlaneBlend_onInterpolationBetweenFrames);

    public: //methods

        //************************************************************
        //! @name Constructors and destructor
        //************************************************************
        //@{
        AnimationPlayBN();
        ~AnimationPlayBN();
        //@}

        //************************************************************
        //! @name Execution control
        //************************************************************
        //@{
        /**
		 * \warning Initialization requires a skeleton (through containing blend tree) and an animation to be properly set
		 */
        void init() override;
        void init(int64_t entityIndex) override;   // once per entity, internally call the resetTimeAndPosition method
        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;

        /**
         * \warning Node must have been initialized
         */
        void updateTime(int64_t entityIndex, glm::Time dt, SyncTime syncData[]);

        /**
		 * \warning Node must have been initialized
		 */
        void updateSync(int64_t /*entityIndex*/, glm::Time /*dt*/, SyncTime /*syncData*/[]) override
        {
        }

        void updateActiveBones(int64_t entityIndex, glm::Time dt) override;
        void setFullPostureUpdateNeeded(int64_t entityIndex, bool fullPostureUpdateNeeded) override;
        void updateFullPostureNeededFlag(int64_t entityIndex, glm::Time dt) override;

        /**
		 * \warning Node must have been initialized
		 */
        void updatePosture(int64_t entityIndex, glm::Time dt) override;

        float computeSyncInterpolationCoefficient(const Time& syncDuration, Time& syncDurationSpent, const Time& dt, const SyncDurationMode::Value& syncMethod, int64_t entityIndex);
        
        /**
		* \brief Set the current time and transforms to all slaves node.
		* \warning Slaves should be set in LoopMode::MANUAL mode
		* \warning Slaves should be locked on the desired axis
		* \warning Should be done at the end of updateTime, and before updatePosture to ensure slaves won't suffer a 1 frame delay
		*/
        void syncronizeSlaves(int64_t entityIndex, glm::Time dt) const;
        //@}

        //************************************************************
        //! @name Configuration
        //************************************************************
        //@{
        /**
		* \warning Node must not be already initialized
		*/
        void setEntityAnimationIndex(int64_t entityIndex, int animationIndex, const float replayRatio = 1.f); // by default, animationIndex will be 0 and replayRatio 1.f if this method is not called
        inline int getEntityAnimationIndex(int64_t entityIndex) const
        {
            return _perEntityData[entityIndex]._animationIndex;
        }
        inline const Vector3& getEntityLoopNormalizedPosition0(int entityIndex) const
        {
            return _perEntityData[entityIndex]._loopOriginNormalizedPosition[0];
        }
        inline const Quaternion& getEntityLoopOriginOrientation0(int entityIndex) const
        {
            return _perEntityData[entityIndex]._loopOriginOrientation[0];
        }

        inline float getEntityReplayRatio(int64_t entityIndex) const
        {
            return _perEntityData[entityIndex]._replayRatio;
        }
        void setEntityReplayRatio(int64_t entityIndex, float replayRatio);

        int addAnimation(const AnimationInterface* animation, const bool mirrored, const AnimationReplayMapping* replayMapping, const bool allowBoneToBoneIdentityMappingIfAvailable = true, const LoopMode::Value loopMode = LoopMode::LOOP, const bool interpolateBetweenFrames = true, const bool forceConstantGroundHeightInAnimation = false);

        inline int getAnimationCount() const
        {
            return static_cast<int>(_perAnimationData.size());
        }

        void initAnimationData(size_t iAnim);
        inline PerAnimationData& getAnimationData(int animationIndex)
        {
            return _perAnimationData[animationIndex];
        }
        inline const PerAnimationData& getAnimationData(int animationIndex) const
        {
            return _perAnimationData[animationIndex];
        }

        inline bool entityHasAnimation(int64_t entityIndex)
        {
            return _perEntityData[entityIndex]._animationIndex < getAnimationCount();
        } // check if animation is valid for this entity
        inline PerAnimationData& getAnimationDataForEntity(int64_t entityIndex)
        {
            return getAnimationData(_perEntityData[entityIndex]._animationIndex);
        }
        inline const PerAnimationData& getAnimationDataForEntity(int64_t entityIndex) const
        {
            return getAnimationData(_perEntityData[entityIndex]._animationIndex);
        }
        inline const PerEntityData& getEntityData(int64_t entityIndex) const
        {
            return _perEntityData[entityIndex];
        }

        /**
		 * \brief This reset the time and position of the blend node as if it was just initialized
		 * \note Internally called by the init method
		 */
        void resetTimeAndPosition(int64_t entityIndex);

        /**
		 * \brief This set the frame index to use.
		 * \param frameIndex frame index to play
		 * \param loopIndex	loop index
		 * \note At next update, the frame will be computed according to the current replay mode
		 */
        void setCurrentFrameIndex(int64_t entityIndex, int frameIndex, int loopIndex = 0);
        /**
		 * \brief Set the current time of the animation.
		 * \param percentTime percent of animation time to use as current time
		 */
        void setCurrentNormalizedTime(int64_t entityIndex, float percentTime);

        /**
		 * \warning Input posture must be computed on blend tree skeleton (if necessary, use the 'computePosture' method)
		 */
        void setSubtrahendPosture(int64_t entityIndex, const BlendTreePosture* subtrahendPosture);
        //@}

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

        glm::Vector3 getZupPelvisWorldPosition(int64_t entityIndex) const override { return _perEntityData[entityIndex]._zupPelvisWorldPosition; }
        void setZupPelvisWorldPosition(int64_t entityIndex, const glm::Vector3& position, bool forcePosXY = false, bool forcePosZ = false) override;

        glm::Quaternion getZupPelvisWorldOrientation(int64_t entityIndex) const override { return _perEntityData[entityIndex]._zupPelvisWorldOrientation; }
        void setZupPelvisWorldOrientation(int64_t entityIndex, const glm::Quaternion& orientation, bool forceOri = false) override;

        float getSpeed(int64_t entityIndex) const override { return _perEntityData[entityIndex]._speed; }
        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
		*/
        void computeTrajectoryNormalizedDeltasPrediction(Vector3* retDeltaPositions, Quaternion* retDeltaOrientations, int64_t entityIndex, unsigned int predictionPointsCount, glm::Time predictionSampleDt) const;
        //@}

        //************************************************************
        //! @name Synchronization system
        //************************************************************
        //@{
        glm::Time getCycleDuration(int64_t entityIndex) const override
        {
            return getDuration(entityIndex);
        }
        SyncChannel* getSyncChannels(int64_t entityIndex) const override
        {
            return _perEntityData[entityIndex]._syncChannels;
        }
        void setChannelSyncTime(int64_t entityIndex, int channelIndex, bool externallySynchronized, float externalCycleTime) override;
        //@}

        inline void setLockTransform(int64_t entityIndex, bool lockPosXY, bool lockPosZ, bool lockOri);

        //************************************************************
        //! @name Accessors
        //************************************************************
        //@{

        const GenericSkeleton* getGenericSkeleton() const { return _genericSkeleton; }

        Time getCurrentTime(int64_t entityIndex) const
        {
            return _perEntityData[entityIndex]._currentTime;
        }
        int getCurrentPostureIndex(int64_t entityIndex) const
        {
            return _perEntityData[entityIndex]._globalReplayTime._postureIndex[0];
        }
        inline float getCurrentAnimationRatio(int64_t entityIndex) const;
        const ReplayTimeChannel& getCurrentReplayTime(int64_t entityIndex) const
        {
            return _perEntityData[entityIndex]._globalReplayTime;
        }

        const ConstraintChannel* const getConstraintChannels(int64_t entityIndex) const override
        {
            return _perEntityData[entityIndex]._constraintChannels;
        }

        bool isMirrorAvailable(const AnimationReplayMapping& animationReplayMapping, const GenericSpecificConverter* converter, glm::GlmString* returnWarningMsg = NULL) const;
        //@}

        //************************************************************
        //! @name Animation related accessors
        //************************************************************
        //@{
        inline Time getDeltaTime(int64_t entityIndex) const; // using replay ratio of specific entity
        inline Time getDuration(int64_t entityIndex) const;  // using replay ratio of specific entity
        //inline Time getDurationFromMasterReplayRatio(int64_t entityIndex) const; // without custom replayRatio
        //@}

        //************************************************************
        //! @name Posture operations
        //************************************************************
        //@{
        /**
		 * \warning Node must have been initialized
		 */
        void computePosture(int animationIndex, int postureIndex, BlendTreePosture& resultPosture) const;
        //@}
        
        void setMask(int64_t entityIndex, const Array<bool>& mask);

        void setCaches(BlendTreePostureCache& blendTreePostureCache, AnimationPlayBNCache& animationPlayBNCache, AnimationCache* animationCache);
        AnimationCache* getAnimationCache() const { return _animationCache; }

        IKPlaneOrientationMode _ikPlaneOrientationMode;
        glm::PODArray<bool> _snsBoneEnabled;
        glm::PODArray<bool> _snsEndOfBoneEnabled;

        float _footprintPreRollRelativeTime;
        float _footprintPostRollRelativeTime;

        MirrorMethod::Value _mirrorMethod;
        float _timeWarpingToPelvisWeight;

    protected:
        //bool getRefreshReplayTimes() const;
        //void refreshReplayTimes(bool);

        void initAnimationData();

        void initSynchronizationChannels(int64_t entityIndex);

        void precomputeWorldPosForAnim(int animationIndex, Array<Vector3>& worldPosFromAnimation, unsigned int animPostureCount, int channelsCount) const;

        void initConstraintsChannelsPerEntity(int64_t entityIndex);
        void initConstraintsChannelsPerAnim(int animationIndex, const Array<Vector3>& worldPosFromAnimation, int worldPosCountPerPosture);
        void initConstraintsWorldGroundHeightPerAnim(int animationIndex, const Array<Vector3>& worldPosFromAnimation, int worldPosCountPerPosture);

        /**
		* \brief Compute the current posture (called by the doUpdate method)
		*/
        void computePosture(int64_t entityIndex, BlendTreePosture& posture);

        void computeConstraintChannels(int64_t entityIndex);

        /**
		* \brief Compute the loop origin (used by the sync slave method)
		*/
        void computeLoopOriginPositionAndOrientation(int64_t entityIndex, glm::Vector3& loopOriginPositionn, glm::Quaternion& loopOriginOrientation, const glm::Vector3& pelvisPosition, const glm::Quaternion& pelvisOrientation) const;

        void precomputeLimbConstraintForAnim(PerAnimationData& animData, LimbConstraint* theLimbConstraint, const GroundSupportSequence& groundSequence, const GenericSpecificConverterMapping* replaySkeleton, int replayChannel, const Array<Vector3>& wPosFromPostures, int boneWPosWOriIndex, int boneWPosWOrifloatStride);
        void precomputeLimbWorldGroundHeightForAnim(PerAnimationData& animData, const GenericSpecificConverterMapping* replaymapping, float skeletonScale, int replayChannel, int validCstrChannelIndex, const Array<Vector3>& wPosFromPostures, int boneWPosWOriIndex, int boneWPosWOrifloatStride, float& worldGroundHeight);

    private:
        AnimationCache* _animationCache;

        BlendTreePostureCache* _blendTreePostureCache;
        AnimationPlayBNCache* _animPlayBNCache;

        const GenericSkeleton* _genericSkeleton;
        glm::Array<ChannelInfo> _channelsInfo;

        glm::Array<PerEntityData> _perEntityData;
        glm::Array<PerAnimationData> _perAnimationData;

        char* _channelsMemoryBuffer;
    };

    //************************************************************
    // Definition of inline methods
    //************************************************************

//    //------------------------------------------------------------
//    inline const GenericPosture&
//    AnimationPlayBN::PerAnimationData::getPosture(int postureIndex) const
//    {
//#ifndef NDEBUG
//        GLM_DEBUG_ASSERT(_generalizedAnimation != NULL);
//        int postureCount = getPosturesCount();
//        GLM_DEBUG_ASSERT(postureCount > 0);
//        GLM_DEBUG_ASSERT(postureIndex >= 0);
//        GLM_DEBUG_ASSERT(postureIndex < postureCount);
//#endif
//        return _generalizedAnimation->_posturesArray[postureIndex];
//    }

    ////------------------------------------------------------------
    //inline int
    //AnimationPlayBN::PerAnimationData::getPosturesCount() const
    //{
    //    return static_cast<int>(_generalizedAnimation->_posturesArray.size());
    //}

    //------------------------------------------------------------
    inline Time
    AnimationPlayBN::getDuration(int64_t entityIndex) const
    {
        int animationIndex = _perEntityData[entityIndex]._animationIndex;
        return (getAnimationData(animationIndex).getPosturesCount() - 1) * getAnimationData(animationIndex).getDeltaTime() / _perEntityData[entityIndex]._replayRatio;
    }

    //------------------------------------------------------------
    inline float
    AnimationPlayBN::getCurrentAnimationRatio(int64_t entityIndex) const
    {
        const PerAnimationData& animData = getAnimationDataForEntity(entityIndex);
        return _perEntityData[entityIndex]._globalReplayTime.getNormalizedTime(animData.getPosturesCount(), _perEntityData[entityIndex]._loopMode);
    }

    //------------------------------------------------------------
    inline Time
    AnimationPlayBN::getDeltaTime(int64_t entityIndex) const
    {
        int animationIndex = _perEntityData[entityIndex]._animationIndex;
        return getAnimationData(animationIndex).getDeltaTime() / _perEntityData[entityIndex]._replayRatio;
    }

    //------------------------------------------------------------
    inline void
    AnimationPlayBN::setLockTransform(int64_t entityIndex, bool lockPosXY, bool lockPosZ, bool lockOri)
    {
        _perEntityData[entityIndex]._lockPosXY = lockPosXY;
        _perEntityData[entityIndex]._lockPosZ = lockPosZ;
        _perEntityData[entityIndex]._lockOri = lockOri;
    }
} // namespace glm
