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

#pragma once

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

namespace glm
{
    namespace crowdio
    {
        class SimulationCacheFactory;

        //************************************************************
        // \class CachedSimulation
        // \brief Store and simplify access to src/modified simulationData/frameData
        // \author Golaem <dev@golaem.com>
        //************************************************************

        void GLM_CROWDIO_API fillAttributesFromSimulationData(const GlmSimulationData* modifiedSimulationData, GlmMap<GlmString, PODArray<int>>& weightAttributeByNames);

        class GLM_CROWDIO_API CachedSimulation
        {
        public:
            //static methods
			GLM_CLASS_STAT_ID(glm::MemoryStatIds::Cache)
            static unsigned int getSimulationhashCode(const char* cacheFileDir, const char* cacheName, const char* simulationName);

        public:
            //structures
            struct CachedFrame
            {
				GLM_CLASS_STAT_ID(glm::MemoryStatIds::Cache)

                CachedFrame()
                    : _frame(NULL)
                    //, _locked(true)
                    , _frameStatus(GSC_END)
                    , _dataSize(0)
                    , _modifiedFrameUseSrc(false)
                {
                    glm::atomicStoreRelease(_locked, 0);
                }
                ~CachedFrame()
                {
                    GLM_DEBUG_ASSERT_MSG(_frame == NULL, "SimulationCacheFactory: The _frame must be destroyed before reaching the destructor (it can't be done here because it needs a simulation data)");
                }

                const GlmFrameData* _frame;
                glm::atomic32_t _locked;
                GlmSimulationCacheStatus _frameStatus;
                mutable int64_t _dataSize;
                bool _modifiedFrameUseSrc;

                // entity assets
                ShaderAssetDataContainer _shaderDataContainer;
            };

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

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

            //simulation configuration
            inline const char* getCacheFileDir() const;
            inline const char* getCacheName() const;
            inline const char* getSimulationName() const;

            //simulation data
            const GlmSimulationData* getSrcSimulationData();
            inline GlmSimulationCacheStatus getSrcSimulationStatus();

            const GlmSimulationData* getModifiedSimulationData(size_t historyIndex);
            inline GlmSimulationCacheStatus getModifiedSimulationStatus(size_t historyIndex);

            const GlmSimulationData* getModifiedSimulationDataNoLock(size_t historyIndex);

            const GlmSimulationData* getFinalSimulationData();
            GlmSimulationCacheStatus getFinalSimulationStatus();

            //history
            inline bool hasHistoryRuntimeStructure() const;
			size_t getLayoutHistoryCount() const;
            glmHistoryRuntimeStructure* getHistoryRuntimeStructure(size_t index); //needs the factory to have been given first
            glmHistoryRuntimeStructure* getHistoryRuntimeStructureNoLock(size_t index); // needs the factory to have been given first
            void getHistoryRuntimeStructures(Array<const glmHistoryRuntimeStructure*>& historyRuntimeStructures);

            //inline void setCurrentLayoutIndex(size_t index);
            //inline glmHistoryRuntimeStructure* getCurrentHistoryRuntimeStructure();

            //frame data
            inline const GlmMap<glm::Time, CachedFrame*>& getSrcFrames() const;
            const GlmFrameData* getSrcFrameData(glm::Time frameIndex, bool interpolateBetweenFrames, bool lockDeletion = false);
            GlmSimulationCacheStatus getSrcFrameStatus(glm::Time frameIndex); //will return END if never atempted to load the frame first (call getSrcFrameData first)

            inline const GlmSimulationData* getPreviousSimulationData(size_t historyIndex);
            inline const GlmSimulationData* getPreviousSimulationDataNoLock(size_t historyIndex);
            inline GlmSimulationCacheStatus getPreviousSimulationStatus(size_t historyIndex);
            inline const GlmFrameData* getPreviousHistoryFrameData(size_t historyIndex, glm::Time frameIndex, bool interpolateBetweenFrames, SimulationCacheVisualFeedbackArray* visualFeedbackArray);
            inline GlmSimulationCacheStatus getPreviousFrameStatus(size_t historyIndex, glm::Time frameIndex, uint32_t layoutNodeID = UINT32_MAX);

            inline size_t getModifiedFramesHistoryCount() const;
            inline const GlmMap<glm::Time, GlmMap<uint32_t, CachedFrame*>>& getModifiedFrames(size_t historyIndex) const;
            inline const GlmFrameData* getModifiedFrameData(size_t historyIndex, glm::Time frameIndex, bool interpolateBetweenFrames, SimulationCacheVisualFeedbackArray* visualFeedbackArray, bool lockDeletion = false);
            const GlmFrameData* getModifiedFrameData(size_t historyIndex, glm::Time frameIndex, uint32_t layoutNodeID, bool interpolateBetweenFrames, SimulationCacheVisualFeedbackArray* visualFeedbackArray, bool lockDeletion = false);
            GlmSimulationCacheStatus getModifiedFrameStatus(size_t historyIndex, glm::Time frameIndex, uint32_t layoutNodeID = UINT32_MAX); //will return GSC_SIMULATION_FRAME_NOT_IN_CACHE if never attempted to load the frame first (call getModifiedFrameData first)

