/**
    interface to nPth with partial pth API emulation

    Copyright (c) 2020-2022 The Creators of Simphone

    See the file COPYING.LESSER.txt for copying permission.
**/

#ifndef _XOPEN_SOURCE
#define _XOPEN_SOURCE 500 /* pthread_mutexattr_settype */
#endif

#include "config.h"
#include <npth.h>
#include "spth.h"

#ifdef DONOT_DEFINE
#include <stdlib.h>
#define sim_new(length) malloc (length)
#define sim_free(pointer, length) free (pointer)
#else
#include "logger.h"
#include "utils.h"
#endif

#include <stdarg.h>
#include <errno.h>

#ifndef SIM_MAX_STACK_SIZE
#define SIM_MAX_STACK_SIZE 65536
#endif

#ifndef _WIN32
#include <limits.h>
#include <semaphore.h>
#endif

#ifdef __APPLE__
#include <unistd.h>
#include <stdio.h>
#endif

static int pth_threads_count = 0;

#define sim_set_error(value, error) (errno = (error), (value))

pth_attr_t pth_attr_new (void) {
  pth_attr_t attr;
  if ((attr = (pth_attr_t) sim_new (sizeof (*attr))) == NULL)
    return sim_set_error (NULL, ENOMEM);
  attr->a_tid = NULL;
  pth_attr_init (attr);
  return attr;
}

int pth_attr_destroy (pth_attr_t attr) {
  if (attr == NULL)
    return sim_set_error (FALSE, EINVAL);
  sim_free (attr, sizeof (*attr));
  return TRUE;
}

int pth_attr_init (pth_attr_t attr) {
  if (attr == NULL)
    return sim_set_error (FALSE, EINVAL);
  if (attr->a_tid != NULL)
    return sim_set_error (FALSE, EPERM);
  attr->a_joinable = TRUE;
  return TRUE;
}

int pth_attr_get (pth_attr_t attr, int field, int *value) {
  if (attr == NULL || field != PTH_ATTR_JOINABLE)
    return sim_set_error (FALSE, EINVAL);
  *value = attr->a_joinable;
  return TRUE;
}

int pth_attr_set (pth_attr_t attr, int field, int value) {
  if (attr == NULL || field != PTH_ATTR_JOINABLE)
    return sim_set_error (FALSE, EINVAL);
  attr->a_joinable = value;
  return TRUE;
}

pth_t pth_self (void) {
  return (pth_t) (long) npth_self ();
}

pth_t pth_spawn (pth_attr_t attr, void *(*entry) (void *), void *arg) {
  int joinable = TRUE, err;
  npth_t th = 0;
  npth_attr_t a;
  pth_attr_get (attr, PTH_ATTR_JOINABLE, &joinable);
  if ((err = npth_attr_init (&a)) == 0) {
#ifndef _WIN32
#ifdef PTHREAD_STACK_MIN
    if (SIM_MAX_STACK_SIZE > PTHREAD_STACK_MIN)
#endif
      err = pthread_attr_setstacksize (&a, SIM_MAX_STACK_SIZE);
#endif
    if (! err) {
      joinable = joinable ? NPTH_CREATE_JOINABLE : NPTH_CREATE_DETACHED;
      if ((err = npth_attr_setdetachstate (&a, joinable)) == 0)
        err = npth_create (&th, &a, entry, arg);
      pth_threads_count += ! err;
    }
    npth_attr_destroy (&a);
  }
  return (errno = err) == 0 ? (pth_t) th : NULL;
}

int pth_join_ (pth_t tid, void **value) {
  int err = npth_join ((npth_t) tid, value);
  pth_threads_count++;
  return (errno = err) == 0;
}

