// 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"

#if !defined( _POSIX_CXX09_EXTENSIONS )

#include "thread.hpp"
#include "event.hpp"

// atomics

extern "C" long __cdecl _InterlockedIncrement( long volatile * );
extern "C" long __cdecl _InterlockedDecrement( long volatile * );

# pragma intrinsic( _InterlockedIncrement )
# pragma intrinsic( _InterlockedDecrement )

void atomic_increment( long * p )
{
    _InterlockedIncrement( p );
}

long atomic_decrement( long * p )
{
    return _InterlockedDecrement( p );
}

// pthread2_state

struct pthread2_state
{
    long refs_;

    //std::mutex mx_;
    //std::condition cn_;
    //bool ended_;

    event ended_;

    pthread_t handle_;

    void attach()
    {
        atomic_increment( &refs_ );
    }

    void detach()
    {
        if( atomic_decrement( &refs_ ) == 0 )
        {
            delete this;
        }
    }

    void on_thread_end()
    {
        //{
        //  std::scoped_lock lock( mx_ );
        //  ended_ = true;
        //}

        //cn_.broadcast();

        ended_.set();
    }

    pthread2_state(): refs_( 0 ), handle_() // , ended_( false )
    {
    }

    virtual ~pthread2_state()
    {
    }

    int join2()
    {
        //std::scoped_lock lock( mx_ );

        //while( !ended_ )
        //{
        //  int r = cn_.wait( lock );
        //  if( r != 0 ) return r;
        //}

        //return 0;

        return ended_.wait();
    }

    int tryjoin2()
    {
        //std::scoped_lock lock( mx_ );
        //return ended_? 0: EBUSY;

        return ended_.try_wait();
    }

    int timedjoin2( timespec const * abstime )
    {
        //std::scoped_lock lock( mx_ );

        //while( !ended_ )
        //{
        //  int r = cn_.timed_wait( lock, abstime );
        //  if( r != 0 ) return r;
        //}

        //return 0;

        if( abstime == 0 ) return EINVAL;
        return ended_.timed_wait( *abstime );
    }

    int cancel()
    {
        std::basic_lock< event > lock( ended_ );

        if( !ended_.unlocked_is_set() )
        {
            return pthread_cancel( handle_ );
        }
        else
        {
            return 0;
        }

        //std::scoped_lock lock( mx_ );

        //if( !ended_ )
        //{
        //  return pthread_cancel( handle_ );
        //}
        //else
        //{
        //  return 0;
        //}
    }
};

int pthread_attach_np( pthread2_t thread )
{
    if( thread )
    {
        thread->attach();
    }

    return 0;
}

int pthread_detach( pthread2_t thread )
{
    if( thread )
    {
        thread->detach();
    }

    return 0;
}

static pthread_key_t s_tsp;
static pthread_once_t s_tsp_once = PTHREAD_ONCE_INIT;

extern "C" static void tsp_destroy( void * pv )
{
    pthread2_state * ps = static_cast< pthread2_state * >( pv );

    if( ps != 0 )
    {
        ps->on_thread_end();
        ps->detach();
    }
}

extern "C" static void tsp_init()
{
    pthread_key_create( &s_tsp, tsp_destroy );
}

static void set_tsp( pthread2_state * ps )
{
    pthread_once( &s_tsp_once, tsp_init );
    pthread_setspecific( s_tsp, ps );
}

static pthread2_state * get_tsp()
{
    pthread_once( &s_tsp_once, tsp_init );
    return static_cast< pthread2_state * >( pthread_getspecific( s_tsp ) );
}

struct thp_param
{
    pthread2_state * ps_;
    void * (*pf_) ( void * );
    void * arg_;
};

extern "C" void * threadproc( void * pv )
{
    std::auto_ptr<thp_param> ptp( static_cast< thp_param * >( pv ) );

    set_tsp( ptp->ps_ );
    ptp->ps_->handle_ = pthread_self(); // race here, hopefully harmless

    return ptp->pf_( ptp->arg_ );
}

int pthread_create( pthread2_t * thread, const pthread_attr_t * attr, void *(*start_routine)(void *), void * arg )
{
    int ds = PTHREAD_CREATE_JOINABLE;

    int r;

    if( attr != 0 )
    {
        r = pthread_attr_getdetachstate( attr, &ds );
        if( r != 0 ) return r;
    }

    std::auto_ptr<pthread2_state> ps( new pthread2_state );

    if( ds == PTHREAD_CREATE_JOINABLE )
    {
        ++ps->refs_;
    }

    std::auto_ptr<thp_param> ptp( new thp_param );

    ptp->ps_ = ps.get();
    ptp->pf_ = start_routine;
    ptp->arg_ = arg;

    ++ps->refs_; // for s_tsp, to avoid the race condition where the caller manages to detach

    r = pthread_create( &ps->handle_, 0, threadproc, ptp.get() );

    if( r == 0 )
    {
        if( ds == PTHREAD_CREATE_JOINABLE )
        {
            pthread_detach( ps->handle_ );
        }

        *thread = ps.release();
        ptp.release();
    }

    return r;
}

