/* Copyright 2005 Chris Thomasson */


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


#if defined ( AC_BUILD_OS_WINDOWS ) || \
    defined ( AC_BUILD_OS_UNDER_WINDOWS )
#include <malloc.h>
#define AC_OS_ALLOCA _alloca
#else
#include <alloca.h>
#define AC_OS_ALLOCA alloca
#endif




static void AC_CDECL
ac_prv_tls_dtor
( void* );


static int AC_APIDECL
prv_thread_alloc
( ac_thread_t**,
  ac_intword_t );


static void* AC_CDECL
prv_thread_entry
( void* );




static pthread_key_t g_tls_key;
static ac_sys_thread_this_t *p_this;




int AC_APIDECL
ac_sys_thread_startup
( ac_sys_thread_this_t *_this )
{
  int ret;

  p_this = _this;

  ret = pthread_key_create
          ( &g_tls_key,
            ac_prv_tls_dtor );
  if ( ret ) { return ac_sys_error( ret ); }

  ac_cpu_stack_mpmc_init( &p_this->node_cache );

  return 0;
}


int AC_APIDECL
ac_sys_thread_shutdown
( void )
{
  int ret;

  ac_sys_thread_tls_shutdown();

  ret = pthread_key_delete( g_tls_key );
  if ( ret ) { return ac_sys_error( ret ); }

  if ( p_this->tls_count ) { assert( ! p_this->tls_count ); abort(); }

  return 0;
}


void AC_APIDECL
ac_sys_thread_tls_shutdown
( void )
{
  ac_thread_t *_this = 
    pthread_getspecific( g_tls_key );
  if ( _this ) { ac_prv_tls_dtor( _this ); }
}


void AC_APIDECL
ac_sys_thread_cache_flush
( void )
{
  ac_i686_node_t *next;
  ac_thread_t *_this;

  while ( p_this->node_cache.front )
  {
    next = p_this->node_cache.front->next;
    _this = ac_cpu_node_get_state( p_this->node_cache.front );
    ac_free( _this->this_mem );
    --p_this->node_count;
    p_this->node_cache.front = next;
  }

  assert( ! p_this->node_count );  
}


int AC_APIDECL
ac_sys_thread_release
( ac_thread_t *_this )
{
  if ( ! ac_atomic_dec_release
          ( &_this->shared.refs ) )
  {
    int ret;
    ac_cpu_node_t *next;

    assert( _this->recurse_index == -1 );

    ret = ac_cpu_tls_free
          ( &_this->cpu_atomic );
    if ( ret ) { assert( ! ret ); abort(); }

    while ( _this->local_cache )
    {
      next = _this->local_cache->next;

      ac_cpu_node_cache_push( _this->local_cache );
      --_this->cache_count;

      _this->local_cache = next;
    }

    if ( _this->cache_count ) 
    { assert( ! _this->cache_count ); abort(); }

    ac_atomic_dec_release( &p_this->tls_count );

    ac_cpu_stack_mpmc_push_cas
      ( &p_this->node_cache, 
        &_this->node );

    ac_atomic_inc_acquire( &p_this->node_count );
  }

  return 0;
}


int AC_APIDECL
ac_thread_alloc
( ac_thread_t **_pthis,
  ac_thread_cfg_t *cfg,
  ac_fp_thread_entry_t fp_entry,
  const void *state )
{
  int ret;
  ac_thread_t *_this;

  ret = prv_thread_alloc( &_this, 2 );
  if ( ret ) { ac_sys_error( ret ); abort(); }

  if ( cfg ) { _this->cfg = *cfg; }

  _this->fp_entry = fp_entry;
  _this->state = state;

  ret = pthread_create
        ( &_this->thread,
          0,
          prv_thread_entry,
          _this );

  if ( ret ) 
  { _this->shared.refs = 1;
    ac_sys_thread_release( _this );
    return ac_sys_error( ret );
  }

  *_pthis = _this;

  return 0;
}


int AC_APIDECL
ac_thread_join
( ac_thread_t *_this,
  void **state )
{
  int ret = pthread_join( _this->thread, state );
  if ( ret ) { return ac_sys_error( ret ); }

  ret = ac_sys_thread_release( _this );
  if ( ret ) { return ac_sys_error( ret ); }

  return 0;
}


int AC_APIDECL
prv_thread_alloc
( ac_thread_t **_pthis,
  ac_intword_t refs )
{    
  int ret;
  ac_thread_t *_this;
  ac_cpu_node_t *node;

  node = np_ac_cpu_stack_mpmc_pop_dwcas
          ( &p_this->node_cache );

  if ( ! node )
  {
    void *this_mem;

    _this = ac_calloc_aligned
              ( &this_mem,
                1,
                sizeof( *_this ),
                AC_CPU_CACHE_LINE );
    if ( ! _this ) { return ac_sys_error( ENOMEM ); }

    _this->this_mem = this_mem;

    ac_cpu_node_init( &_this->node, 0, _this );

    _this->id = ac_atomic_inc_acquire( (ac_intword_t*)&p_this->thread_ids );

    assert( _this->id );
  }

  else
  {
    ac_atomic_dec_release( &p_this->node_count );
    _this = ac_cpu_node_get_state( node );
    _this->cache_count = 0;
    _this->local_cache = 0;
  }

  _this->shared.refs = refs;

  _this->recurse_index = -1;

  ret = ac_cpu_tls_alloc
          ( &_this->cpu_atomic,
            _this );
  if ( ret ) { return ac_sys_error( ret ); }

  ac_atomic_inc_acquire( &p_this->tls_count );

  *_pthis = _this;

  return 0;
}


ac_thread_t* AC_APIDECL
ac_thread_self
( void )
{
  ac_thread_t *_this = 
    pthread_getspecific( g_tls_key );

  if ( ! _this )
  {
    int ret = prv_thread_alloc( &_this, 1 );
    if ( ret ) { ac_sys_error( ret ); abort(); }

    _this->thread = pthread_self();

    ret = pthread_setspecific
            ( g_tls_key,
              _this );
    if ( ret ) 
    { ac_sys_thread_release( _this );
      ac_sys_error( ret );
      abort(); 
    }
  }

  return _this;
}


void AC_CDECL
ac_prv_tls_dtor
( void *ts )
{
  if ( ts )
  {
    int ret;

    ret = ac_sys_thread_release( ts );
    if ( ret ) { assert( ! ret ); abort(); }
    
    ret = pthread_setspecific
            ( g_tls_key,
              0 );
    if ( ret ) { assert( ! ret ); abort(); }
  }
}


void* AC_CDECL
prv_thread_entry
( void *state )
{
  int ret;
  void *uret;
  ac_thread_t *_this = state;

  ret = pthread_setspecific
          ( g_tls_key,
            _this );
  if ( ret ) { assert( ! ret ); abort(); }

  if ( _this->id < 64 )
  {
    AC_OS_ALLOCA( 2048 * _this->id );
    uret = _this->fp_entry( (void*)_this->state );
  }

  else
  {
    uret = _this->fp_entry( (void*)_this->state );
  }

  return uret;
}