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

#pragma once

#include "glmSDKAPI.h"
#include "glmMotionDefinitions.h"
#include "glmEntityType.h"
#include "glmPlayAnimationBehavior.h"
#include "glmAnimationPlayBN.h"

namespace glm
{
    class EntityType;
    /**
	* ActionSelectorCallback is used to select the sync object to use when:
	* - the behavior is starting
	* - and the _syncObjectSelectionMode is configured in SyncObjectSelectionMode::CALLBACK_SELECT_ON_START
	*/
    class GLM_SDK_API ActionSelectorCallback
    {
    public:
		GLM_CLASS_STAT_ID(glm::MemoryStatIds::Simulation)

        inline virtual ~ActionSelectorCallback();

        /**
		* \brief Call the sync object selector
		* \param entityIndex entity index for which doing the selection
		* \return The sync object selection for the entity index
		*/
        virtual int selectSyncObject(EntityType* entityType, int64_t entityIndex) = 0;
    };

    /**
	* EntitySelectorCallback is used to select the sync entity to use when:
	* - the behavior is starting
	* - and the selected sync object is an animation
	* - and the _syncEntitySelectionMode is configured in SyncObjectSelectionMode::CALLBACK_SELECT_ON_START
	*/
    class GLM_SDK_API EntitySelectorCallback
    {
    public:
		GLM_CLASS_STAT_ID(glm::MemoryStatIds::Simulation)

        inline virtual ~EntitySelectorCallback();

        /**
		* \brief Call the sync object selector
		* \param entityIndex entity index for which doing the selection
		* \return The sync object selection for the entity index
		*/
        virtual int selectEntity(EntityType* entityType, int64_t entityIndex) = 0;
    };

    /**
    * \class getNearestPositionCallback
    */
    class GLM_SDK_API NearestPositionCallback
    {
    public:
		GLM_CLASS_STAT_ID(glm::MemoryStatIds::Simulation)

        /**
        * \brief Destructor
        */
        inline virtual ~NearestPositionCallback();

        virtual int getSelectionIndex() = 0;

        /**
        * \brief Call the pointed method/function
		* \param outRecallParameter the return parameter should be the 
        * \return The value returned from the method/function call
        */
        virtual void call(Vector3& outNearestPos, Quaternion& outOri, float* outRecallParameter, const Vector3& entityPosition, const Quaternion& entityOrientation) = 0;
        virtual void recall(Vector3& outNearestPos, Quaternion& outOri, float recallParameter) = 0;
    };

    /**
	 * \brief Behavior playing an animation on a character, synchronized in world space
	 */
    class GLM_SDK_API SyncActionBehavior : public PlayAnimationBehavior
    {
    public: //data
        struct SyncObjectSelectionMode
        {
        public:
            enum Value
            {
                NEAREST_POSITION = 0,
                CALLBACK_SELECT_ON_START = 1,
                END
            };
        };

        class SyncActionDataPerEntity
        {
        public:
            int _selectedSyncActionCallbackIndex;
            float _selectedSyncActionCallbackParameter;

            Vector3 _selectedSyncActionPosition;
            Quaternion _selectedSyncActionOrientation;

            PlayAnimationBehavior* _syncOnAnotherAnimation;
            int _syncOnAnotherAnimationEntityIndex;

            Time _syncDurationSpent;

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

            SyncActionDataPerEntity();
        };

        class SyncActionDataPerAnim
        {
        public:
            Vector3 _animationTotalDeltaPosition;       // per anim
            Quaternion _animationTotalDeltaOrientation; // per anim

            bool _keepFullPostures;
            glm::Array<BlendTreePosture> _fullPostures;
            glm::Array<Vector3> _pelvisPositions;
            glm::Array<Quaternion> _pelvisOrientations;

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

            inline SyncActionDataPerAnim();
        };

        //************************************************************
        //! @name Public configuration data
        //************************************************************
        //@{
        SyncObjectSelectionMode::Value _syncObjectSelectionMode;
        ActionSelectorCallback* _selectSyncObjectCallback;

        SyncObjectSelectionMode::Value _syncEntitySelectionMode; //only when synchronizing with another animation
        EntitySelectorCallback* _selectEntityCallback;

        bool _dynamicReference;          //true to update the sync pos/ori at each update
        bool _computeDistanceFromPelvis; //if false, the distance will be tested on the entity's position instead
        glm::PODArray<NearestPositionCallback*> _syncActionPosOriCallbacks;
		bool _ownSyncActionPosOriCallbacks;
        glm::Array<getPlayAnimationBehaviorCallback*> _syncOnAnotherAnimationCallbacks; //if non null, the pointed anim behavior will be the master (time and transform) to this one
		bool _ownSyncOnAnotherAnimationCallbacks;

        //these options says what part of the animation should be synchronized
        bool _syncPosXY;
        bool _syncPosZ;
        bool _syncOri;

        Time _syncDuration;                                           //time to blend from the current position/orientation to the sync ones
        AnimationPlayBN::SyncDurationMode::Value _syncDurationMethod; //method to spread the transformation during the _syncDuration
        //
        bool _removeAnimationOffset; //sync position as if the first posture is at (0,0,0)

        Quaternion _additionalOrientationOffset; //this additional orientation offset is added to the animation's extracted postures. It should allow to reach the exact same orientation than the one that was really recorded in the original coordinate system

        //@}

    private: //data
        glm::Array<SyncActionDataPerAnim> _syncActionDataPerAnim;
        glm::Array<SyncActionDataPerEntity> _syncActionDataPerEntity;

    public: //types and methods
        GLM_CLASS_STAT_ID( glm::MemoryStatIds::Simulation)

        typedef SmartPointer<SyncActionBehavior> SP;

        static void buildSyncPostures(
            SyncActionDataPerAnim* outSyncActionData, const GenericSkeleton* inSourceSkeleton, const GenericSpecificConverter* inSourceConverter, float scale, const AnimationInterface* inReplayAnimation, const AnimationReplayMapping& inReplayMapping, bool mirrored,
            BlendTreePostureCache& blendTreePostureCache, AnimationCache& animationCache, AnimationPlayBNCache& animationPlayBNCache,
            bool removeAnimationOffset, const Quaternion& correctedOrientation = Quaternion(0, 0, 0, 1));

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

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

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

        void getPelvisPositionAndOrientationAtFrame(int64_t index, BehaviorGraph& graph, Vector3& outPelvisPosition, Quaternion& outPelvisOrientation, const ReplayTimeChannel& replayTime);
    };

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

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

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

    //-------------------------------------------------------------------------
    inline SyncActionBehavior::SyncActionDataPerAnim::SyncActionDataPerAnim()
        : _keepFullPostures(false)
    {
    }

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