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

#pragma once

#include <glmCrowdIOAPI.h>
#include "glmFrameData.h"
#include "glmSimulationData.h"
#include "glmHistory.h"
#include "glmRenderGeometry.h"
#include <glmTypes.h>
#include <glmMap.h>
#include <glmArray.h>
#include <glmMatrix4.h>
#include <glmString.h>
#include <glmSimulationCacheFactorySimulation.h>
#include <glmSimulationCacheFactoryClearMode.h>

namespace glm
{
    namespace crowdio
    {
        //************************************************************
        // \class SimulationCacheFactory
        // \brief Handle several CachedSimulation and unify calls to getSrcSimulationData/getModifiedSimulationData/getSrcFrameData/getModifiedFrameData
        // \author Golaem <dev@golaem.com>
        //************************************************************

        class GLM_CROWDIO_API SimulationCacheFactory
        {
        public: //structures
			GLM_CLASS_STAT_ID( glm::MemoryStatIds::Cache)

            struct LayoutNodeCachedParameters
            {
                LayoutNodeCachedParameters()
                {
                }

                virtual ~LayoutNodeCachedParameters() //virtual destructor is needed to allow the factory to delete parameters without knwoing the specific type
                {
                }
            };

        public: //methods
            SimulationCacheFactory();
            ~SimulationCacheFactory();

            void clear(FactoryClearMode::Value clearMode, uint32_t historyIndex = UINT32_MAX, const glm::Time& frame = -1, uint32_t nodeID = UINT32_MAX, bool isAlreadyLocked=false);

            /**
			* \brief set the memory budget in Mb
			*/
            void setMemoryBudget(size_t memoryBudget);
            size_t getMemoryBudget() const;
            size_t getLastUsedMemoryPeak() const;

            /**
			* \brief Set how to balance the cache between cache replay or layout edition
			*/
            inline void setMemoryBudgetFrameToNodeMetricsBalance(float metricsBalance);

            /**
			* \brief compute the total size of RAM allocated for all the cachedframe and simulation in this factory
			*/
            size_t computeTotalMemory(bool inMb = true) const;

            //cached simulation
            CachedSimulation& getCachedSimulation(const char* cacheFileDir, const char* cacheName, const char* simulationName);
            inline GlmMap<unsigned int, CachedSimulation>& getCachedSimulations();

            //simulation data
            //const GlmSimulationData* getSrcSimulationData(const char* cacheFileDir, const char* cacheName, const char* simulationName);
            //const GlmSimulationData* getModifiedSimulationData(const char* cacheFileDir, const char* cacheName, const char* simulationName);
            void setSimulationProxyMatrix(const Matrix4& simTransformMatrix, const Matrix4& simTransformMatrixInverse);
            inline const Matrix4& getSimulationProxyMatrix() const;
            inline const Matrix4& getSimulationProxyMatrixInverse() const;

            //layout history
            GlmSimulationCacheStatus loadLayoutHistoryFile(size_t historyIndex, const char* newLayoutHistoryFile);
            GlmSimulationCacheStatus loadLayoutHistoryContent(size_t historyIndex, const char* newLayoutHistoryBuffer, size_t bufferSize);
            inline const GlmHistory* getLayoutHistory(size_t index) const;
            inline const Array<GlmHistory*>& getLayoutHistories();
            inline size_t getLayoutHistoryCount() const;

            void setTerrainMeshes(crowdTerrain::TerrainMesh* terrainMeshSource, crowdTerrain::TerrainMesh* terrainMeshDestination);
            inline crowdTerrain::TerrainMesh* getTerrainMeshSource() const;
            inline crowdTerrain::TerrainMesh* getTerrainMeshDestination() const;

            void setGroundAdaptationMode(GlmGroundAdaptationOptions groundAdaptationMode, size_t historyIndex);
            inline GlmGroundAdaptationOptions getGroundAdaptationMode() const;

            //characters
			void loadGolaemCharacters(const glm::GlmString& characterFiles, bool forceReload = true, const Array<GlmString>* dirmapRules = NULL);
            void loadGolaemCharacters(const glm::GlmString& characterFiles, const glm::GlmString& shadingNamespaces, bool forceReload, const Array<GlmString>* dirmapRules = NULL);
            void setGolaemCharacters(glm::PODArray<GolaemCharacter*>& characters);
            const GolaemCharacter* getGolaemCharacter(int characterIdx) const;

            int findGolaemCharacter(const glm::GlmString& characterFile, const Array<GlmString>* dirmapRules = NULL) const;

            int addGolaemCharacter(const glm::GlmString& characterFile, const Array<GlmString>* dirmapRules = NULL);
            int addGolaemCharacter(const glm::GlmString& characterFile, const glm::GlmString& shadingNamespaces, const Array<GlmString>* dirmapRules = NULL);