int pth_mutex_init (pth_mutex_t *mutex) {
  int err;
  npth_mutexattr_t a;
  if ((*mutex = (pth_mutex_t) sim_new (sizeof (npth_mutex_t))) == NULL)
    return sim_set_error (FALSE, ENOMEM);
  if ((err = npth_mutexattr_init (&a)) == 0) {
    if ((err = npth_mutexattr_settype (&a, NPTH_MUTEX_RECURSIVE)) == 0)
      err = npth_mutex_init ((npth_mutex_t *) *mutex, &a);
    npth_mutexattr_destroy (&a);
  }
  return (errno = err) == 0;
}

int pth_mutex_destroy (pth_mutex_t *mutex) {
  int err = npth_mutex_destroy ((npth_mutex_t *) *mutex);
  if (! err)
    sim_free (*mutex, sizeof (npth_mutex_t));
  return (errno = err) == 0;
}

int pth_mutex_acquire_ (pth_mutex_t *mutex, int nonblock, pth_event_t ev) {
  int err;
  if (mutex == NULL)
    return sim_set_error (FALSE, EINVAL);
  if (ev) {
    struct timespec ts;
    if (nonblock || ev->ev_type != PTH_EVENT_TIME)
      return sim_set_error (FALSE, EINVAL);
    if (ev->tv.tv_usec >= 1000000)
      return sim_set_error (FALSE, EPERM);
    ts.tv_sec = ev->tv.tv_sec, ts.tv_nsec = ev->tv.tv_usec * 1000;
    err = npth_mutex_timedlock ((npth_mutex_t *) *mutex, &ts);
  } else if (! nonblock) {
    err = npth_mutex_lock ((npth_mutex_t *) *mutex);
  } else
    return (errno = npth_mutex_trylock ((npth_mutex_t *) *mutex)) == 0;
  pth_threads_count++;
  return (errno = err) == 0;
}

int pth_mutex_release (pth_mutex_t *mutex) {
  return (errno = npth_mutex_unlock (*mutex)) == 0;
}

int pth_cond_init (pth_cond_t *cond) {
  if ((*cond = (pth_cond_t) sim_new (sizeof (npth_cond_t))) == 0)
    return sim_set_error (FALSE, ENOMEM);
  return (errno = npth_cond_init ((npth_cond_t *) *cond, NULL)) == 0;
}

int pth_cond_destroy (pth_cond_t *cond) {
  int err = npth_cond_destroy ((npth_cond_t *) *cond);
  if (! err)
    sim_free (*cond, sizeof (npth_cond_t));
  return (errno = err) == 0;
}

int pth_cond_await_ (pth_cond_t *cond, pth_mutex_t *mutex, pth_event_t ev) {
  int err;
  if (cond == NULL || mutex == NULL)
    return sim_set_error (FALSE, EINVAL);
  if (ev) {
    struct timespec ts;
    if (ev->ev_type != PTH_EVENT_TIME)
      return sim_set_error (FALSE, EINVAL);
    if (ev->tv.tv_sec < 1000000)
      return sim_set_error (FALSE, EPERM);
    ts.tv_sec = ev->tv.tv_sec, ts.tv_nsec = ev->tv.tv_usec * 1000;
    err = npth_cond_timedwait ((npth_cond_t *) *cond, (npth_mutex_t *) *mutex, &ts);
  } else
    err = npth_cond_wait ((npth_cond_t *) *cond, (npth_mutex_t *) *mutex);
  pth_threads_count++;
  return (errno = err) == 0;
}

int pth_cond_notify_ (pth_cond_t *cond, int broadcast) {
  if (cond == NULL)
    return sim_set_error (FALSE, EINVAL);
  if (! broadcast)
    return (errno = npth_cond_signal ((npth_cond_t *) *cond)) == 0;
  return (errno = npth_cond_broadcast ((npth_cond_t *) *cond)) == 0;
}

pth_time_t pth_time (long sec, long usec) {
  pth_time_t tv;
  tv.tv_sec = sec, tv.tv_usec = usec;
  return tv;
}

