/* Threads.c */

#include "Threads.h"

#ifdef ENV_BEOS
#include <kernel/OS.h>
#else
#include <pthread.h>
#include <stdlib.h>
#endif

#include <errno.h>

#if defined(__linux__)
#define PTHREAD_MUTEX_ERRORCHECK PTHREAD_MUTEX_ERRORCHECK_NP
#endif

#ifdef ENV_BEOS

/* TODO : optimize the code and verify the returned values */

WRes Thread_Create(CThread *thread, THREAD_FUNC_RET_TYPE (THREAD_FUNC_CALL_TYPE *startAddress)(void *), LPVOID parameter)
{
	thread->_tid = spawn_thread((int32 (*)(void *))startAddress, "CThread", B_LOW_PRIORITY, parameter);
	if (thread->_tid >= B_OK) {
		resume_thread(thread->_tid);
	} else {
		thread->_tid = B_BAD_THREAD_ID;
	}
	thread->_created = 1;
	return 0; // SZ_OK;
}

WRes Thread_Wait(CThread *thread)
{
  int ret;

  if (thread->_created == 0)
    return EINVAL;

  if (thread->_tid >= B_OK)
  {
    status_t exit_value;
    wait_for_thread(thread->_tid, &exit_value);
    thread->_tid = B_BAD_THREAD_ID;
  } else {
    return EINVAL;
  }

  thread->_created = 0;

  return 0;
}

WRes Thread_Close(CThread *thread)
{
    if (!thread->_created) return SZ_OK;

    thread->_tid = B_BAD_THREAD_ID;
    thread->_created = 0;
    return SZ_OK;
}


WRes Event_Create(CEvent *p, BOOL manualReset, int initialSignaled)
{
  p->_index_waiting = 0;
  p->_manual_reset  = manualReset;
  p->_state         = (initialSignaled ? TRUE : FALSE);
  p->_created       = 1;
  p->_sem           = create_sem(1,"event");
  return 0;
}

WRes Event_Set(CEvent *p) {
  int index;
  acquire_sem(p->_sem);
  p->_state = TRUE;
  for(index = 0 ; index < p->_index_waiting ; index++)
  {
     send_data(p->_waiting[index], '7zCN', NULL, 0);
  }
  p->_index_waiting = 0;
  release_sem(p->_sem);
  return 0;
}

WRes Event_Reset(CEvent *p) {
  acquire_sem(p->_sem);
  p->_state = FALSE;
  release_sem(p->_sem);
  return 0;
}

WRes Event_Wait(CEvent *p) {
  acquire_sem(p->_sem);
  while (p->_state == FALSE)
  {
    thread_id sender;
    p->_waiting[p->_index_waiting++] = find_thread(NULL);
    release_sem(p->_sem);
    /* int msg = */ receive_data(&sender, NULL, 0);
    acquire_sem(p->_sem);
  }
  if (p->_manual_reset == FALSE)
  {
     p->_state = FALSE;
  }
  release_sem(p->_sem);
  return 0;
}

WRes Event_Close(CEvent *p) {
  if (p->_created)
  {
    p->_created = 0;
    delete_sem(p->_sem);
  }
  return 0;
}

WRes Semaphore_Create(CSemaphore *p, UInt32 initiallyCount, UInt32 maxCount)
{
  p->_index_waiting = 0;
  p->_count         = initiallyCount;
  p->_maxCount      = maxCount;
  p->_created       = 1;
  p->_sem           = create_sem(1,"sem");
  return 0;
}

WRes Semaphore_ReleaseN(CSemaphore *p, UInt32 releaseCount)
{
  UInt32 newCount;
  int index;

  if (releaseCount < 1) return EINVAL;

  acquire_sem(p->_sem);
  newCount = p->_count + releaseCount;
  if (newCount > p->_maxCount)
  {
    release_sem(p->_sem);
    return EINVAL;
  }
  p->_count = newCount;
  for(index = 0 ; index < p->_index_waiting ; index++)
  {
     send_data(p->_waiting[index], '7zCN', NULL, 0);
  }
  p->_index_waiting = 0;
  release_sem(p->_sem);
  return 0;
}

