/* Copyright 2005 Chris Thomasson */


#include "../include/sys/ac_sys.h"
#include "../include/ac_eventcount_algo1.h"




int AC_APIDECL
ac_eventcount_algo1_alloc
( ac_eventcount_algo1_t *_this,
  ac_eventcount_algo1_shared_t *_shared )
{
  int ret;

  if ( ! _shared )
  {
    ac_eventcount_algo1_shared_padded_t *_padded;

    /* alloc and align memory */
    _padded = ac_malloc_aligned
              ( &_this->shared_mem,
                sizeof( *_padded ),
                AC_CPU_CACHE_LINE );
    if ( ! _padded ) { return ac_sys_error( ENOMEM ); }
     
    _shared = &_padded->_this;
  }

  else { _this->shared_mem = 0; }

  /* verify shared alignment */
  ret = AC_SYS_IS_MEM_ALIGNED
          ( _shared,
            AC_CPU_WORD_SIZE );
  if ( ret )
  {
    ac_free( _this->shared_mem );
    return ac_sys_error( ret );
  }

  /* init shared */
  _shared->ec = 0;
  _shared->waiters = 0;

  /* init waitset */
  ret = pthread_cond_init( &_this->cond, 0 );
  if ( ret ) 
  { 
    ac_free( _this->shared_mem );
    return ac_sys_error( ret ); 
  }

  ret = pthread_mutex_init( &_this->mutex, 0 );
  if ( ret ) 
  { 
    pthread_mutex_destroy( &_this->mutex );
    ac_free( _this->shared_mem );
    return ac_sys_error( ret ); 
  }

  /* init read-only */
  _this->shared = _shared;
 
  return 0;
}


int AC_APIDECL
ac_eventcount_algo1_free
( ac_eventcount_algo1_t *_this )
{
  int ret;

  if ( _this->shared->waiters )
  {
    return ac_sys_error( EBUSY );
  }

  ret = pthread_cond_destroy( &_this->cond );
  if ( ret ) { return ac_sys_error( ret ); }

  ret = pthread_mutex_destroy( &_this->mutex );
  if ( ret ) { return ac_sys_error( ret ); }

  ac_free( _this->shared_mem );

  return 0;
}


int AC_APIDECL
ac_prv_eventcount_algo1_signal
( ac_eventcount_algo1_t *_this )
{
  ac_intword_t waiters;
  ac_eventcount_cmp_t cmp, old;

  int ret = pthread_mutex_lock( &_this->mutex );
  if ( ret ) { return ac_sys_error( ret ); }

  old = _this->shared->ec;

  do
  {
    cmp = old;
    old = ac_atomic_cas_acquire
            ( &_this->shared->ec,
              cmp,
              ( cmp + 1 ) & 0x7FFFFFFF );
  }
  while ( cmp != old );

  waiters = _this->shared->waiters;
  _this->shared->waiters = 0;

  ret = pthread_mutex_unlock( &_this->mutex );
  if ( ret ) { return ac_sys_error( ret ); }

  if ( waiters )
  {
    ret = pthread_cond_broadcast( &_this->cond );
    if ( ret ) { return ac_sys_error( ret ); }
  }

  return 0;
}


int AC_APIDECL
ac_eventcount_algo1_timedwait
( ac_eventcount_algo1_t *_this,
  ac_thread_t *_tls,
  ac_eventcount_cmp_t cmp,
  ac_timeout_t timeout )
{
  AC_UNUSED( _tls );
  AC_UNUSED( timeout );

  if ( ( (ac_eventcount_cmp_t)ac_mb_load_acquire
          ( &_this->shared->ec ) & 0x7FFFFFFF ) == cmp )
  {
    int ret = pthread_mutex_lock( &_this->mutex );
    if ( ret ) { return ac_sys_error( ret ); }

    if ( ( (ac_eventcount_cmp_t)ac_mb_load_naked
          ( &_this->shared->ec ) & 0x7FFFFFFF ) == cmp )
    {
      ++_this->shared->waiters;
      
      ret = pthread_cond_wait( &_this->cond, &_this->mutex );
      if ( ret ) { return ac_sys_error( ret ); }
    }

    ret = pthread_mutex_unlock( &_this->mutex );
    if ( ret ) { return ac_sys_error( ret ); }
  }

  return 0;
}