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

#pragma once

#include "glmSDKAPI.h"

#include "glmAbstractUserCallbackBehavior.h"

namespace glm
{
    class MotionContext;
    class AnimationData;
    class EntityType;
    class UserCallbackContext;
    class SpecificPosture;

    class GLM_SDK_API MasterSlaveBehavior : public AbstractUserCallbackBehavior
    {
    public:
        GLM_ABSTRACTCLASS_STAT_ID( glm::MemoryStatIds::Simulation);

        /// Offset modes
        struct AssignmentMode
        {
			enum Value
			{
				CLOSEST_ENTITY,
				COLLIDED,
				ENTITYID,
                END
            };
        };

		struct OffsetMode
		{
			enum Value
			{
				AUTOMATIC,
				MANUAL,
				END
			};
		};

        /// Constraint Ori modes
        struct ConstraintOriModes
        {
            enum Value
            {
                ORIENTATION,
                AIMAT,
                NONE,
                END
            };
        };

        class MasterSlaveBehaviorData
        {
        public:
            EntityType* _masterEntityType; // for freezeOnCollided, we may have different masterEntityTypes
            MotionContext* _masterMotionContext;
            AnimationData* _masterAnimationData;
            uint64_t _masterIndex;
            uint32_t _masterBoneIndex;

            AnimationData* _slaveAnimationData;

            Quaternion _oriOffset; //!< Orientation offset
            Vector3 _posOffset;    //!< Position offset
            Quaternion _entityOri; //!< Internal representation of the Entity orientation in quaternion (to remove when getOrientation will return a quat)
        public:
			MasterSlaveBehaviorData();
            ~MasterSlaveBehaviorData();
        };

		AssignmentMode::Value _assignmentMode;
		glm::Array<EntityType*> _masterEntityTypes;             // for quick test. Same simulation = uniques sdkEntityTypeIndex (potentially several per crowdEntityType index)
        GlmMap<EntityType*, glm::PODArray<int>> _masterBoneIdsPerType; // one per typeIndex from 0 to max used. Could be a array<GlmSet> for performance at access

        OffsetMode::Value _masterOffsetMode; // auto / manual
        bool _constraintPos[3];
        ConstraintOriModes::Value _constraintOriMode;
        bool _contraintOriAimAt[3];
        bool _constraintScale;

    protected:
        // KBEntry arrays have a life time == entity life time if KBEntry is not removed explicitely.
        // This pointers are swapped at same time as entity array, by KBContext
        PODArray<int64_t>* _masterHandles;

        //!< Internal
        glm::Array<MasterSlaveBehaviorData> _masterSlaveData;
        MotionContext* _slaveMotionContext;
        UserCallbackContext* _preCallBackContext;

    public:
		MasterSlaveBehavior();
        virtual ~MasterSlaveBehavior();

        virtual void doCallbackComputation(const UserCallbackContext& callbackContext, int64_t index, BehaviorGraph& graph, Time dt) override;
        void setUpdateCallback(ICallBack* callback);

        inline MasterSlaveBehaviorData& getBehaviorData(int behaviorGraphIndex);

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

    protected:
        // cloning
        friend class CloneFactory;
        inline virtual MasterSlaveBehavior* 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 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;

		EntityType* getClosestEntityInEntityTypes(const Vector3& position, const glm::Array<EntityType*>& entityTypes, int64_t& entityIdx);
		void getClosestBoneInEntity(const Vector3& position, const SpecificPosture& posture, const glm::PODArray<int>& bones, uint32_t& boneId);
    };

    //-------------------------------------------------------------------------
    inline MasterSlaveBehavior::MasterSlaveBehaviorData& MasterSlaveBehavior::getBehaviorData(int behaviorGraphIndex)
    {
        return _masterSlaveData[behaviorGraphIndex];
    }

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