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

#pragma once

#include "glmTypes.h"
#include "glmMemory.h"
#include "glmSimuEntityHandle.h"
#include "glmCoreDefinitions.h"
#include "glmAbstractGround.h"
#include "glmGroundAdaptationBN.h"

namespace glm
{
    class Group;
    class Simulation;
    class EntityType;
    class AbstractGround;
    class PhysicsContext_PhysX;

    struct GroundAdaptMode
    {
        enum Value
        {
            AdaptNone = 0, // NO GA in any maner
            AdaptParticle = 1,
            AdaptAnimation = 2
        };
    };

    /**
	* \brief A Golaem Entity is a self-contained simulation unit : virtual human, animal, tree, ...
	*
	* An Entity agregates EntityComponent, fields aspects for Animation, Navigation, Behaviors, Visualization, Collision, Perception, ...
	*
	* \param entityComponents <tt>(glm::PODArray<IEntityComponent*>)</tt> EntityComponents agregated by the Entity
	*
	* \ingroup simulation
	* \author Golaem <dev@golaem.com>
	*/
    class GLM_SDK_API Entity
    {
        friend class Group;
        friend class Simulation;
        friend class EntityType;
        template <class TValue>
        friend class Array;

    public:
        static const char* _crowdFieldIndexKBEntryName;
        static const char* _masterEntityHandleKBEntryName;
        static int64_t _crowdFieldIndexKBEntryHashKey;
        static int64_t _masterEntityHandleKBEntryHashKey;

    public:
        struct GroundAdaptResponsible
        {
            enum Value
            {
                AdaptNone = 0, // NO GA in any maner
                AdaptBlendNode = 1,
                AdaptBehavior = 2,
                AdaptPathComponent = 4,
                AdaptTrafficComponent = 8,
                AdaptBoid2dComponent = 16
            };
        };

        GLM_CLASS_STAT_ID( glm::MemoryStatIds::Simulation);

        static void extractHeading(Quaternion& outHeadingOnlyOrientation, const Quaternion& inFullOrientation, const Vector3& worldUpVector);

        ~Entity();

        inline int64_t getId() const;

        /// compute the velocity from moveOrientation and speed (12+3 operations, you may store it to save computation time)
        void computeVelocity(Vector3& outVelocity) const;
        void computeDesiredVelocity(Vector3& outVelocity) const;

        /// returns last known moveOrientation as available before behaviors / components update loop
        const Quaternion& getLastMoveOrientation() const;

        float _personalSpace;
        Vector3 _worldUpVector;
        Quaternion _bodyOrientation; /// body orientation is body / pelvis / "front" orient of entity, in World coordinates from X axis
        Quaternion _moveOrientation; /// move orientation matches current moving direction, in World coordinates from X axis
        float _speed;
        float _desiredSpeed;
        Quaternion _desiredMoveOrientation;
        Vector3 _position;
        Vector3 _lastPosition;
        float _scale;
        int _lod;
        GroundCellId _groundCellId; // valid on current ground only. if the ground is changed, this is AFTER mesh follow
        float _groundHeight;
        Vector3 _groundCellBarycenter;
        glm::PODArray<int> _geoAssets;          //mesh assets used per entity
        glm::PODArray<int> _skeletonOverridesNodes;  //skeleton overrides used per entity
        glm::PODArray<int> _blindDataOverridesNodes; //blind data overrides used per entity
		glm::PODArray<int> _physicsOverridesNodes;  //physics overrides used per entity
        //AbstractGround::GroundPinMode::Value _groundPinMode; // transfered to ground data itself, as for transform and dynamic

        /**
		* \brief returns the function responsible of ground adaptation
		*/
        int getGroundAdaptResponsible(const AbstractGround& ground) const;
        void setMotionLocomotion(bool motionSynchro);
        void useMotionComponent(bool motionComponent);
        void usePathComponent(bool pathComponent);
        void useTrafficComponent(bool trafficComponent);
        void useBoid2DComponent(bool boid2DComponent);

        void setBeMotionInUse(bool inUse);
        void incrBeGroundAdaptUse();
        void decrBeGroundAdaptUse();

