/*************************************************************************\
* Copyright (c) 2002 The University of Chicago, as Operator of Argonne
*     National Laboratory.
* Copyright (c) 2002 The Regents of the University of California, as
*     Operator of Los Alamos National Laboratory.
* Copyright (c) 2013 ITER Organization.
* SPDX-License-Identifier: EPICS
* EPICS BASE is distributed subject to a Software License Agreement found
* in file LICENSE that is included with this distribution.
\*************************************************************************/

/**
 * \file epicsThread.h
 *
 * \brief C++ and C descriptions for a thread.
 *
 * The epicsThread API is meant as a somewhat minimal interface for
 * multithreaded applications. It can be implementedon a wide variety of
 * systems with the restriction that the system MUST support a
 * multithreaded environment.
 * A POSIX pthreads version is provided.
 *
 * The interface provides the following thread facilities,
 * with restrictions as noted:
 * - Life cycle: a thread starts life as a result of a call to
 *   epicsThreadCreate. It terminates when the thread function returns.
 *   It should not return until it has released all resources it uses.
 *   If a thread is expected to terminate as a natural part of its life
 *   cycle then the thread function must return.
 * - epicsThreadOnce: this provides the ability to have an
 *   initialization function that is guaranteed to be called exactly
 *   once.
 * - main: if a main routine finishes its work but wants to leave other
 *   threads running it can call epicsThreadExitMain, which should be
 *   the last statement in main.
 * - Priorities: ranges between 0 and 99 with a higher number meaning
 *   higher priority. A number of constants are defined for iocCore
 *   specific threads. The underlying implementation may collapse the
 *   range 0 to 99 into a smaller range; even a single priority. User
 *   code should never rely on the existence of multiple thread
 *   priorities to guarantee correct behavior.
 * - Stack Size: epicsThreadCreate accepts a stack size parameter. Three
 *   generic sizes are available: small, medium,and large. Portable code
 *   should always use one of the generic sizes. Some implementation may
 *   ignore the stack size request and use a system default instead.
 *   Virtual memory systems providing generous stack sizes can be
 *   expected to use the system default.
 * - epicsThreadId: every epicsThread has an Id which gets returned by
 *   epicsThreadCreate and is valid as long as that thread still exists.
 *   A value of 0 always means no thread. If a threadId is used after
 *   the thread has terminated,the results are not defined (but will
 *   normally lead to bad things happening). Thus code that looks after
 *   other threads MUST be aware of threads terminating.
 */

#ifndef epicsThreadh
#define epicsThreadh

#include <stddef.h>

#include "libComAPI.h"

