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

#pragma once

#include "glmSDKAPI.h"
#include <glmVector3.h>
#include <glmPODArray.h>
#include <glmSpatialEnvironment.h>
#include <glmIdsFilter.h>

namespace glm
{
    struct PopulationSlot;
    class PathContext;
    class AbstractGround;
    class LooseQuadtreeRootNode;
    class SpatialEnvironment;

    /**
     * \brief Abstract spawning zone.
     *
     * \ingroup placement
     *
     * \author Golaem <dev@golaem.com>
     */
    class GLM_SDK_API AbstractSpawningZone
    {
    public:
        GLM_ABSTRACTCLASS_STAT_ID(glm::MemoryStatIds::Simulation);

        struct EntityTypeData
        {
            Vector3 _perceptionShapeOffset = Vector3(0, 0, 0);
            Vector3 _perceptionShapeExtents = Vector3(0, 0, 0);
            float _scale = 1;
        };

    protected:
        Vector2 _distance = Vector2(0.f);
        Vector2 _safeDistance = Vector2(0.f);
        Vector2 _shrinkSize = Vector2(0.f);
        Angle _orientation = 0.f;
        Angle _orientationOffset = 0.f;
        Angle _orientationAttitude = 0.f;
        Angle _orientationBank = 0.f;
        bool _lookingAtTarget = false;
        Vector3 _lookAtTarget = Vector3(0.f, 0.f, 0.f);
        Vector2 _positionNoiseAmount = Vector2(0.f);
        float _orientationNoiseAmount = 0.f;
        float _orientationAttitudeNoiseAmount = 0.f;
        float _orientationBankNoiseAmount = 0.f;
        bool _collisionsForbidden = true;
        bool _orientIn3D = false;

        Quaternion _rotation = Quaternion::getIdentity();

        glm::PODArray<PopulationSlot*> _placed = glm::PODArray<PopulationSlot*>(getClassMemoryStatId());
        glm::PODArray<PopulationSlot*> _ignored = glm::PODArray<PopulationSlot*>(getClassMemoryStatId());

        glm::PODArray<PopulationSlot*> _sequence = glm::PODArray<PopulationSlot*>(getClassMemoryStatId());

        const SpatialEnvironment* _spatialEnvironment = NULL;
        glm::IdsFilter _navmeshConnexZonesFilter = glm::IdsFilter("*");
        const AbstractGround* _ground = NULL; ///< the ground

        float _numericalPrecision = GLM_NUMERICAL_PRECISION_COARSE;

        bool _quincunxPlacing = false;
        bool _tryUntilSlotPlaced = false;
        glm::Array<const AbstractSpawningZone*> _zonesToCollideWith = glm::Array<const AbstractSpawningZone*>(getClassMemoryStatId());

        Vector3 _maxSlotSize = Vector3(0.f);

        unsigned int _minGroupSize = 1;
        unsigned int _maxGroupSize = 1;

        bool _groupRelativeLookAt = false;

        float _maxClearance = 0.f;
        float _maxHeight = 0.f;

        RandomNumberGenerator _originalRNG;
        RandomNumberGenerator _3DOriNoiseRNG;

        glm::Array<EntityTypeData> _entityTypeData;
        float _simulationScale = 1.f;

        glm::PODArray<int> _groupEntityTypesSequence;
        glm::GlmMap<size_t, glm::PODArray<int>> _entityTypesIdsSequencePerGroup;

    public:
        AbstractSpawningZone();
        virtual ~AbstractSpawningZone();

