#include "TaskDispatcher.h"

#include "QtConcurrent"

typedef std::unique_lock<std::mutex> ULock;

namespace nemo_interface {

TaskDispatcher::TaskDispatcher() : _running(false) {}

TaskDispatcher::~TaskDispatcher() { stop(); }

std::future<QVariant> TaskDispatcher::dispatch(std::unique_ptr<Task> c) {
  ULock lk1(this->_mutex);

  this->_taskQueue.push_back(std::move(c));
  std::promise<QVariant> promise;
  auto future = promise.get_future();
  this->_promiseQueue.push_back(std::move(promise));

  if (!this->_running) {
    lk1.unlock();
    this->start();
  }

  return future;
}

std::future<QVariant> TaskDispatcher::dispatchNext(std::unique_ptr<Task> c) {
  ULock lk1(this->_mutex);

  this->_taskQueue.push_front(std::move(c));
  std::promise<QVariant> promise;
  auto future = promise.get_future();
  this->_promiseQueue.push_front(std::move(promise));

  if (!this->_running) {
    lk1.unlock();
    this->start();
  }

  return future;
}

void TaskDispatcher::clear() {
  ULock lk(this->_mutex);
  this->_taskQueue.clear();
  this->_promiseQueue.clear();
}

void TaskDispatcher::start() {
  ULock lk1(this->_mutex);
  if (!_running) {
    this->_running = true;
    auto p = std::make_shared<std::promise<void>>();
    _threadFuture = p->get_future();
    QtConcurrent::run([this, p = std::move(p)]() mutable {
      return this->run(std::move(*p));
    });
    lk1.unlock();
  }
}

void TaskDispatcher::stop() {
  ULock lk1(this->_mutex);
  if (_running) {
    this->_running = false;
    _threadFuture.wait();
    lk1.unlock();
  }
}

void TaskDispatcher::reset() {
  stop();
  clear();
}

bool TaskDispatcher::isInterruptionRequested() {
  ULock lk1(this->_mutex);
  return !_running;
}

bool TaskDispatcher::isRunning() {
  ULock lk1(this->_mutex);
  return _running;
}

std::size_t TaskDispatcher::pendingTasks() {
  ULock lk1(this->_mutex);
  return this->_taskQueue.size() + (_running ? 1 : 0);
}

bool TaskDispatcher::idle() { return this->pendingTasks() == 0; }

void TaskDispatcher::run(std::promise<void> p) {
  while (true) {
    ULock lk1(this->_mutex);

    if (this->_taskQueue.size() > 0 && this->_running) {
      Q_ASSERT(this->_taskQueue.size() == this->_promiseQueue.size());
      // pop task and promise
      auto pTask = std::move(this->_taskQueue.front());
      auto promise = std::move(this->_promiseQueue.front());
      this->_taskQueue.pop_front();
      this->_promiseQueue.pop_front();
      lk1.unlock();

      // exec task
      QVariant var = pTask->exec();
      bool r = var.toBool();
      Q_ASSERT(r == true || r == false);
      promise.set_value(var);
    } else {
      this->_running = false;
      lk1.unlock();
      break;
    }
  }
  p.set_value();
}

} // namespace nemo_interface
