#pragma once
#include <thread>
#include <atomic>
#include <memory>
#include <queue>
#include <mutex>
#include <functional>
#include <stdexcept>
#include <iostream>

#include "DllDefines.h"

/*
* @link : https://www.cppstories.com/2019/12/threading-loopers-cpp17/
*/

using Runnable = std::function<void()>;

class UTILITYDLL CRunnerTask
{
    // dispatch class
public:
    using Runnable = std::function<void()>;
    class CDispatcher
    {
        friend class CRunnerTask; // Allow the looper to access the private constructor.

    public:
        // Yet to be defined method, which will post the runnable 
        // into the looper-queue.
        bool post(CRunnerTask::Runnable&& aRunnable)
        {
            return mAssignedRunner.post(std::move(aRunnable));
        }

    private: // construction, since we want the looper to expose it's dispatcher exclusively!
        CDispatcher(CRunnerTask& aLooper)
            : mAssignedRunner(aLooper)
        {}

    private:
        // Store a reference to the attached looper in order to 
        // emplace tasks into the queue.
        CRunnerTask& mAssignedRunner;
    };

    // ctor
    CRunnerTask();
    // Copy denied, Move to be implemented

    ~CRunnerTask();

    // To be called, once the runner should start looping.
    bool run();

    void stop();

    bool running() const;

    Runnable next();

    std::shared_ptr<CDispatcher> getDispatcher()
    {
        return mDispatcher;
    }


private:
    // Conditionally-infinite loop doing sth. iteratively
    void runFunc();

    // Shared implementation of exiting the loop-function and joining 
// to the main thread.
    void abortAndJoin();

    bool post(Runnable&& aRunnable);

    // attributes
    std::thread mThread;

    std::atomic_bool mRunning;
    std::atomic_bool mAbortRequested;

    //
    std::recursive_mutex mRunnablesMutex;
    std::queue<Runnable> mRunnables;

    std::shared_ptr<CDispatcher> mDispatcher;
};