        /**
         * \name Slots
         */
        //@{
        void insert(PopulationSlot* slot);
        template <class SlotIterator>
        void insert(SlotIterator from, SlotIterator to);
        void remove(PopulationSlot* slot);
        virtual void clear();
        virtual void clearPlaced();
        inline size_t getNbPlacedSlots() const;
        inline size_t getNbIgnoredSlots() const;
        inline size_t getNbSlots() const;
        bool isPlaced(PopulationSlot* slot);
        bool hasSlot(PopulationSlot* slot);
        const glm::PODArray<PopulationSlot*>& getPlaced() const;
        const glm::PODArray<PopulationSlot*>& getIgnored() const;
        const glm::PODArray<PopulationSlot*>& getSequence() const;
        const glm::PODArray<int>& getGroupIds() const; //<! -1 is no group, else same number for same group (group slots are contiguous in placed)
        //@}

        //@}
        /**
         * \name PathContext
         * The optionnal path context.
         */
        //@{
        void setSpatialEnvironment(const SpatialEnvironment& spatialEnvironment);
        const SpatialEnvironment& getSpatialEnvironment() const;
        bool hasSpatialEnvironment() const;
        void clearSpatialEnvironment();

        void setNavmeshConnexZonesFilter(const GlmString& expression);
        //@}

        /**
         * \name Distance
         */
        //@{
        inline const Vector2& getDistance() const;
        inline void setDistance(const Vector2& distance);
        //@}

        inline const Vector2& getSafeDistance() const;
        inline void setSafeDistance(const Vector2& distance);

        /**
         * \name Orientation
         */
        //@{
        inline Angle getOrientation() const;
        inline Angle getOrientationOffset() const;
        inline void setOrientation(Angle orientation);
        inline void setOrientationOffset(Angle orientationOffset);
        inline void setOrientationAttitude(Angle orientationAttitude);
        inline void setOrientationBank(Angle orientationBank);
        //@}

        /**
         * \name lookingAtTarget
         */
        bool isLookingAtTarget() const;
        void setLookingAtTarget(bool lookingAtTarget);
        //@}

        /**
         * \name lookAtTarget
         */
        const Vector3& getLookAtTarget() const;
        void setLookAtTarget(const Vector3& lookAtTarget);
        //@}

        /**
         * \name Position noise
         */
        //@{
        const Vector2& getPositionNoiseAmount() const;
        void setPositionNoiseAmount(const Vector2& noiseAmount);
        //@}

        /**
         * \name Orientation noise
         */
        //@{
        float getOrientationNoiseAmount() const;
        void setOrientationNoiseAmount(float orientationNoiseAmount);
        void setOrientationAttitudeNoiseAmount(float orientationAttitudeNoiseAmount);
        void setOrientationBankNoiseAmount(float orientationBankNoiseAmount);
        //@}

        void setOrientIn3D(bool);

        /**
         * \name Collision forbidden
         */
        //@{
        bool isCollisionsForbidden() const;
        void setCollisionsForbidden(bool collisionsForbidden);
        //@}

        /**
         *
         */
        //@{
        float getNumericalPrecision() const;
        void setNumericalPrecision(float numericalPrecision);
        //@}

        /**
         * \name Quincunx
         */
        //@{
        bool isQuincunxPlacing() const;
        void setQuincunxPlacing(bool quincunxPlacing);
        //@}

        /**
         *
         */
        //@{
        bool tryUntilSlotPlaced() const;
        void setTryUntilSlotPlaced(bool tryUntilSlotPlaced);
        //@}

        /**
         *
         */
        //@{
        void setZonesToCollideWith(const glm::Array<const AbstractSpawningZone*>& zonesToCollideWith);
        //@}

        /**
         * \brief Refresh the placement of the slots;
         */
        virtual void refreshPlacement(bool random = true) = 0;

        /**
         * \brief Refresh the positions of the slots;
         */
        virtual void refreshPositionsAndOrientations() = 0;

        /**
         * \brief The ground
         */
        //@{
        const AbstractGround* getGround() const;
        void setGround(const AbstractGround* ground);
        //@}

        /**
         * \brief Group Size min/max
         */
        //@{
        unsigned int getGroupMinSize() const;
        void setGroupMinSize(unsigned int value);
        unsigned int getGroupMaxSize() const;
        void setGroupMaxSize(unsigned int value);
        //@}