#ifdef __cplusplus
extern "C" {
#endif

typedef void (*EPICSTHREADFUNC)(void *parm);

/** @name Some Named Thread Priorities
 * @{
 */
#define epicsThreadPriorityMax          99
#define epicsThreadPriorityMin           0

/* some generic values */
#define epicsThreadPriorityLow          10
#define epicsThreadPriorityMedium       50
#define epicsThreadPriorityHigh         90

/* some iocCore specific values */
#define epicsThreadPriorityCAServerLow  20
#define epicsThreadPriorityCAServerHigh 40
#define epicsThreadPriorityScanLow      60
#define epicsThreadPriorityScanHigh     70
#define epicsThreadPriorityIocsh        91
#define epicsThreadPriorityBaseMax      91
/** @} */

/** Stack sizes for each stackSizeClass are implementation and CPU dependent. */
typedef enum {
    epicsThreadStackSmall, epicsThreadStackMedium, epicsThreadStackBig
} epicsThreadStackSizeClass;

typedef enum {
    epicsThreadBooleanStatusFail, epicsThreadBooleanStatusSuccess
} epicsThreadBooleanStatus;

/**
 * Get a stack size value that can be given to epicsThreadCreate().
 * \param size one of the values epicsThreadStackSmall,
 * epicsThreadStackMedium or epicsThreadStackBig.
 **/
LIBCOM_API unsigned int epicsStdCall epicsThreadGetStackSize(
    epicsThreadStackSizeClass size);

/** (epicsThreadId)0 is guaranteed to be an invalid thread id */
typedef struct epicsThreadOSD *epicsThreadId;

typedef epicsThreadId epicsThreadOnceId;
#define EPICS_THREAD_ONCE_INIT 0

/** Perform one-time initialization.
 *
 * Run the provided function if it has not run, and is not running in
 * some other thread.
 *
 * For each unique epicsThreadOnceId, epicsThreadOnce guarantees that
 * -# myInitFunc will only be called only once.
 * -# myInitFunc will have returned before any other epicsThreadOnce
 *   call using the same epicsThreadOnceId returns.
 *
 * \code
 * static epicsThreadOnceId onceId = EPICS_THREAD_ONCE_INIT;
 * static void myInitFunc(void *arg) { ... }
 * static void some Function(void) {
 *     epicsThreadOnce(&onceId, &myInitFunc, NULL);
 * }
 * \endcode
 */
LIBCOM_API void epicsStdCall epicsThreadOnce(
    epicsThreadOnceId *id, EPICSTHREADFUNC, void *arg);

/**
 * When real-time scheduling is active, attempt any post-init operations
 * that preserve real-time performance. For POSIX targets this locks the
 * process into RAM, preventing swap-related VM faults.
 **/
LIBCOM_API void epicsThreadRealtimeLock(void);

/**
 * If the main routine is done but wants to let other threads run it can
 * call this routine. This should be the last call in main, except the
 * final return. On most systems epicsThreadExitMain never returns.This
 * must only be called by the main thread.
 **/
LIBCOM_API void epicsStdCall epicsThreadExitMain(void);

/** For use with epicsThreadCreateOpt() */
typedef struct epicsThreadOpts {
    /** Thread priority in OSI range (cf. epicsThreadPriority*) */
    unsigned int priority;
    /** Thread stack size, either in bytes for this architecture or
     * an enum epicsThreadStackSizeClass value.
     */
    unsigned int stackSize;
    /** Should thread be joinable? (default (0) is not joinable).
     * If joinable=1, then epicsThreadMustJoin() must be called for cleanup thread resources.
     */
    unsigned int joinable;
} epicsThreadOpts;

/** Default initial values for epicsThreadOpts
 * Applications should always use this macro to initialize an epicsThreadOpts
 * structure. Additional fields may be added in the future, and the order of
 * the fields might also change, thus code that assumes the above definition
 * might break if these rules are not followed.
 */
#define EPICS_THREAD_OPTS_INIT { \
    epicsThreadPriorityLow, epicsThreadStackMedium, 0}

/** \brief Allocate and start a new OS thread.
 * \param name A name describing this thread.  Appears in various log and error message.
 * \param funptr The thread main function.
 * \param parm Passed to thread main function.
 * \param opts Modifiers for the new thread, or NULL to use target specific defaults.
 * \return NULL on error
 */
LIBCOM_API epicsThreadId epicsThreadCreateOpt (
    const char * name,
    EPICSTHREADFUNC funptr, void * parm,
    const epicsThreadOpts *opts );
/** Short-hand for epicsThreadCreateOpt() to create an un-joinable thread. */
LIBCOM_API epicsThreadId epicsStdCall epicsThreadCreate (
    const char * name, unsigned int priority, unsigned int stackSize,
    EPICSTHREADFUNC funptr,void * parm );
/** Short-hand for epicsThreadCreateOpt() to create an un-joinable thread.
 * On error calls cantProceed()
 */
LIBCOM_API epicsThreadId epicsStdCall epicsThreadMustCreate (
    const char * name, unsigned int priority, unsigned int stackSize,
    EPICSTHREADFUNC funptr,void * parm );

