/* -*- mode: c; tab-width: 2; indent-tabs-mode: nil; -*-
Copyright (c) 2012 Marcus Geelnard
Copyright (c) 2013-2016 Evan Nemerson

This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.

Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:

    1. The origin of this software must not be misrepresented; you must not
    claim that you wrote the original software. If you use this software
    in a product, an acknowledgment in the product documentation would be
    appreciated but is not required.

    2. Altered source versions must be plainly marked as such, and must not be
    misrepresented as being the original software.

    3. This notice may not be removed or altered from any source
    distribution.
*/

#ifndef _TINYCTHREAD_H_
#define _TINYCTHREAD_H_

#ifdef __cplusplus
extern "C" {
#endif

// jcheng 2017-11-03: _XOPEN_SOURCE 600 is necessary to prevent Solaris headers
// from complaining about the combination of C99 and _XOPEN_SOURCE <= 500. The
// error message starts with:
// "Compiler or options invalid for pre-UNIX 03 X/Open applications"
#if defined(sun) && (__STDC_VERSION__ - 0 >= 199901L) &&                       \
    (!defined(_XOPEN_SOURCE) || ((_XOPEN_SOURCE - 0) < 600))
#undef _XOPEN_SOURCE
#define _XOPEN_SOURCE 600
#endif

/**
 * @file
 * @mainpage TinyCThread API Reference
 *
 * @section intro_sec Introduction
 * TinyCThread is a minimal, portable implementation of basic threading
 * classes for C.
 *
 * They closely mimic the functionality and naming of the C11 standard, and
 * should be easily replaceable with the corresponding standard variants.
 *
 * @section port_sec Portability
 * The Win32 variant uses the native Win32 API for implementing the thread
 * classes, while for other systems, the POSIX threads API (pthread) is used.
 *
 * @section misc_sec Miscellaneous
 * The following special keywords are available: #_Thread_local.
 *
 * For more detailed information, browse the different sections of this
 * documentation. A good place to start is:
 * tinycthread.h.
 */

/* Which platform are we on? */
#if !defined(_TTHREAD_PLATFORM_DEFINED_)
#if defined(_WIN32) || defined(__WIN32__) || defined(__WINDOWS__)
#define _TTHREAD_WIN32_
#else
#define _TTHREAD_POSIX_
#endif
#define _TTHREAD_PLATFORM_DEFINED_
#endif

/* Activate some POSIX functionality (e.g. clock_gettime and recursive mutexes)
 */
#if defined(_TTHREAD_POSIX_)
#undef _FEATURES_H
#if !defined(_GNU_SOURCE)
#define _GNU_SOURCE
#endif
#if !defined(_POSIX_C_SOURCE) || ((_POSIX_C_SOURCE - 0) < 199309L)
#undef _POSIX_C_SOURCE
#define _POSIX_C_SOURCE 199309L
#endif
#if !defined(_XOPEN_SOURCE) || ((_XOPEN_SOURCE - 0) < 500)
#undef _XOPEN_SOURCE
#define _XOPEN_SOURCE 500
#endif
#define _XPG6
#endif

/* Generic includes */
#include <time.h>

/* Platform specific includes */
#if defined(_TTHREAD_POSIX_)
#include <pthread.h>
#elif defined(_TTHREAD_WIN32_)
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#define __UNDEF_LEAN_AND_MEAN
#endif
#include <windows.h>
#ifdef __UNDEF_LEAN_AND_MEAN
#undef WIN32_LEAN_AND_MEAN
#undef __UNDEF_LEAN_AND_MEAN
#endif
#endif

#include "badthreads.h"

/* Compiler-specific information */
#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L
#define TTHREAD_NORETURN _Noreturn
#elif defined(__GNUC__)
#define TTHREAD_NORETURN __attribute__((__noreturn__))
#else
#define TTHREAD_NORETURN
#endif

/* If TIME_UTC is missing, provide it and provide a wrapper for
   timespec_get. */
#ifndef TIME_UTC
#define TIME_UTC 1
#define _TTHREAD_EMULATE_TIMESPEC_GET_

#if defined(_TTHREAD_WIN32_)
struct _tthread_timespec {
  time_t tv_sec;
  long tv_nsec;
};
#define timespec _tthread_timespec
#endif

int _tthread_timespec_get(struct timespec *ts, int base);
#define timespec_get _tthread_timespec_get
#endif

/** TinyCThread version (major number). */
#define TINYCTHREAD_VERSION_MAJOR 1
/** TinyCThread version (minor number). */
#define TINYCTHREAD_VERSION_MINOR 2
/** TinyCThread version (full version). */
#define TINYCTHREAD_VERSION                                                    \
  (TINYCTHREAD_VERSION_MAJOR * 100 + TINYCTHREAD_VERSION_MINOR)

/**
 * @def _Thread_local
 * Thread local storage keyword.
 * A variable that is declared with the @c _Thread_local keyword makes the
 * value of the variable local to each thread (known as thread-local storage,
 * or TLS). Example usage:
 * @code
 * // This variable is local to each thread.
 * _Thread_local int variable;
 * @endcode
 * @note The @c _Thread_local keyword is a macro that maps to the corresponding
 * compiler directive (e.g. @c __declspec(thread)).
 * @note This directive is currently not supported on Mac OS X (it will give
 * a compiler error), since compile-time TLS is not supported in the Mac OS X
 * executable format. Also, some older versions of MinGW (before GCC 4.x) do
 * not support this directive, nor does the Tiny C Compiler.
 * @hideinitializer
 */

#if !(defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201102L)) &&           \
    !defined(_Thread_local)
#if defined(__GNUC__) || defined(__INTEL_COMPILER) || defined(__SUNPRO_CC) ||  \
    defined(__IBMCPP__)
#define _Thread_local __thread
#else
#define _Thread_local __declspec(thread)
#endif
#elif defined(__GNUC__) && defined(__GNUC_MINOR__) &&                          \
    (((__GNUC__ << 8) | __GNUC_MINOR__) < ((4 << 8) | 9))
#define _Thread_local __thread
#endif

/* Macros */
#if defined(_TTHREAD_WIN32_)
#define TCT_TSS_DTOR_ITERATIONS (4)
#else
#define TCT_TSS_DTOR_ITERATIONS PTHREAD_DESTRUCTOR_ITERATIONS
#endif

/* Function return values */
#define tct_thrd_error 0   /**< The requested operation failed */
#define tct_thrd_success 1 /**< The requested operation succeeded */
#define tct_thrd_timedout                                                      \
  2 /**< The time specified in the call was reached without acquiring the      \
       requested resource */
#define tct_thrd_busy                                                          \
  3 /**< The requested operation failed because a tesource requested by a test \
       and return function is already in use */
#define tct_thrd_nomem                                                         \
  4 /**< The requested operation failed because it was unable to allocate      \
       memory */

/* Mutex types */
#define tct_mtx_plain 0
#define tct_mtx_timed 1
#define tct_mtx_recursive 2

/* Mutex */
#if defined(_TTHREAD_WIN32_)
typedef struct {
  union {
    CRITICAL_SECTION
    cs;               /* Critical section handle (used for non-timed mutexes) */
    HANDLE mut;       /* Mutex handle (used for timed mutex) */
  } mHandle;          /* Mutex handle */
  int mAlreadyLocked; /* TRUE if the mutex is already locked */
  int mRecursive;     /* TRUE if the mutex is recursive */
  int mTimed;         /* TRUE if the mutex is timed */
} tct_mtx_t;
#else
typedef pthread_mutex_t tct_mtx_t;
#endif

/** Create a mutex object.
 * @param mtx A mutex object.
 * @param type Bit-mask that must have one of the following six values:
 *   @li @c mtx_plain for a simple non-recursive mutex
 *   @li @c mtx_timed for a non-recursive mutex that supports timeout
 *   @li @c mtx_plain | @c mtx_recursive (same as @c mtx_plain, but recursive)
 *   @li @c mtx_timed | @c mtx_recursive (same as @c mtx_timed, but recursive)
 * @return @ref thrd_success on success, or @ref thrd_error if the request could
 * not be honored.
 */
int tct_mtx_init(tct_mtx_t *mtx, int type);

/** Release any resources used by the given mutex.
 * @param mtx A mutex object.
 */
void tct_mtx_destroy(tct_mtx_t *mtx);

/** Lock the given mutex.
 * Blocks until the given mutex can be locked. If the mutex is non-recursive,
 * and the calling thread already has a lock on the mutex, this call will block
 * forever.
 * @param mtx A mutex object.
 * @return @ref thrd_success on success, or @ref thrd_error if the request could
 * not be honored.
 */
int tct_mtx_lock(tct_mtx_t *mtx);

/** Lock the given mutex, or block until a specific point in time.
 * Blocks until either the given mutex can be locked, or the specified TIME_UTC
 * based time.
 * @param mtx A mutex object.
 * @param ts A UTC based calendar time
 * @return @ref The mtx_timedlock function returns thrd_success on success, or
 * thrd_timedout if the time specified was reached without acquiring the
 * requested resource, or thrd_error if the request could not be honored.
 */
int tct_mtx_timedlock(tct_mtx_t *mtx, const struct timespec *ts);

/** Try to lock the given mutex.
 * The specified mutex shall support either test and return or timeout. If the
 * mutex is already locked, the function returns without blocking.
 * @param mtx A mutex object.
 * @return @ref thrd_success on success, or @ref thrd_busy if the resource
 * requested is already in use, or @ref thrd_error if the request could not be
 * honored.
 */
int tct_mtx_trylock(tct_mtx_t *mtx);

/** Unlock the given mutex.
 * @param mtx A mutex object.
 * @return @ref thrd_success on success, or @ref thrd_error if the request could
 * not be honored.
 */
int tct_mtx_unlock(tct_mtx_t *mtx);

/* Condition variable */
#if defined(_TTHREAD_WIN32_)
typedef struct {
  HANDLE mEvents[2];                  /* Signal and broadcast event HANDLEs. */
  unsigned int mWaitersCount;         /* Count of the number of waiters. */
  CRITICAL_SECTION mWaitersCountLock; /* Serialize access to mWaitersCount. */
} tct_cnd_t;
#else
typedef pthread_cond_t tct_cnd_t;
#endif

/** Create a condition variable object.
 * @param cond A condition variable object.
 * @return @ref thrd_success on success, or @ref thrd_error if the request could
 * not be honored.
 */
int tct_cnd_init(tct_cnd_t *cond);

/** Release any resources used by the given condition variable.
 * @param cond A condition variable object.
 */
void tct_cnd_destroy(tct_cnd_t *cond);

/** Signal a condition variable.
 * Unblocks one of the threads that are blocked on the given condition variable
 * at the time of the call. If no threads are blocked on the condition variable
 * at the time of the call, the function does nothing and return success.
 * @param cond A condition variable object.
 * @return @ref thrd_success on success, or @ref thrd_error if the request could
 * not be honored.
 */
int tct_cnd_signal(tct_cnd_t *cond);

/** Broadcast a condition variable.
 * Unblocks all of the threads that are blocked on the given condition variable
 * at the time of the call. If no threads are blocked on the condition variable
 * at the time of the call, the function does nothing and return success.
 * @param cond A condition variable object.
 * @return @ref thrd_success on success, or @ref thrd_error if the request could
 * not be honored.
 */
int tct_cnd_broadcast(tct_cnd_t *cond);

/** Wait for a condition variable to become signaled.
 * The function atomically unlocks the given mutex and endeavors to block until
 * the given condition variable is signaled by a call to cnd_signal or to
 * cnd_broadcast. When the calling thread becomes unblocked it locks the mutex
 * before it returns.
 * @param cond A condition variable object.
 * @param mtx A mutex object.
 * @return @ref thrd_success on success, or @ref thrd_error if the request could
 * not be honored.
 */
int tct_cnd_wait(tct_cnd_t *cond, tct_mtx_t *mtx);

/** Wait for a condition variable to become signaled.
 * The function atomically unlocks the given mutex and endeavors to block until
 * the given condition variable is signaled by a call to cnd_signal or to
 * cnd_broadcast, or until after the specified time. When the calling thread
 * becomes unblocked it locks the mutex before it returns.
 * @param cond A condition variable object.
 * @param mtx A mutex object.
 * @param xt A point in time at which the request will time out (absolute time).
 * @return @ref thrd_success upon success, or @ref thrd_timeout if the time
 * specified in the call was reached without acquiring the requested resource,
 * or
 * @ref thrd_error if the request could not be honored.
 */
int tct_cnd_timedwait(tct_cnd_t *cond, tct_mtx_t *mtx,
                      const struct timespec *ts);

/* Thread */
#if defined(_TTHREAD_WIN32_)
typedef HANDLE tct_thrd_t;
#else
typedef pthread_t tct_thrd_t;
#endif

/** Thread start function.
 * Any thread that is started with the @ref thrd_create() function must be
 * started through a function of this type.
 * @param arg The thread argument (the @c arg argument of the corresponding
 *        @ref thrd_create() call).
 * @return The thread return value, which can be obtained by another thread
 * by using the @ref thrd_join() function.
 */
typedef int (*tct_thrd_start_t)(void *arg);

/** Create a new thread.
 * @param thr Identifier of the newly created thread.
 * @param func A function pointer to the function that will be executed in
 *        the new thread.
 * @param arg An argument to the thread function.
 * @return @ref thrd_success on success, or @ref thrd_nomem if no memory could
 * be allocated for the thread requested, or @ref thrd_error if the request
 * could not be honored.
 * @note A thread’s identifier may be reused for a different thread once the
 * original thread has exited and either been detached or joined to another
 * thread.
 */
int tct_thrd_create(tct_thrd_t *thr, tct_thrd_start_t func, void *arg);

/** Identify the calling thread.
 * @return The identifier of the calling thread.
 */
tct_thrd_t tct_thrd_current(void);

/** Dispose of any resources allocated to the thread when that thread exits.
 * @return thrd_success, or thrd_error on error
 */
int tct_thrd_detach(tct_thrd_t thr);

/** Compare two thread identifiers.
 * The function determines if two thread identifiers refer to the same thread.
 * @return Zero if the two thread identifiers refer to different threads.
 * Otherwise a nonzero value is returned.
 */
int tct_thrd_equal(tct_thrd_t thr0, tct_thrd_t thr1);

/** Terminate execution of the calling thread.
 * @param res Result code of the calling thread.
 */
TTHREAD_NORETURN void tct_thrd_exit(int res);

/** Wait for a thread to terminate.
 * The function joins the given thread with the current thread by blocking
 * until the other thread has terminated.
 * @param thr The thread to join with.
 * @param res If this pointer is not NULL, the function will store the result
 *        code of the given thread in the integer pointed to by @c res.
 * @return @ref thrd_success on success, or @ref thrd_error if the request could
 * not be honored.
 */
int tct_thrd_join(tct_thrd_t thr, int *res);

/** Put the calling thread to sleep.
 * Suspend execution of the calling thread.
 * @param duration  Interval to sleep for
 * @param remaining If non-NULL, this parameter will hold the remaining
 *                  time until time_point upon return. This will
 *                  typically be zero, but if the thread was woken up
 *                  by a signal that is not ignored before duration was
 *                  reached @c remaining will hold a positive time.
 * @return 0 (zero) on successful sleep, -1 if an interrupt occurred,
 *         or a negative value if the operation fails.
 */
int tct_thrd_sleep(const struct timespec *duration, struct timespec *remaining);

/** Yield execution to another thread.
 * Permit other threads to run, even if the current thread would ordinarily
 * continue to run.
 */
void tct_thrd_yield(void);

/* Thread local storage */
#if defined(_TTHREAD_WIN32_)
typedef DWORD tct_tss_t;
#else
typedef pthread_key_t tct_tss_t;
#endif

/** Destructor function for a thread-specific storage.
 * @param val The value of the destructed thread-specific storage.
 */
typedef void (*tct_tss_dtor_t)(void *val);

/** Create a thread-specific storage.
 * @param key The unique key identifier that will be set if the function is
 *        successful.
 * @param dtor Destructor function. This can be NULL.
 * @return @ref thrd_success on success, or @ref thrd_error if the request could
 * not be honored.
 * @note On Windows, the @c dtor will definitely be called when
 * appropriate for threads created with @ref thrd_create.  It will be
 * called for other threads in most cases, the possible exception being
 * for DLLs loaded with LoadLibraryEx.  In order to be certain, you
 * should use @ref thrd_create whenever possible.
 */
int tct_tss_create(tct_tss_t *key, tct_tss_dtor_t dtor);

/** Delete a thread-specific storage.
 * The function releases any resources used by the given thread-specific
 * storage.
 * @param key The key that shall be deleted.
 */
void tct_tss_delete(tct_tss_t key);

/** Get the value for a thread-specific storage.
 * @param key The thread-specific storage identifier.
 * @return The value for the current thread held in the given thread-specific
 * storage.
 */
void *tct_tss_get(tct_tss_t key);

/** Set the value for a thread-specific storage.
 * @param key The thread-specific storage identifier.
 * @param val The value of the thread-specific storage to set for the current
 *        thread.
 * @return @ref thrd_success on success, or @ref thrd_error if the request could
 * not be honored.
 */
int tct_tss_set(tct_tss_t key, void *val);

#if defined(_TTHREAD_WIN32_)
typedef struct {
  LONG volatile status;
  CRITICAL_SECTION lock;
} tct_once_flag;
#define TCT_ONCE_FLAG_INIT                                                     \
  {                                                                            \
      0,                                                                       \
  }
#else
typedef pthread_once_t tct_once_flag;
#define TCT_ONCE_FLAG_INIT PTHREAD_ONCE_INIT
#endif

/** Invoke a callback exactly once
 * @param flag Flag used to ensure the callback is invoked exactly
 *        once.
 * @param func Callback to invoke.
 */
#if defined(_TTHREAD_WIN32_)
void tct_call_once(tct_once_flag *flag, void (*func)(void));
#else
#define tct_call_once(flag, func) pthread_once(flag, func)
#endif

#ifdef __cplusplus
}
#endif

#endif /* _TINYTHREAD_H_ */