        void setGroupRelativeLookAt(bool groupRelativeLookAt);

        /**
         * \brief returns true if the given slot does not collide with any slot this Spawning Zone
         */
        virtual bool isColliding(const PopulationSlot* slot) const = 0;

        void setSeed(int seed);

        // rotation of all grid about x axis
        inline const Quaternion& getRotation() const;
        inline void setRotation(const Quaternion& rotation);

        void clearEntityTypeData();
        void addEntityTypeData(const Vector3& perceptionShapeOffset, const Vector3& perceptionShapeExtents, float entityTypeScale);

        inline void setSimulationScale(float simulationScale);
        inline void setGroupEntityTypesSequence(const glm::PODArray<int>& groupEntityTypesSequence);
        inline void setEntityTypesIdsSequencePerGroup(const glm::GlmMap<size_t, glm::PODArray<int>>& entityTypesIdsSequencePerGroup);

    protected:
        virtual bool checkCandidate(PopulationSlot* candidate, const Vector3& upVector) const;

        void computeSlotSize(PopulationSlot* slot);
    };

    //*****************************************************************************
    // AbstractSpawningZone inline definitions
    //*****************************************************************************

    //-----------------------------------------------------------------------------
    // setPathContext
    //-----------------------------------------------------------------------------
    inline void AbstractSpawningZone::setSpatialEnvironment(const SpatialEnvironment& spatialEnv)
    {
        _spatialEnvironment = &spatialEnv;
    }

    //-----------------------------------------------------------------------------
    // getPathContext
    //-----------------------------------------------------------------------------
    inline const SpatialEnvironment& AbstractSpawningZone::getSpatialEnvironment() const
    {
        GLM_ASSERT_MSG(_spatialEnvironment != NULL, "This spawning zone doesn't have a path context.");
        return *_spatialEnvironment;
    }

    //-----------------------------------------------------------------------------
    // hasPathContext
    //-----------------------------------------------------------------------------
    inline bool AbstractSpawningZone::hasSpatialEnvironment() const
    {
        return _spatialEnvironment != NULL;
    }

    //-----------------------------------------------------------------------------
    // clearPathContext
    //-----------------------------------------------------------------------------
    inline void AbstractSpawningZone::clearSpatialEnvironment()
    {
        _spatialEnvironment = NULL;
    }

    //-----------------------------------------------------------------------------
    inline void AbstractSpawningZone::setNavmeshConnexZonesFilter(const GlmString& expression)
    {
        _navmeshConnexZonesFilter = IdsFilter(expression);
    }

    //-----------------------------------------------------------------------------
    // insert (single slot)
    //-----------------------------------------------------------------------------
    inline void AbstractSpawningZone::insert(PopulationSlot* slot)
    {
        GLM_ASSERT_MSG(slot != NULL, "Can't insert a NULL slot.");
        _ignored.push_back(slot);
        _sequence.push_back(slot);
        slot->setSafeDistance(Vector3(_safeDistance.x, _safeDistance.y, 0));
    }

    //-----------------------------------------------------------------------------
    // insert (from iterators)
    //-----------------------------------------------------------------------------
    template <class SlotIterator>
    void AbstractSpawningZone::insert(SlotIterator from, SlotIterator to)
    {
        for (SlotIterator it(from); it != to; ++it)
        {
            _ignored.push_back(*it);
            _sequence.push_back(*it);
        }
    }

    //-----------------------------------------------------------------------------
    // clear
    //-----------------------------------------------------------------------------
    inline void AbstractSpawningZone::clear()
    {
        _placed.clear();
        _ignored.clear();
        _sequence.clear();
    }

    //-----------------------------------------------------------------------------
    // clearPlaced
    //-----------------------------------------------------------------------------
    inline void AbstractSpawningZone::clearPlaced()
    {
        _placed.clear();
    }

