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

#pragma once

#include "glmTypes.h"
#include "glmMemory.h"
#include "glmEntity.h"
#include "glmAbstractBehavior.h"
#include "glmBehaviorGraph.h"
#include "glmKnowledgeBase.h"
#include "glmGenericSpecificConverter.h"
#include "glmFastBoundingBox.h"
#include "glmGroundAdaptationBN.h"
#include "glmBasePhysicsDefinitions.h"
#include "glmAbstractTrigger.h"

#include "glmString.h"
#include "glmPODArray.h"
#include "glmArray.h"
#include "glmVoxelField.h"
#include "glmRandomNumberGenerator.h"

namespace glm
{
    class Simulation;
    class IContext;

    struct GLM_SDK_API EntityTypePerceptionData
    {
        glm::Array<glm::Array<glm::Triangle3>> _perceptionGeo;
        bool _perceptionGeoIsBox = true;
        Vector3 _perceptionShapeOffset = Vector3(0, 0, 0);
        FastBoundingBox _perceptionBoundingBox;
        Vector3 _perceptionShapeExtents = Vector3(0.1f, 0.1f, 0.1f);

        unsigned int _maxVoxelCount = 6;
        bool _enableBestVoxelCountSearch = true;
        unsigned int _referenceVoxelCountMultiplier = 2;
        VoxelField _voxelField;

        glm::PODArray<size_t> _navVoxels;

        Vector2 _perceptionClearance = Vector2(0.5f);
        float _perceptionClearanceNorm = sqrtf(0.5f);
        Vector2 _voxel2DClearance = Vector2(0.5f);
        float _voxel2DClearanceNorm = sqrtf(0.5f);

        EntityTypePerceptionData();
        EntityTypePerceptionData(const EntityTypePerceptionData& other);
        EntityTypePerceptionData& operator=(const EntityTypePerceptionData& other);
    };

    /**
	* \brief A Golaem Entity Type is a model to create bunch of identical entities.
	*
	* \ingroup simulation
	* \author Golaem <dev@golaem.com>
	*/
    class GLM_SDK_API EntityType
    {
        friend class IContext;
        friend class Simulation;

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

        //entry for the entityType KB
        static const char* _crowdEntityTypeIdKBEntryName;
        static int64_t _crowdEntityTypeIdKBEntryHashKey;
        //@}

        glm::GlmString _name;

        mutable RWSpinLock _KBAccessMutex; // mutex to use access the KB for reading or writing (if needed)

        float _scaleMin;
        float _scaleMax;
        int _scaleRandomSamples;

        float _lengthPivotPercent;

        // for navigation
        float _personalSpaceRadius;

        EntityTypePerceptionData _perceptionData;

        // for animation
        glm::GlmString _characterFile;
        float _pelvisHeightRatio;
        GenericSpecificConverter::SpecificPostureBlindData::Value _specificAnimationBlindDataMode;
        bool _defaultGroundAdaptation;
        GroundAdaptationBN::GroundEvaluation::Value _defaultGroundEvaluationMode;
        bool _defaultPerception;
        int _upSmoothValueCount;
        float _rollExtractionBaseOffset;

        //physics collision filter
        CollisionFilterMode::Value _collisionFilterMode = CollisionFilterMode::DEFAULT;
        AbstractTrigger::SP _advancedCollisionFilter;

        // for visualization and triggers
        uint32_t _colorABGR;

    private:
        Array<Entity> _entities;                  // preallocated array of entities
        PODArray<uint8_t> _inUse;                 // is this index valid (i.e. not destroyed)
        PODArray<uint8_t> _componentsInitialized; // used to check components init
        BehaviorGraph _behaviorGraph;             // Todo -> in BehaviorContext ?
        KnowledgeBase _knowledgeBase;             // Todo -> in KB Context ?
        Simulation* _simulation;

        int64_t _validEntitiesCount;
        int32_t _entityTypeId; // is const once set at construction
        int64_t _inUseHelper;  // points to last used node to avoid rechecking all in use
        uint8_t _updateOrder;  // 0 to n, add one for each parenting level above this EntityType

    public:
        virtual ~EntityType();

        Simulation* getSimulation() const
        {
            return _simulation;
        }

        bool isIndexInUse(int64_t entityIndex) const
        {
            return _inUse[entityIndex] == 1;
        } // is entity in use (not destroyed) ?

        const int getUpdateOrder() const
        {
            return _updateOrder;
        } // is const once set at construction

        int getEntityTypeId() const
        {
            return _entityTypeId;
        } // is const once set at construction

        Entity& getEntity(int64_t entityIndex)
        {
            return _entities[entityIndex];
        }

        const Entity& getEntity(int64_t entityIndex) const
        {
            return _entities[entityIndex];
        }

        const int64_t getMaxCount() const
        {
            return static_cast<int64_t>(_entities.capacity());
        }
        const int64_t getEntityCount() const
        {
            return static_cast<int64_t>(_entities.size());
        } // can still be unused, check with _inUse data

        BehaviorGraph& getBehaviorGraph()
        {
            return _behaviorGraph;
        }

        const KnowledgeBase& getKB() const
        {
            return _knowledgeBase;
        } //warning: use the _KBAccessMutex if there is any chance of multithreading access with writing
        KnowledgeBase& getKB()
        {
            return _knowledgeBase;
        } //warning: use the _KBAccessMutex if there is any chance of multithreading access with writing

        void addBehavior(AbstractBehavior*, bool startAtInit = true);

        static void createDefaultPerceptionGeo(const GolaemCharacter* character, const Vector3& shapeOffset, glm::Array<glm::Array<glm::Triangle3>>& perceptionGeo, const Vector3& halfExtent = Vector3(0.5f));

        void initPerceptionData();
        void initPerceptionData(const GolaemCharacter* character);

        static void initPerceptionData(EntityTypePerceptionData& perceptionData, const GolaemCharacter* character, const Vector3& halfExtent = Vector3(0.5f));
        static void initPerceptionData(EntityTypePerceptionData& perceptionData, const glm::GlmString& characterFile, const Vector3& halfExtent = Vector3(0.5f));

        static void initVoxelFieldFromGeometry(VoxelField& voxelField, bool enableBestVoxelCountSearch, const glm::Array<glm::Array<glm::Triangle3>>& geometry, bool geometryIsBox, const Vector3& geometryBoxExtents, unsigned int maxVoxelCount, unsigned int referenceVoxelCountMultiplier = 2);

        static float computeRandomScale(glm::RandomNumberGenerator& rng, float scaleMin, float scaleMax, int scaleRandomSamples);

    protected:
        int64_t createEntity(const int64_t uniqueEntityId); // return entityIndex in this array
        void destroyEntity(int64_t entityIndex);

        void createEntities(int64_t* entityIndices, int64_t count, int64_t& currentUniqueEntityId);
        void destroyEntities(int64_t* entityIndices, int64_t count);

        void updateEntities();
        void clear();

        void setComponentsInitialized(bool value);

        void setSimulation(Simulation* simulation)
        {
            _simulation = simulation;
        }

    private:
        EntityType(int64_t maxEntities = 0, int updateOrder = 0);
    };

} // namespace glm
