////////////////////////////////////////////////////
// intent is to make join a try/timed/optional
// operation, like in Java but using C++ smart 
// pointers on top of C interface/impl so that 
// it would also support calling "legacy" C 
// threaded code/libraries called in the context 
// of "my_thread" with no restrictions other than 
// not detaching the current thread and some danger
// wrt to sharing pthread_t objs obtained from 
// pthread_self).
// ----
// i think that the following closely matches Java
// model with respect to thread objects ("automatic" 
// lifetime management of thread objects with 
// optional/multiple/concurrent "join" and creation 
// of initially suspended threads with "start" method 
// to resume initially suspended thread and start 
// execution of user code; call user provided thread
// start routine)
// ----
// i think the later could be useful in C++ library 
// as well, e.g:
//
// // User:
//
// mutex mtx;     // Dynamic init
// condition cnd; // Dynamic init
//
// int f1( int,.... ); // Uses mtx/cnd
// int f2( int,.... ); // Uses mtx/cnd
//
// thread_ref< int,.... > tr1( f1, <parms> ); // Dynamic init; BUT library will enforce suspension
// thread_ref< int,.... > tr2( f2, <parms> ); // Dynamic init; BUT library will enforce suspension
//
// // Library:
// int main( int argc,char* argv[] )
// {
// 
//   // Adopt main thread
//   //...
// 
//   // resume threads created (with 
//   // suspension enforced) before "main" 
//   //...
// 
//   main_thread::main( argc,argv );
// 
//   pthread_exit( NULL );
// 
//   return 0;
// 
// }
// 
// // User:
// void main_thread::main( int argc,char* argv[] )
// {
// 
//   // ...
// 
//   if ( error )
//     exit( -1 );
// 
//   // return from main_thread::main does *not* invoke 
//   // process termination! main_thread::main behaves 
//   // just like any other thread function. process 
//   // termination occurs ether explicitly (exit/...) 
//   // or implicitly on last thread termination.
// 
//   exit( tr1.join() + tr2.join() )
//
// }
//
// -and-
// 
// void do_something( ... )
// {
// 
//   // need 2 threads
//   thread_ref< ... > tr1( ...,SUSPENDED );
//   thread_ref< ... > tr2( ...,SUSPENDED );
// 
//   // now ready to do the job with both
//   // threads created successfully.
// 
//   tr1.start();
//   tr2.start();
// 
//   tr1.join();
//   tr2.join();
// 
//   // in the case of failure (e.g. out of memory) while 
//   // creating the 2nd thread we will return "immediately"
//   // not letting first thread do some "useless"
//   // work (invoke its thread function) w/o having
//   // the 2nd thread ready as well. that would 
//   // be "difficult" to achieve without 
//   // initially suspended thread state.
//   
// }
////////////////////////////////////////////////////

#include <pthread.h>
#include <time.h>

////////////////////////////////////////////////////

struct my_thread;
typedef struct my_thread* my_thread_t;

int my_thread_create( my_thread_t* pmt, 
                      void* (*start_routine)(void*), 
                      void* start_routine_arg,
                      int suspended );

int my_thread_start( my_thread_t mt );

int my_thread_cancel( my_thread_t mt );

my_thread_t my_thread_current();

int my_thread_join( my_thread_t mt,void** value_ptr );

int my_thread_tryjoin( my_thread_t mt,void** value_ptr );

int my_thread_timedjoin( my_thread_t mt,
                         struct timespec* timeout,
                         void** value_ptr );

int my_thread_addref( my_thread_t mt );

int my_thread_unref( my_thread_t mt );

////////////////////////////////////////////////////


enum my_thread_state {
  MTS_START_RUNNING,
  MTS_START_SUSPENDED,
  MTS_START_FAILED,
  MTS_SUSPENDED,
  MTS_EXIT_SUSPENDED,
  MTS_RUNNING,
  MTS_EXITING,
  MTS_JOINED
};

struct my_thread {
  pthread_t            tid;
  enum my_thread_state eState; 
  void*                pExitValue;
  unsigned long        ulRefCounter;
  pthread_mutex_t      mtxStateLock;
  pthread_cond_t       cndStateChanged;
  // add some mapping for "unlimited" 
  // number of thread locals here
};

struct my_thread_start {
  my_thread_t mt;
  void* (*    fnStartRoutine )( void* );
  void*       pStartRoutineArg;
  int         nStartError;
};

typedef struct my_thread_start* my_thread_start_t;

static pthread_key_t  my_thread_key;
static int            my_thread_key_status;
static pthread_once_t my_thread_once_control = PTHREAD_ONCE_INIT;

