/* Threads <threads.h>

   This file is part of the Public Domain C Library (PDCLib).
   Permission is granted to use, modify, and / or redistribute at will.
*/

/* This header does not include any platform-specific information, but as
   a whole is optional (depending on whether threads are supported or not,
   ref. __STDC_NO_THREADS__), which is why it is located in the example
   platform instead of the general include directory.
*/

#ifndef _PDCLIB_THREADS_H
#define _PDCLIB_THREADS_H _PDCLIB_THREADS_H

#ifdef __cplusplus
extern "C" {
#endif

#include <time.h>

#if __STDC_NO_THREADS__ == 1
#error __STDC_NO_THREADS__ defined but <threads.h> included. Something is wrong about your setup.
#endif

#if __STDC_VERSION__ >= 201112L
/* The rest of <threads.h> can work with a pre-C11 compiler just fine. */
#define thread_local _Thread_local
#endif

/* Initializing value for an object of type once_flag */
#define ONCE_FLAG_INIT _PDCLIB_ONCE_FLAG_INIT

/* Maximum number of times destructors are called on thread termination */
#define TSS_DTOR_ITERATIONS _PDCLIB_TSS_DTOR_ITERATIONS

/* Condition variable */
typedef _PDCLIB_cnd_t cnd_t;

/* Thread */
typedef _PDCLIB_thrd_t thrd_t;

/* Thread-specific storage */
typedef _PDCLIB_tss_t tss_t;

/* Mutex */
typedef _PDCLIB_mtx_t mtx_t;

/* TSS destructor */
typedef void (*tss_dtor_t)( void * );

/* Thread start function */
typedef int (*thrd_start_t)( void * );

/* Flag for use with call_once() */
typedef _PDCLIB_once_flag once_flag;

/* TODO: Documentation. */
enum
{
    mtx_plain,
    mtx_recursive,
    mtx_timed
};

/* TODO: Documentation. */
enum
{
    thrd_timedout,
    thrd_success,
    thrd_busy,
    thrd_error,
    thrd_nomem
};

/* Initialization functions */

/* Ensure that func is called only the first time call_once() is called
   for a given flag.
*/
_PDCLIB_PUBLIC void call_once( once_flag * flag, void (*func)( void ) );

/* Condition variable functions */

/* Unblock threads waiting on given condition.
   Returns thrd_success if successful, thrd_error if the request could not
   be honored.
*/
_PDCLIB_PUBLIC int cnd_broadcast( cnd_t * cond );

/* Destroy condition variable.
   No threads may be waiting on a condition when it is destroyed.
*/
_PDCLIB_PUBLIC void cnd_destroy( cnd_t * cond );

/* Initialize condition variable.
   Returns thrd_success if successful, thrd_nomem if out of memory, and
   thrd_error if the request could not be honored.
   Initializes the variable in a way that a thread calling cnd_wait() on it
   would block.
*/
_PDCLIB_PUBLIC int cnd_init( cnd_t * cond );

/* Unblock one thread waiting on the condition variable.
   Returns thrd_success if successful, thrd_error if the request could not
   be honored.
*/
_PDCLIB_PUBLIC int cnd_signal( cnd_t * cond );

/* TODO: Documentation.
   Returns thrd_success if successful, thrd_timedout if the specified time
   is reached without acquiring the resource, or thrd_error if the request
   could not be honored.
*/
_PDCLIB_PUBLIC int cnd_timedwait( cnd_t * _PDCLIB_restrict cond, mtx_t * _PDCLIB_restrict mtx, const struct timespec * _PDCLIB_restrict ts );

/* TODO: Documentation.
   Returns thrd_success if successful, thrd_error if the request could not
   be honored.
*/ 
int cnd_wait( cnd_t * cond, mtx_t * mtx );

/* Mutex functions */

/* Destroy mutex variable.
   No threads may be waiting on a mutex when it is destroyed.
*/
_PDCLIB_PUBLIC void mtx_destroy( mtx_t * mtx );

/* Initialize mutex variable.
   Returns thrd_success if successful, thrd_error if the request could not
   be honored.
   Type must have one of the following values:
   mtx_plain                 -- non-recursive mutex
   mtx_timed                 -- non-recursive mutex supporting timeout
   mtx_plain | mtx_recursive -- recursive mutex
   mtx_timed | mtx_recursive -- recursive mutex supporting timeout
*/
_PDCLIB_PUBLIC int mtx_init( mtx_t * mtx, int type );

/* Try to lock the given mutex (blocking).
   Returns thrd_success if successful, thrd_error if the request could not
   be honored.
   If the given mutex is non-recursive, it must not be already locked by
   the calling thread.
*/
_PDCLIB_PUBLIC int mtx_lock( mtx_t * mtx );

/* TODO: Documentation.
   Returns thrd_success if successful, thrd_timedout if the specified time
   is reached without acquiring the resource, or thrd_error if the request
   could not be honored.
*/
_PDCLIB_PUBLIC int mtx_timedlock( mtx_t * _PDCLIB_restrict mtx, const struct timespec * _PDCLIB_restrict ts );

/* Try to lock the given mutex (non-blocking).
   Returns thrd_success if successful, thrd_busy if the resource is already
   locked, or thrd_error if the request could not be honored.
*/
_PDCLIB_PUBLIC int mtx_trylock( mtx_t * mtx );

/* Unlock the given mutex.
   Returns thrd_success if successful, thrd_error if the request could not
   be honored.
   The given mutex must be locked by the calling thread.
*/
_PDCLIB_PUBLIC int mtx_unlock( mtx_t * mtx );

/* Thread functions */

/* Create a new thread.
   Returns thrd_success if successful, thrd_nomem if out of memory, and
   thrd_error if the request could not be honored.
   Create a new thread executing func( arg ), and sets thr to identify
   the created thread. (Identifiers may be reused afer a thread exited
   and was either detached or joined.)
*/
_PDCLIB_PUBLIC int thrd_create( thrd_t * thr, thrd_start_t func, void * arg );

/* Identify the calling thread.
   Returns the identifier of the calling thread.
*/
_PDCLIB_PUBLIC thrd_t thrd_current( void );

/* Notify the OS to destroy all resources of a given thread once it
   terminates.
   Returns thrd_success if successful, thrd_error if the request could not
   be honored.
   The given thread must not been previously detached or joined.
*/
_PDCLIB_PUBLIC int thrd_detach( thrd_t thr );

/* Compare two thread identifiers for equality.
   Returns nonzero if both parameters identify the same thread, zero
   otherwise.
*/
_PDCLIB_PUBLIC int thrd_equal( thrd_t thr0, thrd_t thr1 );

/* Terminate calling thread, set result code to res.
   When the last thread of a program terminates the program shall terminate
   normally as if by exit( EXIT_SUCCESS ).
*/
_PDCLIB_PUBLIC _PDCLIB_Noreturn void thrd_exit( int res );

/* Join the given thread with the calling thread.
   Returns thrd_success if successful, thrd_error if the request could not
   be honored.
   Function blocks until the given thread terminates. If res is not NULL,
   the given thread's result code will be stored at that address.
*/
_PDCLIB_PUBLIC int thrd_join( thrd_t thr, int * res );

/* Suspend the calling thread for the given duration or until a signal not
   being ignored is received.
   Returns zero if the requested time has elapsed, -1 if interrupted by a
   signal, negative if the request failed.
   If remaining is not NULL, and the sleeping thread received a signal that
   is not being ignored, the remaining time (duration minus actually elapsed
   time) shall be stored at that address.
*/
_PDCLIB_PUBLIC int thrd_sleep( const struct timespec * duration, struct timespec * remaining );

/* Permit other threads to run. */
_PDCLIB_PUBLIC void thrd_yield( void );

/* Thread-specific storage functions */

/* Initialize thread-specific storage, with optional destructor
   Returns thrs_success if successful, thrd_error otherwise (in this case
   key is set to an undefined value).
*/
_PDCLIB_PUBLIC int tss_create( tss_t * key, tss_dtor_t dtor );

/* Release all resources of a given thread-specific storage. */
_PDCLIB_PUBLIC void tss_delete( tss_t key );

/* Returns the value for the current thread associated with the given key.
*/
_PDCLIB_PUBLIC void * tss_get( tss_t key );

/* Sets the value associated with the given key for the current thread.
   Returns thrd_success if successful, thrd_error if the request could not
   be honored.
*/
_PDCLIB_PUBLIC int tss_set( tss_t key, void * val );

/* Extension hook for downstream projects that want to have non-standard
   extensions to standard headers.
*/
#ifdef _PDCLIB_EXTEND_THREADS_H
#include _PDCLIB_EXTEND_THREADS_H
#endif

#ifdef __cplusplus
}
#endif

#endif
