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

#pragma once

#include "glmSDKAPI.h"
#include "glmMotionDefinitions.h"
#include "glmAnimationReplayMapping.h"
#include "glmAnimationPlayBN.h"
#include <glmMotionEntityComponent.h>

#include "glmAbstractMotionBehavior.h"
#include <glmGolaemMotionDeclarations.h> // GolaemMotion declarations

namespace glm
{
    class AnimationReplayMapping;
    class SimpleBlendBN;
    class GenericSpecificConverter;
    class AnimationCache;
    class BlendTreePostureCache;
    class MotionContext;
    class SyncActionBehavior;
    class PlayAnimationBehavior;
    class MotionCollectionInterface;

    class getPlayAnimationBehaviorCallback
    {
    public:
		GLM_CLASS_STAT_ID(glm::MemoryStatIds::Simulation)
        virtual inline ~getPlayAnimationBehaviorCallback();

        virtual int getSelectionIndex() = 0;

        virtual PlayAnimationBehavior* getPlayAnimBehavior() = 0;
        virtual bool getPlayAnimBehaviorWarning() = 0;
        virtual void setPlayAnimBehaviorWarning(bool setWarning) = 0;
    };

    // Play an animation on a character
    class GLM_SDK_API PlayAnimationBehavior : public AbstractMotionBehavior
    {
    public:
        /*
		* \brief Enumerated type defining the blend mode to use
		*
		* Blend mode can be set to either to standard (STANDARD_BLEND) or additive ones (ADDITIVE_BLEND_...). First, animations set up with standard
		* blend are blended together throught a SimpleBlendBN node, according to animation priorities and weights. Then animations set up in with
		* additive blend are "added" through an AdditiveBlendBN node, according to their animation weights.
		*
		* Additive blend is usually performed by computing at each frame the difference between the current frame of the animation and a given frame
		* (referred as "subtrahend" frame/posture in the followings). Then this difference is used for addition by the AdditiveBlendBN node. It can
		* be configured by using one of this blend mode:
		* - ADDITIVE_BLEND_NO_SUBSTRACT: Do not use any subtrahend frame (animation is added "as is")
		* - ADDITIVE_BLEND_SUBSTRACT_CLEAR_FRAME: Subtrahend frame is the neutral posture (e.g. the animation skeleton cleared posture)
		* - ADDITIVE_BLEND_SUBSTRACT_FIRST_FRAME: Subtrahend frame is the the animation 1st frame
		* - ADDITIVE_BLEND_SUBSTRACT_AVERAGE_FRAME: Subtrahend frame is the the animation average frame (computed at initialization)
		*/
        struct BlendMode
        {
        public:
            enum Value
            {
                STANDARD_BLEND = 0,
                ADDITIVE_BLEND_NO_SUBSTRACT = 1,
                ADDITIVE_BLEND_SUBSTRACT_CLEAR_FRAME = 2,
                ADDITIVE_BLEND_SUBSTRACT_FIRST_FRAME = 3,
                ADDITIVE_BLEND_SUBSTRACT_AVERAGE_FRAME = 4,
                END
            };
        };

        /*
		* \brief Enumerated type defining the replay ratio mode to use
		* NONE Replay Ratio is static
		* AUTOMATIC Replay Ratio is driven by the motion clip
		* CALLBACK Replay Ratio is driven by the update calback
		*/
        struct ReplayRatioMode
        {
        public:
            enum Value
            {
                NONE,
                AUTOMATIC,
                FROMCALLBACK,
				FROMATTRIBUTE,
                END
            };
        };

        struct ChannelMask
        {
            int _channelIndex;
            bool _limbMask;
            bool _globalEffectorMask;
            glm::Array<bool> _effectorsMask;

            inline ChannelMask(int channelIndex, bool limbMask, bool effectorMask);
            inline ChannelMask(int channelIndex, bool limbMask, const glm::Array<bool>& effectorsMask);
        };

        //-------------------------------------------------------------------------
        class PlayAnimDataPerEntity
        {
        public:
            float _loopLastComputedWeight = 0.f;
            float _replayRatio = 1.f;
            float _animationVelocity = -1.f; /// Initial motion velocity (for automatic replay ratio computation)
            uint16_t _animationIndex = 0; /// animation used for this entity
            char _animationBlendNodeActive = 0;
            bool _looping = false;

        public:
            GLM_CLASS_STAT_ID( glm::MemoryStatIds::Simulation);
            PlayAnimDataPerEntity()
            {}
        };

        //-------------------------------------------------------------------------
        class PlayAnimDataPerAnim
        {
            friend class PlayAnimationBehavior;
            friend class SyncActionBehavior;