    //-----------------------------------------------------------------------------
    // getNbPlacedSlots
    //-----------------------------------------------------------------------------
    inline size_t AbstractSpawningZone::getNbPlacedSlots() const
    {
        return _placed.size();
    }

    //-----------------------------------------------------------------------------
    // getNbIgnoredSlots
    //-----------------------------------------------------------------------------
    inline size_t AbstractSpawningZone::getNbIgnoredSlots() const
    {
        return _ignored.size();
    }

    //-----------------------------------------------------------------------------
    // getNbSlots
    //-----------------------------------------------------------------------------
    inline size_t AbstractSpawningZone::getNbSlots() const
    {
        return _placed.size() + _ignored.size();
    }

    //-----------------------------------------------------------------------------
    // isPlaced
    //-----------------------------------------------------------------------------
    inline bool AbstractSpawningZone::isPlaced(PopulationSlot* slot)
    {
        return std::find(_placed.begin(), _placed.end(), slot) != _placed.end();
    }

    //-----------------------------------------------------------------------------
    // getPlaced
    //-----------------------------------------------------------------------------
    inline const glm::PODArray<PopulationSlot*>& AbstractSpawningZone::getPlaced() const
    {
        return _placed;
    }

    //-----------------------------------------------------------------------------
    // getIgnored
    //-----------------------------------------------------------------------------
    inline const glm::PODArray<PopulationSlot*>& AbstractSpawningZone::getIgnored() const
    {
        return _ignored;
    }

    //-----------------------------------------------------------------------------
    // getSequence
    //-----------------------------------------------------------------------------
    inline const glm::PODArray<PopulationSlot*>& AbstractSpawningZone::getSequence() const
    {
        return _sequence;
    }

    //-----------------------------------------------------------------------------
    // getDistance
    //-----------------------------------------------------------------------------
    inline const Vector2& AbstractSpawningZone::getDistance() const
    {
        return _distance;
    }

    //-----------------------------------------------------------------------------
    // setDistance
    //-----------------------------------------------------------------------------
    inline void AbstractSpawningZone::setDistance(const Vector2& distance)
    {
        _distance = distance;
        _shrinkSize.setNull();
        if (_distance.x < 0)
        {
            _shrinkSize.x = -_distance.x;
            _distance.x = 0;
        }
        if (_distance.y < 0)
        {
            _shrinkSize.y = -_distance.y;
            _distance.y = 0;
        }
    }

    //-----------------------------------------------------------------------------
    inline const Vector2& AbstractSpawningZone::getSafeDistance() const
    {
        return _safeDistance;
    }

    //-----------------------------------------------------------------------------
    inline void AbstractSpawningZone::setSafeDistance(const Vector2& safeDistance)
    {
        _safeDistance = safeDistance;
    }

    //-----------------------------------------------------------------------------
    // getOrientation
    //-----------------------------------------------------------------------------
    inline Angle AbstractSpawningZone::getOrientation() const
    {
        return _orientation;
    }

    //-----------------------------------------------------------------------------
    inline Angle AbstractSpawningZone::getOrientationOffset() const
    {
        return _orientationOffset;
    }

    //-----------------------------------------------------------------------------
    inline void AbstractSpawningZone::setOrientationOffset(Angle orientationOffset)
    {
        _orientationOffset = orientationOffset;
    }

    //-----------------------------------------------------------------------------
    // setOrientation
    //-----------------------------------------------------------------------------
    inline void AbstractSpawningZone::setOrientation(Angle orientation)
    {
        _orientation = orientation;
    }

    //-----------------------------------------------------------------------------
    // setOrientationAttitude
    //-----------------------------------------------------------------------------
    inline void AbstractSpawningZone::setOrientationAttitude(Angle orientationAttitude)
    {
        _orientationAttitude = orientationAttitude;
    }