/* This gets undefined in osdThread.h on VxWorks < 6.9 */
#define EPICS_THREAD_CAN_JOIN
/** Wait for a joinable thread to exit (return from its main function) */
LIBCOM_API void epicsThreadMustJoin(epicsThreadId id);
/** Block the current thread until epicsThreadResume(). */
LIBCOM_API void epicsStdCall epicsThreadSuspendSelf(void);
/** Resume a thread suspended with epicsThreadSuspendSelf() */
LIBCOM_API void epicsStdCall epicsThreadResume(epicsThreadId id);
/** Return thread OSI priority */
LIBCOM_API unsigned int epicsStdCall epicsThreadGetPriority(
    epicsThreadId id);
/** Return thread OSI priority */
LIBCOM_API unsigned int epicsStdCall epicsThreadGetPrioritySelf(void);
/** Change OSI priority of target thread. */
LIBCOM_API void epicsStdCall epicsThreadSetPriority(
    epicsThreadId id,unsigned int priority);
/** Lookup the next usage OSI priority such that priority > *pPriorityJustBelow
 *  if this is possible with the current target configuration and privlages.
 */
LIBCOM_API epicsThreadBooleanStatus epicsStdCall
    epicsThreadHighestPriorityLevelBelow (
        unsigned int priority, unsigned *pPriorityJustBelow);
/** Lookup the next usage OSI priority such that priority < *pPriorityJustBelow
 *  if this is possible with the current target configuration and privlages.
 */
LIBCOM_API epicsThreadBooleanStatus epicsStdCall
    epicsThreadLowestPriorityLevelAbove (
        unsigned int priority, unsigned *pPriorityJustAbove);
/** Test if two thread IDs actually refer to the same OS thread */
LIBCOM_API int epicsStdCall epicsThreadIsEqual(
    epicsThreadId id1, epicsThreadId id2);
/** How and why a thread can be suspended is implementation dependent. A
 * thread calling epicsThreadSuspendSelf() should result in this routine
 * returning true for that thread, but a thread may also be suspended
 * for other reasons.
 **/
LIBCOM_API int epicsStdCall epicsThreadIsSuspended(epicsThreadId id);
/** \brief Block the calling thread for at least the specified time.
 * \param seconds Time to wait in seconds.  Values <=0 blocks for the shortest possible time.
 */
LIBCOM_API void epicsStdCall epicsThreadSleep(double seconds);
/** \brief Query a value approximating the OS timer/scheduler resolution.
 * \return A value in seconds >=0
 *
 * \warning On targets other than vxWorks and RTEMS, the quantum value often isn't
 *          meaningful.  Use of this function is discouraged in portable code.
 */
LIBCOM_API double epicsStdCall epicsThreadSleepQuantum(void);
/** Find an epicsThreadId associated with the current thread.
 * For non-EPICS threads, a new epicsThreadId may be allocated.
 */
LIBCOM_API epicsThreadId epicsStdCall epicsThreadGetIdSelf(void);
/** Attempt to find the first instance of a thread by name.
 * \return An epicsThreadId, or NULL if no such thread is currently running.
 *         Note that a non-NULL ID may still be invalid if this call races
 *         with thread exit.
 *
 * \warning Safe use of this function requires external knowledge that this
 *          thread will not return.
 */
LIBCOM_API epicsThreadId epicsStdCall epicsThreadGetId(const char *name);
/** Return a value approximating the number of threads which this target
 *  can run in parallel.  This value is advisory.
 * \return >=1
 */
LIBCOM_API int epicsThreadGetCPUs(void);

/** Return the name of the current thread.
 *
 * \return Never NULL.  Storage lifetime tied to epicsThreadId.
 *
 * This is either a copy of the string passed to epicsThread*Create*(),
 * or an arbitrary unique string for non-EPICS threads.
 */
LIBCOM_API const char * epicsStdCall epicsThreadGetNameSelf(void);