        void setPhysicUserPointer(const void* physicUserPointer);
        const void* getPhysicUserPointer() const;
        /**
		* \brief mode selected by user
		*/
        void pushGroundAdaptationMode(GroundAdaptMode::Value adaptationMode, void* setter);
        bool removeGroundAdaptationMode(void* setter);
        void pushGroundEvaluationMode(GroundAdaptationBN::GroundEvaluation::Value evaluationMode, void* setter);
        bool removeGroundEvaluationMode(void* setter);
        void pushGround(const AbstractGround& abstractGround, void* owner);
        bool popGround(void* setter);
        bool hasGround() const
        {
            return !_entityGrounds.empty();
        }
        const AbstractGround& getGround() const;

        GroundAdaptMode::Value getGroundAdaptationMode() const;
        GroundAdaptationBN::GroundEvaluation::Value getGroundEvaluationMode() const;

        inline SimuEntityHandle getHandle() const
        {
            return _handle;
        }

        inline Group* getParentGroup() const
        {
            return _parentGroup;
        }

        // called by ground follow context at beginning of each frame (after updating the grounds)
        void followGroundTransform(PhysicsContext_PhysX* physXContext);
        void storeGroundCellId(float height);

        // called by path, traffic and flock if needed, update _position, _worldUpVector and orientations
        bool unfoldDisplacementOnGround(const Vector3& startPosition, const Vector3& unpinnedEndPosition, float height);

    protected:
        Entity(const glm::Vector3& position = glm::Vector3::getNullVector(), const glm::Quaternion& orientation = glm::Quaternion::getIdentity(), float personalSpaceRadius = 0.2f);

        Quaternion _lastMoveOrientation; /// to deduce angular speed

        Group* _parentGroup;
        SimuEntityHandle _handle;

        int64_t _id;

        bool _beMotionUse;
        int _beGroundAdaptUse;
        PODArray<GroundAdaptMode::Value> _groundAdaptMode;
        PODArray<void*> _groundAdaptModeSetter;
        PODArray<GroundAdaptationBN::GroundEvaluation::Value> _groundEvaluationMode;
        PODArray<void*> _groundEvaluationModeSetter;

        //float	_groundPinRatio; // clamped to 0-1 -> for now hard pinning only, matches pin to ground of the terrain locator
        PODArray<const AbstractGround*> _entityGrounds; // stack the grounds for this entity (can be set by meshPin, to be used for example to land on a boat)
        PODArray<void*> _entityGroundSetter;

        const void* _physicUserPointer; // need to filter physics from ragdollcomponent (self intersection) in motion & path don't know where to put that elsewhere

        bool _motionComponent;
        bool _pathComponent;
        bool _trafficComponent;
        bool _boid2DComponent;
        bool _motionLocomotion;

    private:
        //GLM_DISALLOW_COPY_AND_ASSIGN(Entity); needed for Array, let default operator=() and copy constructor Entity(const Entity&){}
    };

    //-------------------------------------------------------------------------
    inline int64_t Entity::getId() const
    {
        return _id;
    }

    //-------------------------------------------------------------------------
    inline const Quaternion& Entity::getLastMoveOrientation() const
    {
        return _lastMoveOrientation;
    }

    //-------------------------------------------------------------------------
    inline void Entity::computeVelocity(Vector3& outVelocity) const
    {
        outVelocity = _moveOrientation.getColumnX() * _speed;
    }

    //-------------------------------------------------------------------------
    inline void Entity::computeDesiredVelocity(Vector3& outDesiredVelocity) const
    {
        outDesiredVelocity = _desiredMoveOrientation.getColumnX() * _desiredSpeed;
    }

    //-------------------------------------------------------------------------
    inline void Entity::setMotionLocomotion(bool motionLocomotion)
    {
        _motionLocomotion = motionLocomotion;
    };

    //-------------------------------------------------------------------------
    inline void Entity::useMotionComponent(bool motionComponent)
    {
        _motionComponent = motionComponent;
    }

