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

class ThreadPool
{
public:
    ThreadPool(int num) : running_(true)
    {
        for (int i = 0; i < num; i++)
        {
            threads_.emplace_back([this](){
                while (true)
                {
                    std::function<void()> task;
                    {
                        std::unique_lock<std::mutex> lock(mtx_);
                        // 线程阻塞直到有任务(需要执行的函数)到任务队列中
                        cv_.wait(lock, [this]{ return !tasks_.empty() || !running_; });
                        // 如果队列空了并且线程池需要被停止了,直接返回,结束循环
                        if (tasks_.empty() && !running_)
                        {
                            return;
                        }
                         // 从任务队列中取出任务
                        task = std::move(tasks_.front());
                        tasks_.pop();
                    }
                    // 这样通过 std::bind 创建的 task 对象实际上是一个可调用对象。
                    // 当你调用 task() 时，它会使用绑定的 f 和 args... 来执行原始的函数或 Lambda 表达式。
                    task();
                }
            });
        }
    }

    // 析构函数
    // 1. 设置 running_ 为false
    // 2. 通知所有线程
    // 3. 等待线程执行完成
    ~ThreadPool()
    {
        // 将running_ 设置为std::atomic<bool>，可以不用手动加锁了
        running_ = false;
        cv_.notify_all();
        for(auto& thread : threads_)
        {
            if (thread.joinable()) thread.join();
        }
    }

    // 提交任务到任务队列中
    // 1. 使用变长参数模板接收函数的所有参数
    // 2. 使用std::forward 完美转发保证参数的左值右值状态, 避免无意间的拷贝或移动，从而提高了效率。
    // 3. 通知线程有数据到队列中了
    // F&& f, Args&&... args 这个是万能引用
    template<typename Func, typename ...Args>
    void submitTask(Func&& func, Args&& ... args)
    {
        // std::function<void()> 是一个通用的可调用对象包装器
        // 它可以存储任何符合 void() 签名的可调用对象
        // 这里是将 task 定义为一个没有返回值（void）且无参数的函数。
        // std::bind 是一个用于创建“绑定”函数对象的标准库功能
        // 它能够将一个函数（或者可调用对象）与固定的参数绑定，返回一个新的可调用对象。
        std::function<void()> task = 
            std::bind(std::forward<Func>(func), std::forward<Args>(args)...);
        {
            std::unique_lock<std::mutex> lock(mtx_);
            tasks_.emplace(task);
        }
        cv_.notify_one();
    }

    // 带有返回结果的submit函数
    template<typename Func, typename ...Args>
    auto submitTaskWithResult(Func&& func, Args&& ... args) -> std::future<decltype(func(args...))>
    {
        // 推导函数返回的类型,rtype代替函数返回类型
        using rtype = decltype(func(args...));
        // 使用std::packaged_task 接收 std::bind绑定的可调用对象
        // 这里使用shard_ptr 是为了将task放入到队列中时, 不会被销毁
        // 有可能放入到队列后还没有执行, 这里已经执行完了,导致task被销毁
        std::shared_ptr<std::packaged_task<rtype()>> task_ptr = std::make_shared<std::packaged_task<rtype()>>(
            std::bind(std::forward<Func>(func), std::forward<Args>(args)...));
        auto fut = task_ptr->get_future();
        {
            std::unique_lock<std::mutex> lock(mtx_);
            // 这个任务队列里面接收到一个lambda函数,这个函数做了执行task函数
            // 保持和没有返回值的线程池保持一致, 使用lambda函数在做一层封装
            // 不用在线程中解引用再执行
            tasks_.emplace([task_ptr]{ (*task_ptr)(); });
        }
        cv_.notify_one();
        return fut;
    }

private:
    // 线程列表
    std::vector<std::thread> threads_;
    // 任务队列
    std::queue<std::function<void()>> tasks_;
    std::condition_variable cv_;
    std::mutex mtx_;

    std::atomic<bool> running_;
};
