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

#pragma once

#include "glmEntityType.h"
#include "glmGroupType.h"
#include "glmSimuEntityHandle.h"
#include "glmGroundManager.h"
#include "glmTypes.h"
#include "glmMemory.h"
#include "glmTimer.h"
#include "glmEvent.h"
#include "glmIContext.h"
#include "glmMutex.h"
#include "glmKnowledgeBase.h"
#include "glmBehaviorContext.h"
#include "glmKBContext.h"

namespace glm
{
    const int maxUpdateLevels = 10;

    class SimulationsCollection;
    class IContext;

    // Root of a simulation. The Simulation contains Contexts and EntityTypes
    class GLM_SDK_API Simulation
    {
        friend class SimulationsCollection;
        friend class IContext;
        friend class EngineUpdateThread;

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

        static const char* _indexInCrowdFieldKBEntryName;
        static int64_t _indexInCrowdFieldKBEntryHashKey;

        Simulation();
        ~Simulation();

        // blocking batch
        static void emptyBlockingBatch(ProcessingBatch& batch); // used between multithread components and groupComponents updates
        static void enqueueBlockingBatch(glm::GlmQueue<ProcessingBatch>& batchQueue);

        // batch processing
        static void preInitBatch(ProcessingBatch& batch);
        static void setComponentsInitializedBatch(ProcessingBatch& batch);
        static void postInitBatch(ProcessingBatch& batch);
        static void preUpdateBatch(ProcessingBatch& batch);
        // updatse per subset are handled in IContext
        static void postUpdateBatch(ProcessingBatch& batch);
        static void finishBatch(ProcessingBatch& batch);

        void reset();

        bool isInitialized() const;

        /**
		* \brief get accessor to the simulation duration
		* \return the time since beginning of the simulation, in seconds
		*/
        Time getDuration() const;
        int getCurrentFrame() const;
        void setCurrentFrame(int); // used by crowd to synchronize simulations frames (useful for shared physic update). Must be set to frame -1 before update cause it is incremented in preUpdateBatch
        inline Time getLastUpdateDt() const;

        const GroundManager& getGroundManager() const;
        GroundManager& touchGroundManager();

        SimulationsCollection* getSimulationsCollection();

        /** forward of aggregated classes methods often used **/
        inline bool isEntityIndexInUse(SimuEntityHandle handle);
        inline bool isEntityIndexInUse(int entityTypeIndex, int64_t entityIndex);
        inline Entity& getEntity(SimuEntityHandle handle);
        inline Group& getGroup(SimuEntityHandle handle);
        inline Entity& getEntity(int entityTypeIndex, int64_t entityIndex);
        inline Group& getGroup(int groupTypeIndex, int64_t groupIndex);
        inline const Entity& getEntity(SimuEntityHandle handle) const;
        inline const Group& getGroup(SimuEntityHandle handle) const;
        inline const Entity& getEntity(int entityTypeIndex, int64_t entityIndex) const;
        inline const Group& getGroup(int groupTypeIndex, int64_t groupIndex) const;
        inline int64_t getEntityCount() const;
        inline int64_t getGroupCount() const;
        inline int64_t getEntityCount(int updateOrder) const;
        inline int64_t getGroupCount(int updateOrder) const;

        int createEntityType(const int64_t maxCount, int updateOrder = 0); // EntityTypes are kept until a simulation reset (no destroy)
        int createGroupType(const int64_t maxCount, int updateOrder = 0);  // EntityTypes are kept until a simulation reset (no destroy)

        SimuEntityHandle createEntity(int entityTypeId, const int64_t uniqueEntityId);
        SimuEntityHandle createGroup(int groupTypeId, const int64_t uniqueGroupId);

        void setEntityId(Entity& entity, int64_t uniqueEntityId);

        void destroyEntity(SimuEntityHandle handle);
        void destroyGroup(SimuEntityHandle handle);

        void clearEntities();
        void clearGroups();

        IContext* getContext(IContext::ContextType::Value componentType) const;
        template <class T>
        T* getContext() const;

        void addContext(IContext* context);
        void removeContext(IContext* context);

        inline unsigned int getContextCount() const;
        inline IContext* getContextByIndex(unsigned int index);

        /**
		* \brief running is true if the engine is initialized and the end has not been requested
		*/
        bool isRunning();

