/* -*-c++-*- OpenThreads library, Copyright (C) 2002 - 2007  The Open Thread Group
 *
 * This library is open source and may be redistributed and/or modified under
 * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or
 * (at your option) any later version.  The full license is in LICENSE file
 * included with this distribution, and on the openscenegraph.org website.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * OpenSceneGraph Public License for more details.
*/


//
// Thread - C++ Thread class
// ~~~~~~~~
//

#ifndef _OPENTHREADS_THREAD_
#define _OPENTHREADS_THREAD_

#include <sys/types.h>

#include <OpenThreads/Mutex>
#include <OpenThreads/Affinity>

namespace OpenThreads {

/**
 *  Get the number of processors.
 *
 *  Note, systems where no support exists for querying the number of processors, 1 is returned.
 *
 */
extern OPENTHREAD_EXPORT_DIRECTIVE int GetNumberOfProcessors();

/**
 *  Set the processor affinity of current thread.
 */
extern OPENTHREAD_EXPORT_DIRECTIVE int SetProcessorAffinityOfCurrentThread(const Affinity& affinity);


/**
 *  @class Thread
 *  @brief  This class provides an object-oriented thread interface.
 */
class OPENTHREAD_EXPORT_DIRECTIVE Thread {

public:

    /**
     *  Set the concurrency level for a running application.  This method
     *  only has effect if the pthreads thread model is being used, and
     *  then only when that model is many-to-one (eg. irix).
     *  in other cases it is ignored.  The concurrency level is only a
     *  *hint* as to the number of execution vehicles to use, the actual
     *  implementation may do anything it wants.  Setting the value
     *  to 0 returns things to their default state.
     *
     *  @return previous concurrency level, -1 indicates no-op.
     */
    static int SetConcurrency(int concurrencyLevel);

    /**
     *  Get the concurrency level for a running application.  In this
     *  case, a return code of 0 means that the application is in default
     *  mode.  A return code of -1 means that the application is incapable
     *  of setting an arbitrary concurrency, because it is a one-to-one
     *  execution model (sprocs, linuxThreads)
     */
    static int GetConcurrency();

    /**
     *  Enumerated Type for thread priority
     */
    enum ThreadPriority {

        THREAD_PRIORITY_MAX,      /**< The maximum possible priority  */
        THREAD_PRIORITY_HIGH,     /**< A high (but not max) setting   */
        THREAD_PRIORITY_NOMINAL,  /**< An average priority            */
        THREAD_PRIORITY_LOW,      /**< A low (but not min) setting    */
        THREAD_PRIORITY_MIN,      /**< The miniumum possible priority */
        THREAD_PRIORITY_DEFAULT   /**< Priority scheduling default    */

    };

    /**
     *  Enumerated Type for thread scheduling policy
     */
    enum ThreadPolicy {

        THREAD_SCHEDULE_FIFO,        /**< First in, First out scheduling         */
        THREAD_SCHEDULE_ROUND_ROBIN, /**< Round-robin scheduling (LINUX_DEFAULT) */
        THREAD_SCHEDULE_TIME_SHARE,  /**< Time-share scheduling (IRIX DEFAULT)   */
        THREAD_SCHEDULE_DEFAULT      /**< Default scheduling                     */

    };

    /**
     *  Constructor
     */
    Thread();

    /**
     *  Destructor
     */
    virtual ~Thread();


    /**
     *  Return a pointer to the current running thread, returns NULL for a non OpenThreads thread.
     */
    static Thread *CurrentThread();

    /**
     *  Return the id of the current thread
     */
    static size_t CurrentThreadId();


    /**
     *  Initialize Threading in a program.  This method must be called before
     *  you can do any threading in a program.
     */
    static void Init();

    /**
     *  Yield the processor.
     *
     *  @note This method operates on the calling process.  And is
     *  equivalent to calling sched_yield().
     *
     *  @return 0 if normal, -1 if errno set, errno code otherwise.
     */
    static int YieldCurrentThread();

