/** Suport for parallel task processing on multiple CPUs.
 * (c) 2013 - 2020 Jaroslav Fojtik.
 * This code could be distributed under LGPL licency. */
#include "jobs.h"
#include <stdio.h>


volatile JOB_COUNT_TYPE JobBase::JobCount = 0;


//////////////// TRUE MULTITHREADING ///////////////////
#ifdef _REENTRANT

#include <stdlib.h>
#include <string.h>

#ifdef _WIN32
 #include <direct.h>
 #include <process.h>
 #include <io.h>
#endif

#include "csext.h"


#define POOLING_TIMEOUTms	5000


JobBase::JobBase()
{
  InterlockedIncrement(&JobCount);
}

JobBase::~JobBase()
{
  InterlockedDecrement(&JobCount);
}


CRITICAL_SECTION jobs_cs;

int WorkersRunning = 0;		///< Amount of asynchronnous workers running.
JobBase **JOB_CACHE = NULL;

#ifdef _WIN32
 HANDLE JobAlertEvent;
 #define ALERT_EVENT(XXX_EVENT) SetEvent(XXX_EVENT)
 #define ALERT_ALL(XXX_EVENT)   SetEvent(XXX_EVENT)
#else
 pthread_cond_t JobAlertEvent;
 pthread_mutex_t mtx_JobAlertEvent;
 #define ALERT_EVENT(XXX_EVENT) pthread_cond_signal(&XXX_EVENT)
 #define ALERT_ALL(XXX_EVENT)   pthread_cond_broadcast(&XXX_EVENT);
#endif


/** The thread worker, that is executed multiple times. */
#ifdef _WIN32
unsigned int __stdcall JobThread(void * param)
#else
void *JobThread(void * param)
#endif
{
int i;
JobBase *JOB = NULL;

  while(WorkersRunning>0)
  {
    //printf(">>Wake %u<<", GetCurrentThreadId());
    EnterCriticalSection(&jobs_cs);
    for(i=0; i<WorkersRunning; i++)
      {
      if(JOB_CACHE[i]!=NULL)
          {
          JOB = JOB_CACHE[i];
          JOB_CACHE[i] = NULL;
	  break;
	  }
      }
    LeaveCriticalSection(&jobs_cs);

    if(JOB)
      {
      JOB->Run();
      delete JOB;
      JOB = NULL;
      continue;
      }    

#ifdef _WIN32
    WaitForSingleObject(JobAlertEvent, POOLING_TIMEOUTms);
#else
    struct timespec ts;
    clock_gettime(CLOCK_REALTIME, &ts);
    ts.tv_sec += POOLING_TIMEOUTms / 1000;  // ten seconds
    ts.tv_nsec += 1000000*(POOLING_TIMEOUTms % 1000);
    if(ts.tv_nsec > 1000000000)
    {
      ts.tv_nsec -= 1000000000;
      ts.tv_sec++;
    }
    pthread_cond_timedwait(&JobAlertEvent, &mtx_JobAlertEvent, &ts);
#endif
  }

  ALERT_ALL(JobAlertEvent);	// Re-Activate event to speedup app finish.
  return 0;
}


int InitJobs(int ThrCount)
{
  if(ThrCount<0)
    {
    const char *str = getenv("OMP_NUM_THREADS");
    if(str!=NULL && str[0]!=0)
      ThrCount = atoi(str);
    if(ThrCount<=0)
      {
	// @TODO: parse /proc/cpuinfo

      str = getenv("NUMBER_OF_PROCESSORS");
      if(str!=NULL && str[0]!=0)
        ThrCount = atoi(str);
      if(ThrCount<=0) ThrCount=2;
      }
    }

  InitializeCriticalSection(&jobs_cs);
  JOB_CACHE = (JobBase **)calloc(ThrCount,sizeof(JobBase*));

#ifdef _WIN32
  if(!(JobAlertEvent = CreateEvent(NULL, FALSE, FALSE, NULL)))
  {
    printf("Cannot create alert event.");
    return -2;
  }
#else
  pthread_cond_init(&JobAlertEvent, NULL);
  pthread_mutex_init(&mtx_JobAlertEvent, NULL);
#endif

  for(WorkersRunning=0; WorkersRunning<ThrCount; WorkersRunning++)
  {
#ifdef _WIN32
     HANDLE thread;
     thread = (HANDLE)_beginthreadex(NULL,0,&JobThread,0,0,NULL);
#else
     pthread_t WriteThread;
     pthread_create(&WriteThread, NULL, JobThread, NULL);
#endif
  }

  return 1;
}


/** Add a new job to the job cache.
 * @param[in]	js	Job object - it must NOT be deleted from caller.
 *                      Job will be erased asynchronously from worker thread. */
int RunJob(JobBase *js)
{
int i;
  if(js==NULL) return -1;

  if(WorkersRunning > 0)
    {
    EnterCriticalSection(&jobs_cs);
    for(i=0; i<WorkersRunning; i++)
      {
      if(JOB_CACHE[i]==NULL)
        {
        JOB_CACHE[i] = js;
        LeaveCriticalSection(&jobs_cs);
        ALERT_EVENT(JobAlertEvent);	// Wake one thread.
        return 1;
        }
      }    
    LeaveCriticalSection(&jobs_cs);
    }

  js->Run();
  delete js;
  return 0;
}


/** Wait for all jobs to be finished.
 * This function could be called several times.
 * It calls unprocessed jobs from current thread. Already processed
 * jobs cannot be touched. */
void FinishJobs(int WaitMs)
{
  ALERT_ALL(JobAlertEvent);	// Wake up any active worker.

  EnterCriticalSection(&jobs_cs);
  for(int i=0; i<WorkersRunning; i++)
    {
    if(JOB_CACHE[i]!=NULL)
      {
      JobBase *JOB = JOB_CACHE[i];
      JOB_CACHE[i] = NULL;
      LeaveCriticalSection(&jobs_cs);
      JOB->Run();	// Do the job from the parent thread.
      delete JOB;
      EnterCriticalSection(&jobs_cs);      
      }
    }
  LeaveCriticalSection(&jobs_cs);
  
  while(JobBase::JobCount>0 && WaitMs>0)
  {
    Sleep(50);		// e.g. Wait 50ms, loops this twice.
    WaitMs -= 50;
  }
}


/** Destroy all resources used by jobs library. */
void EndJobs(void)
{
  FinishJobs();
  WorkersRunning = 0;		// Worker exit flag.
  ALERT_ALL(JobAlertEvent);	// Alert all workers.
}


#else
//////////////// FAKE MULTITHREADING ///////////////////

JobBase::JobBase()
{
  JobCount++;
}

JobBase::~JobBase()
{
  JobCount--;
}

unsigned int __stdcall JobThread(void * param)
{
  return 0;
}

int InitJobs(int ThrCount)
{
  return 1;
}

int RunJob(JobBase *js)
{
  if(js==NULL) return -1;
  js->Run();
  delete js;
  return 0;
}

void FinishJobs(int WaitMs)
{
}

void EndJobs(void)
{
}
#endif
