/*
  This file is part of CDO. CDO is a collection of Operators to manipulate and analyse Climate model Data.

  Author: Uwe Schulzweida

*/

#if defined(_OPENMP)
#include <omp.h>
#include "cdo_options.h"
#endif

#include "cdo_task.h"

namespace cdo
{

void
Task::task(cdo::Task *taskInfo)
{
#if defined(_OPENMP)
  omp_set_num_threads(Threading::ompNumThreads);  // Has to be called for every thread!
#endif

  // cond.wait mutex must be locked before we can wait
  std::unique_lock<std::mutex> workLock(taskInfo->workMutex);
  // ensure boss is waiting
  taskInfo->bossMutex.lock();
  // signal to boss that setup is complete
  taskInfo->state = State::IDLE;
  // wake-up signal
  taskInfo->bossCond.notify_one();
  taskInfo->bossMutex.unlock();

  while (1)
    {
      taskInfo->workCond.wait(workLock);

      if (State::DIE == taskInfo->state) break;      // kill thread
      if (State::IDLE == taskInfo->state) continue;  // accidental wake-up

      // do blocking task
      // printf("<worker> JOB start\n");
      if (taskInfo->useFunction)
        taskInfo->function();
      else
        taskInfo->result = taskInfo->routine(taskInfo->arg);
      // printf("<worker> JOB end\n");
      // ensure boss is waiting
      taskInfo->bossMutex.lock();
      // indicate that job is done
      taskInfo->state = State::IDLE;
      // wake-up signal
      taskInfo->bossCond.notify_one();
      taskInfo->bossMutex.unlock();
    }
}

void
Task::doAsync(const std::function<void()> &_function)
{
  // ensure worker is waiting
  std::lock_guard<std::mutex> _(workMutex);
  // set job information & state
  this->function = _function;
  this->useFunction = true;
  this->state = State::JOB;
  // wake-up signal
  workCond.notify_one();
}

void
Task::start(void *(*taskRoutine)(void *), void *taskArg)
{
  // ensure worker is waiting
  std::lock_guard<std::mutex> _(workMutex);
  // set job information & state
  this->routine = taskRoutine;
  this->arg = taskArg;
  this->state = State::JOB;
  // wake-up signal
  workCond.notify_one();
}

void *
Task::wait()
{
  while (1)
    {
      if (State::IDLE == this->state) break;
      bossCond.wait(bossMutex);
    }

  return this->result;
}

Task::Task()
{
  bossMutex.lock();
  this->thread = std::thread(this->task, this);
  this->wait();
}

Task::~Task()
{
  // ensure the worker is waiting
  workMutex.lock();
  // printf("Task::delete: send DIE to <worker>\n");
  this->state = State::DIE;
  // wake-up signal
  workCond.notify_one();
  workMutex.unlock();
  // wait for thread to exit
  this->thread.join();
  bossMutex.unlock();
}

}  // namespace cdo

#ifdef TEST_CDO_TASK
// g++ -g -Wall -O2 -DTEST_CDO_TASK cdo_task.cc

void *
myfunc(void *arg)
{
  printf("run myfunc\n");
  return nullptr;
}

void
mytask1(void)
{
  cdo::Task task;

  void *myarg = nullptr;
  void *myresult;

  task.start(myfunc, myarg);
  myresult = task.wait();
}

void
mytask2(void)
{
  bool useTask = true;
  auto task = useTask ? std::make_unique<cdo::Task>() : nullptr;

  void *myarg = nullptr;
  void *myresult;

  if (useTask)
    task->start(myfunc, myarg);
  else
    myfunc(myarg);

  if (useTask) myresult = task->wait();
}

int
main(int argc, char **argv)
{
  mytask1();
  mytask2();

  return 0;
}
#endif