    /**
     *  This method will return the ThreadPriority of the master process.
     *  (ie, the one calling the thread->start() methods for the first time)
     *  The method will almost certainly return
     *  Thread::THREAD_PRIORITY_DEFAULT if
     *  Init() has not been called.
     *
     *  @return the Thread::ThreadPriority of the master thread.
     */
    static ThreadPriority GetMasterPriority() {return s_masterThreadPriority;};


    /**
     *  Get a unique thread id.  This id is monotonically increasing.
     *
     *  @return a unique thread identifier
     */
    size_t getThreadId();

    /**
     *  Get the thread's process id.  This is the pthread_t or pid_t value
     *  depending on the threading model being used.
     *
     *  @return thread process id.
     */
    size_t getProcessId();

    /**
     *  Start the thread.  This method will configure the thread, set
     *  it's priority, and spawn it.
     *
     *  @note if the stack size specified setStackSize is smaller than the
     *  smallest allowable stack size,  the threads stack size will be set to
     *  the minimum allowed, and may be retrieved via the getStackSize()
     *
     *  @return 0 if normal, -1 if errno set, errno code otherwise.
     */
    int start();
    int startThread();

    /**
     * Test the cancel state of the thread.  If the thread has been canceled
     * this method will cause the thread to exit now.  This method operates
     * on the calling thread.
     *
     * Returns 0 if normal, -1 if called from a thread other that this.
     */
    int testCancel();


    /**
     *  Cancel the thread.  Equivalent to SIGKILL.
     *
     *  @return 0 if normal, -1 if errno set, errno code otherwise.
     */
    virtual int cancel();

    /**
     *  Set the thread's schedule priority.  This is a complex method.
     *  Beware of thread priorities when using a many-to-many kernel
     *  entity implementation (such as IRIX pthreads).  If one is not careful
     *  to manage the thread priorities, a priority inversion deadlock can
     *  easily occur (Although the OpenThreads::Mutex & OpenThreads::Barrier
     *  constructs have been designed with this scenario in mind).  Unless
     *  you have explicit need to set the schedule priorities for a given
     *  task, it is best to leave them alone.
     *
     *  @note some implementations (notably LinuxThreads and IRIX Sprocs)
     *  only allow you to decrease thread priorities dynamically.  Thus,
     *  a lower priority thread will not allow it's priority to be raised
     *  on the fly.
     *
     *  @note setting the environment variable OUTPUT_THREADLIB_SCHEDULING_INFO
     *  will output scheduling information for each thread to stdout.
     *
     *  @return 0 if normal, -1 if errno set, errno code otherwise.
     */
    int setSchedulePriority(ThreadPriority priority);

    /**
     *  Get the thread's schedule priority (if able)
     *
     *  @note setting the environment variable OUTPUT_THREADLIB_SCHEDULING_INFO
     *  will output scheduling information for each thread to stdout.
     *
     *  @return 0 if normal, -1 if errno set, errno code otherwise.
     */
    int getSchedulePriority();

    /**
     *  Set the thread's scheduling policy (if able)
     *
     *  @note On some implementations (notably IRIX Sprocs & LinuxThreads)
     *  The policy may prohibit the use of SCHEDULE_ROUND_ROBIN and
     *  SCHEDULE_FIFO policies - due to their real-time nature, and
     *  the danger of deadlocking the machine when used as super-user.
     *  In such cases, the command is a no-op.
     *
     *  @note setting the environment variable OUTPUT_THREADLIB_SCHEDULING_INFO
     *  will output scheduling information for each thread to stdout.
     *
     *  @return 0 if normal, -1 if errno set, errno code otherwise.
     */
    int setSchedulePolicy(ThreadPolicy policy);

