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

class ThreadPool {
public:
    ThreadPool(size_t numThreads);
    ThreadPool();

    // Submit a task to the pool. Returns a unique task ID.
    int submit(std::function<void()> task);

    // Wait for a task with the given ID to complete.
    void wait(int taskId);

private:
    // Worker threads
    std::vector<std::thread> workers;

    // Task queue: pairs of task IDs and tasks
    std::queue<std::pair<int, std::packaged_task<void()>>> tasks;

    // Synchronization primitives
    std::mutex queueMutex;
    std::condition_variable condition;
    bool stop;

    // Task ID management
    int nextTaskId;
    std::mutex idMutex;

    // Map to keep track of task futures associated with task IDs
    std::unordered_map<int, std::shared_future<void>> taskFutures;
    std::mutex taskFuturesMutex;

    // Function executed by worker threads
    void workerThread();
};

// Constructor: starts the specified number of worker threads
ThreadPool::ThreadPool(size_t numThreads) : stop(false), nextTaskId(0) {
    for (size_t i = 0; i < numThreads; ++i) {
        workers.emplace_back(&ThreadPool::workerThread, this);
    }
}

// Destructor: joins all threads and cleans up
ThreadPool::ThreadPool() {
    // Notify all threads to stop
    {
        std::unique_lock<std::mutex> lock(queueMutex);
        stop = true;
    }
    condition.notify_all();

    // Join all threads
    for (std::thread &worker : workers) {
        worker.join();
    }
}

// Submits a task to the pool and returns a unique task ID
int ThreadPool::submit(std::function<void()> task) {
    // Wrap the task in a packaged_task
    std::packaged_task<void()> packagedTask(task);
    // Get a shared future to the task's result
    std::shared_future<void> future = packagedTask.get_future().share();

    int taskId;
    {
        // Generate a unique task ID
        std::lock_guard<std::mutex> lock(idMutex);
        taskId = nextTaskId++;
    }

    {
        // Enqueue the task
        std::lock_guard<std::mutex> lock(queueMutex);
        tasks.emplace(taskId, std::move(packagedTask));
    }

    // Store the future associated with the task ID
    {
        std::lock_guard<std::mutex> lock(taskFuturesMutex);
        taskFutures[taskId] = future;
    }

    // Notify one worker thread
    condition.notify_one();

    return taskId;
}

// Waits for the task with the specified ID to complete
void ThreadPool::wait(int taskId) {
    std::shared_future<void> future;

    {
        std::lock_guard<std::mutex> lock(taskFuturesMutex);
        auto it = taskFutures.find(taskId);
        if (it != taskFutures.end()) {
            future = it->second;
        } else {
            std::cerr << "Error: Task ID " << taskId << " not found." << std::endl;
            return;
        }
    }

    // Wait for the task to complete
    future.wait();

    // Optionally remove the task from the map
    {
        std::lock_guard<std::mutex> lock(taskFuturesMutex);
        taskFutures.erase(taskId);
    }
}

// The function that each worker thread runs
void ThreadPool::workerThread() {
    while (true) {
        std::pair<int, std::packaged_task<void()>> taskPair;
        {
            std::unique_lock<std::mutex> lock(queueMutex);
            // Wait until there is a task to process or the pool is stopping
            condition.wait(lock, [this] { return stop || !tasks.empty(); });

            // If stopping and no tasks are left, exit the thread
            if (stop && tasks.empty())
                return;

            // Get the next task
            taskPair = std::move(tasks.front());
            tasks.pop();
        }

        // Execute the task outside the lock
        int taskId = taskPair.first;
        std::packaged_task<void()> taskFunc = std::move(taskPair.second);
        taskFunc();
    }
}

// Example usage:
int main() {
    // Create a thread pool with 4 worker threads
    ThreadPool pool(4);

    // Submit a task that sleeps for 2 seconds
    int taskId1 = pool.submit([] {
        std::cout << "Task 1 is running.\n";
        std::this_thread::sleep_for(std::chrono::seconds(2));
        std::cout << "Task 1 is done.\n";
    });

    // Submit another task that sleeps for 1 second
    int taskId2 = pool.submit([] {
        std::cout << "Task 2 is running.\n";
        std::this_thread::sleep_for(std::chrono::seconds(1));
        std::cout << "Task 2 is done.\n";
    });

    // Wait for task 1 to finish
    pool.wait(taskId1);
    std::cout << "Confirmed: Task 1 has finished.\n";

    // Wait for task 2 to finish
    pool.wait(taskId2);
    std::cout << "Confirmed: Task 2 has finished.\n";

    return 0;
}

#include <functional>
#include <mutex>
#include <deque>
#include <unordered_set>
#include <thread>
#include <condition_variable>

struct TP_ID
{
 explicit TP_ID(const uint_fast8_t& tnum = std::thread::hardware_concurrency()) noexcept
 {
  threads_.reserve(tnum);
  thread_id_set_->reserve(tnum);
  for (uint_fast8_t i = 0; i < tnum; i++)
  {
   threads_.emplace_back([this]
    {
     std::function<void()> function;
     thread_id_type thread_id;
     while (true)
     {
      {
       std::unique_lock lock(m_);
       condition_variable_.wait(lock, [this] { return stop_ || !tasks_.empty(); });
       if (stop_ && tasks_.empty()) return;
       thread_id = tasks_.front().first;
       function = std::move(tasks_.front().second);
       tasks_.pop_front();
      }
      function();
      thread_id_set_->erase(thread_id);
      condition_variable2_.notify_one();
     }
    });
  }
 }
 ~TP_ID() noexcept
 {
  stop_ = true;
  condition_variable_.notify_all();
  if (thread_id_set_ != nullptr)
   for (const thread_id_type thread_id : *thread_id_set_)
    wait(thread_id);
  for (auto& thread : threads_)
   thread.join();
 }
 template<typename type>
 void enqueue(type&& function, const int_fast8_t thread_id) noexcept
 {
  {
   std::unique_lock lock(m_);
   condition_variable2_.wait(lock, [this, thread_id] { return !thread_id_set_->contains(thread_id); });
   tasks_.emplace_back(std::make_pair(thread_id, std::forward<type>(function)));
  }
  thread_id_set_->emplace(thread_id);
  condition_variable_.notify_one();
 }
 template<typename... thread_id_list, typename = int_fast8_t>
 void wait(thread_id_list ...thread_ids) noexcept
 {
  std::unique_lock lock(m2_);
  condition_variable2_.wait(lock, [this, thread_ids...] { return (!thread_id_set_->contains(thread_ids) && ...); });
 }
private:
 typedef int_fast8_t thread_id_type;
 std::deque<std::pair<thread_id_type, std::function<void()>>> tasks_;
 std::vector<std::thread> threads_;
 std::condition_variable condition_variable2_;
 bool stop_ = false;
 std::shared_ptr<std::unordered_set<thread_id_type>> thread_id_set_ = std::make_shared<std::unordered_set<thread_id_type>>();
 std::mutex m_;
 std::mutex m2_;
 std::condition_variable condition_variable_;
};
compare these codes to eachother