#ifndef THREAD_HPP_INCLUDED
#define THREAD_HPP_INCLUDED

// Copyright (c) 2007 Peter Dimov
//
// Distributed under the Boost Software License, Version 1.0.
// http://www.boost.org/LICENSE_1_0.txt

#include "pthread.hpp"
#include <boost/bind.hpp>
#include <boost/shared_ptr.hpp>
#include <exception>
#include <memory>
#include <assert.h>
#include <errno.h>

#ifndef ETIMEDOUT
# define ETIMEDOUT 10060
#endif

namespace std
{

// Exceptions

class thread_exit
{
private:

    explicit thread_exit( void * value_ptr );

public:

    void * value_ptr() const;
};

class thread_cancel: public thread_exit
{
private:

    thread_cancel();
};

class thread_error: public std::exception
{
private:

    int r_;

public:

    explicit thread_error( int r ): r_( r )
    {
    }

    virtual char const * what() throw()
    {
        return "std::thread_error";
    }

    int error() const
    {
        return r_;
    }
};

// Mutex

template< class Mx > class basic_condition;

class mutex_attr
{
private:

    pthread_mutexattr_t attr_;

public:

    mutex_attr()
    {
        int r = pthread_mutexattr_init( &attr_ );

        if( r != 0 )
        {
            throw thread_error( r );
        }
    }

    ~mutex_attr()
    {
        pthread_mutexattr_destroy( &attr_ );
    }

    explicit mutex_attr( pthread_mutexattr_t const * attr )
    {
        int r = pthread_mutexattr_init( &attr_ );

        if( r != 0 )
        {
            throw thread_error( r );
        }

        if( attr )
        {
            r = pthread_mutexattr_copy_np( &attr_, attr );

            if( r != 0 )
            {
                pthread_mutexattr_destroy( &attr_ );
                throw thread_error( r );
            }
        }
    }

    mutex_attr( mutex_attr const & attr )
    {
        int r = pthread_mutexattr_init( &attr_ );

        if( r != 0 )
        {
            throw thread_error( r );
        }

        r = pthread_mutexattr_copy_np( &attr_, attr );

        if( r != 0 )
        {
            pthread_mutexattr_destroy( &attr_ );
            throw thread_error( r );
        }
    }

    mutex_attr & operator=( mutex_attr const & attr )
    {
        int r = pthread_mutexattr_copy_np( &attr_, attr );

        if( r != 0 )
        {
            throw thread_error( r );
        }
    }

    operator pthread_mutexattr_t * ()
    {
        return &attr_;
    }

    operator pthread_mutexattr_t const * () const
    {
        return &attr_;
    }

    int get_type() const
    {
        int type = PTHREAD_MUTEX_DEFAULT;

        int r = pthread_mutexattr_gettype( &attr_, &type );

        assert( r == 0 );

        return type;
    }

    int set_type( int type )
    {
        return pthread_mutexattr_settype( &attr_, type );
    }

    int get_pshared() const
    {
        int pshared = PTHREAD_PROCESS_PRIVATE;

        int r = pthread_mutexattr_getpshared( &attr_, &pshared );

        assert( r == 0 );

        return pshared;
    }

    int set_pshared( int pshared )
    {
        return pthread_mutexattr_setpshared( &attr_, pshared );
    }
};

class mutex
{
private:

    pthread_mutex_t mx_;

    mutex( mutex const & );
    mutex & operator=( mutex const & );

    template< class Mx > friend class basic_condition;

public:

    explicit mutex( pthread_mutexattr_t const * attr = 0 )
    {
        int r = pthread_mutex_init( &mx_, attr );

        if( r != 0 )
        {
            throw thread_error( r );
        }
    }

    explicit mutex( int type )
    {
        assert( type == PTHREAD_MUTEX_DEFAULT || type == PTHREAD_MUTEX_NORMAL || type == PTHREAD_MUTEX_ERRORCHECK || type == PTHREAD_MUTEX_RECURSIVE );

        mutex_attr attr;

        int r = attr.set_type( type );

        if( r != 0 )
        {
            throw thread_error( r );
        }

        r = pthread_mutex_init( &mx_, attr );

        if( r != 0 )
        {
            throw thread_error( r );
        }
    }