    //-----------------------------------------------------------------------------
    // setOrientation
    //-----------------------------------------------------------------------------
    inline void AbstractSpawningZone::setOrientationBank(Angle orientationBank)
    {
        _orientationBank = orientationBank;
    }

    //-----------------------------------------------------------------------------
    // isLookingAtTarget
    //-----------------------------------------------------------------------------
    inline bool AbstractSpawningZone::isLookingAtTarget() const
    {
        return _lookingAtTarget;
    }

    //-----------------------------------------------------------------------------
    // setLookingAtTarget
    //-----------------------------------------------------------------------------
    inline void AbstractSpawningZone::setLookingAtTarget(bool lookingAtTarget)
    {
        _lookingAtTarget = lookingAtTarget;
    }

    //-----------------------------------------------------------------------------
    // getLookAtTarget
    //-----------------------------------------------------------------------------
    inline const Vector3& AbstractSpawningZone::getLookAtTarget() const
    {
        return _lookAtTarget;
    }

    //-----------------------------------------------------------------------------
    // setLookAtTarget
    //-----------------------------------------------------------------------------
    inline void AbstractSpawningZone::setLookAtTarget(const glm::Vector3& lookAtTarget)
    {
        _lookAtTarget = lookAtTarget;
    }

    //-----------------------------------------------------------------------------
    // getPositionNoiseAmount
    //-----------------------------------------------------------------------------
    inline const Vector2& AbstractSpawningZone::getPositionNoiseAmount() const
    {
        return _positionNoiseAmount;
    }

    //-----------------------------------------------------------------------------
    // setPositionNoiseAmount
    //-----------------------------------------------------------------------------
    inline void AbstractSpawningZone::setPositionNoiseAmount(const Vector2& noiseAmount)
    {
        _positionNoiseAmount = noiseAmount;
    }

    //-----------------------------------------------------------------------------
    inline float AbstractSpawningZone::getOrientationNoiseAmount() const
    {
        return _orientationNoiseAmount;
    }

    //-----------------------------------------------------------------------------
    inline void AbstractSpawningZone::setOrientationNoiseAmount(float orientationNoiseRange)
    {
        _orientationNoiseAmount = orientationNoiseRange;
    }

    //-----------------------------------------------------------------------------
    inline void AbstractSpawningZone::setOrientationAttitudeNoiseAmount(float orientationAttitudeNoiseRange)
    {
        _orientationAttitudeNoiseAmount = orientationAttitudeNoiseRange;
    }

    //-----------------------------------------------------------------------------
    inline void AbstractSpawningZone::setOrientationBankNoiseAmount(float orientationBankNoiseRange)
    {
        _orientationBankNoiseAmount = orientationBankNoiseRange;
    }

    //-----------------------------------------------------------------------------
    inline void AbstractSpawningZone::setOrientIn3D(bool orientIn3D)
    {
        _orientIn3D = orientIn3D;
    }

    //-----------------------------------------------------------------------------
    // getNumericalPrecision
    //-----------------------------------------------------------------------------
    inline float AbstractSpawningZone::getNumericalPrecision() const
    {
        return _numericalPrecision;
    }

    //-----------------------------------------------------------------------------
    // setNumericalPrecision
    //-----------------------------------------------------------------------------
    inline void AbstractSpawningZone::setNumericalPrecision(float numericalPrecision)
    {
        _numericalPrecision = numericalPrecision;
    }

    //-----------------------------------------------------------------------------
    // isCollisionsForbidden
    //-----------------------------------------------------------------------------
    inline bool AbstractSpawningZone::isCollisionsForbidden() const
    {
        return _collisionsForbidden;
    }

    //-----------------------------------------------------------------------------
    // setCollisionsForbidden
    //-----------------------------------------------------------------------------
    inline void AbstractSpawningZone::setCollisionsForbidden(bool collisionsForbidden)
    {
        _collisionsForbidden = collisionsForbidden;
    }