WRes Semaphore_Wait(CSemaphore *p) {
  acquire_sem(p->_sem);
  while (p->_count < 1)
  {
    thread_id sender;
    p->_waiting[p->_index_waiting++] = find_thread(NULL);
    release_sem(p->_sem);
    /* int msg = */ receive_data(&sender, NULL, 0);
    acquire_sem(p->_sem);
  }
  p->_count--;
  release_sem(p->_sem);
  return 0;
}

WRes Semaphore_Close(CSemaphore *p) {
  if (p->_created)
  {
    p->_created = 0;
    delete_sem(p->_sem);
  }
  return 0;
}

WRes CriticalSection_Init(CCriticalSection * lpCriticalSection)
{
  lpCriticalSection->_sem = create_sem(1,"cc");
  return 0;
}

#else /* !ENV_BEOS */

WRes Thread_Create(CThread *thread, THREAD_FUNC_RET_TYPE (THREAD_FUNC_CALL_TYPE *startAddress)(void *), LPVOID parameter)
{
	pthread_attr_t attr;
	int ret;

	thread->_created = 0;

	ret = pthread_attr_init(&attr);
	if (ret) return ret;

	ret = pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_JOINABLE);
	if (ret) return ret;

	ret = pthread_create(&thread->_tid, &attr, (void *)startAddress, parameter);

	/* ret2 = */ pthread_attr_destroy(&attr);

	if (ret) return ret;

	thread->_created = 1;

	return 0; // SZ_OK;
}

WRes Thread_Wait(CThread *thread)
{
  void *thread_return;
  int ret;

  if (thread->_created == 0)
    return EINVAL;

  ret = pthread_join(thread->_tid,&thread_return);
  thread->_created = 0;

  return ret;
}

WRes Thread_Close(CThread *thread)
{
    if (!thread->_created) return SZ_OK;

    pthread_detach(thread->_tid);
    thread->_tid = 0;
    thread->_created = 0;
    return SZ_OK;
}

#ifdef DEBUG_SYNCHRO

#include <stdio.h>

static void dump_error(int ligne,int ret,const char *text,void *param)
{
  fprintf(stderr, "\n##T%d#ERROR2 (l=%d) %s : param=%p ret = %d (%s)##\n",(int)pthread_self(),ligne,text,param,ret,strerror(ret));
    // abort();
}

WRes Event_Create(CEvent *p, BOOL manualReset, int initialSignaled)
{
  int ret;
  pthread_mutexattr_t mutexattr;
  memset(&mutexattr,0,sizeof(mutexattr));
  ret = pthread_mutexattr_init(&mutexattr);
  if (ret != 0) dump_error(__LINE__,ret,"Event_Create::pthread_mutexattr_init",&mutexattr);
  ret = pthread_mutexattr_settype(&mutexattr,PTHREAD_MUTEX_ERRORCHECK);
  if (ret != 0) dump_error(__LINE__,ret,"Event_Create::pthread_mutexattr_settype",&mutexattr);
  ret = pthread_mutex_init(&p->_mutex,&mutexattr);
  if (ret != 0) dump_error(__LINE__,ret,"Event_Create::pthread_mutexattr_init",&p->_mutex);
  if (ret == 0)
  {
    ret = pthread_cond_init(&p->_cond,0);
    if (ret != 0) dump_error(__LINE__,ret,"Event_Create::pthread_cond_init",&p->_cond);
    p->_manual_reset = manualReset;
    p->_state        = (initialSignaled ? TRUE : FALSE);
    p->_created = 1;
  }
  return ret;
}