pth_time_t pth_timeout (long sec, long usec) {
  struct timeval tv;
#ifdef _WIN32 /* really need to be in sync with nPth here */
  FILETIME ftime;
  ULARGE_INTEGER systime;
  GetSystemTimeAsFileTime (&ftime);
  systime.LowPart = ftime.dwLowDateTime;
  systime.HighPart = ftime.dwHighDateTime;
  tv.tv_sec = (unsigned long) (systime.QuadPart / 10000000);
  tv.tv_usec = (unsigned long) (systime.QuadPart % 10000000) / 10;
#elif defined(CLOCK_REALTIME)
  struct timespec ts;
  if (clock_gettime (CLOCK_REALTIME, &ts))
    return pth_time (0, 0);
  tv.tv_sec = ts.tv_sec, tv.tv_usec = ts.tv_nsec / 1000;
#else
  if (gettimeofday (&tv, NULL))
    return pth_time (0, 0);
#endif
  sec += tv.tv_sec;
  if ((usec += tv.tv_usec) >= 1000000) {
    usec -= 1000000;
    sec++;
  }
  return pth_time (sec, usec);
}

pth_event_t pth_event (unsigned long spec, ...) {
  pth_event_t ev;
  va_list ap;
  if (spec != PTH_EVENT_MSG && spec != PTH_EVENT_TIME)
    return sim_set_error (NULL, EINVAL);
  if ((ev = (pth_event_t) sim_new (sizeof (*ev))) == NULL)
    return sim_set_error (NULL, ENOMEM);
  va_start (ap, spec);
  if ((ev->ev_type = spec) == PTH_EVENT_MSG) {
    ev->mp = va_arg (ap, pth_msgport_t);
  } else
    ev->tv = va_arg (ap, pth_time_t);
  va_end (ap);
  return ev;
}

int pth_event_free (pth_event_t ev, int mode) {
  if (ev == NULL || mode != PTH_FREE_THIS)
    return sim_set_error (FALSE, EINVAL);
  sim_free (ev, sizeof (*ev));
  return TRUE;
}

int pth_wait_ (pth_event_t ev) {
  int res;
  if (ev == NULL || ev->ev_type != PTH_EVENT_MSG)
    return sim_set_error (-1, EINVAL);
  while (! pth_msgport_pending (ev->mp)) {
#ifdef _WIN32
    PTH_UNPROTECT_PROTECT_ (res = WaitForSingleObject (ev->mp->semaphore, INFINITE));
    if (res != WAIT_OBJECT_0) {
      errno = GetLastError ();
      return -1;
    }
#else
    PTH_UNPROTECT_PROTECT_ (res = sem_wait (ev->mp->semaphore));
    if (res && errno != EINTR)
      return -1;
#endif
  }
  return 1;
}

#define pth_ring_get(ring) ((ring) == NULL ? NULL : (ring)->r_hook)
#define pth_ring_count(ring) ((ring) == NULL ? ((unsigned) -1) : (ring)->r_nodes)

static void pth_ring_init (pth_ring_t *ring) {
  if (ring == NULL)
    return;
  ring->r_hook = NULL;
  ring->r_nodes = 0;
}

static void pth_ring_append (pth_ring_t *ring, pth_ringnode_t *ringnode) {
  if (ring == NULL || ringnode == NULL)
    return;
  if (ring->r_hook == NULL) {
    ring->r_hook = ringnode;
    ringnode->rn_next = ringnode;
    ringnode->rn_prev = ringnode;
  } else {
    ringnode->rn_next = ring->r_hook;
    ringnode->rn_prev = ring->r_hook->rn_prev;
    ringnode->rn_next->rn_prev = ringnode;
    ringnode->rn_prev->rn_next = ringnode;
  }
  ring->r_nodes++;
}

static void pth_ring_delete (pth_ring_t *ring, pth_ringnode_t *ringnode) {
  if (ring == NULL || ringnode == NULL)
    return;
  if (ring->r_hook == ringnode && ringnode->rn_prev == ringnode && ringnode->rn_next == ringnode) {
    ring->r_hook = NULL;
  } else {
    if (ring->r_hook == ringnode)
      ring->r_hook = ringnode->rn_next;
    ringnode->rn_prev->rn_next = ringnode->rn_next;
    ringnode->rn_next->rn_prev = ringnode->rn_prev;
  }
  ring->r_nodes--;
}

