/*
 * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License
 * v. 1.0 which is available at
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
 */

/* _GNU_SOURCE is required for pthread_getname_np and pthread_setname_np. */
#define _GNU_SOURCE
#include <assert.h>
#include <errno.h>
#include <pthread.h>

#include <sys/types.h>
#include <unistd.h>
#include <strings.h>
#include <string.h>
#include <stdio.h>
#include <signal.h>

#include <limits.h>

#include "dds/ddsrt/heap.h"
#include "dds/ddsrt/log.h"
#include "dds/ddsrt/string.h"
#include "dds/ddsrt/threads_priv.h"
#include "dds/ddsrt/types.h"

#if defined(__linux)
#include <sys/syscall.h>
#define MAXTHREADNAMESIZE (15) /* 16 bytes including null-terminating byte. */
#elif defined(__APPLE__)
#include <mach/thread_info.h> /* MAXTHREADNAMESIZE */
#elif defined(__sun)
#define MAXTHREADNAMESIZE (31)
#elif defined(__FreeBSD__)
/* Required for pthread_get_name_np and pthread_set_name_np. */
#include <pthread_np.h>
#include <sys/thr.h>
#define MAXTHREADNAMESIZE (MAXCOMLEN)
#elif defined(__VXWORKS__)
#include <taskLib.h>
/* VX_TASK_NAME_LENGTH is the maximum number of bytes, excluding
   null-terminating byte, for a thread name. */
#define MAXTHREADNAMESIZE (VX_TASK_NAME_LENGTH)
#endif /* __APPLE__ */

size_t
ddsrt_thread_getname(char *str, size_t size)
{
  char buf[MAXTHREADNAMESIZE + 1] = "";
  size_t cnt = 0;

  assert(str != NULL);
  assert(size > 0);

#if defined(__linux)
  /* Thread names are limited to 16 bytes on Linux. ERANGE is returned if the
     buffer is smaller than 16 bytes. Use an intermediate buffer. */
  (void)pthread_getname_np(pthread_self(), buf, sizeof(buf));
  cnt = ddsrt_strlcpy(str, buf, size);
#elif defined(__APPLE__)
  /* pthread_getname_np on APPLE uses strlcpy to copy the thread name, but
     does not return the number of bytes (that would have been) written. Use
     an intermediate buffer. */
  (void)pthread_getname_np(pthread_self(), buf, sizeof(buf));
  cnt = ddsrt_strlcpy(str, buf, size);
#elif defined(__FreeBSD__)
  (void)pthread_get_name_np(pthread_self(), buf, sizeof(buf));
  cnt = ddsrt_strlcpy(str, buf, size);
#elif defined(__sun)
  (void)pthread_getname_np(pthread_self(), buf, sizeof(buf));
  cnt = ddsrt_strlcpy(str, buf, size);
#elif defined(__VXWORKS__)
  {
    char *ptr;
    /* VxWorks does not support retrieving the name of a task through the
       POSIX thread API, but the task API offers it through taskName. */
    /* Do not free the pointer returned by taskName. See
       src/wind/taskInfo.c for details. */
    ptr = taskName(taskIdSelf());
    if (ptr == NULL) {
      ptr = buf;
    }
    cnt = ddsrt_strlcpy(str, ptr, size);
  }
#endif

  /* Thread identifier is used as fall back if thread name lookup is not
     supported or the thread name is empty. */
  if (cnt == 0) {
    ddsrt_tid_t tid = ddsrt_gettid();
    cnt = (size_t)snprintf(str, size, "%"PRIdTID, tid);
  }

  return cnt;
}

void
ddsrt_thread_setname(const char *__restrict name)
{
  assert(name != NULL);

#if defined(__linux)
  /* Thread names are limited to 16 bytes on Linux. ERANGE is returned if the
     name exceeds the limit, so silently truncate. */
  char buf[MAXTHREADNAMESIZE + 1] = "";
  ddsrt_strlcpy(buf, name, sizeof(buf));
  (void)pthread_setname_np(pthread_self(), name);
#elif defined(__APPLE__)
  (void)pthread_setname_np(name);
#elif defined(__FreeBSD__)
  (void)pthread_set_name_np(pthread_self(), name);
#elif defined(__sun)
  /* Thread names are limited to 31 bytes on Solaris. Excess bytes are
     silently truncated. */
  (void)pthread_setname_np(pthread_self(), name);
#else
  /* VxWorks does not support the task name to be set after a task is created.
     Setting the name of a task can be done through pthread_attr_setname. */
#warning "ddsrt_thread_setname is not supported"
#endif
}

/** \brief Wrap thread start routine
 *
 * \b os_startRoutineWrapper wraps a threads starting routine.
 * before calling the user routine, it sets the threads name
 * in the context of the thread. With \b pthread_getspecific,
 * the name can be retreived for different purposes.
 */