static void* my_thread_fatal_error( my_thread_t mt,int error )
{

  // Handle fatal error
  // ...
  exit( -1 );

  // Should never reach this point
  return NULL;

}

static void my_thread_tsd_dtor( void* ptr )
{
  int         status;
  my_thread_t mt = (my_thread_t)ptr;

  // Destroy second level thread locals
  // ...

  if ( 0 == (status = pthread_mutex_lock( &mt->mtxStateLock )) ) {

    if ( 0 != --mt->ulRefCounter ) {

      mt->eState = MTS_EXITING;

      if ( 0 == (status = pthread_cond_broadcast( &mt->cndStateChanged )) )
        status = pthread_mutex_unlock( &mt->mtxStateLock );

    }
    else {

      if ( 0 == (status = pthread_detach( mt->tid )) &&
           0 == (status = pthread_mutex_unlock( &mt->mtxStateLock )) &&
           0 == (status = pthread_mutex_destroy( &mt->mtxStateLock )) &&
           0 == (status = pthread_cond_destroy( &mt->cndStateChanged )) ) {

        free( mt );

      }

    }

  }

  if ( 0 != status )
    my_thread_fatal_error( mt,status );

}

static void my_thread_suspended_cancel_cleanup( void* ptr ) 
{
  int         status;
  my_thread_t mt = (my_thread_t)ptr;

  mt->eState = MTS_RUNNING; 

  if ( 0 != (status = pthread_mutex_unlock( &mt->mtxStateLock )) )
    my_thread_fatal_error( mt,status );

}

static void* my_thread_start_routine( void* ptr )
{
  int               status;
  my_thread_start_t mts = (my_thread_start_t)ptr;
  void* (*          start_routine )( void* ) = mts->fnStartRoutine;
  void*             start_routine_arg = mts->pStartRoutineArg;
  my_thread_t       mt  = mts->mt;
  
  if ( 0 != (status = pthread_mutex_lock( &mt->mtxStateLock )) )
    return my_thread_fatal_error( mt,status );

  mt->tid = pthread_self();

  if ( 0 == (status = pthread_setspecific( my_thread_key,mt )) ) {

    if ( MTS_START_SUSPENDED == mt->eState ) {

      mt->eState = MTS_SUSPENDED;

      if ( 0 != (status = pthread_cond_signal( &mt->cndStateChanged )) )
        return my_thread_fatal_error( mt,status );

      pthread_cleanup_push( &my_thread_suspended_cancel_cleanup,mt );

      do {

        if ( 0 != (status = pthread_cond_wait( &mt->cndStateChanged,
                                               &mt->mtxStateLock )) )
          return my_thread_fatal_error( mt,status );

      } while ( MTS_SUSPENDED == mt->eState );

      pthread_cleanup_pop( 0 );

      if ( MTS_EXIT_SUSPENDED == mt->eState )
        mts = NULL;

      if ( 0 != (status = pthread_mutex_unlock( &mt->mtxStateLock )) )
        return my_thread_fatal_error( mt,status );

      if ( NULL == mts )
        return NULL;

    }
    else {

      mt->eState = MTS_RUNNING;

      if ( 0 != (status = pthread_mutex_unlock( &mt->mtxStateLock )) ||
           0 != (status = pthread_cond_signal( &mt->cndStateChanged )) )
        return my_thread_fatal_error( mt,status );

    }

    return start_routine( start_routine_arg );

  }
  else { // ENOMEM or something else went wrong

    mt->eState = MTS_START_FAILED;
    mts->nStartError = status;

    if ( 0 != (status = pthread_cond_signal( &mt->cndStateChanged )) ||
         0 != (status = pthread_mutex_unlock( &mt->mtxStateLock )) )
      return my_thread_fatal_error( mt,status );

  }

  return NULL;

}

static void my_thread_key_init()
{

  my_thread_key_status = pthread_key_create( &my_thread_key,
                                             &my_thread_tsd_dtor );

}