        protected:
            GlmString _animationPath = "";
            AnimationInterface* _animation = NULL;
            AnimationReplayMapping _animationReplayMapping;
            bool _allowBoneToBoneIdentityMappingIfAvailable = true;
            bool _mirrored = false;
            bool _forceConstantGroundHeightInAnimation = false;

        public:
            GLM_CLASS_STAT_ID( glm::MemoryStatIds::Simulation);
            PlayAnimDataPerAnim()
            {}

            inline const char* getAnimationPath() const;
            inline const AnimationReplayMapping& getAnimationReplayMapping() const;
            inline void setAnimationReplayMapping(const AnimationReplayMapping& replayMapping, const bool allowBoneToBoneIdentityMappingIfAvailable);
            inline bool getMirrored() const;
            inline bool getForceConstantGroundHeightInAnimation() const;
        };

    public:
        /// \name Public configuration data
        //@{
        int _animationLoopMode;
        bool _supportSelfLoop = false;  //support behavior loops (blending the same motion at different frames when the behavior loops on itself: perf killer but sometimes usefull)
        AnimationPlayBN::IKPlaneOrientationMode _ikPlaneOrientationMode;
        MotionEntityComponent::EntityAndSkeletonTransformSynchronization::Value _entityAndSkeletonTransformSynchronizationMode;
        MotionEntityComponent::EntityAndSkeletonTransformBaseOri::Value _entityAndSkeletonTransformBaseOri;
        MirrorMethod::Value _mirrorMethod;
        int64_t _replayRatioAttributeHashKey = -1;
        //@}

    protected:
        glm::Array<PlayAnimDataPerAnim> _playAnimDataPerAnim;
        int _blendMode;
        Array<bool> _mask;
        bool _interpolateBetweenFrames;
        ReplayRatioMode::Value _replayRatioMode;

        ICallBack::SP _startBehaviorCallBack;
        ICallBack::SP _updateCallback;
        ICallBack::SP _stopBehaviorCallBack;

        glm::Array<PlayAnimDataPerEntity> _playAnimDataPerEntity;

        SimpleBlendBN* _loopBlendNode;
        AnimationPlayBN* _animationBlendNodes[2];

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

        PlayAnimationBehavior(BlendMode::Value blendMode = BlendMode::STANDARD_BLEND, const bool interpolateBetweenFrames = true, const ReplayRatioMode::Value replayRatioMode = ReplayRatioMode::NONE);

        virtual ~PlayAnimationBehavior();

        //************************************************************
        //! @name Animation
        //************************************************************
        //@{
        //const char* getAnimationPath() const;
        inline int getAnimationsCount() const;
        int addAnimationCollection(MotionCollectionInterface* motionCollection, const AnimationReplayMapping* replayMapping, const bool allowBoneToBoneIdentityMappingIfAvailable);
        int addAnimationPath(const char* animationPath, const AnimationReplayMapping* replayMapping = NULL, const bool allowBoneToBoneIdentityMappingIfAvailable = true, const bool mirrored = false, const bool forceGConstantroundHeightInAnimation = false);
        PlayAnimDataPerAnim& getAnimationData(const unsigned int animationIndex);
        const PlayAnimDataPerAnim& getAnimationData(const unsigned int animationIndex) const;
		AnimationReplayMapping& getAnimationReplayMapping(const unsigned int animationIndex);
        //@}

        /// \name per entity animation data
        //@{
        void setEntityAnimationIndex(const int64_t entityIndex, const unsigned int animationIndex, const float replayRatio = 1.f); // by default, using animationIndex 0 and replay ratio 1.f if this method is not called.
        inline bool entityHasAnimation(const int64_t entityIndex) const;
        inline int getEntityAnimationIndex(const int64_t entityIndex) const;
        inline void setReplayRatio(const int64_t entityIndex, float replayRatio);
        inline float getReplayRatio(const int64_t entityIndex) const;

        Time getDeltaTime(int64_t index) const;

        Time getDuration(int64_t index) const;

        void setAnimationVelocity(const float animationTranslation, int64_t index);

        float getCurrentFrameRatio(int64_t index) const;

        int getCurrentFrame(int64_t entityIndex) const;

        void setUpdateCallback(ICallBack* callback);

        //@}

        inline void setReplayRatioMode(ReplayRatioMode::Value replayRatioMode);
        inline void setInterpolateBetweenFrames(bool interpolate);
        inline bool getInterpolateBetweenFrames() const;

        void setBlendMode(PlayAnimationBehavior::BlendMode::Value blendMode);
        PlayAnimationBehavior::BlendMode::Value getBlendMode() const;

