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

#pragma once

#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable : 4949)
#pragma managed(push, off)
#endif

#include "glmEntity.h"
#include "glmEntityType.h"
#include "glmGroupType.h"
#include "glmAtomic.h"
#include "glmPODArray.h"

// Each context should serialize only useful components, but allocate whole array to maxEntities.
// we need to know in the same call the allocated size, and useful size + type of allocation
// these macros are helpers to define management of components size.

namespace glm
{
    class SimulationsCollection;
    class Simulation;
    class EntitiesManager;
    class GroupsManager;
    class ProcessingBatch;

    template <class ComponentType>
    class ComponentsArray
    {
        GLM_CLASS_STAT_ID( glm::MemoryStatIds::Simulation);

    public:
        //-----------------------------------------------------------------------------
        ComponentsArray(const MemAllocStatId memAllocStatId = glm::MemoryStatIds::GolaemShared)
            : _componentsPerType(memAllocStatId)
        {
            _memAllocStatId = memAllocStatId;
        }

        //-----------------------------------------------------------------------------
        ~ComponentsArray()
        {
            for (size_t i = 0; i < _componentsPerType.size(); i++)
            {
                delete _componentsPerType[i];
            }
            _componentsPerType.clear();
        }

        //-----------------------------------------------------------------------------
        void addType(int64_t maxCount)
        {
            _componentsPerType.push_back(new glm::Array<ComponentType>(_memAllocStatId));
            _componentsPerType.back()->reserve(maxCount); // allocate
        }

        //-----------------------------------------------------------------------------
        void reserveType(int entityTypeIndex, int64_t maxCount) // in case we added the type with maxCount = 0 to save until init
        {
            GLM_DEBUG_ASSERT(entityTypeIndex < (int)_componentsPerType.size());
            _componentsPerType[entityTypeIndex]->reserve(maxCount); // allocate
        }

        //-----------------------------------------------------------------------------
        ComponentType& addComponent(int entityTypeIndex, int64_t entityIndex)
        {
            // insure the current value is allocated
            if (static_cast<int64_t>(_componentsPerType[entityTypeIndex]->size()) <= entityIndex)
            {
                GLM_DEBUG_ASSERT(static_cast<int64_t>(_componentsPerType[entityTypeIndex]->size()) == entityIndex); // should grow one by one
                _componentsPerType[entityTypeIndex]->addOne();                                                      // should be
                return (*_componentsPerType[entityTypeIndex])[entityIndex];
            }
            else
            {
                // recycling : constructor has been recalled in removeComponent to be ready
                ComponentType& currentComponent = (*(_componentsPerType[entityTypeIndex]))[entityIndex];
                return currentComponent;
            }
        }

        //-----------------------------------------------------------------------------
        void removeComponent(int entityTypeIndex, int64_t entityIndex)
        {
            GLM_DEBUG_ASSERT(static_cast<int>(_componentsPerType.size()) > entityTypeIndex);
            GLM_DEBUG_ASSERT(static_cast<int64_t>(_componentsPerType[entityTypeIndex]->size()) > entityIndex);

            ComponentType& currentComponent = (*(_componentsPerType[entityTypeIndex]))[entityIndex];
            currentComponent.~ComponentType();

            // call directly a placement new to be back to initial state, and to avoid any crash when double deleting an item (here + final componentArray deletion)
            // EntityComponents allocations should be done in doInit, so this shoul not have an heavy impact on the memory kept (and anyway, this is to be reused)
            new (&currentComponent) ComponentType();
        }

        //-----------------------------------------------------------------------------
        ComponentType& getComponent(int entityTypeIndex, int64_t entityIndex)
        {
            GLM_DEBUG_ASSERT(entityTypeIndex < static_cast<int>(_componentsPerType.size()));
            GLM_DEBUG_ASSERT(entityIndex < static_cast<int64_t>(_componentsPerType[entityTypeIndex]->size()));
            return (*(_componentsPerType[entityTypeIndex]))[entityIndex];
        }

        //-----------------------------------------------------------------------------
        glm::Array<ComponentType>& getComponents(int entityTypeIndex)
        {
            GLM_DEBUG_ASSERT(entityTypeIndex < static_cast<int>(_componentsPerType.size()));
            return (*(_componentsPerType[entityTypeIndex]));
        }

        //-----------------------------------------------------------------------------
        inline int getTypeCount()
        {
            return static_cast<int>(_componentsPerType.size());
        }

    private:
        glm::Array<glm::Array<ComponentType>*> _componentsPerType;
        MemAllocStatId _memAllocStatId;
    };