        EntityType* getEntityType(int entityTypeId) const;
        GroupType* getGroupType(int groupTypeId) const;
        int getEntityTypeCount() const;
        int getGroupTypeCount() const;

        KnowledgeBase& getKB()
        {
            return _globalKB;
        }
        const KnowledgeBase& getKB() const
        {
            return _globalKB;
        }

        bool isValidEntity(SimuEntityHandle handle) const;
        bool isValidEntity(int typeIndex, int64_t index) const;
        bool isValidGroup(SimuEntityHandle handle) const;
        bool isValidGroup(int typeIndex, int64_t index) const;

        void setInitBehaviorsRng(bool value);
        bool getInitBehaviorsRng() const;

        //inline int getPreallocatedEntityTypeCount() { return _preAllocatedEntityTypes; }
        //inline int getPreallocatedGroupTypeCount() { return _preAllocatedGroupTypes; }

        void findAttributesInKB(GlmMap<GlmString, PODArray<int>>& weightAttributeByNames); // set keys, values will be filled or set to -1 if not found

    protected:
        void enqueueInit(glm::GlmQueue<ProcessingBatch>& batchQueue);
        void enqueueUpdate(glm::GlmQueue<ProcessingBatch>& batchQueue, Time dt);
        void enqueueFinish(glm::GlmQueue<ProcessingBatch>& batchQueue);

    private:
        GLM_DISALLOW_COPY_AND_ASSIGN(Simulation);

        void addContextInternal(glm::IContext* context);

        GroundManager _groundManager;

        SimulationsCollection* _simulationsCollection;

        /// simulation duration
        Time _duration;
        int32_t _frame;
        Time _lastUpdateDt;

        /// init flag, used for addEntity
        bool _initialized;

        /// Component contexts
        glm::PODArray<glm::IContext*> _contexts;

        glm::PODArray<EntityType*> _entityTypes;
        glm::PODArray<GroupType*> _groupTypes;

        KnowledgeBase _globalKB;
        KBContext* _kbContext;

        BehaviorContext* _behaviorContext;

        int64_t _entityCount;
        int64_t _groupCount;

        int64_t* _entityCountPerUpdateOrder;
        int64_t* _groupCountPerUpdateOrder;

        int64_t _needEntitiesInit;
        int64_t _needGroupsInit;

        //int16_t _preAllocatedEntityTypes;	// to preallocate entityType arrays and avoid shifting their content in memory. has a default value of 20, this is a soft max (extensible by createEntityType)
        //int16_t _preAllocatedGroupTypes;	// to preallocate entityType arrays and avoid shifting their content in memory. has a default value of 20, this is a soft max (extensible by createGroupType)

        bool _initBehaviorsRng;
    };

    //-------------------------------------------------------------------------
    inline const GroundManager& Simulation::getGroundManager() const
    {
        return _groundManager;
    }

    //-------------------------------------------------------------------------
    inline GroundManager& Simulation::touchGroundManager()
    {
        return _groundManager;
    }

    //-------------------------------------------------------------------------
    inline SimulationsCollection* Simulation::getSimulationsCollection()
    {
        return _simulationsCollection;
    }

    //-------------------------------------------------------------------------
    inline bool Simulation::isInitialized() const
    {
        return _initialized;
    }

    //-------------------------------------------------------------------------
    inline Time Simulation::getDuration() const
    {
        return _duration;
    }

    //-------------------------------------------------------------------------
    inline int Simulation::getCurrentFrame() const
    {
        return _frame;
    }

    inline void Simulation::setCurrentFrame(int newFrame)
    {
        _frame = newFrame;
    }

    //-------------------------------------------------------------------------
    inline Time Simulation::getLastUpdateDt() const
    {
        return _lastUpdateDt;
    }
    //-------------------------------------------------------------------------
    inline bool Simulation::isRunning()
    {
        return _initialized;
    }

