//===--------------------- TaskPool.h ---------------------------*- C++ -*-===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

#ifndef utility_TaskPool_h_
#define utility_TaskPool_h_

#if defined(__cplusplus) && defined(_MSC_VER) && (_HAS_EXCEPTIONS == 0)
// Compiling MSVC libraries with _HAS_EXCEPTIONS=0, eliminates most but not all
// calls to __uncaught_exception.  Unfortunately, it does seem to eliminate
// the delcaration of __uncaught_excpeiton.  Including <eh.h> ensures that it is
// declared.  This may not be necessary after MSVC 12.
#include <eh.h>
#endif

#if defined(_MSC_VER)
// Due to another bug in MSVC 2013, including <future> will generate hundreds of
// warnings in the Concurrency Runtime.  This can be removed when we switch to
// MSVC 2015
#pragma warning(push)
#pragma warning(disable : 4062)
#endif

#include <cassert>
#include <cstdint>
#include <functional>
#include <future>
#include <list>
#include <queue>
#include <thread>
#include <vector>

// Global TaskPool class for running tasks in parallel on a set of worker thread
// created the first
// time the task pool is used. The TaskPool provide no guarantee about the order
// the task will be run
// and about what tasks will run in parallel. None of the task added to the task
// pool should block
// on something (mutex, future, condition variable) what will be set only by the
// completion of an
// other task on the task pool as they may run on the same thread sequentally.
class TaskPool {
public:
  // Add a new task to the task pool and return a std::future belonging to the
  // newly created task.
  // The caller of this function has to wait on the future for this task to
  // complete.
  template <typename F, typename... Args>
  static std::future<typename std::result_of<F(Args...)>::type>
  AddTask(F &&f, Args &&... args);

  // Run all of the specified tasks on the task pool and wait until all of them
  // are finished
  // before returning. This method is intended to be used for small number tasks
  // where listing
  // them as function arguments is acceptable. For running large number of tasks
  // you should use
  // AddTask for each task and then call wait() on each returned future.
  template <typename... T> static void RunTasks(T &&... tasks);

private:
  TaskPool() = delete;

  template <typename... T> struct RunTaskImpl;

  static void AddTaskImpl(std::function<void()> &&task_fn);
};

// Wrapper class around the global TaskPool implementation to make it possible
// to create a set of
// tasks and then wait for the tasks to be completed by the
// WaitForNextCompletedTask call. This
// class should be used when WaitForNextCompletedTask is needed because this
// class add no other
// extra functionality to the TaskPool class and it have a very minor
// performance overhead.
template <typename T> // The return type of the tasks what will be added to this
                      // task runner
                      class TaskRunner {
public:
  // Add a task to the task runner what will also add the task to the global
  // TaskPool. The
  // function doesn't return the std::future for the task because it will be
  // supplied by the
  // WaitForNextCompletedTask after the task is completed.
  template <typename F, typename... Args> void AddTask(F &&f, Args &&... args);

  // Wait for the next task in this task runner to finish and then return the
  // std::future what
  // belongs to the finished task. If there is no task in this task runner
  // (neither pending nor
  // comleted) then this function will return an invalid future. Usually this
  // function should be
  // called in a loop processing the results of the tasks until it returns an
  // invalid std::future
  // what means that all task in this task runner is completed.
  std::future<T> WaitForNextCompletedTask();

  // Convenience method to wait for all task in this TaskRunner to finish. Do
  // NOT use this class
  // just because of this method. Use TaskPool instead and wait for each
  // std::future returned by
  // AddTask in a loop.
  void WaitForAllTasks();

private:
  std::list<std::future<T>> m_ready;
  std::list<std::future<T>> m_pending;
  std::mutex m_mutex;
  std::condition_variable m_cv;
};

template <typename F, typename... Args>
std::future<typename std::result_of<F(Args...)>::type>
TaskPool::AddTask(F &&f, Args &&... args) {
  auto task_sp = std::make_shared<
      std::packaged_task<typename std::result_of<F(Args...)>::type()>>(
      std::bind(std::forward<F>(f), std::forward<Args>(args)...));

  AddTaskImpl([task_sp]() { (*task_sp)(); });

  return task_sp->get_future();
}

template <typename... T> void TaskPool::RunTasks(T &&... tasks) {
  RunTaskImpl<T...>::Run(std::forward<T>(tasks)...);
}

template <typename Head, typename... Tail>
struct TaskPool::RunTaskImpl<Head, Tail...> {
  static void Run(Head &&h, Tail &&... t) {
    auto f = AddTask(std::forward<Head>(h));
    RunTaskImpl<Tail...>::Run(std::forward<Tail>(t)...);
    f.wait();
  }
};

template <> struct TaskPool::RunTaskImpl<> {
  static void Run() {}
};

template <typename T>
template <typename F, typename... Args>
void TaskRunner<T>::AddTask(F &&f, Args &&... args) {
  std::unique_lock<std::mutex> lock(m_mutex);
  auto it = m_pending.emplace(m_pending.end());
  *it = std::move(TaskPool::AddTask(
      [this, it](F f, Args... args) {
        T &&r = f(std::forward<Args>(args)...);

        std::unique_lock<std::mutex> lock(this->m_mutex);
        this->m_ready.splice(this->m_ready.end(), this->m_pending, it);
        lock.unlock();

        this->m_cv.notify_one();
        return r;
      },
      std::forward<F>(f), std::forward<Args>(args)...));
}

template <>
template <typename F, typename... Args>
void TaskRunner<void>::AddTask(F &&f, Args &&... args) {
  std::unique_lock<std::mutex> lock(m_mutex);
  auto it = m_pending.emplace(m_pending.end());
  *it = std::move(TaskPool::AddTask(
      [this, it](F f, Args... args) {
        f(std::forward<Args>(args)...);

        std::unique_lock<std::mutex> lock(this->m_mutex);
        this->m_ready.emplace_back(std::move(*it));
        this->m_pending.erase(it);
        lock.unlock();

        this->m_cv.notify_one();
      },
      std::forward<F>(f), std::forward<Args>(args)...));
}

template <typename T> std::future<T> TaskRunner<T>::WaitForNextCompletedTask() {
  std::unique_lock<std::mutex> lock(m_mutex);
  if (m_ready.empty() && m_pending.empty())
    return std::future<T>(); // No more tasks

  if (m_ready.empty())
    m_cv.wait(lock, [this]() { return !this->m_ready.empty(); });

  std::future<T> res = std::move(m_ready.front());
  m_ready.pop_front();

  lock.unlock();
  res.wait();

  return std::move(res);
}

template <typename T> void TaskRunner<T>::WaitForAllTasks() {
  while (WaitForNextCompletedTask().valid())
    ;
}

#if defined(_MSC_VER)
#pragma warning(pop)
#endif

#endif // #ifndef utility_TaskPool_h_