/** Copy out the thread name into the provided buffer.
 *
 * Guaranteed to be null terminated.
 * size is number of bytes in buffer to hold name (including terminator).
 * Failure results in an empty string stored in name.
 */
LIBCOM_API void epicsStdCall epicsThreadGetName(
    epicsThreadId id, char *name, size_t size);

/**
 * Is it OK for a thread to block? This can be called by support code
 * that does not know if it is called in a thread that should not block.
 * For example the errlog system calls this to decide when messages
 * should be displayed on the console.
 **/
LIBCOM_API int epicsStdCall epicsThreadIsOkToBlock(void);
/**
 * When a thread is started the default is that it is not allowed to
 * block. This method can be called to change the state. For example
 * iocsh calls this to specify that it is OK to block.
 **/
LIBCOM_API void epicsStdCall epicsThreadSetOkToBlock(int isOkToBlock);

/** Print to stdout information about all running EPICS threads.
 * \param level 0 prints minimal output.  Higher values print more details.
 */
LIBCOM_API void epicsStdCall epicsThreadShowAll(unsigned int level);
/** Print info about a single EPICS thread. */
LIBCOM_API void epicsStdCall epicsThreadShow(
    epicsThreadId id,unsigned int level);

/**
 * Hooks called when a thread starts, map function called once for every thread.
 **/
typedef void (*EPICS_THREAD_HOOK_ROUTINE)(epicsThreadId id);

/**
 * Register a routine to be called by every new thread before the thread
 * function gets run. Hook routines will often register a thread exit
 * routine with epicsAtThreadExit() to release thread-specific resources
 * they have allocated.
 */
LIBCOM_API int epicsThreadHookAdd(EPICS_THREAD_HOOK_ROUTINE hook);

/**
 * Remove routine from the list of hooks run at thread creation time.
 **/
LIBCOM_API int epicsThreadHookDelete(EPICS_THREAD_HOOK_ROUTINE hook);

/**
 * Print the current list of hook function pointers.
 **/
LIBCOM_API void epicsThreadHooksShow(void);

/**
 * Call func once for every known thread.
 **/
LIBCOM_API void epicsThreadMap(EPICS_THREAD_HOOK_ROUTINE func);

/** Thread local storage */
typedef struct epicsThreadPrivateOSD * epicsThreadPrivateId;
/** Allocate a new thread local variable.
 * This variable will initially hold NULL for each thread.
 */
LIBCOM_API epicsThreadPrivateId epicsStdCall epicsThreadPrivateCreate(void);
/** Free a thread local variable */
LIBCOM_API void epicsStdCall epicsThreadPrivateDelete(epicsThreadPrivateId id);
/** Update thread local variable */
LIBCOM_API void epicsStdCall epicsThreadPrivateSet(epicsThreadPrivateId,void *);
/** Fetch the current value of a thread local variable */
LIBCOM_API void * epicsStdCall epicsThreadPrivateGet(epicsThreadPrivateId);

#ifdef __cplusplus
}
#endif

#ifdef __cplusplus

#include "epicsEvent.h"
#include "epicsMutex.h"

//! Interface used with class epicsThread
class LIBCOM_API epicsThreadRunable {
public:
    virtual ~epicsThreadRunable () = 0;
    //! Thread main function.
    //! C++ exceptions which propagate from this method will be caught and a warning printed.
    //! No other action is taken.
    virtual void run () = 0;
    //! Optional.  Called via epicsThread::show()
    virtual void show ( unsigned int level ) const;
};

extern "C" void epicsThreadCallEntryPoint ( void * );

/** \brief An OS thread
 *
 * A wrapper around the epicsThread* C API.
 *
 * \note Threads must be start() ed.
 */
