
#include "Thread.h"

/*
  Utility functions associated with threads.
 */
void
s_usleep (slong us) {
#if __WIN32__ || __WIN64__
  /* We are a windows system, so we have to implement our own little sleeper.*/
  Sleep (us / 1000)
#else
  /* We are not a windows system, so lets assume that we have posix's nanosleep
   * Taken from:
   */
  struct timespec req = {0};
  req.tv_sec = (int)(us / 1000);
  req.tv_nsec = us * 1000000L;
  nanosleep(&req, (struct timespec *)NULL);
#endif
}

char *
s_thread_status_get_name (SThreadStatus status) {
  switch (status) {
    case (S_THREAD_STATUS_SUCCESS):
      return SThreadStatusName[0];
      break;
    case (S_THREAD_STATUS_TIMEOUT):
      return SThreadStatusName[1];
      break;
    case (S_THREAD_STATUS_ERROR):
      return SThreadStatusName[2];
      break;
    case (S_THREAD_STATUS_BUSY):
      return SThreadStatusName[3];
      break;
    case (S_THREAD_STATUS_NO_MEM):
      return SThreadStatusName[4];
      break;
    case (S_THREAD_STATUS_LAST):
      return SThreadStatusName[5];
      break;
    default:
      return NULL;
  }
  
}

/* ****************************************************************************
 ********************************** SMutex ************************************
 **************************************************************************** */

struct SMutex {
  mtx_t *  mutex;
  _Atomic(sboolean) locked;
};

schar *
s_mutex_flag_get_name (SMutexFlag flag) {
  switch (flag) {
    case (S_MUTEX_FLAG_PLAIN):
      return SMutexFlagName[0];
      break;
    case (S_MUTEX_FLAG_TIMED):
      return SMutexFlagName[1];
      break;
    case (S_MUTEX_FLAG_RECURSIVE):
      return SMutexFlagName[2];
      break;
    case (S_MUTEX_FLAG_LAST):
      return SMutexFlagName[3];
      break;
    default:
      return NULL;
  }
}

SMutex *
s_mutex_new () {
  SMutex * self = s_malloc (sizeof (SMutex));

  self->mutex = s_malloc (sizeof (mtx_t));

  atomic_init(&(self->locked), FALSE);

  sint status = mtx_init (self->mutex, mtx_plain);

  if (status == thrd_success) {
    return self;
  }
  s_free (self);
  s_err_print ("Could not create thrad. Error: %s.\n",
               s_thread_status_get_name (status));
  print_backtrace ();
  return NULL;
}

void
s_mutex_free (SMutex * self) {
  mtx_destroy (self->mutex);
  s_free (self->mutex);
  s_free (self);
}

SThreadStatus
s_mutex_lock (SMutex * self) {
  sint ret_val = mtx_lock (self->mutex);
  atomic_store(&(self->locked), TRUE);
  return ret_val;
}

SThreadStatus
s_mutex_unlock (SMutex * self) {
  sint ret_val = mtx_unlock (self->mutex);
  atomic_store(&(self->locked), FALSE);
  return ret_val;
}

SThreadStatus
s_mutex_check_lock (SMutex * self) {
  return atomic_load(&(self->locked));
}



/* ****************************************************************************
 ********************************** SThread ***********************************
 **************************************************************************** */

/*
 * Since SThread are, effectivaly, the same as thrd_t, we can cast it
 * back and forth without any real problems...?
 */
struct SThread {
  thrd_t thread;
  RunFunc func;

  _Atomic(sboolean) is_running;
};

SThread *
s_thread_new (RunFunc func) {
  SThread * self = s_malloc (sizeof (SThread));

  self->func = func;

  atomic_store (&(self->is_running), FALSE);

  return self;
}

void
s_thread_free (SThread * self) {
  assert (!(atomic_load(&(self->is_running))));
  s_free (self);
}

SThreadStatus
s_thread_run (SThread * self, spointer user_data) {
  if (atomic_load(&(self->is_running)) == TRUE ) {
    s_err_print ("Trying to run a thread that allready is running."
                  "Returning.\n");
    return S_THREAD_STATUS_ERROR;
  }
  atomic_store(&(self->is_running), TRUE);
  return thrd_create ((thrd_t *)self, (thrd_start_t)self->func, user_data);
}

void
s_thread_stop (SThread * self, sint res) {
  if (!thrd_equal (self->thread, thrd_current ())) {
    s_err_print ("Trying to exit thread that is not owned by current thread.\n"
                  "Can only be called from running thread.\n");
    return;
  }
  atomic_store (&(self->is_running), FALSE);
  thrd_exit (res);
}



