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

#pragma once

#include "glmSDKAPI.h"
#include "glmMotionDefinitions.h"

#include <glmEvent.h>
#include <glmAbstractUserCallbackBehavior.h>

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

    /**
	 * \brief Abstract behavior dedicated to Motion component
	 * 
	 * AbstractMotionBehaviors are specific behaviors dedicated to Motion, e.g. behaviors that interact on a blend tree:
	 * - either by configuring/sending requests to a preconfigured elemnt of the blend tree, such as GroundAdaptation, AimAt or IK blend nodes;
	 * - or by connecting to the blend tree a custom sub blend tree, consisting of SimpleBlend and AnimationPlay blend nodes. Such behaviors are
	 * prioritized in order to handle competition between them.
	 * 
	 * The MotionBehaviorsHandler is responsible for collecting and initializing all the declared AbstractMotionBehaviors. Furthermore, it is also
	 * responsible for connecting to the blend tree and computing blend weights at behaviors start/stop for the AbstractMotionBehaviors that handle a
	 * sub blend tree and so can be prioritized.
	 *
	 * Custom motion behaviors which needs to connect a custom sub-blend tree must register with the MotionBehaviorsHandler
	 * calling either registerStandardBehavior() or registerAdditiveBehavior(), when behavior gets added to entity type.
	 * Currently, this registration is performed in each custom motion behavior SetBehaviorGraph() overridden function.
	 * 
	 * Notice that at engine initialization, the behavior connection request ("connectToBlendTree" method) is called BEFORE the behavior init ("init"
	 * method). When behavior init is called, the blend tree has already been configured and can not be edited anymore (so any operation that consists 
	 * in configuration blend tree must be done in "connectToBlendTree" method, not in "doInit" method). Here is initialization call sequence:
	 *   Simulation::init()
	 *   | ContextsManager::init()
	 *   | | IContext::init()
	 *   | | | EntityComponent::init()
	 *   | | | | MotionEntityComponent::doInit()
	 *   | | | | | MotionBehaviorsHandler::init()
	 *   | | | | | | CustomMotionBehavior::connectToBlendTree()
	 *   | | | | | | x 
	 *   | EntitiesManager::init()
	 *   | | Entity::init
	 *   | | | AbstractBehavior::init()
	 *   | | | | CustomMotionBehavior::doInit()
	 *
	 */
    class GLM_SDK_API AbstractMotionBehavior : public AbstractUserCallbackBehavior
    {
    public:
        /// Priorities and weights are evaluated only when any motion behavior is started or stopped
        int _motionPriority;

        bool _forceMovingDirectionComputation;

		GlmString _motionWeightAttributeName;

    private:
        glm::PODArray<float> _motionStartDurations;   /// starting duration per entity
        glm::PODArray<float> _motionStopDurations;    /// stopping duration per entity
        glm::PODArray<float> _motionRatios;           /// ratios per entity
        glm::PODArray<float> _motionWeights;          /// weights per entity
        glm::PODArray<float> _animationStartPercents; /// weights per entity

        MotionBehaviorsHandler* _motionBehaviorsHandler; // for faster access only

    protected:
        MotionContext* _motionContext;

    public:
        GLM_ABSTRACTCLASS_STAT_ID( glm::MemoryStatIds::Simulation)

        AbstractMotionBehavior();
        virtual ~AbstractMotionBehavior();

        inline void setMotionStartDuration(int64_t index, float startDuration);
        inline void setMotionStopDuration(int64_t index, float stopDuration);
        inline void setWeight(int64_t index, float weight);
        inline void setMotionRatio(int64_t index, float motionRatio);
        inline void setAnimationStartPercent(int64_t index, float animationStartPercent);

        inline float getMotionStartDuration(int64_t index) const;
        inline float getMotionStopDuration(int64_t index) const;
        inline float getWeight(int64_t index) const;
        inline float getMotionRatio(int64_t index) const;
        inline float getAnimationStartPercent(int64_t index) const;

        /// Get the root of the sub blend tree hold by current behavior, that will be connected to the blend tree through a dedicated blend node (see MotionBehaviorsHandler)
        inline virtual const AbstractBlendNode* getSubBlendTreeRootNode() const;

        /// If behavior is using a sub blend tree, connect/disconnect sub blend tree to/from main blend tree
        inline virtual void connectToBlendTree(BlendTree& /*blendTree*/, AbstractBlendNode* /*connectionNode*/, BehaviorGraph& /*graph*/);
        inline virtual void disconnectFromBlendTree(BlendTree& /*blendTree*/);

        /// getSubBlendTreeRootNode() should not be null before any call to this method
        /// Active bones/blinddata masks are stored as float for convenience (faster use of multiplication), values stored are always 0.0f (false) or 1.0f (true)
        virtual const float* getActiveBones(int64_t index) const;
        virtual const float* getActiveBlindData(int64_t index) const;

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

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

        glm::MotionBehaviorsHandler* getMotionBehaviorsHandler() const;

        /// Register/unregister behavior to motion behaviors handler and possibly initialize behavior start/stop callbacks
        inline virtual void registerToMotionBehaviorsHandler();
        inline virtual void unregisterFromMotionBehaviorsHandler();
        inline virtual void doCallbackComputation(const UserCallbackContext&, int64_t, BehaviorGraph&, Time);

        virtual void doAllocate(BehaviorGraph& graph) override;
        virtual void doDeallocate() override;
        virtual void doResize(BehaviorGraph& graph) override;
        inline virtual void doFinish(BehaviorGraph& /*graph*/) override;
        virtual void doFinish(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;
    };

    //-------------------------------------------------------------------------
    inline void AbstractMotionBehavior::setMotionStartDuration(int64_t index, float startDuration)
    {
        _motionStartDurations[index] = startDuration;
    }

    //-------------------------------------------------------------------------
    inline void AbstractMotionBehavior::setMotionStopDuration(int64_t index, float stopDuration)
    {
        _motionStopDurations[index] = stopDuration;
    }

    //-------------------------------------------------------------------------
    inline void AbstractMotionBehavior::setWeight(int64_t index, float weight)
    {
        _motionWeights[index] = weight;
    }

    //-------------------------------------------------------------------------
    inline void AbstractMotionBehavior::setMotionRatio(int64_t index, float motionRatio)
    {
        _motionRatios[index] = motionRatio;
    }

    //-------------------------------------------------------------------------
    inline void AbstractMotionBehavior::setAnimationStartPercent(int64_t index, float animationStartPercent)
    {
        _animationStartPercents[index] = animationStartPercent;
    }

    //-------------------------------------------------------------------------
    inline float AbstractMotionBehavior::getMotionStartDuration(int64_t index) const
    {
        return _motionStartDurations[index];
    }

    //-------------------------------------------------------------------------
    inline float AbstractMotionBehavior::getMotionStopDuration(int64_t index) const
    {
        return _motionStopDurations[index];
    }

    //-------------------------------------------------------------------------
    inline float AbstractMotionBehavior::getWeight(int64_t index) const
    {
        return _motionWeights[index];
    }

    //-------------------------------------------------------------------------
    inline float AbstractMotionBehavior::getMotionRatio(int64_t index) const
    {
        return _motionRatios[index];
    }

    //-------------------------------------------------------------------------
    inline float AbstractMotionBehavior::getAnimationStartPercent(int64_t index) const
    {
        return _animationStartPercents[index];
    }

    //-------------------------------------------------------------------------
    inline const AbstractBlendNode* AbstractMotionBehavior::getSubBlendTreeRootNode() const
    {
        return NULL;
    }

    //-------------------------------------------------------------------------
    inline void AbstractMotionBehavior::connectToBlendTree(BlendTree& /*blendTree*/, AbstractBlendNode* /*connectionNode*/, BehaviorGraph& /*graph*/)
    {
    }

    //-------------------------------------------------------------------------
    inline void AbstractMotionBehavior::disconnectFromBlendTree(BlendTree& /*blendTree*/)
    {
    }

    //-------------------------------------------------------------------------
    inline void AbstractMotionBehavior::registerToMotionBehaviorsHandler()
    {
    }

    //-------------------------------------------------------------------------
    inline void AbstractMotionBehavior::unregisterFromMotionBehaviorsHandler()
    {
    }

    //-------------------------------------------------------------------------
    inline void AbstractMotionBehavior::doCallbackComputation(const UserCallbackContext&, int64_t, BehaviorGraph&, Time)
    {
    }

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

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