int my_thread_create( my_thread_t* pmt, 
                      void* (*start_routine)(void*), 
                      void* start_routine_arg,
                      int suspended )
{
  my_thread_t            mt;
  pthread_t              tid;
  struct my_thread_start mts;
  int                    status, status1;
  int                    old_cancel_state;

  if ( suspended && NULL == pmt )
    return EINVAL;

  if ( 0 != (status = pthread_once( &my_thread_once_control,
                                    &my_thread_key_init )) )
    return status;

  if ( 0 != my_thread_key_status )
    return my_thread_key_status;

  if ( NULL == (mt = (my_thread_t)malloc( sizeof( struct my_thread ) )) ) 
    return ENOMEM;

  if ( 0 != (status = pthread_mutex_init( &mt->mtxStateLock,NULL )) ) {

    free( mt );
    return status;

  }

  if ( 0 != (status = pthread_cond_init( &mt->cndStateChanged,NULL )) ) {

    if ( 0 != (status1 = pthread_mutex_destroy( &mt->mtxStateLock )) ) {

      status = status1;

    } 
    else {

      free( mt );

    }

    return status;

  }

  mts.mt               = mt;
  mts.fnStartRoutine   = start_routine;
  mts.pStartRoutineArg = start_routine_arg;

  mt->eState = ( suspended ) ? MTS_START_SUSPENDED : MTS_START_RUNNING;
  mt->ulRefCounter = ( NULL == pmt ) ? 2 : 3; // including TSD reference
   
  if ( 0 != (status = pthread_create( &tid, 
                                      NULL,
                                      &my_thread_start_routine, 
                                      &mts )) ) {

    if ( 0 != (status1 = pthread_cond_destroy( &mt->cndStateChanged )) ||
         0 != (status1 = pthread_mutex_destroy( &mt->mtxStateLock )) ) {

      status = status1;

    }
    else {

      free( mt );

    }

    return status;

  }

  status = pthread_mutex_lock( &mt->mtxStateLock );

  for ( ;; ) {

    if ( 0 != status )
      return status;

    if ( mt->eState != (( suspended ) ? MTS_START_SUSPENDED :
                                        MTS_START_RUNNING) ) 
      break;
 
    if ( 0 == (status = pthread_setcancelstate( PTHREAD_CANCEL_DISABLE,
                                                &old_cancel_state )) ) {

      status = pthread_cond_wait( &mt->cndStateChanged,&mt->mtxStateLock );

      if ( 0 != (status1 = pthread_setcancelstate( old_cancel_state,
                                                   &old_cancel_state )) &&
           0 == status )
        status = status1;    

    }

  }

  if ( MTS_START_FAILED == mt->eState ) {

    if ( 0 == (status = pthread_cond_destroy( &mt->cndStateChanged )) &&
         0 == (status = pthread_mutex_unlock( &mt->mtxStateLock )) &&
         0 == (status = pthread_mutex_destroy( &mt->mtxStateLock )) &&
         0 == (status = pthread_setcancelstate( PTHREAD_CANCEL_DISABLE,
                                                &old_cancel_state )) ) {

      status = pthread_join( tid,NULL );

      if ( 0 != (status1 = pthread_setcancelstate( old_cancel_state,
                                                   &old_cancel_state )) &&
           0 == status )
        status = status1;

    }

    if ( 0 == status )  {

      status = mts.nStartError;
      free( mt );

    }

  }
  else if ( 0 == --mt->ulRefCounter ) {

    if ( 0 == (status = pthread_cond_destroy( &mt->cndStateChanged )) &&
         0 == (status = pthread_mutex_unlock( &mt->mtxStateLock )) &&
         0 == (status = pthread_mutex_destroy( &mt->mtxStateLock )) &&
         (MTS_JOINED == mt->eState ||
          0 == (status = pthread_detach( tid ))) ) {

      free( mt );

    }

  }
  else { 

    status = pthread_mutex_unlock( &mt->mtxStateLock );

    if ( NULL != pmt )
      *pmt = mt;

  }

  return status;

}

int my_thread_start( my_thread_t mt )
{
  enum my_thread_state state;
  int                  status;

  if ( 0 != (status = pthread_mutex_lock( &mt->mtxStateLock )) )
    return status;    

  if ( MTS_SUSPENDED == (state = mt->eState) )
    mt->eState = MTS_RUNNING;

  if ( 0 == (status = pthread_mutex_unlock( &mt->mtxStateLock )) ) 
    status = ( MTS_SUSPENDED == state ) ?
      pthread_cond_broadcast( &mt->cndStateChanged ) : EPERM;

  return status;

}

int my_thread_cancel( my_thread_t mt )
{
  int status;

  if ( 0 != (status = pthread_mutex_lock( &mt->mtxStateLock )) )
    return status;    

  if ( MTS_JOINED == mt->eState ||
       0 == (status = pthread_cancel( mt->tid )) )
    status = pthread_mutex_unlock( &mt->mtxStateLock );

  return status;

}

my_thread_t my_thread_current()
{

  return (my_thread_t)pthread_getspecific( my_thread_key );

}

static void my_thread_join_cancel_cleanup( void* ptr ) 
{
  int         status;
  my_thread_t mt = (my_thread_t)ptr;

  if ( 0 != (status = pthread_mutex_unlock( &mt->mtxStateLock )) )
    my_thread_fatal_error( mt,status );

}