    /**
     *  Get the thread's policy (if able)
     *
     *  @note setting the environment variable OUTPUT_THREADLIB_SCHEDULING_INFO
     *  will output scheduling information for each thread to stdout.
     *
     *  @return policy if normal, -1 if errno set, errno code otherwise.
     */
    int getSchedulePolicy();

    /**
     *  Set the thread's desired stack size (in bytes).
     *  This method is an attribute of the thread and must be called
     *  *before* the start() method is invoked.
     *
     *  @note a return code of 13 (EACESS) means that the thread stack
     *  size can no longer be changed.
     *
     *  @return 0 if normal, -1 if errno set, errno code otherwise.
     */
    int setStackSize(size_t size);

    /**
     *  Get the thread's desired stack size.
     *
     *  @return the thread's stack size.  0 indicates that the stack size
     *   has either not yet been initialized, or not yet been specified by
     *   the application.
     */
    size_t getStackSize();

    /**
     *  Print the thread's scheduling information to stdout.
     */
    void printSchedulingInfo();

    /**
     *  Detach the thread from the calling process.
     *
     *  @return 0 if normal, -1 if errno set, errno code otherwise.
     */
    int detach();

    /**
     *  Join the calling process with the thread
     *
     *  @return 0 if normal, -1 if errno set, errno code otherwise.
     */
    int join();

    /**
     *  Disable thread cancellation altogether. Thread::cancel() has no effect.
     *
     *  @return 0 if normal, -1 if errno set, errno code otherwise.
     */
    int setCancelModeDisable();

    /**
     *  Mark the thread to cancel asynchronously on Thread::cancel().
     *  (May not be available with process-level implementations).
     *
     *  @return 0 if normal, -1 if errno set, errno code otherwise.
     */
    int setCancelModeAsynchronous();

    /**
     *  Mark the thread to cancel at the earliest convenience on
     *  Thread::cancel() (This is the default)
     *
     *  @return 0 if normal, -1 if errno set, errno code otherwise.
     */
    int setCancelModeDeferred();

    /**
     *  Query the thread's running status
     *
     *  @return true if running, false if not.
     */
    bool isRunning();

    /**
     *  Thread's run method.  Must be implemented by derived classes.
     *  This is where the action happens.
     */
    virtual void run() = 0;

    /**
     *  Thread's cancel cleanup routine, called upon cancel(), after the
     *  cancellation has taken place, but before the thread exits completely.
     *  This method should be used to repair parts of the thread's data
     *  that may have been damaged by a pre-mature cancel.  No-op by default.
     */
    virtual void cancelCleanup() {};

    void* getImplementation(){ return _prvData;	};

    /** Set the Thread's processor affinity to all, a single CPU or multiple CPU's
      * This call must be made before
      * start() or startThread() and has no effect after the thread
      * has been running. Returns 0 on success, implementation's
      * error on failure, or -1 if ignored.
      */
    int setProcessorAffinity( const Affinity& affinity);


    /** microSleep method, equivalent to the posix usleep(microsec).
      *  This is not strictly thread API but is used
      * so often with threads. It's basically UNIX usleep. Parameter is
      * number of microseconds we current thread to sleep. Returns 0 on
      * success, non-zero on failure (UNIX errno or GetLastError() will give
      * detailed description.
      */
    static int microSleep( unsigned int microsec);

private:

    /**
     *  The Private Actions class is allowed to operate on private data.
     */
    friend class ThreadPrivateActions;

     /**
      *  Private copy constructor, to prevent tampering.
      */
    Thread(const Thread &/*t*/) {};

    /**
      *  Private copy assignment, to prevent tampering.
      */
    Thread &operator=(const Thread &/*t*/) {return *(this);};

    /**
     *  Implementation-specific data
     */
    void * _prvData;

    /**
     *  Master thread's priority, set by Thread::Init.
     */
    static ThreadPriority s_masterThreadPriority;

    /**
     *  Is initialized flag
     */
    static bool s_isInitialized;
};

}

#endif // !_OPENTHREADS_THREAD_