    ~mutex()
    {
        pthread_mutex_destroy( &mx_ );
    }

    int lock()
    {
        return pthread_mutex_lock( &mx_ );
    }

    int try_lock()
    {
        return pthread_mutex_trylock( &mx_ );
    }

    int timed_lock( timespec const & abstime )
    {
        return pthread_mutex_timedlock( &mx_, &abstime );
    }

    int unlock()
    {
        return pthread_mutex_unlock( &mx_ );
    }
};

class lock_error: public exception
{
private:

    int r_;

public:

    explicit lock_error( int r ): r_( r )
    {
    }

    virtual char const * what() throw()
    {
        return "std::lock_error";
    }

    int error() const
    {
        return r_;
    }
};

template< class Mx > class basic_lock
{
private:

    Mx * pmx_;
    bool locked_;

    basic_lock( basic_lock const & );
    basic_lock & operator=( basic_lock const & );

public:

    typedef Mx mutex_type;

    explicit basic_lock( Mx & mx_, bool locked = true ): pmx_( &mx_ ), locked_( false )
    {
        if( locked )
        {
            lock();
        }
    }

    ~basic_lock()
    {
        if( locked_ )
        {
            unlock();
        }
    }

    void lock()
    {
        assert( !locked_ );

        int r = pmx_->lock();

        if( r == 0 )
        {
            locked_ = true;
        }
        else
        {
            throw lock_error( r );
        }
    }

    bool try_lock()
    {
        assert( !locked_ );

        int r = pmx_->try_lock();

        if( r == 0 )
        {
            locked_ = true;
        }
        else if( r == EBUSY )
        {
        }
        else
        {
            throw lock_error( r );
        }

        return locked_;
    }

    bool timed_lock( timespec const & abstime )
    {
        assert( !locked_ );

        int r = pmx_->timed_lock( abstime );

        if( r == 0 )
        {
            locked_ = true;
        }
        else if( r == ETIMEDOUT )
        {
        }
        else
        {
            throw lock_error( r );
        }

        return locked_;
    }

    void unlock()
    {
        assert( locked_ );

        int r = pmx_->unlock();

        assert( r == 0 );

        locked_ = false;
    }

    bool locked() const
    {
        return locked_;
    }

    Mx * mutex()
    {
        return pmx_;
    }
};

typedef basic_lock< mutex > scoped_lock;

// Once

template< class F > void __call_once_helper( void * pv )
{
    F * pf = static_cast< F * >( pv );
    (*pf)();
}

template< class F > void call_once( pthread_once_t & once_control, F f )
{
    pthread_once2_np( &once_control, &__call_once_helper<F>, &f );
}

// Condition

class condition_attr
{
private:

    pthread_condattr_t attr_;

public:

    condition_attr()
    {
        int r = pthread_condattr_init( &attr_ );

        if( r != 0 )
        {
            throw thread_error( r );
        }
    }

    ~condition_attr()
    {
        pthread_condattr_destroy( &attr_ );
    }

    explicit condition_attr( pthread_condattr_t const * attr )
    {
        int r = pthread_condattr_init( &attr_ );

        if( r != 0 )
        {
            throw thread_error( r );
        }

        if( attr )
        {
            r = pthread_condattr_copy_np( &attr_, attr );

            if( r != 0 )
            {
                pthread_condattr_destroy( &attr_ );
                throw thread_error( r );
            }
        }
    }

    condition_attr( condition_attr const & attr )
    {
        int r = pthread_condattr_init( &attr_ );

        if( r != 0 )
        {
            throw thread_error( r );
        }

        r = pthread_condattr_copy_np( &attr_, attr );

        if( r != 0 )
        {
            pthread_condattr_destroy( &attr_ );
            throw thread_error( r );
        }
    }

    condition_attr & operator=( condition_attr const & attr )
    {
        int r = pthread_condattr_copy_np( &attr_, attr );

        if( r != 0 )
        {
            throw thread_error( r );
        }
    }

