/**
 * @file   threadpool.h
 * @author Nat Goodspeed
 * @date   2021-10-21
 * @brief  ThreadPool configures a WorkQueue along with a pool of threads to
 *         service it.
 *
 * $LicenseInfo:firstyear=2021&license=viewerlgpl$
 * Copyright (c) 2021, Linden Research, Inc.
 * $/LicenseInfo$
 */

#if ! defined(LL_THREADPOOL_H)
#define LL_THREADPOOL_H

#include "threadpool_fwd.h"
#include "workqueue.h"
#include <memory>                   // std::unique_ptr
#include <string>
#include <thread>
#include <utility>                  // std::pair
#include <vector>

namespace LL
{

    class ThreadPoolBase: public LLInstanceTracker<ThreadPoolBase, std::string>
    {
    private:
        using super = LLInstanceTracker<ThreadPoolBase, std::string>;

    public:
        /**
         * Pass ThreadPoolBase a string name. This can be used to look up the
         * relevant WorkQueue.
         *
         * The number of threads you pass sets the compile-time default. But
         * if the user has overridden the LLSD map in the "ThreadPoolSizes"
         * setting with a key matching this ThreadPool name, that setting
         * overrides this parameter.
         */
        ThreadPoolBase(const std::string& name, size_t threads,
                       WorkQueueBase* queue, bool auto_shutdown = true);
        virtual ~ThreadPoolBase();

        /**
         * Launch the ThreadPool. Until this call, a constructed ThreadPool
         * launches no threads. That permits coders to derive from ThreadPool,
         * or store it as a member of some other class, but refrain from
         * launching it until all other construction is complete.
         */
        void start();

        /**
         * ThreadPool listens for application shutdown messages on the "LLApp"
         * LLEventPump. Call close() to shut down this ThreadPool early.
         */
        virtual void close();

        std::string getName() const { return mName; }
        size_t getWidth() const { return mThreads.size(); }

        /**
         * Override run() if you need special processing. The default run()
         * implementation simply calls WorkQueue::runUntilClose().
         */
        virtual void run();

        /**
         * getConfiguredWidth() returns the setting, if any, for the specified
         * ThreadPool name. Returns dft if the "ThreadPoolSizes" map does not
         * contain the specified name.
         */
        static
        size_t getConfiguredWidth(const std::string& name, size_t dft=0);

        /**
         * This getWidth() returns the width of the instantiated ThreadPool
         * with the specified name, if any. If no instance exists, returns its
         * getConfiguredWidth() if any. If there's no instance and no relevant
         * override, return dft. Presumably dft should match the threads
         * parameter passed to the ThreadPool constructor call that will
         * eventually instantiate the ThreadPool with that name.
         */
        static
        size_t getWidth(const std::string& name, size_t dft);

    protected:
        std::unique_ptr<WorkQueueBase> mQueue;
        std::vector<std::pair<std::string, std::thread>> mThreads;
        bool mAutomaticShutdown;

    private:
        void run(const std::string& name);

        std::string mName;
        size_t mThreadCount;
    };

    /**
     * Specialize with WorkQueue or, for timestamped tasks, WorkSchedule
     */
    template <class QUEUE>
    struct ThreadPoolUsing: public ThreadPoolBase
    {
        using queue_t = QUEUE;

        /**
         * Pass ThreadPoolUsing a string name. This can be used to look up the
         * relevant WorkQueue.
         *
         * The number of threads you pass sets the compile-time default. But
         * if the user has overridden the LLSD map in the "ThreadPoolSizes"
         * setting with a key matching this ThreadPool name, that setting
         * overrides this parameter.
         *
         * Pass an explicit capacity to limit the size of the queue.
         * Constraining the queue can cause a submitter to block. Do not
         * constrain any ThreadPool accepting work from the main thread.
         */
        ThreadPoolUsing(const std::string& name,
                        size_t threads=1,
                        size_t capacity=1024*1024,
                        bool auto_shutdown = true):
            ThreadPoolBase(name, threads, new queue_t(name, capacity), auto_shutdown)
        {}
        ~ThreadPoolUsing() override {}

        /**
         * obtain a non-const reference to the specific WorkQueue subclass to
         * post work to it
         */
        queue_t& getQueue() { return static_cast<queue_t&>(*mQueue); }
    };

    /// ThreadPool is shorthand for using the simpler WorkQueue
    using ThreadPool = ThreadPoolUsing<WorkQueue>;

} // namespace LL

#endif /* ! defined(LL_THREADPOOL_H) */