    //-------------------------------------------------------------------------
    inline Entity& Simulation::getEntity(const SimuEntityHandle handle)
    {
        if (handle._typeIndex >= _entityTypes.size())
        {
            GLM_SDK_TRACE_ERROR("Type index " << handle._typeIndex << "is higher than the count of entity types in the simulation (" << _entityTypes.size() << ")");
        }
        if (handle._index >= _entityTypes[handle._typeIndex]->_entities.size())
        {
            GLM_SDK_TRACE_ERROR("Entity index " << handle._index << "is higher than the count of entity types in the simulation (" << _entityTypes[handle._typeIndex]->_entities.size() << ")");
        }

        GLM_DEBUG_ASSERT(handle._typeIndex < _entityTypes.size());
        GLM_DEBUG_ASSERT(handle._index < _entityTypes[handle._typeIndex]->_entities.size());
        return _entityTypes[handle._typeIndex]->getEntity(handle._index);
    }
    //-------------------------------------------------------------------------
    inline Group& Simulation::getGroup(const SimuEntityHandle handle)
    {
        GLM_DEBUG_ASSERT(handle._typeIndex < _groupTypes.size());
        GLM_DEBUG_ASSERT(handle._index < _groupTypes[handle._typeIndex]->_groups.size());
        return _groupTypes[handle._typeIndex]->getGroup(handle._index);
    }
    //-------------------------------------------------------------------------
    inline bool Simulation::isEntityIndexInUse(SimuEntityHandle handle)
    {
        if (handle._typeIndex >= 0 &&
            handle._typeIndex < _entityTypes.size() &&
            handle._index >= 0 &&
            handle._index < _entityTypes[handle._typeIndex]->_entities.size())
            return _entityTypes[handle._typeIndex]->isIndexInUse(handle._index);
        return false;
    }
    //-------------------------------------------------------------------------
    inline bool Simulation::isEntityIndexInUse(int entityTypeIndex, int64_t entityIndex)
    {
        return isEntityIndexInUse(SimuEntityHandle(entityTypeIndex, entityIndex));
    }
    //-------------------------------------------------------------------------
    inline Entity& Simulation::getEntity(int entityTypeIndex, int64_t entityIndex)
    {
        GLM_DEBUG_ASSERT(entityTypeIndex < static_cast<int>(_entityTypes.size()));
        GLM_DEBUG_ASSERT(entityIndex < static_cast<int>(_entityTypes[entityTypeIndex]->_entities.size()));
        return _entityTypes[entityTypeIndex]->getEntity(entityIndex);
    }
    //-------------------------------------------------------------------------
    inline Group& Simulation::getGroup(int groupTypeIndex, int64_t groupIndex)
    {
        GLM_DEBUG_ASSERT(groupTypeIndex < static_cast<int>(_groupTypes.size()));
        GLM_DEBUG_ASSERT(groupIndex < static_cast<int>(_groupTypes[groupTypeIndex]->_groups.size()));
        return _groupTypes[groupTypeIndex]->getGroup(groupIndex);
    }

    //-------------------------------------------------------------------------
    inline const Entity& Simulation::getEntity(const SimuEntityHandle handle) const
    {
        GLM_DEBUG_ASSERT(handle._typeIndex < _entityTypes.size());
        GLM_DEBUG_ASSERT(handle._index < _entityTypes[handle._typeIndex]->_entities.size());
        return _entityTypes[handle._typeIndex]->getEntity(handle._index);
    }
    //-------------------------------------------------------------------------
    inline const Group& Simulation::getGroup(const SimuEntityHandle handle) const
    {
        GLM_DEBUG_ASSERT(handle._typeIndex < _groupTypes.size());
        GLM_DEBUG_ASSERT(handle._index < _groupTypes[handle._typeIndex]->_groups.size());
        return _groupTypes[handle._typeIndex]->getGroup(handle._index);
    }

    //-------------------------------------------------------------------------
    inline const Entity& Simulation::getEntity(int entityTypeIndex, int64_t entityIndex) const
    {
        GLM_DEBUG_ASSERT(entityTypeIndex < static_cast<int>(_entityTypes.size()));
        GLM_DEBUG_ASSERT(entityIndex < static_cast<int>(_entityTypes[entityTypeIndex]->_entities.size()));
        return _entityTypes[entityTypeIndex]->getEntity(entityIndex);
    }
    //-------------------------------------------------------------------------
    inline const Group& Simulation::getGroup(int groupTypeIndex, int64_t groupIndex) const
    {
        GLM_DEBUG_ASSERT(groupTypeIndex < static_cast<int>(_groupTypes.size()));
        GLM_DEBUG_ASSERT(groupIndex < static_cast<int>(_groupTypes[groupTypeIndex]->_groups.size()));
        return _groupTypes[groupTypeIndex]->getGroup(groupIndex);
    }

