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

#pragma once

#include "glmCoreAPI.h"
#include "glmTypes.h"
#include "glmSynchroBarrier.h"
#include "glmProcessingBatch.h"
#include "glmAtomic.h"
#include "glmThreading.h"
#include "glmMutex.h"
#include "glmArray.h"
#include "glmString.h"

namespace glm
{
    class MultiThreadProcessor;

    /**
	* \brief The processing thread class, manages updates of contexts, actions and entities updates
	*
	* \author Golaem <dev@golaem.com>
	*/
    class WorkerThread
    {
    public:
        /**
		* \brief the launched thread "main"
		*/
        static void startRoutine(void* threadArgument);
    };

    // index is current index in max array, up to 2^20 = 1 million
    // counter is used to compare reaffectation
    // unused : the entity is not created.
    // -----------------------------------------------------------------------------
    class GLM_CORE_API MultiThreadProcessor
    {
    public:
        friend class WorkerThread;

        GLM_CLASS_STAT_ID( glm::MemoryStatIds::Simulation);

        MultiThreadProcessor(const char* workerThreadsName = "GlmMultiThreadProcessor");
        virtual ~MultiThreadProcessor();

        virtual void init(const int threadCountRequested = glm::getSupportedConcurrentThreadCount()); // do not init simulations, setup threading engine, reset log trace counts
        virtual void finish();                                                                        // finish all simulations still in it, close threading engine, delete still registered simulations

        // threading facility for instant method threading
        void processThreadable(ProcessingBatch::ThreadUpdateMethod* process, void* userData, int64_t inclusiveMaxIndex, int64_t batchSize, Time dt = 0);

        /**
		* \brief get the thread count to use, VALID ONLY AFTER INIT
		* by default, initialized with all logical CPUs including main thread
		* \return threads count n use (1 for monothread)
		*/
        const int getThreadCount() const;

        /**
		* \brief get the configured name for threads
		*/
        inline const char* getThreadsName() const { return _workerThreadsName.c_str(); }

        /**
		* \brief wait for all threads readiness
		*/
        void waitForThreadedUpdateReady();

        /**
		* \brief launch the currently registered thread method in multithread
		*/
        void launchThreadedUpdate();

        /**
		* \brief wait for all threads completion on currently registered thread method
		*/
        void waitForThreadedUpdateEnd();

        /**
		* \brief setup the requested number of additional threads if any
		*/
        void setupWorkerThreads(const int threadCountRequested, const size_t threadStackSize);

        /**
		* \brief close the additional worker threads if any
		*/
        void closeWorkerThreads();

        /**
		* \brief get number of logical CPUs detected on this host
		*/
        const int getLogicalCPUsCount()
        {
            return _logicalCPUsCount;
        }

        /**
        * \brief set the thread count to use
        * by default, initialized with all logical CPUs including main thread
        * set to 1 for monothread
        * \param threadCount threads count to use
        */
        void updateThreadCount(const int threadCountRequested);

    protected:

        void consumeBatchQueueTask();
        uint32_t fetchAndIncrementIndexOfBatchToProcess();

        uint32_t getThreadDoneCount() const;
        void incrementThreadDoneCount();
        void resetThreadDoneCount();

        atomic32_t _threadsDoneCount; // is incremented / decremented by threads

        GlmString _workerThreadsName = "GlmMultiThreadProcessor";
        int _workerThreadCount;
        threaddef_t* _workerThreads; // threads used to process entities updates.

        atomic32_t _stopThreads; // threads stop signal

        int _logicalCPUsCount; //!< number of logical CPUs available on this host

        SynchroBarrier _launchUpdateProcessBarrier; // synchro barrier before launching multithreaded processing : waits for all threads to be ready to process

        volatile bool _someThreadsDied; //!< some thread may have died waiting (timeout) for example : re set-up all threads. set to true in multithread env, but set back to false only when in monothread
        bool _threadsLaunchFailed;      //!< says not to wait for threads if their launch failed
        bool _threadedProcessingInProgress;

        glm::Array<ProcessingBatch> _batchQueueForMultithread; // addition and clear are done in monothread context, only reading is multithread, glm::Array is safe

        atomic32_t _currentBatchToProcess; //!< is incremented by threads

		SpinLock _mySpinLock;

        int _threadsInUse;
    };

    inline uint32_t MultiThreadProcessor::fetchAndIncrementIndexOfBatchToProcess()
    {
        return static_cast<uint32_t>(atomicFetchAddSequentiallyConsistent(_currentBatchToProcess, 1));
    }
} // namespace glm
