/* -*- mode: c++; c-basic-offset: 4; -*-
 *
 * MICO --- a CORBA implementation
 * Copyright (C) 1997-98 Kay Roemer & Arno Puder
 * 
 * OSThread: An abstract Thread class for MICO
 * Copyright (C) 1999 Andy Kersting & Andreas Schultz
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * 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 GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the Free
 * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * Send comments and/or bug reports to:
 *                mico@informatik.uni-frankfurt.de
 *
 * $Id: pthreads.h,v 1.18 2000/02/09 18:11:17 akerst Exp $
 *
 * support for POSIX Draft 10ish Pthreads for Linux as developed by
 * Xavier LeRoy@inria.fr
 *
 */

#ifndef __os_thread_pthreads_h__
#define __os_thread_pthreads_h__

static void _init (MICO_ULong _tpsize, MICO_ULong _tpincr);

/********************************* Mutex *************************************/

class CondVar;

class Mutex __NAME( :public _namedObject ) {
 protected:
    pthread_mutex_t _mutex;
    
 public:

    friend class CondVar;

    typedef enum { Normal, Recursive } Attribute;
    typedef enum { NoError, AlreadyLocked, TryAgain, Invalid, Fault,
                   DeadLock, UnknownError } ErrorType;

    Mutex( MICO_Boolean locked = FALSE, Attribute attr = Normal );
    ~Mutex();
    
    MICO_ULong
    trylock() {

        MICOMT::Mutex::ErrorType ret;

        ret = MICOMT::Mutex::NoError;

        MUTEX_DEBUG( MT_TRACE( MICODebug::instance()->printer()
                        __NAME (<< name ()) << ": Mutex::trylock ()" << endl));

        switch (pthread_mutex_trylock (&_mutex)) {
            case EBUSY:   ret = MICOMT::Mutex::AlreadyLocked;   break;
            case EAGAIN:  ret = MICOMT::Mutex::TryAgain;        break;
            case EINVAL:  ret = MICOMT::Mutex::Invalid;         break;
            case EFAULT:  ret = MICOMT::Mutex::Fault;           break;
            case EDEADLK: ret = MICOMT::Mutex::DeadLock;        break;
            default:      ret = MICOMT::Mutex::UnknownError;
        }

        return ret;
    }

    void lock() { 
        MUTEX_DEBUG( MT_TRACE( MICODebug::instance()->printer()
                                   << name () << ": Mutex::lock ()" << endl ));
        pthread_mutex_lock( &_mutex );
    }
    
    void unlock() {
        MUTEX_DEBUG( MT_TRACE( MICODebug::instance()->printer()
                                 << name () << ": Mutex::unlock ()" << endl ));
        pthread_mutex_unlock( &_mutex );
    }
};

/***************************** Conditional Variable  *******************************/

class CondVar __NAME( :public _namedObject ) {
private:
    pthread_cond_t  _cond;
public:
    CondVar();
    ~CondVar();
    
    MICO_Boolean wait( Mutex &_mutex )
	{
	    COND_DEBUG( MT_TRACE( MICODebug::instance()->printer()
				  << name () << ": CondVar::wait ()" << endl ));
	    return ( pthread_cond_wait( &_cond, &_mutex._mutex ) == 0 );
	};
    
    MICO_Boolean timedwait( Mutex &_mutex, MICO_ULong tmout )
	{ 
	    timespec timeout;
	    
	    COND_DEBUG( MT_TRACE( MICODebug::instance()->printer()
				  << name () << ": CondVar::timedwait ()" << endl ));
	    timeout.tv_sec  = 0;
	    timeout.tv_nsec = tmout * 1000;
	    return ( pthread_cond_timedwait( &_cond, &_mutex._mutex, &timeout )  == 0 );
	};
    
    void broadcast()
	{ 
	    COND_DEBUG( MT_TRACE( MICODebug::instance()->printer()
				  << name () << ": CondVar::broadcast ()" << endl ));
	    pthread_cond_broadcast( &_cond );
	};
    
    void signal()
	{ 
	    COND_DEBUG( MT_TRACE( MICODebug::instance()->printer()
				  << name () << ": CondVar::signal ()" << endl ));
	    pthread_cond_signal( &_cond );
	};
};

/**************************** reader/writer lock *****************************/
class rwLock __NAME( :public _namedObject ) {
 private:
    pthread_rwlock_t _rwlock;
    
 public:
    rwLock();
    ~rwLock();
    
    void rdlock()
	{
	    RWL_DEBUG( MT_TRACE( MICODebug::instance()->printer()
				 << name () << ": rwLock::rdlock ()" << endl ));
	    pthread_rwlock_rdlock( &_rwlock );
	}
    
    void wrlock()
	{
	    RWL_DEBUG( MT_TRACE( MICODebug::instance()->printer()
				 << name () << ": rwLock::wrlock ()" << endl ));
	    pthread_rwlock_wrlock( &_rwlock );
	}
    
    void unlock()
	{
	    RWL_DEBUG( MT_TRACE( MICODebug::instance()->printer()
				 << name () << ": rwLock::unlock ()" << endl ));
	    pthread_rwlock_unlock( &_rwlock );
	}
};

/********************************* Semaphore *********************************/
class Semaphore __NAME( :public _namedObject ) {
 private:
    sem_t s;

 public:
    typedef enum { NoError, NoPermission, TryAgain, SemInvalid,
                   Interrupted, UnknownError } ErrorType;