    operator pthread_condattr_t * ()
    {
        return &attr_;
    }

    operator pthread_condattr_t const * () const
    {
        return &attr_;
    }

    int get_pshared() const
    {
        int pshared = PTHREAD_PROCESS_PRIVATE;

        int r = pthread_condattr_getpshared( &attr_, &pshared );

        assert( r == 0 );

        return pshared;
    }

    int set_pshared( int pshared )
    {
        return pthread_condattr_setpshared( &attr_, pshared );
    }

    clockid_t get_clock() const
    {
        clockid_t clock_id = CLOCK_REALTIME;

        int r = pthread_condattr_getclock( &attr_, &clock_id );

        assert( r == 0 );

        return clock_id;
    }

    int set_clock( clockid_t clock_id )
    {
        return pthread_condattr_setclock( &attr_, clock_id );
    }
};

template<> class basic_condition< mutex >;
typedef basic_condition< mutex > condition;

template< class Lock > class __scoped_unlock
{
private:

    Lock & lock_;

public:

    explicit __scoped_unlock( Lock & lock ): lock_( lock )
    {
        assert( lock_.locked() );
        lock_.unlock();
    }

    ~__scoped_unlock()
    {
        lock_.lock();
    }
};

inline mutex_attr __mutexattr_from_condattr( pthread_condattr_t const * ca )
{
    mutex_attr ma;

    if( ca != 0 )
    {
        int pshared;

        if( pthread_condattr_getpshared( ca, &pshared ) == 0 )
        {
            ma.set_pshared( pshared );
        }
    }

    return ma;
}

template< class Mx > class basic_condition
{
private:

    condition cond_;
    mutex mx_;

    basic_condition( basic_condition const & );
    basic_condition & operator=( basic_condition const & );

public:

    basic_condition()
    {
    }

    explicit basic_condition( pthread_condattr_t const * attr ): cond_( attr ), mx_( __mutexattr_from_condattr( attr ) )
    {
    }

    template< class Lock > int wait( Lock & lock )
    {
        assert( lock.locked() );

        scoped_lock lk( mx_ );
        __scoped_unlock< Lock > unlock( lock );

        return cond_.wait( lk );
    }

    template< class Lock > int timed_wait( Lock & lock, timespec const & abstime )
    {
        assert( lock.locked() );

        scoped_lock lk( mx_ );
        __scoped_unlock< Lock > unlock( lock );

        return cond_.timed_wait( lk, abstime );
    }

    int signal()
    {
        scoped_lock lock( mx_ );
        return cond_.signal();
    }

    int broadcast()
    {
        scoped_lock lock( mx_ );
        return cond_.broadcast();
    }
};

template<> class basic_condition< mutex >
{
private:

    pthread_cond_t cond_;

    basic_condition( basic_condition const & );
    basic_condition & operator=( basic_condition const & );

public:

    explicit basic_condition( pthread_condattr_t const * attr = 0 )
    {
        int r = pthread_cond_init( &cond_, attr );

        if( r != 0 )
        {
            throw thread_error( r );
        }
    }

    ~basic_condition()
    {
        pthread_cond_destroy( &cond_ );
    }

    template< class Lock > int wait( Lock & lock )
    {
        assert( lock.locked() );
        return pthread_cond_wait( &cond_, &lock.mutex()->mx_ );
    }

    template< class Lock > int timed_wait( Lock & lock, timespec const & abstime )
    {
        assert( lock.locked() );
        return pthread_cond_timedwait( &cond_, &lock.mutex()->mx_, &abstime );
    }

    int signal()
    {
        return pthread_cond_signal( &cond_ );
    }

    int broadcast()
    {
        return pthread_cond_broadcast( &cond_ );
    }
};

// Read/write lock

class rwlock_attr
{
private:

    pthread_rwlockattr_t attr_;

public:

    rwlock_attr()
    {
        int r = pthread_rwlockattr_init( &attr_ );

        if( r != 0 )
        {
            throw thread_error( r );
        }
    }