static void *os_startRoutineWrapper (void *threadContext)
{
  thread_context_t *context = threadContext;
  uintptr_t resultValue;

  ddsrt_thread_setname(context->name);

  /* Call the user routine */
  resultValue = context->routine (context->arg);

  /* Free the thread context resources, arguments is responsibility */
  /* for the caller of os_procCreate                                */
  ddsrt_free(context->name);
  ddsrt_free(context);

#if defined(__VXWORKS__) && !defined(_WRS_KERNEL)
  struct sched_param sched_param;
  int max, policy = 0;

  /* There is a known issue in pthread_join on VxWorks 6.x RTP mode.

     WindRiver: When pthread_join returns, it does not indicate end of a
     thread in 100% of the situations. If the thread that calls pthread_join
     has a higher priority than the thread that is currently terminating,
     pthread_join could return before pthread_exit has finished. This
     conflicts with the POSIX specification that dictates that pthread_join
     must only return when the thread is really terminated. The workaround
     suggested by WindRiver support is to increase the priority of the thread
     (task) to be terminated before handing back the semaphore to ensure the
     thread exits before pthread_join returns.

     This bug was submitted to WindRiver as TSR 815826. */

  /* Note that any possible errors raised here are not terminal since the
     thread may have exited at this point anyway. */
  if (pthread_getschedparam(thread.v, &policy, &sched_param) == 0) {
    max = sched_get_priority_max(policy);
    if (max != -1) {
      (void)pthread_setschedprio(thread.v, max);
    }
  }
#endif

  /* return the result of the user routine */
  return (void *)resultValue;
}

dds_retcode_t
ddsrt_thread_create (
  ddsrt_thread_t *threadptr,
  const char *name,
  const ddsrt_threadattr_t *threadAttr,
  uint32_t (*start_routine) (void *),
  void *arg)
{
  pthread_attr_t attr;
  thread_context_t *ctx;
  ddsrt_threadattr_t tattr;
  int result, create_ret;

  assert (threadptr != NULL);
  assert (name != NULL);
  assert (threadAttr != NULL);
  assert (start_routine != NULL);
  tattr = *threadAttr;

  if (pthread_attr_init (&attr) != 0)
    return DDS_RETCODE_ERROR;

#if defined(__VXWORKS__)
  /* pthread_setname_np is not available on VxWorks. Use pthread_attr_setname
     instead (proprietary VxWorks extension). */
  (void)pthread_attr_setname (&attr, name);
#endif

  if (pthread_attr_setscope (&attr, PTHREAD_SCOPE_SYSTEM) != 0 ||
      pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_JOINABLE) != 0)
    goto err;

  if (tattr.stackSize != 0)
  {
#ifdef PTHREAD_STACK_MIN
    if (tattr.stackSize < PTHREAD_STACK_MIN)
      tattr.stackSize = PTHREAD_STACK_MIN;
#endif
    if ((result = pthread_attr_setstacksize (&attr, tattr.stackSize)) != 0)
    {
      DDS_ERROR ("ddsrt_thread_create(%s): pthread_attr_setstacksize(%"PRIu32") failed with error %d\n", name, tattr.stackSize, result);
      goto err;
    }
  }

  if (tattr.schedClass == DDSRT_SCHED_DEFAULT)
  {
    if (tattr.schedPriority != 0)
    {
      /* If caller doesn't set the class, he must not try to set the priority, which we
       approximate by expecting a 0. FIXME: should do this as part of config validation */
      DDS_ERROR("ddsrt_thread_create(%s): schedClass DEFAULT but priority != 0 is unsupported\n", name);
      goto err;
    }
  }
  else
  {
    int policy;
    struct sched_param sched_param;
    if ((result = pthread_getschedparam (pthread_self (), &policy, &sched_param) != 0) != 0)
    {
      DDS_ERROR("ddsrt_thread_create(%s): pthread_attr_getschedparam(self) failed with error %d\n", name, result);
      goto err;
    }
    switch (tattr.schedClass)
    {
      case DDSRT_SCHED_DEFAULT:
        assert (0);
        break;
      case DDSRT_SCHED_REALTIME:
        policy = SCHED_FIFO;
        break;
      case DDSRT_SCHED_TIMESHARE:
        policy = SCHED_OTHER;
        break;
    }
    if ((result = pthread_attr_setschedpolicy (&attr, policy)) != 0)
    {
      DDS_ERROR("ddsrt_thread_create(%s): pthread_attr_setschedpolicy(%d) failed with error %d\n", name, policy, result);
      goto err;
    }
    sched_param.sched_priority = tattr.schedPriority;
    if ((result = pthread_attr_setschedparam (&attr, &sched_param)) != 0)
    {
      DDS_ERROR("ddsrt_thread_create(%s): pthread_attr_setschedparam(priority = %d) failed with error %d\n", name, tattr.schedPriority, result);
      goto err;
    }
    if ((result = pthread_attr_setinheritsched (&attr, PTHREAD_EXPLICIT_SCHED)) != 0)
    {
      DDS_ERROR("ddsrt_thread_create(%s): pthread_attr_setinheritsched(EXPLICIT) failed with error %d\n", name, result);
      goto err;
    }
  }

  /* Construct context structure & start thread */
  ctx = ddsrt_malloc (sizeof (thread_context_t));
  ctx->name = ddsrt_malloc (strlen (name) + 1);
  strcpy (ctx->name, name);
  ctx->routine = start_routine;
  ctx->arg = arg;
  if ((create_ret = pthread_create (&threadptr->v, &attr, os_startRoutineWrapper, ctx)) != 0)
  {
    DDS_ERROR ("os_threadCreate(%s): pthread_create failed with error %d\n", name, create_ret);
    goto err_create;
  }
  pthread_attr_destroy (&attr);
  return DDS_RETCODE_OK;