            const GlmFrameData* getFinalFrameData(glm::Time frameIndex, uint32_t layoutNodeID, bool interpolateBetweenFrames, SimulationCacheVisualFeedbackArray* visualFeedbackArray = NULL, bool lockDeletion = false);
            GlmSimulationCacheStatus getFinalFrameStatus(glm::Time frameIndex, uint32_t layoutNodeID = UINT32_MAX); //will return GSC_SIMULATION_FRAME_NOT_IN_CACHE if never attempted to load the frame first (call getModifiedFrameData first)

            void unlockFrame(glm::Time frameIndex, uint32_t layoutNodeID);
            //void releaseFinalFrameDataBefore(glm::Time frameIndex);
            //void releaseFinalFrameData(glm::Time frameIndex, uint32_t layoutNodeID);

            void storeModifiedFrameData(size_t historyIndex, glm::Time frameIndex, uint32_t layoutNodeID, GlmFrameData* frameData); //the cachedSimulation takes ownership of the given frame data. If a frame data is already stored for this frame/nodeID, it will be deleted to store the given one.

            // entity assets
            const glm::Array<glm::PODArray<int>>& getSrcEntityAssets();
            const glm::Array<glm::PODArray<int>>& getModifiedEntityAssets(size_t historyIndex, glm::Time currentTime, bool forceComputation = false);
            const glm::ShaderNameReplacement& getShaderNameReplacements(size_t historyIndex);

            const ShaderAssetDataContainer* getSrcShaderData(glm::Time frameIndex, bool interpolateBetweenFrames);
            const ShaderAssetDataContainer* getModifiedShaderData(size_t historyIndex, glm::Time frameIndex, uint32_t layoutNodeID, bool interpolateBetweenFrames, SimulationCacheVisualFeedbackArray* visualFeedbackArray);

            const glm::Array<glm::PODArray<int>>& getFinalEntityAssets(glm::Time currentTime, bool forceComputation = false);
            const glm::Array<glm::PODArray<int>>& getFinalDeepEntityAssets(glm::Time currentTime, bool forceComputation = false);

            const ShaderAssetDataContainer* getFinalShaderData(glm::Time frameIndex, uint32_t layoutNodeID, bool interpolateBetweenFrames, SimulationCacheVisualFeedbackArray* visualFeedbackArray = NULL);
            const glm::ShaderNameReplacement& getFinalShaderNameReplacements();

            //factory it belong to
            inline SimulationCacheFactory* getFactory() const;

            inline bool isFrameDeletionLocked() const;
            void lockFrameDeletion();
            void unlockFrameDeletion();
            void unlockFrameDeletionNoBalance();

            void updateFramesAvailableOnDisk();

            bool getSrcFrameRangeAvailableOnDisk(int& firstFrame, int& lastFrame);

        protected:

            //data
            GlmString _cacheFileDir;
            GlmString _cacheName;
            GlmString _simulationName;

            //simu
            mutable glm::SpinLock _srcSimulationDataSpinLock;
            GlmSimulationData* _srcSimulationData;
            GlmSimulationCacheStatus _srcSimuStatus;

            mutable glm::SpinLock _modifiedSimulationDataSpinLock;
            PODArray<bool> _modifiedSimUsePrevious;
            PODArray<GlmSimulationData*> _modifiedSimulationData;
            PODArray<GlmSimulationCacheStatus> _modifiedSimuStatus;

            mutable int64_t _simulationDataSize;
            mutable PODArray<int64_t> _modifiedSimulationDataSize;

            // entity assets
            glm::Array<glm::PODArray<int>> _srcEntityAssets; // MeshAssets
            glm::Array<glm::PODArray<int>> _srcDeepEntityAssets; // DeepDeformerAssets

            glm::Array<glm::PODArray<int>> _modifiedEntityAssets; // MeshAssets
            glm::Array<glm::PODArray<int>> _modifiedDeepEntityAssets; // DeepDeformerAssets
            glm::Array<glm::ShaderOverrides> _shaderOverrides;
            glm::ShaderNameReplacement _shaderNameReplacements;

            //frames
            PODArray<int> _sortedSrcFrameAvailableOnDisk;

            mutable glm::SpinLock _srcFramesSpinLock; // allows to get a pointer in multithread environment, Wargning : not safe against deletion !! Do not call delete / memory balance while using the pointer
            GlmMap<glm::Time, CachedFrame*> _srcFrames;

            mutable glm::SpinLock _modifiedFramesSpinLock;                            // allows to get a pointer in multithread environment, Wargning : not safe against deletion !! Do not call delete / memory balance while using the pointer
            Array<GlmMap<glm::Time, GlmMap<uint32_t, CachedFrame*>>> _modifiedFrames; // array of layoutHistories, first key is the frame, second key is the node ID