static pth_ringnode_t *pth_ring_pop (pth_ring_t *ring) {
  pth_ringnode_t *ringnode = pth_ring_get (ring);
  if (ringnode != NULL)
    pth_ring_delete (ring, ringnode);
  return ringnode;
}

pth_msgport_t pth_msgport_create (const char *name) {
#ifdef __APPLE__
  int count = 0;
#endif
  pth_msgport_t mp;
  if ((mp = (pth_msgport_t) sim_new (sizeof (*mp))) == NULL)
    return sim_set_error (NULL, ENOMEM);
#ifdef _WIN32
  if ((mp->semaphore = CreateEvent (NULL, FALSE, FALSE, NULL)) != NULL) {
    pth_ring_init (&mp->mp_queue);
    return mp;
  }
  errno = GetLastError ();
#elif defined(__APPLE__)
  do {
    sprintf (mp->semaphore_name, "/simcore-msgport-%lu-%u", (unsigned long) getpid (), ++count);
    if ((mp->semaphore = sem_open (mp->semaphore_name, O_CREAT | O_EXCL, S_IRUSR | S_IWUSR, 1)) != SEM_FAILED) {
      pth_ring_init (&mp->mp_queue);
      return mp;
    }
  } while (errno == EEXIST && count < 1000000);
#else
  if ((mp->semaphore = sim_new (sizeof (sem_t))) != NULL) {
    if (sem_init (mp->semaphore, 0, 1) != -1) {
      pth_ring_init (&mp->mp_queue);
      return mp;
    }
    sim_free (mp->semaphore, sizeof (sem_t));
  }
#endif
  sim_free (mp, sizeof (*mp));
  return NULL;
}

void pth_msgport_destroy (pth_msgport_t mp) {
  pth_message_t *m;
  if (mp == NULL)
    return;
  while ((m = pth_msgport_get (mp)) != NULL)
    pth_msgport_reply (m);
#ifdef _WIN32
  CloseHandle (mp->semaphore);
#elif defined(__APPLE__)
  sem_close (mp->semaphore);
  sem_unlink (mp->semaphore_name);
#else
  sem_destroy (mp->semaphore);
  sim_free (mp->semaphore, sizeof (sem_t));
#endif
  sim_free (mp, sizeof (*mp));
}

int pth_msgport_pending (pth_msgport_t mp) {
  return mp == NULL ? (unsigned) sim_set_error (-1, EINVAL) : pth_ring_count (&mp->mp_queue);
}

int pth_msgport_put (pth_msgport_t mp, pth_message_t *m) {
  int empty, err = 0;
  if (mp == NULL)
    return sim_set_error (FALSE, EINVAL);
  empty = ! pth_ring_count (&mp->mp_queue);
  pth_ring_append (&mp->mp_queue, (pth_ringnode_t *) m);
#ifdef _WIN32
  if (empty && ! SetEvent (mp->semaphore))
    err = GetLastError ();
#else
  if (empty && sem_post (mp->semaphore))
    err = errno ? errno : ESRCH;
#endif
  return (errno = err) == 0;
}

pth_message_t *pth_msgport_get (pth_msgport_t mp) {
  return mp == NULL ? sim_set_error (NULL, EINVAL) : (pth_message_t *) pth_ring_pop (&mp->mp_queue);
}

int pth_msgport_reply (pth_message_t *m) {
  return m == NULL ? sim_set_error (FALSE, EINVAL) : pth_msgport_put (m->m_replyport, m);
}

int pth_key_create (pth_key_t *key, void (*func) (void *)) {
  npth_key_t newkey;
  errno = npth_key_create (&newkey, func);
  *key = (pth_key_t) (long) newkey;
  return errno == 0;
}

int pth_key_delete (pth_key_t key) {
  return (errno = npth_key_delete ((npth_key_t) (long) key)) == 0;
}