WRes Event_Set(CEvent *p) {
  int ret = pthread_mutex_lock(&p->_mutex);
  if (ret != 0) dump_error(__LINE__,ret,"ES::pthread_mutex_lock",&p->_mutex);
  if (ret == 0)
  {
    p->_state = TRUE;
    ret = pthread_cond_broadcast(&p->_cond);
    if (ret != 0) dump_error(__LINE__,ret,"ES::pthread_cond_broadcast",&p->_cond);
    if (ret == 0)
    {
       ret = pthread_mutex_unlock(&p->_mutex);
       if (ret != 0) dump_error(__LINE__,ret,"ES::pthread_mutex_unlock",&p->_mutex);
    }
  }
  return ret;
}

WRes Event_Reset(CEvent *p) {
  int ret = pthread_mutex_lock(&p->_mutex);
  if (ret != 0) dump_error(__LINE__,ret,"ER::pthread_mutex_lock",&p->_mutex);
  if (ret == 0)
  {
    p->_state = FALSE;
    ret = pthread_mutex_unlock(&p->_mutex);
    if (ret != 0) dump_error(__LINE__,ret,"ER::pthread_mutex_unlock",&p->_mutex);
  }
  return ret;
}

WRes Event_Wait(CEvent *p) {
  int ret = pthread_mutex_lock(&p->_mutex);
  if (ret != 0) dump_error(__LINE__,ret,"EW::pthread_mutex_lock",&p->_mutex);
  if (ret == 0)
  {
    while ((p->_state == FALSE) && (ret == 0))
    {
       ret = pthread_cond_wait(&p->_cond, &p->_mutex);
       if (ret != 0) dump_error(__LINE__,ret,"EW::pthread_cond_wait",&p->_mutex);
    }
    if (ret == 0)
    {
       if (p->_manual_reset == FALSE)
       {
         p->_state = FALSE;
       }
       ret = pthread_mutex_unlock(&p->_mutex);
       if (ret != 0) dump_error(__LINE__,ret,"EW::pthread_mutex_unlock",&p->_mutex);
    }
  }
  return ret;
}

WRes Event_Close(CEvent *p) {
  if (p->_created)
  {
    int ret;
    p->_created = 0;
    ret = pthread_mutex_destroy(&p->_mutex);
    if (ret != 0) dump_error(__LINE__,ret,"EC::pthread_mutex_destroy",&p->_mutex);
    ret = pthread_cond_destroy(&p->_cond);
    if (ret != 0) dump_error(__LINE__,ret,"EC::pthread_cond_destroy",&p->_cond);
  }
  return 0;
}

WRes Semaphore_Create(CSemaphore *p, UInt32 initiallyCount, UInt32 maxCount)
{
  int ret;
  pthread_mutexattr_t mutexattr;
  memset(&mutexattr,0,sizeof(mutexattr));
  ret = pthread_mutexattr_init(&mutexattr);
  if (ret != 0) dump_error(__LINE__,ret,"SemC::pthread_mutexattr_init",&mutexattr);
  ret = pthread_mutexattr_settype(&mutexattr,PTHREAD_MUTEX_ERRORCHECK);
  if (ret != 0) dump_error(__LINE__,ret,"SemC::pthread_mutexattr_settype",&mutexattr);
  ret = pthread_mutex_init(&p->_mutex,&mutexattr);
  if (ret != 0) dump_error(__LINE__,ret,"SemC::pthread_mutexattr_init",&p->_mutex);
  if (ret == 0)
  {
    ret = pthread_cond_init(&p->_cond,0);
    if (ret != 0) dump_error(__LINE__,ret,"SemC::pthread_cond_init",&p->_mutex);
    p->_count    = initiallyCount;
    p->_maxCount = maxCount;
    p->_created = 1;
  }
  return ret;
}