    /**
	* \brief Interface for a component context, to be implemented as a global context for each simulation field : Animation, Navigation, Behaviors, Visualization, Collision, Perception, ...
	*
	* \ingroup simulation
	* \author Golaem <dev@golaem.com>
	*/
    class GLM_SDK_API IContext
    {
        friend class SimulationsCollection;
        friend class Simulation;
        friend class EntitiesManager;
        friend class GroupsManager;

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

        struct ContextType
        {
            enum Value
            {
                USER_BEGIN,
                GROUND_FOLLOW_PRE, // FOLLOW mesh transform / deform for pinned grounds
                TRAFFIC,
                BOID,
                NAVIGATION,
                //GROUND_UNFOLD,	// UNFOLD for all nav if ground is responsible (use both grounds) -> done wherever it is needed (when setting pos in output of each traffic / boid 2D / path / ground adapt callback)
                USER_PREMOTION,
                ANIMATION, // need to handle ground follow at entry of motion comp if not synced, + constriants (barycenter
                MESHPIN,   // meshPin is after animation : it must be able to "patch" the computed posture, to force it on the ground. Else it would either not be able to smooth or would have an unfixed one-frame delta in movement displacement (if world-up vector is applied without smoothing to compute correct movement displacement, there won't be smoothing).
                USER_PREPHYSIC,
                UVPIN_PRE_PHYSICS,
                PHYSICS,
                USER_END,
                UVPIN_POST_PHYSICS,
                REPLAY,
                PRE_PERCEPTION, // perception occurs after components have done their job (moved entities), and before behaviors take decisions. It is used by visu so get it before display so we see why decisions are taken
                PERCEPTION,     // perception occurs after components have done their job (moved entities), and before behaviors take decisions. It is used by visu so get it before display so we see why decisions are taken
                VISUALIZATION,
                BEHAVIOR,
                GROUND_FOLLOW_POST, // FOLLOW mesh store ground cell Id at end of update
                KNOWLEDGE_BASE,
                END
            };
        };

        static void initBatch(ProcessingBatch& batch);
        static void initEntityTypeBatch(ProcessingBatch& batch);
        static void initGroupTypeBatch(ProcessingBatch& batch);

        static void initEntityComponentsBatch(ProcessingBatch& batch);
        static void initGroupComponentsBatch(ProcessingBatch& batch);

        static void preUpdateBatch(ProcessingBatch& batch);
        static void preUpdateSubsetBatch(ProcessingBatch& batch);
        static void postUpdateSubsetBatch(ProcessingBatch& batch);
        static void postUpdateBatch(ProcessingBatch& batch);
        static void updateEntityComponentsBatch(ProcessingBatch& batch);
        static void updateGroupComponentsBatch(ProcessingBatch& batch);

        void finish(); // finish is called by simulation, it does not need threading so a single batch of finish is done at SimulationsCollection level, calling simulations, calling contexts.

        void pause();
        void unpause();

        virtual ~IContext();
        inline bool isInitialized() const;
        inline bool isPaused() const;
        inline ContextType::Value getContextType() const;

        int _lod;

        inline Simulation* getSimulation() const;

    protected:
        IContext(bool isMultithread, bool processGroupsFirst = false);

        virtual void setContextType() = 0;

        virtual void doInit(){};
        virtual void doUpdate(Time /*dt*/)
        {
        } // using all entities/groups if required
        virtual void doUpdateSubset(Time /*dt*/, int /*updateOrder*/)
        {
        }
        // will update components per subset
        virtual void doPostUpdateSubset(Time /*dt*/, int /*updateOrder*/)
        {
        }
        virtual void doPostUpdate(Time /*dt*/)
        {
        } // using all entities/groups if required
        virtual void doFinish(){};

        // EntityTypes
        virtual bool registeredOnCreateEntityType()
        {
            return false;
        }
        virtual bool registeredOnInitEntityType()
        {
            return false;
        }
        //virtual bool registeredOnUpdateEntityType() { return false; }
        virtual bool registeredOnFinishEntityType()
        {
            return false;
        }
        virtual void onCreateEntityType(int64_t /*maxCount*/){}; // index should be the same in each context as we create EntityTypes on all contexts
        virtual void onInitEntityType(int /*entityTypeIndex*/){};
        //virtual void onUpdateEntityType(int /*entityTypeIndex*/) {};
        virtual void onFinishEntityType(int /*entityTypeIndex*/){};

        // GroupTypes
        virtual bool registeredOnCreateGroupType()
        {
            return false;
        }
        virtual bool registeredOnInitGroupType()
        {
            return false;
        }
        //virtual bool registeredOnUpdateGroupType() { return false; }
        virtual bool registeredOnFinishGroupType()
        {
            return false;
        }
        virtual void onCreateGroupType(int64_t /*maxCount*/){};
        virtual void onInitGroupType(int /*groupTypeIndex*/){};
        //virtual void onUpdateGroupType(int /*groupTypeIndex*/) {};
        virtual void onFinishGroupType(int /*groupTypeIndex*/){};