void *sim_get_key_data (pth_key_t key) {
  return key == (pth_key_t) -1 ? sim_set_error (NULL, EINVAL) : npth_getspecific ((npth_key_t) (long) key);
}

int sim_set_key_data (pth_key_t key, const void *value) {
  if (key == (pth_key_t) -1)
    return sim_set_error (FALSE, EINVAL);
  return (errno = npth_setspecific ((npth_key_t) (long) key, value)) == 0;
}

int pth_usleep_ (unsigned int usec) {
  int err = npth_usleep (usec);
  pth_threads_count++;
  return err;
}

unsigned int pth_sleep_ (unsigned int sec) {
  unsigned int res = npth_sleep (sec);
  pth_threads_count++;
  return res;
}

int pth_connect_ (int fd, const struct sockaddr *addr, socklen_t addrlen) {
  int err = npth_connect (fd, addr, addrlen);
  pth_threads_count++;
  return err;
}

int pth_accept_ (int fd, struct sockaddr *addr, socklen_t *addrlen) {
  int err = npth_accept (fd, addr, addrlen);
  pth_threads_count++;
  return err;
}

int pth_select_ (int nfd, fd_set *rfds, fd_set *wfds, fd_set *efds, struct timeval *timeout) {
  int err = npth_select (nfd, rfds, wfds, efds, timeout);
  pth_threads_count++;
  return err;
}

ssize_t pth_read_ (int fd, void *buf, size_t nbytes) {
  ssize_t res = npth_read (fd, buf, nbytes);
  pth_threads_count++;
  return res;
}

ssize_t pth_write_ (int fd, const void *buf, size_t nbytes) {
  ssize_t res = npth_write (fd, buf, nbytes);
  pth_threads_count++;
  return res;
}

ssize_t pth_recv_ (int fd, void *buf, size_t nbytes, int flags) {
  ssize_t res;
  if (flags)
    return sim_set_error (FALSE, EINVAL);
  PTH_UNPROTECT_PROTECT_ (res = recv (fd, buf, nbytes, flags));
  return res;
}

ssize_t pth_send_ (int fd, const void *buf, size_t nbytes, int flags) {
  ssize_t res;
  if (flags)
    return sim_set_error (FALSE, EINVAL);
  PTH_UNPROTECT_PROTECT_ (res = send (fd, buf, nbytes, flags));
  return res;
}

#ifndef _WIN32
int pth_poll_ (struct pollfd *fds, unsigned nfd, int timeout) {
  int res;
  PTH_UNPROTECT_PROTECT_ (res = poll (fds, nfd, timeout));
  return res;
}

int pth_recvmsg_ (int fd, struct msghdr *msg, int flags) {
  int res = npth_recvmsg (fd, msg, flags);
  pth_threads_count++;
  return res;
}

int pth_sendmsg_ (int fd, const struct msghdr *msg, int flags) {
  int res = npth_sendmsg (fd, msg, flags);
  pth_threads_count++;
  return res;
}

int pth_waitpid_ (pid_t pid, int *status, int options) {
  int res;
  PTH_UNPROTECT_PROTECT_ (res = waitpid (pid, status, options));
  return res;
}
#else
void *pth_get_handle (pth_t tid) {
  return npth_gethandle_np ((npth_t) tid);
}
#endif

void pth_unprotect (void) {
#ifdef SIM_LOG_FATAL
  if (! npth_is_protected ())
    log_fatal_ (NULL, 0, "simcore has been terminated by double semaphore release\n");
#endif
  npth_unprotect ();
}

void pth_protect_ (void) {
  npth_protect ();
  pth_threads_count++;
}

int pth_yield (pth_t tid) {
  int res = pth_threads_count <= 1;
  pth_threads_count = 0;
  return res;
}

int pth_init (void) {
  int err = npth_init ();
#ifndef HAVE_LIBPTH
  npth_unprotect ();
#endif
  return (errno = err) == 0;
}

int pth_kill (void) {
  return TRUE;
}