        inline const Array<bool>& getMask() const {  return _mask;   }
        inline void setMask(const Array<bool>& mask) {  _mask = mask;   }

        AnimationPlayBN* getAnimationBlendNode(unsigned int bnIndex) const;            // bnIndex == 0, or 1 only for loop
        void setAnimationBlendNode(unsigned int bnIndex, AnimationPlayBN* animPlayBN); // bnIndex == 0, or 1 only for loop

        // Should be called only once the setAnimationReplayMapping was called
        bool isMirrorAvailable(int animationIndex, const GenericSpecificConverter* converter, glm::GlmString* returnWarningMsg = NULL) const;

        // AbstractMotionBehavior implementation
        virtual const AbstractBlendNode* getSubBlendTreeRootNode() const;
        virtual void connectToBlendTree(BlendTree& blendTree, AbstractBlendNode* connectionNode, BehaviorGraph& graph);
        virtual void disconnectFromBlendTree(BlendTree& blendTree);

        // type checking - value derived from this type
        virtual bool isThisType(const AbstractBehavior* value) const override;

    protected:
        // cloning
        friend class CloneFactory;
        inline virtual PlayAnimationBehavior* create() const override;

        // AbstractMotionBehavior implementation
        virtual void registerToMotionBehaviorsHandler() override;

        // AbstractBehavior implementation
        virtual void doAllocate(BehaviorGraph& graph) override;
        virtual void doDeallocate() override;
        virtual void doResize(BehaviorGraph& graph) override;
        inline virtual void doInit(BehaviorGraph& /*graph*/) override;
        inline virtual void doFinish(BehaviorGraph& /*graph*/) override;
        virtual void doInit(int64_t index, BehaviorGraph& graph) override;
        virtual void doFinish(int64_t index, BehaviorGraph& graph) override;
        virtual void doStart(int64_t index, BehaviorGraph& graph) override;
        virtual void doUpdate(int64_t index, BehaviorGraph& graph, Time dt) override;
        virtual AbstractBehavior::ResultStatus::Value doStop(int64_t index, BehaviorGraph& graph, Time dt) override;

    private:
        void createAndInitBlendNodes(AnimationCache& animationCache, BlendTreePostureCache& blendTreePostureCacheForAnimInit, BehaviorGraph& graph, BlendTree& blendTree, AbstractBlendNode* connectionNode, const bool loopable);

        /*
		 * \note Assumes that looping flag is set
		 * \return True if looping flag is still set
		 */
        bool updateBlendWeightsWhenLooping(Time dt, int64_t index);
        /*
		 * \brief Check if the current active animation node is still used for blending, e.g. any of its blend weights (per bone) is not null
		 * \note Assumes that loopable flag is set
		 */
        bool isStillBlending(int64_t index) const;

        void onBehaviorStart(int64_t index);
        void onBehaviorStop(int64_t index);
    };

    //-------------------------------------------------------------------------
    inline getPlayAnimationBehaviorCallback::~getPlayAnimationBehaviorCallback()
    {
    }

    //-------------------------------------------------------------------------
    inline PlayAnimationBehavior::ChannelMask::ChannelMask(int channelIndex, bool limbMask, bool effectorMask)
        : _channelIndex(channelIndex)
        , _limbMask(limbMask)
        , _globalEffectorMask(effectorMask)
    {
    }

    //-------------------------------------------------------------------------
    inline PlayAnimationBehavior::ChannelMask::ChannelMask(int channelIndex, bool limbMask, const glm::Array<bool>& effectorsMask)
        : _channelIndex(channelIndex)
        , _limbMask(limbMask)
        , _globalEffectorMask(false)
        , _effectorsMask(effectorsMask)
    {
    }

    //-------------------------------------------------------------------------
    inline const char* PlayAnimationBehavior::PlayAnimDataPerAnim::getAnimationPath() const
    {
        return _animationPath.c_str();
    }

    //-------------------------------------------------------------------------
    inline const AnimationReplayMapping& PlayAnimationBehavior::PlayAnimDataPerAnim::getAnimationReplayMapping() const
    {
        return _animationReplayMapping;
    }

    //-------------------------------------------------------------------------
    inline void PlayAnimationBehavior::PlayAnimDataPerAnim::setAnimationReplayMapping(const AnimationReplayMapping& replayMapping, const bool allowBoneToBoneIdentityMappingIfAvailable)
    {
        // instance copy, not pointer
        _animationReplayMapping = replayMapping;
        _allowBoneToBoneIdentityMappingIfAvailable = allowBoneToBoneIdentityMappingIfAvailable;
    }