int my_thread_join( my_thread_t mt,void** value_ptr )
{
  int status = pthread_mutex_lock( &mt->mtxStateLock );

  for ( ;; ) {

    if ( 0 != status )
      return status;

    if ( MTS_EXITING == mt->eState ) {

      pthread_cleanup_push( &my_thread_join_cancel_cleanup,mt );

      if ( 0 == (status = pthread_join( mt->tid,&mt->pExitValue )) ) {

        mt->eState = MTS_JOINED;

      }
    
      pthread_cleanup_pop( 0 );

      if ( 0 != status )
        return status;

      break;

    }

    if ( MTS_JOINED == mt->eState )
      break;

    pthread_cleanup_push( &my_thread_join_cancel_cleanup,mt );

    status = pthread_cond_wait( &mt->cndStateChanged,&mt->mtxStateLock );
    
    pthread_cleanup_pop( 0 );

  }

  if ( 0 == (status = pthread_mutex_unlock( &mt->mtxStateLock )) &&
       NULL != value_ptr ) {

    *value_ptr = mt->pExitValue;

  }

  return status;

}

int my_thread_tryjoin( my_thread_t mt,void** value_ptr )
{
  int status, status1;

  if ( 0 != (status = pthread_mutex_lock( &mt->mtxStateLock )) )
    return status;

  if ( MTS_EXITING == mt->eState ) {

    pthread_cleanup_push( &my_thread_join_cancel_cleanup,mt );

    if ( 0 == (status = pthread_join( mt->tid,&mt->pExitValue )) ) {
 
      mt->eState = MTS_JOINED;

    }
    
    pthread_cleanup_pop( 0 );

    if ( 0 != status )
      return status;

  }
  else if ( MTS_JOINED != mt->eState )  {

    status = EBUSY;

  }

  if ( 0 != (status1 = pthread_mutex_unlock( &mt->mtxStateLock )) ) {

    status = status1;

  }
  else if ( 0 == status && NULL != value_ptr ) {

    *value_ptr = mt->pExitValue;

  }

  return status;

}

int my_thread_timedjoin( my_thread_t mt,
                         struct timespec* timeout,
                         void** value_ptr )
{
  int status, status1;

  status = pthread_mutex_lock( &mt->mtxStateLock );

  do {

    if ( 0 != status )
      return status;

    if ( MTS_EXITING == mt->eState ) {

      pthread_cleanup_push( &my_thread_join_cancel_cleanup,mt );

      if ( 0 == (status = pthread_join( mt->tid,&mt->pExitValue )) ) {

        mt->eState = MTS_JOINED;

      }
    
      pthread_cleanup_pop( 0 );

      if ( 0 != status )
        return status;

      break;

    }

    if ( MTS_JOINED == mt->eState )
      break;

    pthread_cleanup_push( &my_thread_join_cancel_cleanup,mt );

    status = pthread_cond_timedwait( &mt->cndStateChanged,
                                     &mt->mtxStateLock,
                                     timeout );
    
    pthread_cleanup_pop( 0 );

  } while ( ETIMEDOUT != status );

  if ( 0 != (status1 = pthread_mutex_unlock( &mt->mtxStateLock )) ) {

    status = status1;

  }
  else if ( 0 == status && NULL != value_ptr ) {

    *value_ptr = mt->pExitValue;

  }

  return status;

}

int my_thread_addref( my_thread_t mt )
{
  int status;

  if ( 0 != (status = pthread_mutex_lock( &mt->mtxStateLock )) )
    return status;    
  
  ++mt->ulRefCounter;

  return pthread_mutex_unlock( &mt->mtxStateLock );

}

int my_thread_unref( my_thread_t mt )
{
  int           status;
  unsigned long counter;

  if ( 0 != (status = pthread_mutex_lock( &mt->mtxStateLock )) )
    return status;    

  if ( 1 == (counter = --mt->ulRefCounter) &&
       MTS_SUSPENDED == mt->eState ) {

    mt->eState = MTS_EXIT_SUSPENDED;

    if ( 0 == (status = pthread_cond_signal( &mt->cndStateChanged )) ) 
      status = pthread_mutex_unlock( &mt->mtxStateLock );

    return status;

  }

  if ( 0 == (status = pthread_mutex_unlock( &mt->mtxStateLock )) &&
       0 == counter &&
       0 == (status = pthread_mutex_destroy( &mt->mtxStateLock )) &&
       0 == (status = pthread_cond_destroy( &mt->cndStateChanged )) &&
       (MTS_JOINED == mt->eState ||
        0 == (status = pthread_detach( mt->tid ))) ) {

    free( mt );

  }

  return status;

}