            void loadGolaemCharacterFurs(const glm::Array<GlmString>& dirmap);
            const glm::Array<FurCache::SP>& getGolaemCharacterFurs(int characterIdx);

            inline const glm::PODArray<GolaemCharacter*>& getGolaemCharacters() const;

            inline void setLastAccessValues(const Time& lastAccededFrameIndex, unsigned int lastAccededNodeIndex);

            inline void setEnabled(bool enabled)
            {
                _enabled = enabled;
            }

        public: //data
            Array<GlmMap<uint32_t, LayoutNodeCachedParameters*>> _layoutHistoryCachedParameters;
            glm::Array<glm::GlmString> _dirmap;

        protected: //friends
            friend class CachedSimulation;

        public: //methods
            void keepMemoryBudgetBalance();

        protected: //data
            GlmMap<unsigned int, CachedSimulation> _simulationDatas;
            PODArray<CachedSimulation*> _cachedSimInCallOrder;
            GlmSet<CachedSimulation*> _cachedSimInCallOrderAdded; // just to keep track in a sorted way of what sim has been added
            Array<GlmHistory*> _layoutHistory;

            bool _ownGolaemCharacters;
            mutable glm::RWSpinLock _golaemCharactersLock;
            glm::PODArray<GolaemCharacter*> _golaemCharacters;
            glm::Array<glm::Array<FurCache::SP>> _golaemCharacterFurs;

            Matrix4 _simTransformMatrix;        // world matrix of the transformation node holding the simulation
            Matrix4 _simTransformMatrixInverse; // inverse of the world matrix

            crowdTerrain::TerrainMesh* _terrainMeshSource;
            crowdTerrain::TerrainMesh* _terrainMeshDestination;
            GlmGroundAdaptationOptions _groundAdaptationMode;

            size_t _memoryBudget;             //in bytes
            size_t _lastUsedMemoryPeak;       //in bytes
            float _metricsFrameToNodebalance; //when computing the metrics to decide which frame to keep in cache, this is the balance between keeping display frames or keeping intermediary nodes
            Time _lastAccededFrameIndex;
            unsigned int _lastAccededNodeIndex;

            Array<PODArray<uint32_t>> _duplicateCountPerNode; // stack for all simus, by historyIndex (shared by several simus)

            bool _enabled; // returns src everywhere in 'final' if disabled
        };

        //-----------------------------------------------------------------------------
        inline GlmMap<unsigned int, CachedSimulation>& SimulationCacheFactory::getCachedSimulations()
        {
            return _simulationDatas;
        }

        //-----------------------------------------------------------------------------
        inline const Matrix4& SimulationCacheFactory::getSimulationProxyMatrix() const
        {
            return _simTransformMatrix;
        }

        //-----------------------------------------------------------------------------
        inline const Matrix4& SimulationCacheFactory::getSimulationProxyMatrixInverse() const
        {
            return _simTransformMatrixInverse;
        }

        //-----------------------------------------------------------------------------
        inline const GlmHistory* SimulationCacheFactory::getLayoutHistory(size_t index) const
        {
            return index < _layoutHistory.size() ? _layoutHistory[index] : NULL;
        }

        inline const Array<GlmHistory*>& SimulationCacheFactory::getLayoutHistories()
        {
            return _layoutHistory;
        }

        //-----------------------------------------------------------------------------
        inline size_t SimulationCacheFactory::getLayoutHistoryCount() const
        {
            return _layoutHistory.size();
        }

        //-----------------------------------------------------------------------------
        inline crowdTerrain::TerrainMesh* SimulationCacheFactory::getTerrainMeshSource() const
        {
            return _terrainMeshSource;
        }

        //-----------------------------------------------------------------------------
        inline crowdTerrain::TerrainMesh* SimulationCacheFactory::getTerrainMeshDestination() const
        {
            return _terrainMeshDestination;
        }

        //-----------------------------------------------------------------------------
        inline GlmGroundAdaptationOptions SimulationCacheFactory::getGroundAdaptationMode() const
        {
            return _groundAdaptationMode;
        }

        //-----------------------------------------------------------------------------
        inline const glm::PODArray<GolaemCharacter*>& SimulationCacheFactory::getGolaemCharacters() const
        {
            return _golaemCharacters;
        }

        //-----------------------------------------------------------------------------
        inline void SimulationCacheFactory::setLastAccessValues(const Time& lastAccededFrameIndex, unsigned int lastAccededNodeIndex)
        {
            _lastAccededFrameIndex = lastAccededFrameIndex;
            _lastAccededNodeIndex = lastAccededNodeIndex;
        }

        //-----------------------------------------------------------------------------
        inline void SimulationCacheFactory::setMemoryBudgetFrameToNodeMetricsBalance(float metricsBalance)
        {
            _metricsFrameToNodebalance = metricsBalance;
        }

    } // namespace crowdio
} // namespace glm