pthread2_t __pthread_self()
{
    pthread2_state * ps = get_tsp();

    if( ps == 0 )
    {
        // we're being called from a foreign thread, adopt it

        ps = new pthread2_state;
        ps->handle_ = pthread_self();

        set_tsp( ps );
        ps->attach();
    }

    return ps;
}

int pthread_cancel( pthread2_t thread )
{
    return thread? thread->cancel(): 0;
}

int pthread_join2_np( pthread2_t thread )
{
    return thread? thread->join2(): 0;
}

int pthread_tryjoin2_np( pthread2_t thread )
{
    return thread? thread->tryjoin2(): 0;
}

int pthread_timedjoin2_np( pthread2_t thread, const struct timespec * abstime )
{
    return thread? thread->timedjoin2( abstime ): 0;
}

static pthread_key_t s_once2_key;
static pthread_once_t s_once2_ctrl = PTHREAD_ONCE_INIT;

extern "C" static void once2_init()
{
    pthread_key_create( &s_once2_key, 0 );
}

struct once2_params
{
    void (*init_routine)( void * );
    void * arg;
};

extern "C" static void once2_execute()
{
    void * pv = pthread_getspecific( s_once2_key );

    once2_params * pp2 = static_cast< once2_params * >( pv );

    pp2->init_routine( pp2->arg );
}

int pthread_once2_np( pthread_once_t * once_control, void (*init_routine)(void*), void * arg )
{
    int r = pthread_once( &s_once2_ctrl, &once2_init );
    if( r != 0 ) return r;

    once2_params p2 = { init_routine, arg };

    r = pthread_setspecific( s_once2_key, &p2 );
    if( r != 0 ) return r;

    return pthread_once( once_control, once2_execute );
}

#define ATTR_COPY( prefix, Tp, name ) \
    Tp v_##name; \
    r = pthread_##prefix##attr_get##name( source, &v_##name ); \
    if( r != 0 ) return r; \
    r = pthread_##prefix##attr_set##name( target, v_##name ); \
    if( r != 0 ) return r;

int pthread_attr_copy_np( pthread_attr_t * target, pthread_attr_t const * source )
{
    int r;

    ATTR_COPY( , int, detachstate )
    ATTR_COPY( , size_t, stacksize )
    ATTR_COPY( , size_t, guardsize )

    // ATTR_COPY( , struct sched_param, schedparam )
    struct sched_param v_schedparam;
    r = pthread_attr_getschedparam( source, &v_schedparam );
    if( r != 0 ) return r;
    r = pthread_attr_setschedparam( target, &v_schedparam );
    if( r != 0 ) return r;

    //ATTR_COPY( , int, inheritsched )
    //ATTR_COPY( , int, schedpolicy )
    //ATTR_COPY( , int, scope )

    return 0;
}

int pthread_mutexattr_copy_np( pthread_mutexattr_t * target, pthread_mutexattr_t const * source )
{
    int r;

    ATTR_COPY( mutex, int, type )
    ATTR_COPY( mutex, int, pshared )

    // ATTR_COPY( mutex, int, prioceiling )
    // ATTR_COPY( mutex, int, protocol )

    return 0;
}

int pthread_condattr_copy_np( pthread_condattr_t * target, pthread_condattr_t const * source )
{
    int r;

    ATTR_COPY( cond, int, pshared )
    ATTR_COPY( cond, int, clock )

    return 0;
}

int pthread_rwlockattr_copy_np( pthread_rwlockattr_t * target, pthread_rwlockattr_t const * source )
{
    int r;

    ATTR_COPY( rwlock, int, pshared )

    return 0;
}

#endif // !defined( _POSIX_CXX09_EXTENSIONS )

#ifdef PTW32_VERSION

int clock_gettime( clockid_t clock_id, struct timespec * tp );
int nanosleep( const struct timespec * rqtp, struct timespec * rmtp );

int pthread_attr_getguardsize( const pthread_attr_t * attr, size_t * guardsize )
{
    *guardsize = 4096;
    return 0;
}

int pthread_attr_setguardsize( pthread_attr_t * attr, size_t guardsize )
{
    return guardsize <= 4096? 0: ENOTSUP;
}

int pthread_condattr_getclock( const pthread_condattr_t * attr, clockid_t * clock_id )
{
    *clock_id = CLOCK_REALTIME;
    return 0;
}

int pthread_condattr_setclock( pthread_condattr_t * attr, clockid_t clock_id )
{
    return clock_id == CLOCK_REALTIME? 0: ENOTSUP;
}

#endif // PTW32_VERSION