    ~rwlock_attr()
    {
        pthread_rwlockattr_destroy( &attr_ );
    }

    explicit rwlock_attr( pthread_rwlockattr_t const * attr )
    {
        int r = pthread_rwlockattr_init( &attr_ );

        if( r != 0 )
        {
            throw thread_error( r );
        }

        if( attr )
        {
            r = pthread_rwlockattr_copy_np( &attr_, attr );

            if( r != 0 )
            {
                pthread_rwlockattr_destroy( &attr_ );
                throw thread_error( r );
            }
        }
    }

    rwlock_attr( rwlock_attr const & attr )
    {
        int r = pthread_rwlockattr_init( &attr_ );

        if( r != 0 )
        {
            throw thread_error( r );
        }

        r = pthread_rwlockattr_copy_np( &attr_, attr );

        if( r != 0 )
        {
            pthread_rwlockattr_destroy( &attr_ );
            throw thread_error( r );
        }
    }

    rwlock_attr & operator=( rwlock_attr const & attr )
    {
        int r = pthread_rwlockattr_copy_np( &attr_, attr );

        if( r != 0 )
        {
            throw thread_error( r );
        }
    }

    operator pthread_rwlockattr_t * ()
    {
        return &attr_;
    }

    operator pthread_rwlockattr_t const * () const
    {
        return &attr_;
    }

    int get_pshared() const
    {
        int pshared = PTHREAD_PROCESS_PRIVATE;

        int r = pthread_rwlockattr_getpshared( &attr_, &pshared );

        assert( r == 0 );

        return pshared;
    }

    int set_pshared( int pshared )
    {
        return pthread_rwlockattr_setpshared( &attr_, pshared );
    }
};

class rwlock
{
private:

    pthread_rwlock_t rwl_;

    rwlock( rwlock const & );
    rwlock & operator=( rwlock const & );

public:

    explicit rwlock( pthread_rwlockattr_t const * attr = 0 )
    {
        int r = pthread_rwlock_init( &rwl_, 0 /*attr*/ ); // pthreads-win32 2.8 is broken

        if( r != 0 )
        {
            throw thread_error( r );
        }
    }

    ~rwlock()
    {
        pthread_rwlock_destroy( &rwl_ );
    }

    int lock()
    {
        return pthread_rwlock_wrlock( &rwl_ );
    }

    int try_lock()
    {
        return pthread_rwlock_trywrlock( &rwl_ );
    }

    int timed_lock( timespec const & abstime )
    {
        return pthread_rwlock_timedwrlock( &rwl_, &abstime );
    }

    int unlock()
    {
        return pthread_rwlock_unlock( &rwl_ );
    }

    int rdlock()
    {
        return pthread_rwlock_rdlock( &rwl_ );
    }

    int try_rdlock()
    {
        return pthread_rwlock_tryrdlock( &rwl_ );
    }

    int timed_rdlock( timespec const & abstime )
    {
        return pthread_rwlock_timedrdlock( &rwl_, &abstime );
    }

    int rdunlock()
    {
        return pthread_rwlock_unlock( &rwl_ );
    }
};

typedef basic_lock< rwlock > write_lock;

template< class Mx > class basic_read_lock
{
private:

    Mx * pmx_;
    bool locked_;

    basic_read_lock( basic_read_lock const & );
    basic_read_lock & operator=( basic_read_lock const & );

public:

    typedef Mx mutex_type;

    explicit basic_read_lock( Mx & mx_, bool locked = true ): pmx_( &mx_ ), locked_( false )
    {
        if( locked )
        {
            lock();
        }
    }

    ~basic_read_lock()
    {
        if( locked_ )
        {
            unlock();
        }
    }

    void lock()
    {
        assert( !locked_ );

        int r = pmx_->rdlock();

        if( r == 0 )
        {
            locked_ = true;
        }
        else
        {
            throw lock_error( r );
        }
    }

    bool try_lock()
    {
        assert( !locked_ );

        int r = pmx_->try_rdlock();

        if( r == 0 )
        {
            locked_ = true;
        }
        else if( r == EBUSY )
        {
        }
        else
        {
            throw lock_error( r );
        }

        return locked_;
    }

    bool timed_lock( timespec const & abstime )
    {
        assert( !locked_ );

        int r = pmx_->timed_rdlock( abstime );

        if( r == 0 )
        {
            locked_ = true;
        }
        else if( r == ETIMEDOUT )
        {
        }
        else
        {
            throw lock_error( r );
        }

        return locked_;
    }

    void unlock()
    {
        assert( locked_ );

        int r = pmx_->rdunlock();

        assert( r == 0 );

        locked_ = false;
    }

    bool locked() const
    {
        return locked_;
    }

    Mx * mutex()
    {
        return pmx_;
    }
};

typedef basic_read_lock< rwlock > read_lock;

// Thread

class thread_attr
{
private:

    pthread_attr_t attr_;

public:

    thread_attr()
    {
        int r = pthread_attr_init( &attr_ );

        if( r != 0 )
        {
            throw thread_error( r );
        }
    }

    ~thread_attr()
    {
        pthread_attr_destroy( &attr_ );
    }

    thread_attr( thread_attr const & attr )
    {
        int r = pthread_attr_init( &attr_ );

        if( r != 0 )
        {
            throw thread_error( r );
        }

        r = pthread_attr_copy_np( &attr_, attr );

        if( r != 0 )
        {
            pthread_attr_destroy( &attr_ );
            throw thread_error( r );
        }
    }

    explicit thread_attr( pthread_attr_t const * attr )
    {
        int r = pthread_attr_init( &attr_ );

        if( r != 0 )
        {
            throw thread_error( r );
        }

        if( attr )
        {
            r = pthread_attr_copy_np( &attr_, attr );

            if( r != 0 )
            {
                pthread_attr_destroy( &attr_ );
                throw thread_error( r );
            }
        }
    }

    thread_attr & operator=( thread_attr const & attr )
    {
        int r = pthread_attr_copy_np( &attr_, attr );

        if( r != 0 )
        {
            throw thread_error( r );
        }
    }

    operator pthread_attr_t * ()
    {
        return &attr_;
    }

    operator pthread_attr_t const * () const
    {
        return &attr_;
    }

    int get_detach_state() const
    {
        int detachstate = PTHREAD_CREATE_JOINABLE;

        int r = pthread_attr_getdetachstate( &attr_, &detachstate ); // pthreads-win32 2.8 doesn't conform
        assert( r == 0 );

        return detachstate;
    }

    int set_detach_state( int detachstate )
    {
        return pthread_attr_setdetachstate( &attr_, detachstate );
    }

    sched_param get_sched_param() const
    {
        sched_param schedparam = {};

        int r = pthread_attr_getschedparam( &attr_, &schedparam );
        assert( r == 0 );

        return schedparam;
    }

    int set_sched_param( sched_param const & schedparam )
    {
        return pthread_attr_setschedparam( &attr_, &schedparam );
    }

    size_t get_stack_size() const
    {
        size_t stacksize = 0;

        int r = pthread_attr_getstacksize( &attr_, &stacksize );
        assert( r == 0 );

        return stacksize;
    }

    int set_stack_size( size_t stacksize )
    {
        return pthread_attr_setstacksize( &attr_, stacksize );
    }
};

namespace thread
{

class handle
{
public: // private

    pthread2_t __pt;

    handle( pthread2_t pt, bool attach ): __pt( pt )
    {
        if( attach )
        {
            pthread_attach_np( __pt );
        }
    }

    void swap( handle & rhs )
    {
        std::swap( __pt, rhs.__pt );
    }

public:

    handle(): __pt()
    {
    }

    handle( handle const & rhs ): __pt( rhs.__pt )
    {
        pthread_attach_np( __pt );
    }

    handle & operator=( handle const & rhs )
    {
        handle( rhs ).swap( *this );
        return *this;
    }

