// SGThread - Simple pthread class wrappers.
//
// Written by Bernie Bright, started April 2001.
//
// Copyright (C) 2001  Bernard Bright - bbright@bigpond.net.au
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program 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 GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
//
// $Id: SGThread.hxx,v 1.9 2008/07/25 18:35:42 ehofman Exp $

#ifndef SGTHREAD_HXX_INCLUDED
#define SGTHREAD_HXX_INCLUDED 1

#include <simgear/compiler.h>

#include <pthread.h>
#include <cassert>
#include <cerrno>

class SGThread;

extern "C" {
    void* start_handler( void* );
};

/**
 * Encapsulate generic threading methods.
 * Users derive a class from SGThread and implement the run() member function.
 */
class SGThread
{
public:
    /**
     * SGThread cancelation modes.
     */
    enum cancel_t
    {
	CANCEL_DISABLE = 0,
	CANCEL_DEFERRED,
	CANCEL_IMMEDIATE
    };
public:

    /**
     * Create a new thread object.
     * When a SGThread object is created it does not begin execution
     * immediately.  It is started by calling the start() member function.
     */
    SGThread();

    /**
     * Start the underlying thread of execution.
     * @param cpu An optional parameter to specify on which CPU to run this
     * thread (only supported on IRIX at this time).
     * @return Pthread error code if execution fails, otherwise returns 0.
     */
    int start( unsigned cpu = 0 );

    /**
     * Sends a cancellation request to the underlying thread.  The target
     * thread will either ignore the request, honor it immediately or defer
     * it until it reaches a cancellation point.
     */
    void cancel();

    /**
     * Suspends the exection of the calling thread until this thread
     * terminates.
     */
    void join();

protected:
    /**
     * Destroy a thread object.
     * This is protected so that its illegal to simply delete a thread
     * - it must return from its run() function.
     */
    virtual ~SGThread();

    /**
     * Set the threads cancellation mode.
     * @param mode The required cancellation mode.
     */
    void set_cancel( cancel_t mode );

    /**
     * All threads execute by deriving the run() method of SGThread.
     * If this function terminates then the thread also terminates.
     */
    virtual void run() = 0;

private:

    /**
     * Pthread thread identifier.
     */
    pthread_t tid;

    friend void* start_handler( void* );

private:
    // Disable copying.
    SGThread( const SGThread& );
    SGThread& operator=( const SGThread& );
};

inline
SGThread::SGThread()
{
}

inline
SGThread::~SGThread()
{
}

inline int
SGThread::start( unsigned cpu )
{
    int status = pthread_create( &tid, 0, start_handler, this );
    assert( status == 0 );
    (void)status;
#if defined( sgi )
    if ( !status && !cpu )
        pthread_setrunon_np( cpu );
#endif
    return status;
}

inline void
SGThread::join()
{
    int status = pthread_join( tid, 0 );
    assert( status == 0 );
    (void)status;
}

inline void
SGThread::cancel()
{
    int status = pthread_cancel( tid );
    assert( status == 0 );
    (void)status;
}

/**
 * A mutex is used to protect a section of code such that at any time
 * only a single thread can execute the code.
 */
class SGMutex
{
    friend class SGPthreadCond;

public:

    /**
     * Create a new mutex.
     * Under Linux this is a 'fast' mutex.
     */
    SGMutex();

    /**
     * Destroy a mutex object.
     * Note: it is the responsibility of the caller to ensure the mutex is
     * unlocked before destruction occurs.
     */
    ~SGMutex();

    /**
     * Lock this mutex.
     * If the mutex is currently unlocked, it becomes locked and owned by
     * the calling thread.  If the mutex is already locked by another thread,
     * the calling thread is suspended until the mutex is unlocked.  If the
     * mutex is already locked and owned by the calling thread, the calling
     * thread is suspended until the mutex is unlocked, effectively causing
     * the calling thread to deadlock.
     *
     * @see SGMutex::trylock
     */
    void lock();

    /**
     * Try to lock the mutex for the current thread.  Behaves like lock except
     * that it doesn't block the calling thread.
     * @return true if mutex was successfully locked, otherwise false.
     * @see SGMutex::lock
     */
    bool trylock();

    /**
     * Unlock this mutex.
     * It is assumed that the mutex is locked and owned by the calling thread.
     */
    void unlock();

protected:

    /**
     * Pthread mutex.
     */
    pthread_mutex_t mutex;
};

inline SGMutex::SGMutex()
{
    int status = pthread_mutex_init( &mutex, 0 );
    assert( status == 0 );
    (void)status;
}

inline SGMutex::~SGMutex()
{
    int status = pthread_mutex_destroy( &mutex );
    assert( status == 0 );
    (void)status;
}

inline void SGMutex::lock()
{
    int status = pthread_mutex_lock( &mutex );
    assert( status == 0 );
    (void)status;
}

inline void SGMutex::unlock()
{
    int status = pthread_mutex_unlock( &mutex );
    assert( status == 0 );
    (void)status;
}

/**
 * A condition variable is a synchronization device that allows threads to 
 * suspend execution until some predicate on shared data is satisfied.
 * A condition variable is always associated with a mutex to avoid race
 * conditions. 
 */
class SGPthreadCond
{
public:
    /**
     * Create a new condition variable.
     */
    SGPthreadCond();

    /**
     * Destroy the condition object.
     */
    ~SGPthreadCond();

    /**
     * Wait for this condition variable to be signaled.
     *
     * @param SGMutex& reference to a locked mutex.
     */
    void wait( SGMutex& );

    /**
     * Wait for this condition variable to be signaled for at most
     * 'ms' milliseconds.
     *
     * @param mutex reference to a locked mutex.
     * @param ms milliseconds to wait for a signal.
     *
     * @return 
     */
    bool wait( SGMutex& mutex, unsigned long ms );

    /**
     * Wake one thread waiting on this condition variable.
     * Nothing happens if no threads are waiting.
     * If several threads are waiting exactly one thread is restarted.  It
     * is not specified which.
     */
    void signal();

    /**
     * Wake all threads waiting on this condition variable.
     * Nothing happens if no threads are waiting.
     */
    void broadcast();

private:
    // Disable copying.
    SGPthreadCond(const SGPthreadCond& );
    SGPthreadCond& operator=(const SGPthreadCond& );

private:

    /**
     * The Pthread conditon variable.
     */
    pthread_cond_t cond;
};

inline SGPthreadCond::SGPthreadCond()
{
    int status = pthread_cond_init( &cond, 0 );
    assert( status == 0 );
    (void)status;
}

inline SGPthreadCond::~SGPthreadCond()
{
    int status = pthread_cond_destroy( &cond );
    assert( status == 0 );
    (void)status;
}

inline void SGPthreadCond::signal()
{
    int status = pthread_cond_signal( &cond );
    assert( status == 0 );
    (void)status;
}

inline void SGPthreadCond::broadcast()
{
    int status = pthread_cond_broadcast( &cond );
    assert( status == 0 );
    (void)status;
}

inline void SGPthreadCond::wait( SGMutex& mutex )
{
    int status = pthread_cond_wait( &cond, &mutex.mutex );
    assert( status == 0 );
    (void)status;
}

#endif /* SGTHREAD_HXX_INCLUDED */