    //-------------------------------------------------------------------------
    inline int64_t Simulation::getEntityCount() const
    {
        return _entityCount;
    }

    //-------------------------------------------------------------------------
    inline int64_t Simulation::getGroupCount() const
    {
        return _groupCount;
    }

    //-------------------------------------------------------------------------
    inline int64_t Simulation::getEntityCount(int updateOrder) const
    {
        GLM_DEBUG_ASSERT(updateOrder < maxUpdateLevels);
        return _entityCountPerUpdateOrder[updateOrder];
    }

    //-------------------------------------------------------------------------
    inline int64_t Simulation::getGroupCount(int updateOrder) const
    {
        GLM_DEBUG_ASSERT(updateOrder < maxUpdateLevels);
        return _groupCountPerUpdateOrder[updateOrder];
    }

    //-------------------------------------------------------------------------
    inline int Simulation::getEntityTypeCount() const
    {
        return (static_cast<int>(_entityTypes.size()));
    }

    //-------------------------------------------------------------------------
    inline EntityType* Simulation::getEntityType(int entityTypeIndex) const
    {
        GLM_ASSERT(entityTypeIndex < static_cast<int>(_entityTypes.size()));
        return _entityTypes[entityTypeIndex];
    }

    //-------------------------------------------------------------------------
    inline int Simulation::getGroupTypeCount() const
    {
        return (static_cast<int>(_groupTypes.size()));
    }

    //-------------------------------------------------------------------------
    inline GroupType* Simulation::getGroupType(int groupTypeId) const
    {
        GLM_ASSERT(groupTypeId < static_cast<int>(_groupTypes.size()));
        return _groupTypes[groupTypeId];
    }

    //-------------------------------------------------------------------------
    template <class T>
    T* Simulation::getContext() const
    {
        IContext* context(NULL);

        glm::Array<glm::IContext*>::const_iterator ite(_contexts.begin());
        for (; (ite != _contexts.end()) && (context == NULL); ++ite)
        {
            if ((*ite)->getContextType() == T::getStaticContextType())
            {
                context = *ite;
            }
        }
        return static_cast<T*>(context);
    }

    //-------------------------------------------------------------------------
    inline unsigned int Simulation::getContextCount() const
    {
        return static_cast<unsigned int>(_contexts.size());
    }

    //-------------------------------------------------------------------------
    inline IContext* Simulation::getContextByIndex(unsigned int index)
    {
        GLM_DEBUG_ASSERT(index < static_cast<unsigned int>(_contexts.size()));

        return _contexts[index];
    }

    //-------------------------------------------------------------------------
    inline bool Simulation::isValidEntity(SimuEntityHandle handle) const
    {
        return !(handle._isGroup) && handle._typeIndex < static_cast<uint32_t>(_entityTypes.size()) && handle._index < static_cast<uint32_t>(_entityTypes[handle._typeIndex]->getEntityCount()) && _entityTypes[handle._typeIndex]->isIndexInUse(handle._index);
    }

    //-------------------------------------------------------------------------
    inline bool Simulation::isValidGroup(SimuEntityHandle handle) const
    {
        return handle._isGroup && handle._typeIndex < static_cast<uint32_t>(_groupTypes.size()) && handle._index < static_cast<uint32_t>(_groupTypes[handle._typeIndex]->getGroupCount()) && _groupTypes[handle._typeIndex]->isIndexInUse(handle._index);
    }

    //-------------------------------------------------------------------------
    inline bool Simulation::isValidEntity(int typeIndex, int64_t index) const
    {
        return typeIndex < static_cast<int>(_entityTypes.size()) && index < _entityTypes[typeIndex]->getEntityCount() && _entityTypes[typeIndex]->isIndexInUse(index);
    }

    //-------------------------------------------------------------------------
    inline bool Simulation::isValidGroup(int typeIndex, int64_t index) const
    {
        return typeIndex < static_cast<int>(_groupTypes.size()) && index < _groupTypes[typeIndex]->getGroupCount() && _groupTypes[typeIndex]->isIndexInUse(index);
    }

    //-------------------------------------------------------------------------
    inline void Simulation::setInitBehaviorsRng(bool value)
    {
        _initBehaviorsRng = value;
    }

    //-------------------------------------------------------------------------
    inline bool Simulation::getInitBehaviorsRng() const
    {
        return _initBehaviorsRng;
    }
} // namespace glm