    Semaphore( unsigned int val = 0);
    ~Semaphore();
    
    void wait() {

        SEMA_DEBUG( MT_TRACE( MICODebug::instance()->printer()
                               << name () << ": Semaphore::wait ()" << endl ));
        while ( sem_wait( &s ) == EINTR ) {}
    }

    Semaphore::ErrorType
    trylock() {

        Semaphore::ErrorType ret = MICOMT::Semaphore::NoError;

        SEMA_DEBUG( MT_TRACE( MICODebug::instance()->printer()
                   __NAME (<< name ()) << ": Semaphore::trylock ()" << endl ));     
        if (sem_trywait (&s) < 0)

            switch (errno) {
                case EPERM:  ret = MICOMT::Semaphore::NoPermission; break;
                case EAGAIN: ret = MICOMT::Semaphore::TryAgain;     break;
                case EINVAL: ret = MICOMT::Semaphore::SemInvalid;   break;
                case EINTR:  ret = MICOMT::Semaphore::Interrupted;  break;
                default:     ret = MICOMT::Semaphore::UnknownError;
            }

        return ret;

    }

    void post() {

        SEMA_DEBUG( MT_TRACE( MICODebug::instance()->printer()
                               << name () << ": Semaphore::post ()" << endl ));
        sem_post( &s );
    }
};

/******************************* basic Thread class *********************************/

class Thread __NAME( :public _namedObject ) {
 public:
    // for POSIX-Threads, a ThreadID is simply a pthread_t:
    typedef pthread_t     ThreadID;
    typedef pthread_key_t ThreadKey;
    typedef MICO_ULong    ThreadNo;

    typedef enum { Detached, NotDetached } DetachFlag;
    typedef enum { NoError, ThreadFailure, InvalidPriority, MutexAlreadyLocked,
		   NotStarted, UnknownError } ErrorType;

    // start method
    static void *ThreadWrapper (void *arg);
    
    static int get_priority_max() { 
	return sched_get_priority_max( sched_getscheduler(0) ); 
    };
    static int get_priority_min() {
	return sched_get_priority_min( sched_getscheduler(0) );
    };

    // key handling
    static void createKey( ThreadKey &key, void (*__cleanup) (void *) = 0 ) {
	assert( pthread_key_create( &key, __cleanup ) == 0 );
    };
    static void deleteKey( ThreadKey key ) {
	assert( pthread_key_delete( key ) == 0 );
    };
    static void *getSpecific( ThreadKey key ) {
	return pthread_getspecific( key );
    };
    static void setSpecific( ThreadKey key, void *value ) {
	assert( pthread_setspecific( key, value ) == 0 );
    };

 protected:
    void *_arg;                   // parameters passed in via start()
    
    ThreadID _id;       // records the id from pthread_create()
    ThreadNo _no;
    DetachFlag _detached;
    
#ifdef _THR_CREATE_AND_BLOCK
    Mutex _ready;
#endif
    
 public:
    Thread (DetachFlag detached = NotDetached);
    
    void _thr_startup(void *arg) {
#ifdef _THR_CREATE_AND_BLOCK
	_ready.lock();
#endif
	_run( arg );
    };

    virtual void _run (void *arg) = 0;  // the _run() method sets up
    // anything else this object needs
    // including other threads.           
    
    // Start the thread ala JAVA:
    virtual ErrorType start (void *arg = NULL);
    
    // get this thread's ID:
    ThreadID id () const        { return _id; };
    static ThreadID self ()     { return pthread_self(); };

    ThreadNo no () const        { return _no; };
    void no( const ThreadNo n ) { _no = n; };
    
    // see if thread `thread' is the same as this thread:
    virtual MICO_Boolean operator == (const Thread &thread) const;
    
    // create a new thread with _run a its start methode
    // maybe we should throw a exception instead of returning an error code
    ErrorType create_thread () {
	THREAD_DEBUG( MT_TRACE( MICODebug::instance()->printer()
				<< "Thread::create_thread ()" << endl ));
#ifdef _THR_CREATE_AND_BLOCK
	_ready.lock();
#endif

        int r;

        if ( r = pthread_create (&_id, NULL, ThreadWrapper, this))
        {
            THREAD_DEBUG (MT_TRACE (MICODebug::instance ()->printer ()
                                    << "Thread: pthread_create () returned: "
                                    << strerror (r) << endl ));
            return ThreadFailure;
        }
	
	if (_detached == Detached)
	    pthread_detach (_id);
	
	return NoError;
    };

    // yield execution
    void yield() {
#ifdef HAVE_SCHED_H
	sched_yield();
#endif
    };

    // tell this thread to die:
    void terminate (void *exitval) {
	THREAD_DEBUG( MT_TRACE( MICODebug::instance()->printer()
				<< name ()
                                << ": Thread::terminate (void *exitval)"
                                << endl ));
	assert (!pthread_cancel (_id));
    };

    // set the priority of this thread to `new_priority'.
    // let's avoid using this if at all possible, some pthreads impls don't
    // support it fully or at all:
    virtual void priority (MICO_Long new_priority);
    
    // wait for this thread to finish:
    void wait () {
	THREAD_DEBUG( MT_TRACE( MICODebug::instance()->printer()
				<< name () << ": Thread::wait()" << endl ));
	pthread_join (id(), NULL);
    };
    
    virtual ~Thread ();
};

#endif // __os_thread_pthreads_h__