WRes Semaphore_ReleaseN(CSemaphore *p, UInt32 releaseCount)
{
  int ret;
  if (releaseCount < 1) return EINVAL;

  ret = pthread_mutex_lock(&p->_mutex);
  if (ret != 0) dump_error(__LINE__,ret,"SemR::pthread_mutex_lock",&p->_mutex);
  if (ret == 0)
  {
    UInt32 newCount = p->_count + releaseCount;
    if (newCount > p->_maxCount)
    {
      ret = pthread_mutex_unlock(&p->_mutex);
      if (ret != 0) dump_error(__LINE__,ret,"SemR::pthread_mutex_unlock",&p->_mutex);
      return EINVAL;
    }
    p->_count = newCount;
    ret = pthread_cond_broadcast(&p->_cond);
    if (ret != 0) dump_error(__LINE__,ret,"SemR::pthread_cond_broadcast",&p->_cond);
    if (ret == 0)
    {
       ret = pthread_mutex_unlock(&p->_mutex);
       if (ret != 0) dump_error(__LINE__,ret,"SemR::pthread_mutex_unlock",&p->_mutex);
    }
  }
  return ret;
}

WRes Semaphore_Wait(CSemaphore *p) {
  int ret = pthread_mutex_lock(&p->_mutex);
  if (ret != 0) dump_error(__LINE__,ret,"SemW::pthread_mutex_lock",&p->_mutex);
  if (ret == 0)
  {
    while ((p->_count < 1) && (ret == 0))
    {
       ret = pthread_cond_wait(&p->_cond, &p->_mutex);
       if (ret != 0) dump_error(__LINE__,ret,"SemW::pthread_cond_wait",&p->_mutex);
    }
    if (ret == 0)
    {
      p->_count--;
      ret = pthread_mutex_unlock(&p->_mutex);
      if (ret != 0) dump_error(__LINE__,ret,"SemW::pthread_mutex_unlock",&p->_mutex);
    }
  }
  return ret;
}

WRes Semaphore_Close(CSemaphore *p) {
  if (p->_created)
  {
    int ret;
    p->_created = 0;
    ret = pthread_mutex_destroy(&p->_mutex);
    if (ret != 0) dump_error(__LINE__,ret,"Semc::pthread_mutex_destroy",&p->_mutex);
    ret = pthread_cond_destroy(&p->_cond);
    if (ret != 0) dump_error(__LINE__,ret,"Semc::pthread_cond_destroy",&p->_cond);
  }
  return 0;
}

WRes CriticalSection_Init(CCriticalSection * lpCriticalSection)
{
	if (lpCriticalSection)
	{
		int ret;
		pthread_mutexattr_t mutexattr;
		memset(&mutexattr,0,sizeof(mutexattr));
		ret = pthread_mutexattr_init(&mutexattr);
		if (ret != 0) dump_error(__LINE__,ret,"CS I::pthread_mutexattr_init",&mutexattr);
		ret = pthread_mutexattr_settype(&mutexattr,PTHREAD_MUTEX_ERRORCHECK);
		if (ret != 0) dump_error(__LINE__,ret,"CS I::pthread_mutexattr_settype",&mutexattr);
		ret = pthread_mutex_init(&lpCriticalSection->_mutex,&mutexattr);
		if (ret != 0) dump_error(__LINE__,ret,"CS I::pthread_mutexattr_init",&lpCriticalSection->_mutex);
		return ret;
	}
	return EINTR;
}

void CriticalSection_Enter(CCriticalSection * lpCriticalSection)
{
	if (lpCriticalSection)
	{
		int ret = pthread_mutex_lock(&(lpCriticalSection->_mutex));
                if (ret != 0) dump_error(__LINE__,ret,"CS::pthread_mutex_lock",&(lpCriticalSection->_mutex));
	}
}

void CriticalSection_Leave(CCriticalSection * lpCriticalSection)
{
	if (lpCriticalSection)
	{
		int ret = pthread_mutex_unlock(&(lpCriticalSection->_mutex));
                if (ret != 0) dump_error(__LINE__,ret,"CS::pthread_mutex_unlock",&(lpCriticalSection->_mutex));
	}
}

