#pragma once

#include <vector>
#include <thread>
#include <queue>
#include <mutex>
#include <future>

class ThreadPool {
private:
    std::condition_variable m_cond_var;
    bool m_destruct_pool;
    std::mutex m_mutex;
    std::vector<std::thread> m_threads;
    std::queue<std::function<void()>> m_work_queue;

public:
    ThreadPool(unsigned int initial_pool_size);
    ~ThreadPool();
    template <typename F, typename...Args>
    inline auto SubmitWork(F &&f, Args&&... args) -> std::future<decltype(f(args...))> {
        auto func = std::bind(std::forward<F>(f), std::forward<Args>(args)...);
        auto task = std::make_shared<std::packaged_task<decltype(f(args...))()>>(std::forward<decltype(func)>(func));
        {
            std::lock_guard<std::mutex> lock(m_mutex);
            // wrap packed task into a void return function type so that it can be stored in queue
            m_work_queue.push([task]() { (*task)(); });
        }

        m_cond_var.notify_one(); // unblocks one of the waiting threads
        return task->get_future();
    }
};