            //history
            glm::Array<glmHistoryRuntimeStructure*> _historyRuntimeStructures;

            SimulationCacheFactory* _factory;

        private:
            //data
            unsigned int _noFrameDeletionLocks; // number of request to NOT delete frames (a bunch of frames are temporary needed when doing a render if there is some frame offset, for instance)

            friend class SimulationCacheFactory;
        };

        //-----------------------------------------------------------------------------
        inline const char* CachedSimulation::getCacheFileDir() const
        {
            return _cacheFileDir.c_str();
        }

        //-----------------------------------------------------------------------------
        inline const char* CachedSimulation::getCacheName() const
        {
            return _cacheName.c_str();
        }

        //-----------------------------------------------------------------------------
        inline const char* CachedSimulation::getSimulationName() const
        {
            return _simulationName.c_str();
        }

        //-----------------------------------------------------------------------------
        inline GlmSimulationCacheStatus CachedSimulation::getSrcSimulationStatus()
        {
            return _srcSimuStatus;
        }

        //-----------------------------------------------------------------------------
        inline GlmSimulationCacheStatus CachedSimulation::getModifiedSimulationStatus(size_t historyIndex)
        {
            glm::ScopedLock<glm::SpinLock> lock(_modifiedSimulationDataSpinLock);
            return _modifiedSimuStatus[historyIndex];
        }

        //-----------------------------------------------------------------------------
        const GlmMap<glm::Time, CachedSimulation::CachedFrame*>& CachedSimulation::getSrcFrames() const
        {
            return _srcFrames;
        }

        //-----------------------------------------------------------------------------
        size_t CachedSimulation::getModifiedFramesHistoryCount() const
        {
            return _modifiedFrames.size();
        }

        //-----------------------------------------------------------------------------
        const GlmMap<glm::Time, GlmMap<uint32_t, CachedSimulation::CachedFrame*>>& CachedSimulation::getModifiedFrames(size_t historyIndex) const
        {
            return _modifiedFrames[historyIndex];
        }

        //-----------------------------------------------------------------------------
        const GlmFrameData* CachedSimulation::getModifiedFrameData(size_t historyIndex, glm::Time frameIndex, bool interpolateBetweenFrames, SimulationCacheVisualFeedbackArray* visualFeedbackArray, bool lockDeletion)
        {
            return getModifiedFrameData(historyIndex, frameIndex, UINT32_MAX, interpolateBetweenFrames, visualFeedbackArray, lockDeletion);
        }

        //-----------------------------------------------------------------------------
        const GlmSimulationData* CachedSimulation::getPreviousSimulationData(size_t historyIndex)
        {
            glm::ScopedLock<glm::SpinLock> lock(_modifiedSimulationDataSpinLock);
            return getPreviousSimulationDataNoLock(historyIndex);
        }

        //-----------------------------------------------------------------------------
        const GlmSimulationData* CachedSimulation::getPreviousSimulationDataNoLock(size_t historyIndex)
        {
            if (historyIndex == 0)
                return getSrcSimulationData();
            else
                return getModifiedSimulationDataNoLock(historyIndex - 1);
        }

        //-----------------------------------------------------------------------------
        GlmSimulationCacheStatus CachedSimulation::getPreviousSimulationStatus(size_t historyIndex)
        {
            return historyIndex == 0 ? _srcSimuStatus : _modifiedSimuStatus[historyIndex - 1];
        }

        //-----------------------------------------------------------------------------
        const GlmFrameData* CachedSimulation::getPreviousHistoryFrameData(size_t historyIndex, glm::Time frameIndex, bool interpolateBetweenFrames, SimulationCacheVisualFeedbackArray* visualFeedbackArray)
        {
            if (historyIndex == 0)
                return getSrcFrameData(frameIndex, interpolateBetweenFrames);
            else
                return getModifiedFrameData(historyIndex - 1, frameIndex, interpolateBetweenFrames, visualFeedbackArray);
        }

        //-----------------------------------------------------------------------------
        GlmSimulationCacheStatus CachedSimulation::getPreviousFrameStatus(size_t historyIndex, glm::Time frameIndex, uint32_t layoutNodeID)
        {
            return historyIndex == 0 ? getSrcFrameStatus(frameIndex) : getModifiedFrameStatus(historyIndex - 1, frameIndex, layoutNodeID);
        }

        //-----------------------------------------------------------------------------
        inline bool CachedSimulation::hasHistoryRuntimeStructure() const
        {
            return !_historyRuntimeStructures.empty();
        }

        //-----------------------------------------------------------------------------
        inline SimulationCacheFactory* CachedSimulation::getFactory() const
        {
            return _factory;
        }

        //-----------------------------------------------------------------------------
        inline bool CachedSimulation::isFrameDeletionLocked() const
        {
            return _noFrameDeletionLocks > 0;
        }
    } // namespace crowdio
} // namespace glm