        // as EntityTypes are kept all the simulation, there is no finish/destroy callbacks

        // EntityComponents
        virtual bool registeredOnCreateEntityComponent()
        {
            return false;
        }
        virtual bool registeredOnInitEntityComponent()
        {
            return false;
        }
        virtual bool registeredOnUpdateEntityComponent()
        {
            return false;
        }
        virtual bool registeredOnFinishEntityComponent()
        {
            return false;
        }
        virtual bool registeredOnDestroyEntityComponent()
        {
            return false;
        }
        virtual void onCreateEntityComponent(int /*entityTypeIndex*/, int64_t /*entityIndex*/){};
        virtual void onInitEntityComponent(int /*entityTypeIndex*/, int64_t /*entityIndex*/){};
        virtual void onUpdateEntityComponent(Time /*dt*/, int /*entityTypeIndex*/, int64_t /*entityIndex*/){};
        virtual void onFinishEntityComponent(int /*entityTypeIndex*/, int64_t /*entityIndex*/){};
        virtual void onDestroyEntityComponent(int /*entityTypeIndex*/, int64_t /*entityIndex*/){};

        // GroupComponents
        virtual bool registeredOnCreateGroupComponent()
        {
            return false;
        }
        virtual bool registeredOnInitGroupComponent()
        {
            return false;
        }
        virtual bool registeredOnUpdateGroupComponent()
        {
            return false;
        }
        virtual bool registeredOnFinishGroupComponent()
        {
            return false;
        }
        virtual bool registeredOnDestroyGroupComponent()
        {
            return false;
        }
        virtual void onCreateGroupComponent(int /*groupTypeIndex*/, int64_t /*groupIndex*/){};
        virtual void onInitGroupComponent(int /*groupTypeIndex*/, int64_t /*groupIndex*/){};
        virtual void onUpdateGroupComponent(Time /*dt*/, int /*groupTypeIndex*/, int64_t /*groupIndex*/){};
        virtual void onFinishGroupComponent(int /*groupTypeIndex*/, int64_t /*groupIndex*/){};
        virtual void onDestroyGroupComponent(int /*groupTypeIndex*/, int64_t /*groupIndex*/){};

        ContextType::Value _contextType;

        Simulation* _simulation;
        bool _isInitMultithread;
        bool _isUpdateMultithread;

        int64_t _entityBatchSize;
        int64_t _groupBatchSize;

    private:
        void setInitialized(bool initiliazed);
        void setPaused(bool paused);

        void finishEntityComponents();
        void finishGroupComponents();

        void enqueueInit(glm::GlmQueue<ProcessingBatch>& batchQueue, const int entityTypeCount, const int groupTypeCount);

        // utils
        void enqueueBatchedType(glm::GlmQueue<ProcessingBatch>& batchQueue, ProcessingBatch& currentBatch, int64_t count, int64_t batchSize);

        void enqueueEntityTypesInit(glm::GlmQueue<ProcessingBatch>& batchQueue, const int entityTypeCount);
        void enqueueGroupTypesInit(glm::GlmQueue<ProcessingBatch>& batchQueue, const int groupTypeCount);

        void enqueueComponentsInit(glm::GlmQueue<ProcessingBatch>& batchQueue, const int64_t entityCount, const int64_t groupCount);
        void enqueueGroupComponentsInit(glm::GlmQueue<ProcessingBatch>& batchQueue, int64_t count);
        void enqueueEntityComponentsInit(glm::GlmQueue<ProcessingBatch>& batchQueue, int64_t count);

        void enqueueUpdateSubset(glm::GlmQueue<ProcessingBatch>& batchQueue, const Time dt, int updateOrder);
        void enqueueGroupComponentsSubsetUpdate(glm::GlmQueue<ProcessingBatch>& batchQueue, Time dt, int updateOrder, int64_t groupCount);
        void enqueueEntityComponentsSubsetUpdate(glm::GlmQueue<ProcessingBatch>& batchQueue, Time dt, int updateOrder, int64_t entityCount);

        atomic32_t _initialized;
        atomic32_t _paused;

    protected:
        bool _processGroupsFirst;
    };

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

    //-------------------------------------------------------------------------
    inline bool IContext::isPaused() const
    {
        return atomicLoadAcquireBool(_paused);
    }

    //-------------------------------------------------------------------------
    inline IContext::ContextType::Value IContext::getContextType() const
    {
        return _contextType;
    }

    //-------------------------------------------------------------------------
    inline Simulation* IContext::getSimulation() const
    {
        return _simulation;
    }
} // namespace glm

#ifdef _MSC_VER
#pragma managed(pop)
#pragma warning(pop)
#endif