    //-------------------------------------------------------------------------
    inline bool PlayAnimationBehavior::PlayAnimDataPerAnim::getMirrored() const
    {
        return _mirrored;
    }

    //-------------------------------------------------------------------------
    inline bool PlayAnimationBehavior::PlayAnimDataPerAnim::getForceConstantGroundHeightInAnimation() const
    {
        return _forceConstantGroundHeightInAnimation;
    }

    //-------------------------------------------------------------------------
    inline bool PlayAnimationBehavior::entityHasAnimation(const int64_t entityIndex) const
    {
        return _playAnimDataPerEntity[entityIndex]._animationIndex < static_cast<int>(_playAnimDataPerAnim.size());
    }

    //-------------------------------------------------------------------------
    inline int PlayAnimationBehavior::getEntityAnimationIndex(const int64_t entityIndex) const
    {
        return _playAnimDataPerEntity[entityIndex]._animationIndex;
    }

    //-------------------------------------------------------------------------
    inline void PlayAnimationBehavior::setReplayRatio(const int64_t entityIndex, float replayRatio)
    {
        _playAnimDataPerEntity[entityIndex]._replayRatio = replayRatio;
    }

    //-------------------------------------------------------------------------
    inline float PlayAnimationBehavior::getReplayRatio(const int64_t entityIndex) const
    {
        return _playAnimDataPerEntity[entityIndex]._replayRatio;
    }

    //-------------------------------------------------------------------------
    inline void PlayAnimationBehavior::setReplayRatioMode(ReplayRatioMode::Value replayRatioMode)
    {
        GLM_DEBUG_ASSERT(!isInitialized());
        _replayRatioMode = replayRatioMode;
    }

    //-------------------------------------------------------------------------
    inline void PlayAnimationBehavior::setInterpolateBetweenFrames(bool interpolate)
    {
        GLM_DEBUG_ASSERT(!isInitialized());
        _interpolateBetweenFrames = interpolate;
    }

    //-------------------------------------------------------------------------
    inline bool PlayAnimationBehavior::getInterpolateBetweenFrames() const
    {
        return _interpolateBetweenFrames;
    }

    //-------------------------------------------------------------------------
    inline void PlayAnimationBehavior::doInit(BehaviorGraph& /*graph*/)
    {
    }

    //-------------------------------------------------------------------------
    inline void PlayAnimationBehavior::doFinish(BehaviorGraph& /*graph*/)
    {
    }

	//-------------------------------------------------------------------------
	inline int PlayAnimationBehavior::getAnimationsCount() const
	{
		return _playAnimDataPerAnim.sizeInt();
	}

    //-------------------------------------------------------------------------
    inline PlayAnimationBehavior::PlayAnimDataPerAnim& PlayAnimationBehavior::getAnimationData(const unsigned int animationIndex)
    {
        GLM_DEBUG_ASSERT(animationIndex < _playAnimDataPerAnim.size());
        return _playAnimDataPerAnim[animationIndex];
    }

    //-------------------------------------------------------------------------
    inline const PlayAnimationBehavior::PlayAnimDataPerAnim& PlayAnimationBehavior::getAnimationData(const unsigned int animationIndex) const
    {
        GLM_DEBUG_ASSERT(animationIndex < _playAnimDataPerAnim.size());
        return _playAnimDataPerAnim[animationIndex];
    }

	//-------------------------------------------------------------------------
	inline AnimationReplayMapping& PlayAnimationBehavior::getAnimationReplayMapping(const unsigned int animationIndex)
	{
        GLM_DEBUG_ASSERT(animationIndex < _playAnimDataPerAnim.size());
        return _playAnimDataPerAnim[animationIndex]._animationReplayMapping;
	}

    //-------------------------------------------------------------------------
    inline PlayAnimationBehavior::BlendMode::Value
    PlayAnimationBehavior::getBlendMode() const
    {
        return static_cast<PlayAnimationBehavior::BlendMode::Value>(_blendMode);
    }

    //-------------------------------------------------------------------------
    inline void
    PlayAnimationBehavior::setAnimationVelocity(const float animationVelocity, int64_t index)
    {
        _playAnimDataPerEntity[index]._animationVelocity = animationVelocity;
    }

    //-------------------------------------------------------------------------
    inline PlayAnimationBehavior* PlayAnimationBehavior::create() const
    {
        PlayAnimationBehavior* myNewPlayAnimationBehavior = GLM_NEW(PlayAnimationBehavior, myNewPlayAnimationBehavior, getClassMemoryStatId());
        return myNewPlayAnimationBehavior;
    }
} // namespace glm