    ~handle()
    {
        pthread_detach( __pt );
    }
};

inline bool operator==( handle h1, handle h2 )
{
    return pthread_equal( h1.__pt, h2.__pt );
}

inline bool operator<( handle h1, handle h2 )
{
    return pthread_less_np( h1.__pt, h2.__pt );
}

inline size_t hash_value( handle th )
{
    return pthread_hash_np( th.__pt );
}

template< class F > void * __threadproc( void * pv )
{
    std::auto_ptr< F > pf( static_cast< F * >( pv ) );

    (*pf)();

    return 0;
}

template< class F > handle create( pthread_attr_t const * attr, F f )
{
    std::auto_ptr<F> pf( new F( f ) );

    pthread2_t pt;

    int r;
    
    if( attr != 0 )
    {
        thread_attr attr2( attr );
        attr2.set_detach_state( PTHREAD_CREATE_JOINABLE );

        r = pthread_create( &pt, attr2, &__threadproc<F>, pf.get() );
    }
    else
    {
        r = pthread_create( &pt, 0, &__threadproc<F>, pf.get() );
    }

    if( r != 0 )
    {
        throw thread_error( r );
    }

    pf.release();

    return handle( pt, false );
}

//

template< class F, class A1 > handle create( pthread_attr_t const * attr, F f, A1 a1 )
{
    return create( attr, boost::bind( f, a1 ) );
}

template< class F, class A1, class A2 > handle create( pthread_attr_t const * attr, F f, A1 a1, A2 a2 )
{
    return create( attr, boost::bind( f, a1, a2 ) );
}

template< class F, class A1, class A2, class A3 > handle create( pthread_attr_t const * attr, F f, A1 a1, A2 a2, A3 a3 )
{
    return create( attr, boost::bind( f, a1, a2, a3 ) );
}

//

template< class F > handle create( thread_attr const & attr, F f )
{
    return create( static_cast< pthread_attr_t const * >( attr ), f );
}

template< class F, class A1 > handle create( thread_attr const & attr, F f, A1 a1 )
{
    return create( static_cast< pthread_attr_t const * >( attr ), boost::bind( f, a1 ) );
}

template< class F, class A1, class A2 > handle create( thread_attr const & attr, F f, A1 a1, A2 a2 )
{
    return create( static_cast< pthread_attr_t const * >( attr ), boost::bind( f, a1, a2 ) );
}

//

template< class F > handle create( F f )
{
    return create( static_cast< pthread_attr_t const * >( 0 ), f );
}

template< class F, class A1 > handle create( F f, A1 a1 )
{
    return create( static_cast< pthread_attr_t const * >( 0 ), boost::bind( f, a1 ) );
}

template< class F, class A1, class A2 > handle create( F f, A1 a1, A2 a2 )
{
    return create( static_cast< pthread_attr_t const * >( 0 ), boost::bind( f, a1, a2 ) );
}

inline handle self()
{
    return handle( __pthread_self(), true );
}

inline int join( handle const & th )
{
    return pthread_join2_np( th.__pt );
}

inline int try_join( handle const & th )
{
    return pthread_tryjoin2_np( th.__pt );
}

inline int timed_join( handle const & th, timespec const & abstime )
{
    return pthread_timedjoin2_np( th.__pt, &abstime );
}

inline int cancel( handle const & th )
{
    return pthread_cancel( th.__pt );
}

inline int set_cancel_state( int cs )
{
    assert( cs == PTHREAD_CANCEL_ENABLE || cs == PTHREAD_CANCEL_DISABLE );

    int r = PTHREAD_CANCEL_ENABLE;

    pthread_setcancelstate( cs, &r );

    return r;
}

inline void test_cancel()
{
    pthread_testcancel();
}

class disable_cancelation
{
private:

    int cs_;

public:

    disable_cancelation(): cs_( set_cancel_state( PTHREAD_CANCEL_DISABLE ) )
    {
    }

    ~disable_cancelation()
    {
        set_cancel_state( cs_ );
    }
};

void sleep( timespec const & reltime );

inline void yield()
{
    sched_yield();
}

void yield( unsigned k );

} // namespace thread

} // namespace std

#endif // #ifndef THREAD_HPP_INCLUDED