void CriticalSection_Delete(CCriticalSection * lpCriticalSection)
{
	if (lpCriticalSection)
	{
		int ret = pthread_mutex_destroy(&(lpCriticalSection->_mutex));
                if (ret != 0) dump_error(__LINE__,ret,"CS::pthread_mutex_destroy",&(lpCriticalSection->_mutex));
	}
}

#else

WRes Event_Create(CEvent *p, BOOL manualReset, int initialSignaled)
{
  pthread_mutex_init(&p->_mutex,0);
  pthread_cond_init(&p->_cond,0);
  p->_manual_reset = manualReset;
  p->_state        = (initialSignaled ? TRUE : FALSE);
  p->_created = 1;
  return 0;
}

WRes Event_Set(CEvent *p) {
  pthread_mutex_lock(&p->_mutex);
  p->_state = TRUE;
  pthread_cond_broadcast(&p->_cond);
  pthread_mutex_unlock(&p->_mutex);
  return 0;
}

WRes Event_Reset(CEvent *p) {
  pthread_mutex_lock(&p->_mutex);
  p->_state = FALSE;
  pthread_mutex_unlock(&p->_mutex);
  return 0;
}

WRes Event_Wait(CEvent *p) {
  pthread_mutex_lock(&p->_mutex);
  while (p->_state == FALSE)
  {
     pthread_cond_wait(&p->_cond, &p->_mutex);
  }
  if (p->_manual_reset == FALSE)
  {
     p->_state = FALSE;
  }
  pthread_mutex_unlock(&p->_mutex);
  return 0;
}

WRes Event_Close(CEvent *p) {
  if (p->_created)
  {
    p->_created = 0;
    pthread_mutex_destroy(&p->_mutex);
    pthread_cond_destroy(&p->_cond);
  }
  return 0;
}

WRes Semaphore_Create(CSemaphore *p, UInt32 initiallyCount, UInt32 maxCount)
{
  pthread_mutex_init(&p->_mutex,0);
  pthread_cond_init(&p->_cond,0);
  p->_count    = initiallyCount;
  p->_maxCount = maxCount;
  p->_created  = 1;
  return 0;
}

WRes Semaphore_ReleaseN(CSemaphore *p, UInt32 releaseCount)
{
  UInt32 newCount;

  if (releaseCount < 1) return EINVAL;

  pthread_mutex_lock(&p->_mutex);

  newCount = p->_count + releaseCount;
  if (newCount > p->_maxCount)
  {
    pthread_mutex_unlock(&p->_mutex);
    return EINVAL;
  }
  p->_count = newCount;
  pthread_cond_broadcast(&p->_cond);
  pthread_mutex_unlock(&p->_mutex);
  return 0;
}

WRes Semaphore_Wait(CSemaphore *p) {
  pthread_mutex_lock(&p->_mutex);
  while (p->_count < 1)
  {
     pthread_cond_wait(&p->_cond, &p->_mutex);
  }
  p->_count--;
  pthread_mutex_unlock(&p->_mutex);
  return 0;
}

WRes Semaphore_Close(CSemaphore *p) {
  if (p->_created)
  {
    p->_created = 0;
    pthread_mutex_destroy(&p->_mutex);
    pthread_cond_destroy(&p->_cond);
  }
  return 0;
}

WRes CriticalSection_Init(CCriticalSection * lpCriticalSection)
{
	return pthread_mutex_init(&(lpCriticalSection->_mutex),0);
}

#endif /* DEBUG_SYNCHRO */

#endif /* ENV_BEOS */

WRes ManualResetEvent_Create(CManualResetEvent *p, int initialSignaled)
  { return Event_Create(p, TRUE, initialSignaled); }

WRes ManualResetEvent_CreateNotSignaled(CManualResetEvent *p)
  { return ManualResetEvent_Create(p, 0); }

WRes AutoResetEvent_Create(CAutoResetEvent *p, int initialSignaled)
  { return Event_Create(p, FALSE, initialSignaled); }
WRes AutoResetEvent_CreateNotSignaled(CAutoResetEvent *p)
  { return AutoResetEvent_Create(p, 0); }