    //-------------------------------------------------------------------------
    inline void Entity::usePathComponent(bool pathComponent)
    {
        _pathComponent = pathComponent;
    }
    //-------------------------------------------------------------------------
    inline void Entity::useTrafficComponent(bool trafficComponent)
    {
        _trafficComponent = trafficComponent;
    }
    //-------------------------------------------------------------------------
    inline void Entity::useBoid2DComponent(bool boid2DComponent)
    {
        _boid2DComponent = boid2DComponent;
    }
    //-------------------------------------------------------------------------
    inline void Entity::setBeMotionInUse(bool inUse)
    {
        _beMotionUse = inUse;
    }
    //-------------------------------------------------------------------------
    inline void Entity::incrBeGroundAdaptUse()
    {
        _beGroundAdaptUse++;
    }

    //-------------------------------------------------------------------------
    inline void Entity::decrBeGroundAdaptUse()
    {
        _beGroundAdaptUse--;
    }

    //-------------------------------------------------------------------------
    inline void Entity::pushGroundAdaptationMode(GroundAdaptMode::Value adaptationMode, void* setter)
    {
        _groundAdaptMode.push_back(adaptationMode);
        _groundAdaptModeSetter.push_back(setter);
    }

    //-------------------------------------------------------------------------
    inline bool Entity::removeGroundAdaptationMode(void* setter)
    {
        PODArray<void*>::iterator foundIte = glmFind(_groundAdaptModeSetter.begin(), _groundAdaptModeSetter.end(), setter);
        if (foundIte != _groundAdaptModeSetter.end())
        {
            int64_t index = foundIte - _groundAdaptModeSetter.begin();
            _groundAdaptModeSetter.erase(foundIte);
            _groundAdaptMode.erase(_groundAdaptMode.begin() + index);
            return true;
        }
        return false;
    }

    //-------------------------------------------------------------------------
    inline void Entity::pushGroundEvaluationMode(GroundAdaptationBN::GroundEvaluation::Value evaluationMode, void* setter)
    {
        _groundEvaluationMode.push_back(evaluationMode);
        _groundEvaluationModeSetter.push_back(setter);
    }

    //-------------------------------------------------------------------------
    inline bool Entity::removeGroundEvaluationMode(void* setter)
    {
        PODArray<void*>::iterator foundIte = glmFind(_groundEvaluationModeSetter.begin(), _groundEvaluationModeSetter.end(), setter);
        if (foundIte != _groundEvaluationModeSetter.end())
        {
            int64_t index = foundIte - _groundEvaluationModeSetter.begin();
            _groundEvaluationModeSetter.erase(foundIte);
            _groundEvaluationMode.erase(_groundEvaluationMode.begin() + index);
            return true;
        }
        return false;
    }

    //-------------------------------------------------------------------------
    inline void Entity::pushGround(const AbstractGround& abstractGround, void* setter)
    {
        _entityGrounds.push_back(&abstractGround);
        _entityGroundSetter.push_back(setter);
    }

    //-------------------------------------------------------------------------
    inline bool Entity::popGround(void* setter)
    {
        PODArray<void*>::iterator foundIte = glmFind(_entityGroundSetter.begin(), _entityGroundSetter.end(), setter);
        if (foundIte != _entityGroundSetter.end())
        {
            int64_t index = foundIte - _entityGroundSetter.begin();
            _entityGroundSetter.erase(foundIte);
            _entityGrounds.erase(_entityGrounds.begin() + index);
            return true;
        }
        return false;
    }

    //-------------------------------------------------------------------------
    inline const AbstractGround& Entity::getGround() const
    {
        GLM_DEBUG_ASSERT(!_entityGrounds.empty());
        return *(_entityGrounds.back());
    }

    //-------------------------------------------------------------------------
    inline GroundAdaptMode::Value Entity::getGroundAdaptationMode() const
    {
        return _groundAdaptMode.back();
    }

    //-------------------------------------------------------------------------
    inline GroundAdaptationBN::GroundEvaluation::Value Entity::getGroundEvaluationMode() const
    {
        return _groundEvaluationMode.back();
    }

    //-------------------------------------------------------------------------
    // setPhysicUserPointer
    //-------------------------------------------------------------------------
    inline void Entity::setPhysicUserPointer(const void* physicUserPointer)
    {
        _physicUserPointer = physicUserPointer;
    }
    //-------------------------------------------------------------------------
    // getPhysicUserPointer
    //-------------------------------------------------------------------------
    inline const void* Entity::getPhysicUserPointer() const
    {
        return _physicUserPointer;
    }
} // namespace glm