class LIBCOM_API epicsThread {
public:
    /** Create a new thread with the provided information.
     *
     * cf. epicsThreadOpts
     * \note Threads must be start() ed.
     * @throws epicsThread::unableToCreateThread on error.
     */
    epicsThread ( epicsThreadRunable &,const char *name, unsigned int stackSize,
        unsigned int priority=epicsThreadPriorityLow );
    ~epicsThread () throw ();
    //! Actually start the thread.
    void start () throw ();
    //! Wait for the thread epicsRunnable::run() to return.
    void exitWait () throw ();
    //! Wait for the thread epicsRunnable::run() to return.
    //! \param delay Wait up to this many seconds.
    //! \return true if run() returned.  false on timeout.
    bool exitWait ( const double delay ) throw ();
    //! @throws A special exitException which will be caught and ignored.
    //! \note This exitException doesn't not derive from std::exception
    static void exit ();
    //! cf. epicsThreadResume()
    void resume () throw ();
    //! cf. epicsThreadGetName();
    void getName ( char * name, size_t size ) const throw ();
    //! cf. epicsThreadGetIdSelf()()
    epicsThreadId getId () const throw ();
    //! cf. epicsThreadGetPriority()
    unsigned int getPriority () const throw ();
    //! cf. epicsThreadSetPriority()
    void setPriority ( unsigned int ) throw ();
    bool priorityIsEqual ( const epicsThread & ) const throw ();
    bool isSuspended () const throw ();
    //! \return true if call through this thread's epicsRunnable::run()
    bool isCurrentThread () const throw ();
    bool operator == ( const epicsThread & ) const throw ();
    //! Say something interesting about this thread to stdout.
    void show ( unsigned level ) const throw ();

    /* these operate on the current thread */
    static void suspendSelf () throw ();
    static void sleep (double seconds) throw ();
    static const char * getNameSelf () throw ();
    static bool isOkToBlock () throw ();
    static void setOkToBlock ( bool isOkToBlock ) throw ();

    /* exceptions */
    class unableToCreateThread;
private:
    epicsThreadRunable & runable;
    epicsThreadId id;
    epicsMutex mutex;
    epicsEvent event;
    epicsEvent exitEvent;
    bool * pThreadDestroyed;
    bool begin;
    bool cancel;
    bool terminated;
    bool joined;

    bool beginWait () throw ();
    epicsThread ( const epicsThread & );
    epicsThread & operator = ( const epicsThread & );
    friend void epicsThreadCallEntryPoint ( void * );
    void printLastChanceExceptionMessage (
        const char * pExceptionTypeName,
        const char * pExceptionContext );
    /* exceptions */
    class exitException {};
};

class LIBCOM_API epicsThreadPrivateBase {
public:
    class unableToCreateThreadPrivate {}; /* exception */
protected:
    static void throwUnableToCreateThreadPrivate ();
};

template < class T >
class epicsThreadPrivate :
    private epicsThreadPrivateBase {
public:
    epicsThreadPrivate ();
    ~epicsThreadPrivate () throw ();
    T * get () const throw ();
    void set (T *) throw ();
private:
    epicsThreadPrivateId id;
};

#endif /* __cplusplus */

#include "osdThread.h"

#ifdef __cplusplus

template <class T>
inline epicsThreadPrivate<T>::epicsThreadPrivate ()
{
    this->id = epicsThreadPrivateCreate ();
    if ( this->id == 0 ) {
        epicsThreadPrivateBase::throwUnableToCreateThreadPrivate ();
    }
}

template <class T>
inline epicsThreadPrivate<T>::~epicsThreadPrivate () throw ()
{
    epicsThreadPrivateDelete ( this->id );
}

template <class T>
inline T *epicsThreadPrivate<T>::get () const throw ()
{
    return static_cast<T *> ( epicsThreadPrivateGet (this->id) );
}

template <class T>
inline void epicsThreadPrivate<T>::set (T *pIn) throw ()
{
    epicsThreadPrivateSet ( this->id, static_cast<void *> (pIn) );
}

#endif /* ifdef __cplusplus */

#endif /* epicsThreadh */
