/*
  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>
#endif

#include "workerthread.h"

namespace
{
int workerThread_ompNumMaxThreads = -1;
}

void
workerThread_set_ompNumMaxThreads(int maxThreads)
{
  if (maxThreads > 0) { workerThread_ompNumMaxThreads = maxThreads; }
}

void
WorkerThread::task(WorkerThread *self)
{
#if defined(_OPENMP)
  if (workerThread_ompNumMaxThreads > 0) omp_set_num_threads(workerThread_ompNumMaxThreads);  // Has to be called for every thread!
#endif

  // cond.wait mutex must be locked before we can wait
  std::unique_lock<std::mutex> workLock(self->workMutex);

  {
    std::lock_guard lock(self->bossMutex);  // ensure boss is waiting
    self->state = State::IDLE;              // signal to boss that setup is complete
  }
  self->bossCond.notify_one();  // wake-up signal

  while (true)
  {
    self->workCond.wait(workLock);

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

    // do blocking task
    // printf("<worker> JOB start\n");
    self->function();
    // printf("<worker> JOB end\n");
    {
      std::lock_guard lock(self->bossMutex);  // ensure boss is waiting
      self->state = State::IDLE;              // indicate that job is done
    }
    self->bossCond.notify_one();  // wake-up signal
  }
}

void
WorkerThread::doAsync(std::function<void()> func)
{
  {
    std::lock_guard lock(workMutex);  // ensure worker is waiting
    function = std::move(func);       // set job information & state
    state = State::JOB;
  }
  workCond.notify_one();  // wake-up signal
}

void
WorkerThread::wait()
{
  bossCond.wait(bossLock, [this] { return state == State::IDLE; });
}

WorkerThread::WorkerThread() : bossLock(bossMutex)
{
#if __cpp_lib_jthread >= 201911L
  thread = std::jthread(task, this);
#else
  thread = std::thread(task, this);
#endif
  wait();
}

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

#ifdef TEST_WORKERTHREAD
// g++ -g -Wall -O2 -DTEST_WORKERTHREAD workerthread.cc
#include <iostream>

void
func(int &intArg)
{
  intArg = -1;
  std::cout << "run myfunc\n";
}

void
worker1(void)
{
  WorkerThread workerThread;

  int ivalue = 0;
  std::function<void()> my_task = [&ivalue]() { func(ivalue); };

  workerThread.doAsync(my_task);
  workerThread.wait();

  std::cout << "worker1: " << ivalue << "\n";
}

void
worker2(void)
{
  bool useWorkerThread = true;
  auto workerThread = useWorkerThread ? std::make_unique<WorkerThread>() : nullptr;

  int ivalue = 0;
  std::function<void()> my_task = [&ivalue]() { func(ivalue); };

  useWorkerThread ? workerThread->doAsync(my_task) : my_task();
  if (useWorkerThread) workerThread->wait();

  std::cout << "worker2: " << ivalue << "\n";
}

int
main(int argc, char **argv)
{
  worker1();
  worker2();
  return 0;
}
#endif
