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

#pragma once

#include "glmTypes.h"
#include "glmMemory.h"
#include "glmRandomNumberGenerator.h"
#include "glmEntity.h"
#include "glmFormation.h"
#include "glmSimuEntityHandle.h"
#include "glmCoreDefinitions.h"

#include "glmMutex.h"

namespace glm
{
    class Simulation;
    class GroupType;

    /**
	* \brief Entity for groups (ie socially induced gathering of entities)
	*
	* \ingroup simulation
	* \author Golaem <dev@golaem.com>
	*/
    class GLM_SDK_API Group //: public Entity
    {
        friend class Simulation;
        friend class GroupType;
        template <class TValue>
        friend class Array;

    public:
        class Member
        {
        public:
            GLM_CLASS_STAT_ID( glm::MemoryStatIds::Simulation);

            Member();
            Entity* _entity;
            unsigned int _slotIndex;
        };

        class AssignmentMode
        {
        public:
            enum Value
            {
                DIRECT = 0,       // first slot to first entity, etc.
                SORTED_BY_X = 1,  // sort slots and members by X
                SORTED_BY_Y = 2,  // sort slots and members by Y
                BIDDING_FAST = 3, //
                BIDDING_PRECISE = 4,
            };
        };

        GLM_CLASS_STAT_ID( glm::MemoryStatIds::Simulation)

        Group();
        ~Group();
        //Group(const Group& group);
        //Group& operator=(const Group& group);

        inline int64_t getId() const;

        /**
		* \name Members
		*/
        //@{
        unsigned int getMemberCount() const;
        void addMember(Entity& entity);
        void removeMember(Entity& entity);
        bool hasMember(const Entity& entity) const;
        unsigned int getMemberIndex(const Entity& entity) const;
        Entity* getMember(unsigned int index) const;
        //@}

        /**
		* \name Formation
		*/
        //@{
        const Formation* getFormation() const;
        Formation* touchFormation() const;
        void setFormation(Formation* formation);
        void clearFormation();
        bool hasFormation() const;
        void updateFormation();
        bool hasNewFormation() const;

        void setAssignmentMode(AssignmentMode::Value value);

        /**
		* \brief Assign each member of the group to a slot if available.
		*/
        void computeSlotsAssignment(const Simulation& simulation);
        bool isMemberAssignedToSlot(unsigned int entityIndex) const;
        unsigned int getMemberSlotIndex(unsigned int entityIndex) const;
        void setMemberSlotIndex(unsigned int entityIndex, unsigned int slotIndex);
        void clearMemberSlotIndex(unsigned int entityIndex);
        const Vector3& getMemberSlotPosition(unsigned int entityIndex) const;
        void computeMemberSlotWorldPosition(unsigned int entityIndex, Vector3& outPosition) const;
        const Quaternion& getMemberSlotOrientation(unsigned int entityIndex) const;
        void computeMemberSlotWorldOrientation(unsigned int entityIndex, Quaternion& outOrientation) 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 pushGround(const AbstractGround& abstractGround, void* owner);
        bool popGround(void* setter);
        bool hasGround() const
        {
            return !_grounds.empty();
        }
        const AbstractGround& getGround() const;

        void storeGroundCellId();
        void followGroundTransform();

        // called by path, update _position, _worldUpVector and orientations
        bool unfoldDisplacementOnGround(const Vector3& startPosition, const Vector3& unpinnedEndPosition);

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

        inline SimuEntityHandle getHandle() const
        {
            return _handle;
        }

        Quaternion _moveOrientation;
        float _speed;
        Vector3 _position;
        Vector3 _lastPosition;
        Vector3 _worldUpVector;
        GroundCellId _groundCellId; // valid on current ground only. if the ground have to be changed, this is AFTER mesh follow
        float _groundHeight;
        Vector3 _groundCellBarycenter;
        //AbstractGround::GroundPinMode::Value _groundPinMode; // transfered to ground itself

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

        void computeBiddingAssignment(const Simulation& simulation);
        void computeSortingAssignment(const Simulation& simulation);

        // group member management should only occurs in monothread. If making an entity add/remove group behavior, please lock group at this level.
        // group create/destroy itself should never lead to data race (accessing entities data, udpated in another update pass)

        glm::Array<Member> _members;

        Quaternion _lastMoveOrientation; /// to deduce angular speed

        SimuEntityHandle _handle;

        Formation::SP _formation;
        Formation::SP _newFormation;
        Vector3 _newFormationPosition; // set if there was no previous position only (else keep group position to blend)
        //int _entityRank;
        bool _hasNewFormation;

        int _assignmentMode;

        int64_t _id;

        PODArray<const AbstractGround*> _grounds; // stack the grounds for this entity (can be set by meshPin, to be used for example to land on a boat)
        PODArray<void*> _groundSetter;
    };

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

    //-------------------------------------------------------------------------
    inline Entity* Group::getMember(unsigned int index) const
    {
        return _members[index]._entity;
    }

    //-------------------------------------------------------------------------
    inline const Formation* Group::getFormation() const
    {
        return _formation.getImpl();
    }

    //-------------------------------------------------------------------------
    inline Formation* Group::touchFormation() const
    {
        return _formation.getImpl();
    }

    //-------------------------------------------------------------------------
    inline void Group::clearFormation()
    {
        setFormation(NULL);
    }

    //-------------------------------------------------------------------------
    inline bool Group::hasFormation() const
    {
        return _formation != NULL;
    }

    //-------------------------------------------------------------------------
    inline bool Group::hasNewFormation() const
    {
        return _hasNewFormation;
    }

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

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