/* This file is part of Strawberry.
   Copyright 2012, David Sansome <me@davidsansome.com>

   Strawberry is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   Strawberry is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with Strawberry.  If not, see <http://www.gnu.org/licenses/>.
*/

#ifndef CONCURRENTRUN_H
#define CONCURRENTRUN_H

#include <functional>

#include <QFuture>
#include <QRunnable>
#include <QThreadPool>

/*
  The aim of ThreadFunctor classes and ConcurrentRun::Run() functions is to
  complete QtConcurrentRun, which lack support for using a particular
  QThreadPool, as it always uses QThreadPool::globalInstance().

  This is problematic when we do not want to share the same thread pool over
  all the application, but want to keep the convenient QtConcurrent::run()
  functor syntax.
  With ConcurrentRun::Run(), time critical changes can be performed in their
  own pool, which is not empty by other actions (as it happens when using
  QtConcurrentRun::run()).

  ThreadFunctor classes are used to store a functor and its arguments, and
  Run() functions are used for convenience: to directly create a new
  ThreadFunctor object and start it.
*/

/*
  Base abstract classes ThreadFunctorBase and ThreadFunctor (for void and
  non-void result):
*/
template<typename ReturnType>
class ThreadFunctorBase : public QFutureInterface<ReturnType>, public QRunnable {
 public:
  ThreadFunctorBase() {}

  QFuture<ReturnType> Start(QThreadPool* thread_pool) {
    this->setRunnable(this);
    this->reportStarted();
    Q_ASSERT(thread_pool);
    QFuture<ReturnType> future = this->future();
    thread_pool->start(this, 0 /* priority: currently we do not support changing the priority. Might be added later if needed */);
    return future;
  }

  void run() override = 0;
};

template <typename ReturnType, typename... Args>
class ThreadFunctor : public ThreadFunctorBase<ReturnType> {
 public:
  explicit ThreadFunctor(std::function<ReturnType (Args...)> function, Args... args)
      : function_(std::bind(function, args...)) {
  }

  void run() override {
    this->reportResult(function_());
    this->reportFinished();
  }

 private:
  std::function<ReturnType()> function_;
};

// Partial specialisation for void return type.
template <typename... Args>
class ThreadFunctor <void, Args...> : public ThreadFunctorBase<void> {
 public:
  explicit ThreadFunctor(std::function<void (Args...)> function, Args... args)
      : function_(std::bind(function, args...)) {
  }

  void run() override {
    function_();
    this->reportFinished();
  }

 private:
  std::function<void()> function_;
};


/*
  Run functions
*/
namespace ConcurrentRun {

  // Empty argument form.
  template <typename ReturnType>
  QFuture<ReturnType> Run(QThreadPool* threadpool, std::function<ReturnType ()> function) {
  return (new ThreadFunctor<ReturnType>(function))->Start(threadpool);
  }

  // Function object with arguments form.
  template <typename ReturnType, typename... Args>
  QFuture<ReturnType> Run(QThreadPool* threadpool, std::function<ReturnType (Args...)> function, const Args&... args) {
    return (new ThreadFunctor<ReturnType, Args...>(function, args...))->Start(threadpool);
  }

  // Support passing C function pointers instead of function objects.
  template <typename ReturnType, typename... Args>
  QFuture<ReturnType> Run(QThreadPool* threadpool, ReturnType (*function) (Args...), const Args&... args) {
    return Run(threadpool, std::function<ReturnType (Args...)>(function), args...);
  }
}

#endif  // CONCURRENTRUN_H