err_create:
  ddsrt_free (ctx->name);
  ddsrt_free (ctx);
err:
  pthread_attr_destroy (&attr);
  return DDS_RETCODE_ERROR;
}

ddsrt_tid_t
ddsrt_gettid(void)
{
  ddsrt_tid_t tid;

#if defined(__linux)
  tid = syscall(SYS_gettid);
#elif defined(__FreeBSD__) && (__FreeBSD_version >= 900031)
  /* FreeBSD >= 9.0 */
  tid = pthread_getthreadid_np();
#elif defined(__APPLE__) && !(defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && \
                                      __MAC_OS_X_VERSION_MIN_REQUIRED < 1060)
  /* macOS >= 10.6 */
  pthread_threadid_np(NULL, &tid);
#elif defined(__VXWORKS__)
  tid = taskIdSelf();
#else
  tid = (uintmax_t)((uintptr_t)pthread_self());
#endif

  return tid;
}

ddsrt_thread_t
ddsrt_thread_self(void)
{
  ddsrt_thread_t id = {.v = pthread_self ()};
  return id;
}

bool ddsrt_thread_equal(ddsrt_thread_t a, ddsrt_thread_t b)
{
  return (pthread_equal(a.v, b.v) != 0);
}

dds_retcode_t
ddsrt_thread_join(ddsrt_thread_t thread, uint32_t *thread_result)
{
  int err;
  void *vthread_result;

  assert (thread.v);


  if ((err = pthread_join (thread.v, &vthread_result)) != 0)
  {
    DDS_TRACE ("pthread_join(0x%"PRIxMAX") failed with error %d\n", (uintmax_t)((uintptr_t)thread.v), err);
    return DDS_RETCODE_ERROR;
  }

  if (thread_result)
    *thread_result = (uint32_t) ((uintptr_t) vthread_result);
  return DDS_RETCODE_OK;
}


static pthread_key_t thread_cleanup_key;
static pthread_once_t thread_once = PTHREAD_ONCE_INIT;

static void thread_cleanup_fini(void *arg);

static void thread_init_once(void)
{
  int err;

  err = pthread_key_create(&thread_cleanup_key, &thread_cleanup_fini);
  assert(err == 0);
  (void)err;
}

static void thread_init(void)
{
  (void)pthread_once(&thread_once, &thread_init_once);
}

dds_retcode_t ddsrt_thread_cleanup_push (void (*routine) (void *), void *arg)
{
  int err;
  thread_cleanup_t *prev, *tail;

  assert(routine != NULL);

  thread_init();
  if ((tail = ddsrt_calloc(1, sizeof(*tail))) != NULL) {
    prev = pthread_getspecific(thread_cleanup_key);
    tail->prev = prev;
    tail->routine = routine;
    tail->arg = arg;
    if ((err = pthread_setspecific(thread_cleanup_key, tail)) != 0) {
      assert(err != EINVAL);
      return DDS_RETCODE_OUT_OF_RESOURCES;
    }
    return DDS_RETCODE_OK;
  }
  return DDS_RETCODE_OUT_OF_RESOURCES;
}

dds_retcode_t ddsrt_thread_cleanup_pop (int execute)
{
  int err;
  thread_cleanup_t *tail;

  thread_init();
  if ((tail = pthread_getspecific(thread_cleanup_key)) != NULL) {
    if ((err = pthread_setspecific(thread_cleanup_key, tail->prev)) != 0) {
      assert(err != EINVAL);
      return DDS_RETCODE_OUT_OF_RESOURCES;
    }
    if (execute) {
      tail->routine(tail->arg);
    }
    ddsrt_free(tail);
  }
  return DDS_RETCODE_OK;
}

static void thread_cleanup_fini(void *arg)
{
  thread_cleanup_t *tail, *prev;

  tail = (thread_cleanup_t *)arg;
  while (tail != NULL) {
    prev = tail->prev;
    assert(tail->routine != NULL);
    tail->routine(tail->arg);
    ddsrt_free(tail);
    tail = prev;
  }

  /* Thread-specific value associated with thread_cleanup_key will already be
     nullified if invoked as destructor, i.e. not from ddsrt_thread_fini. */
}

void ddsrt_thread_init(void)
{
  thread_init();
}

void ddsrt_thread_fini(void)
{
  thread_cleanup_t *tail;

  thread_init();
  if ((tail = pthread_getspecific(thread_cleanup_key)) != NULL) {
    thread_cleanup_fini(tail);
    (void)pthread_setspecific(thread_cleanup_key, NULL);
  }
}