    //-----------------------------------------------------------------------------
    // isQuincunxPlacing
    //-----------------------------------------------------------------------------
    inline bool AbstractSpawningZone::isQuincunxPlacing() const
    {
        return _quincunxPlacing;
    }

    //-----------------------------------------------------------------------------
    // setRegion
    //-----------------------------------------------------------------------------
    inline void AbstractSpawningZone::setQuincunxPlacing(bool quincunxPlacing)
    {
        _quincunxPlacing = quincunxPlacing;
    }

    //-----------------------------------------------------------------------------
    // tryUntilSlotPlaced
    //-----------------------------------------------------------------------------
    inline bool AbstractSpawningZone::tryUntilSlotPlaced() const
    {
        return _tryUntilSlotPlaced;
    }

    //-----------------------------------------------------------------------------
    // setTryUntilSlotPlaced
    //-----------------------------------------------------------------------------
    inline void AbstractSpawningZone::setTryUntilSlotPlaced(bool tryUntilSlotPlaced)
    {
        _tryUntilSlotPlaced = tryUntilSlotPlaced;
    }

    //-----------------------------------------------------------------------------
    // setZonesToCollideWith
    //-----------------------------------------------------------------------------
    inline void AbstractSpawningZone::setZonesToCollideWith(const glm::Array<const AbstractSpawningZone*>& zonesToCollideWith)
    {
        _zonesToCollideWith = zonesToCollideWith;
    }

    //-----------------------------------------------------------------------------
    // getGroupMinSize
    //-----------------------------------------------------------------------------
    inline unsigned int AbstractSpawningZone::getGroupMinSize() const
    {
        return _minGroupSize;
    }

    //-----------------------------------------------------------------------------
    // setGroupMinSize
    //-----------------------------------------------------------------------------
    inline void AbstractSpawningZone::setGroupMinSize(unsigned int value)
    {
        _minGroupSize = value;
    }

    //-----------------------------------------------------------------------------
    // setGroupRelativeLookAt
    //-----------------------------------------------------------------------------
    inline void AbstractSpawningZone::setGroupRelativeLookAt(bool groupRelativeLookAt)
    {
        _groupRelativeLookAt = groupRelativeLookAt;
    }
    

    //-----------------------------------------------------------------------------
    // getGroupMaxSize
    //-----------------------------------------------------------------------------
    inline unsigned int AbstractSpawningZone::getGroupMaxSize() const
    {
        return _maxGroupSize;
    }

    //-----------------------------------------------------------------------------
    // setGroupMaxSize
    //-----------------------------------------------------------------------------
    inline void AbstractSpawningZone::setGroupMaxSize(unsigned int value)
    {
        _maxGroupSize = value;
    }

    //-----------------------------------------------------------------------------
    inline const Quaternion& AbstractSpawningZone::getRotation() const
    {
        return _rotation;
    }

    //-----------------------------------------------------------------------------
    inline void AbstractSpawningZone::setRotation(const Quaternion& rotation)
    {
        _rotation = rotation;
    }

    //-----------------------------------------------------------------------------
    inline void AbstractSpawningZone::setSimulationScale(float simulationScale)
    {
        _simulationScale = simulationScale;
    }

    //-----------------------------------------------------------------------------
    inline void AbstractSpawningZone::setGroupEntityTypesSequence(const glm::PODArray<int>& groupEntityTypesSequence)
    {
        _groupEntityTypesSequence = groupEntityTypesSequence;
    }

    //-----------------------------------------------------------------------------
    inline void AbstractSpawningZone::setEntityTypesIdsSequencePerGroup(const glm::GlmMap<size_t, glm::PODArray<int>>& entityTypesIdsSequencePerGroup)
    {
        _entityTypesIdsSequencePerGroup = entityTypesIdsSequencePerGroup;
    }

} // namespace glm
