/** Suport for parallel task processing on multiple CPUs.
 * (c) 2013 - 2024 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


#ifdef _WIN32
 #define THR_RET_TYPE	UINT WINAPI
#else
 #define THR_RET_TYPE	void *
#endif


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


///< Job dtor should cleanup all resources used by job child.
///< This one only decrements amount of queued jobs.
JobBase::~JobBase()
{
  InterlockedDecrement(&JobCount);
}


CRITICAL_SECTION jobs_cs;

volatile int WorkersRunning = 0;	///< Amount of asynchronnous workers running.
JobBase **JOB_QUEUE = 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. */
THR_RET_TYPE JobThread(void * param)
{
int i;
JobBase *JOB = NULL;

  while(WorkersRunning>0)
  {
    //printf(">>Wake %u<<", GetCurrentThreadId());

	// Lease a job
    EnterCriticalSection(&jobs_cs);
    for(i=0; i<WorkersRunning; i++)
      {
      if(JOB_QUEUE[i]!=NULL)
          {
          JOB = JOB_QUEUE[i];
          JOB_QUEUE[i] = NULL;
	  break;
	  }
      }
    LeaveCriticalSection(&jobs_cs);

	// Do the job
    if(JOB)
      {
      JOB->Run();
      delete JOB;
      JOB = NULL;
      continue;		// When job is done, lease another job.
      }   

	// Wait for allerting
#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;
}


/** Initialise jobs infrastructure.
 * @param[in]	ThrCount	Thread count	1,2,3,6; or 0 no additional thread; or <0 autodetect threads.
 * @return	<0 failure; 1 OK */
int InitJobs(int ThrCount)
{
	// Resolve thread count.
  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;
      }
    }

  JOB_QUEUE = (JobBase **)calloc(ThrCount,sizeof(JobBase*));
  if(JOB_QUEUE==NULL) return -2;
  InitializeCriticalSection(&jobs_cs);

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

  while(WorkersRunning<ThrCount)
  {
     WorkersRunning++;	// Should be initialised now, newly started thread needs this value > 0.
#ifdef _WIN32
     const HANDLE thread = (HANDLE)_beginthreadex(NULL,0,&JobThread,0,0,NULL);
     if(thread==0 || (size_t)thread==~0)
#else
     pthread_t WriteThread;
     if(pthread_create(&WriteThread, NULL, JobThread, NULL) != 0)
#endif
     {
       WorkersRunning--;	// Actual worker did not start.
       return -3;
     }
  }

  return 1;
}


/** Add a new job to the job queue.
 * @param[in]	js	Job object - it must NOT be deleted from caller.
 *                      Job will be erased asynchronously from worker thread.
 * @return	1 job is inserted to a queue; 0 job is finished by calling thread, queue overflow; -1 no job needed. */
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_QUEUE[i]==NULL)
        {
        JOB_QUEUE[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)
{
  if(JOB_QUEUE==NULL) return;
  ALERT_ALL(JobAlertEvent);	// Wake up any active worker.

  EnterCriticalSection(&jobs_cs);
  for(int i=0; i<WorkersRunning; i++)
    {
    JobBase *JOB = JOB_QUEUE[i];
    if(JOB!=NULL)
      {
      JOB_QUEUE[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. But all existing jobs are intended to
 * finish their work. */
void EndJobs(void)
{
  FinishJobs();			// Finish all work.
  WorkersRunning = 0;		// Worker exit flag.
  if(JOB_QUEUE)
  {
    ALERT_ALL(JobAlertEvent);	// Alert all workers.
    free(JOB_QUEUE); JOB_QUEUE=NULL;
  }
}


/** Ask jobs to finish and do not work on already scheduled activities. */
void AbortJobs(void)
{
int WorkersRunningBk = WorkersRunning;
  WorkersRunning = 0;		// Worker exit flag.

  if(JOB_QUEUE==NULL) return;	// Jobs not initialised yet.

  ALERT_ALL(JobAlertEvent);	// Alert all workers.

  EnterCriticalSection(&jobs_cs);
  for(int i=0; i<WorkersRunningBk; i++)
  {
    JobBase *JOB = JOB_QUEUE[i];
    if(JOB!=NULL)
    {
      JOB_QUEUE[i] = NULL;
      LeaveCriticalSection(&jobs_cs);
      //JOB->Run();	// Do NOT run the job.
      delete JOB;
      EnterCriticalSection(&jobs_cs);
    }
  }
  LeaveCriticalSection(&jobs_cs);
  free(JOB_QUEUE); JOB_QUEUE=NULL;
}


#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
