// ---------------------------------------------------------------------------
// - cthr.cxx                                                                -
// - standard system library - c thread function implementation              -
// ---------------------------------------------------------------------------
// - This program is free software;  you can redistribute it  and/or  modify -
// - it provided that this copyright notice is kept intact.                  -
// -                                                                         -
// - This program  is  distributed in  the hope  that it will be useful, but -
// - without  any  warranty;  without  even   the   implied    warranty   of -
// - merchantability or fitness for a particular purpose.  In no event shall -
// - the copyright holder be liable for any  direct, indirect, incidental or -
// - special damages arising in any way out of the use of this software.     -
// ---------------------------------------------------------------------------
// - copyright (c) 1999-2008 amaury darsch                                   -
// ---------------------------------------------------------------------------

#include "cthr.hpp"
#include "cerr.hpp"
#include "cthr.hxx"

namespace afnix {

  // the thread info structure
  struct s_thr {
    pthread_t  d_tid;    // the thread id
    t_thrmode  d_mode;   // the thread mode
    t_thrfunc  p_func;   // the start function
    t_thrdtor  p_dtor;   // the object destructor
    void*      p_args;   // the start function argument
    void*      p_result; // the thread result
    bool       d_eflag;  // the end flag for this thread
    long       d_count;  // the reference count for this structure
    s_thr*     p_next;   // next thread in list
    s_thr*     p_prev;   // previous thread in list
    // simple constructror
    s_thr (void) {
      d_mode   = THR_NORMAL;
      p_func   = nilp;
      p_args   = nilp;
      p_result = nilp;
      d_eflag  = false;
      d_count  = 1;
      p_next   = nilp;
      p_prev   = nilp;
    }
    ~s_thr (void) {
      if (p_dtor != nilp) p_dtor (p_result);
      if (p_dtor != nilp) p_dtor (p_args);
    }
  };

  // the main thread id
  static pthread_t       THR_TOP;
  // the main thread object
  static void*           throbj = nilp;
  // the thread system is initialized
  static bool            THR_SET = false;
  // the thread structure key
  static pthread_key_t   KEY_TID;
  // the control for one initialization
  static pthread_once_t  KEY_CTL = AFNIX_PTHREAD_ONCE_INIT;
  // the threads linked list 
  static s_thr*          thrlist = nilp;
  // the global lock for the thread list
  static pthread_mutex_t thrlock = AFNIX_PTHREAD_MUTEX_INITIALIZER;
  // the condition variable during wait all
  static pthread_cond_t  cvwaita = AFNIX_PTHREAD_COND_INITIALIZER;
  // the condition variable during start
  static pthread_cond_t  cvstart = AFNIX_PTHREAD_COND_INITIALIZER;

  // this procedure marks the thread as finished
  static void mark_thread_finished (s_thr* thr) {
    if ((thr == nilp) || (thr->d_eflag == true)) return;
    // get the thread  lock
    pthread_mutex_lock (&thrlock);
    thr->d_eflag = true;
    // signal we have changed some status
    pthread_cond_signal  (&cvwaita);
    pthread_mutex_unlock (&thrlock);
  }

  // this procedure add a new thread in the thread list
  static void insert_thread_list (s_thr* thr) {
    if (thr == nilp) return;
    // get the thread list lock
    pthread_mutex_lock (&thrlock);
    // increase the reference count  and mark as started
    thr->d_count++;
    // insert in the list
    thr->p_next = thrlist;
    if (thrlist != nilp) thrlist->p_prev = thr;
    thrlist = thr;
    // signal we are ready and unlock
    pthread_cond_signal  (&cvstart);
    pthread_mutex_unlock (&thrlock);
  }

  // this procedure removes a thread from the thread list
  static void remove_thread_list (s_thr* thr) {
    if (thr == nilp) return;
    // get the thread list lock
    pthread_mutex_lock (&thrlock);
    if (thr->d_count > 1) {
      thr->d_count--;
      pthread_mutex_unlock (&thrlock);
      return;
    }
    // remove from the list
    if (thr == thrlist) {
      thrlist = thr->p_next;
    } else {
      s_thr* prev = thr->p_prev;
      s_thr* next = thr->p_next;
      if (prev != nilp) prev->p_next = next;
      if (next != nilp) next->p_prev = prev;
    }
    thr->p_next  = nilp;
    thr->p_prev  = nilp;
    // decrement the reference count and eventually remove
    if (--thr->d_count == 0) delete thr;
    // signal we have removed a thread
    pthread_cond_broadcast (&cvwaita);
    // release the lock
    pthread_mutex_unlock (&thrlock);
  }

  // this procedure initialize the key when the first thread is started.
  // this procedure is called by pthread_once
  static void tid_key_once (void) {
    // create the initial key
    pthread_key_create(&KEY_TID, nilp);
    THR_TOP = pthread_self ();
    THR_SET = true;
    // set the unexpected handler
    c_errsetexpt (nilp);
  }

  // this procedure is the start routine for the thread - it takes
  // the thr structure and register the key, insert the thread descriptor
  // in the thread list and finally start the function
  static void* thr_start (void* args) {
    // finish to fill the structure
    s_thr* thr = (s_thr*) args;
    // map the key with the structure
    pthread_setspecific (KEY_TID, (void*) thr);
    // install the thread in the list
    insert_thread_list (thr);
    // run the procedure
    try {
      thr->p_result = thr->p_func (thr->p_args);
      mark_thread_finished (thr);
    } catch (...) {
      mark_thread_finished (thr);
      remove_thread_list   (thr);
      throw;
    }
    remove_thread_list (thr);
    return nilp;
  }

  // create a new thread of control

  void* c_thrstart (t_thrmode mode, t_thrfunc func, void* args, 
		    t_thrdtor dtor) {
    // initialize the tid key
    pthread_once (&KEY_CTL, tid_key_once);

    // set the thread according to the mode
    pthread_attr_t attr;
    if (pthread_attr_init (&attr) != 0) return nilp;
    if (mode == THR_DAEMON)
      if (pthread_attr_setdetachstate (&attr,PTHREAD_CREATE_DETACHED) != 0)
	return nilp;

    // create the thread data structure
    s_thr* thr  = new s_thr;
    thr->d_mode = mode;
    thr->p_func = func;
    thr->p_args = args;
    thr->p_dtor = dtor;
    // take the lock - so we guard the start condition - the condition
    // variable protect us against a race condition if the thr descritptor
    // is destroyed before the thread is started (i.e in the list).
    pthread_mutex_lock (&thrlock);
    // run the thread 
    int status = pthread_create (&(thr->d_tid),&attr, thr_start, (void*) thr);
    if (status != 0) {
      pthread_mutex_unlock (&thrlock);
      remove_thread_list   (thr);
      return nilp;
    }
    // wait until the thread is started
    pthread_cond_wait    (&cvstart, &thrlock);
    pthread_mutex_unlock (&thrlock);
    return (void*) thr;
  }

  // return true if the thread system is initialized

  bool c_thralive (void) {
    return THR_SET;
  }

  // return the thread id

  void* c_thrself (void) {
    return THR_SET ? pthread_getspecific (KEY_TID) : nilp;
  }

  // return true if two thread are equals

  bool c_threqual (void* thr) {
    if (THR_SET == false) return true;
    pthread_t tid = (thr == nilp) ? THR_TOP : ((s_thr*) thr)->d_tid;
    pthread_t sid = pthread_self ();
    return (pthread_equal (tid, sid) == 0) ? false : true;
  }

  // return true if the thread is the master

  bool c_thrmaster (void) {
    return c_threqual (nilp);
  }

  // set the main thread object
  
  void c_thrsetmain (void* object) {
    if (c_thrmaster () == true) throbj = object;
  }

  // get the thread object 

  void* c_thrgetobj (void) {
    s_thr* thr = (s_thr*) c_thrself ();
    if (thr == nilp) return  throbj;
    return thr->p_args;
  }

  // return the thread result
  
  void* c_thrgetres (void* thr) {
    if (thr == nilp) return nilp;
    s_thr* thread = (s_thr*) thr;
    return thread->d_eflag ? thread->p_result : nilp;
  }

  // join a thread and wait for terminate
  
  void c_thrwait (void* thr) {
    s_thr* thread = (s_thr*) thr;
    if (thread == nilp) return;
    // make sure the thread is joinable
    if (thread->d_mode == THR_DAEMON) return;
    pthread_join (thread->d_tid, nilp);
    if (thread->d_eflag == true) return;
    // wait for the thread to be marked as finished
    pthread_mutex_lock (&thrlock);
    while (thread->d_eflag == false) {
      pthread_cond_wait (&cvwaita, &thrlock);
      if (thread->d_eflag == true) break;
    }
    pthread_mutex_unlock (&thrlock);
  }

  // exit a thread - but do not destroy the thr record

  void c_threxit (void) {
    pthread_exit (nilp);
  }

  // destroys a thread record
  
  void c_thrdestroy (void* thr) {
    s_thr* thread = (s_thr*) thr;
    if (thread == nilp) return;
    remove_thread_list (thread);
  }

  // wait for all threads in the thread list to terminate

  void c_thrwaitall (void) {
    // get the lock
    pthread_mutex_lock (&thrlock);
    while (true) {
      // compute the wait flag
      s_thr* thr  = thrlist;
      bool   flag = false;
      while (thr != nilp) {
	if ((thr->d_mode == THR_NORMAL) && (thr->d_eflag == false)) {
	  flag = true;
	  break;
	}
	thr = thr->p_next;
      }
      // check if we have to wait
      if (flag == true)  
	pthread_cond_wait (&cvwaita, &thrlock);
      else
	break;
    }
    // release the lock
    pthread_mutex_unlock (&thrlock);
  }

  // create a new mutex in an unlocked state

  void* c_mtxcreate (void) {
    // mutex default attribute
    pthread_mutexattr_t attr;
    pthread_mutexattr_init (&attr);
    // create the new mutex
    pthread_mutex_t* mtx = new pthread_mutex_t;
    if (mtx != 0) pthread_mutex_init (mtx, &attr);
    return mtx;
  }

  // destroy the mutex argument 

  void c_mtxdestroy (void* mtx) {
    if (mtx == 0) return;
    pthread_mutex_t* mutex = (pthread_mutex_t*) mtx;
    pthread_mutex_destroy (mutex);
    delete mutex;
  }

  // lock the mutex argument

  bool c_mtxlock (void* mtx) {
    if (mtx == 0) return false;    
    pthread_mutex_t* mutex = (pthread_mutex_t*) mtx;
    return (pthread_mutex_lock (mutex) == 0) ? true : false;
  }

  // unlock the mutex argument

  bool c_mtxunlock (void* mtx) {
    if (mtx == 0) return true;    
    pthread_mutex_t* mutex = (pthread_mutex_t*) mtx;
    return (pthread_mutex_unlock (mutex) == 0) ? true : false;
  }
  // try to lock a mutex

  bool c_mtxtry (void* mtx) {
    if (mtx == 0) return false;    
    pthread_mutex_t* mutex = (pthread_mutex_t*) mtx;
    return (pthread_mutex_trylock (mutex) == 0) ? true : false;
  }

  // create a new condition variable

  void* c_tcvcreate (void) {
    // condition variable default attribute
    pthread_condattr_t attr;
    pthread_condattr_init (&attr);
    pthread_cond_t* tcv = new pthread_cond_t;
    if (tcv != 0) pthread_cond_init (tcv, &attr);
    return tcv;
  }

  // wait on a condition variable

  void c_tcvwait (void* tcv, void* mtx) {
    if ((tcv == 0) || (mtx == 0)) return;
    pthread_cond_t*  condv = (pthread_cond_t*)  tcv;
    pthread_mutex_t* mutex = (pthread_mutex_t*) mtx;
    pthread_cond_wait (condv, mutex);
  }

  // signal with a condition variable

  void c_tcvsignal (void* tcv) {
    if (tcv == 0) return;
    pthread_cond_t* condv = (pthread_cond_t*) tcv;
    pthread_cond_signal (condv);
  }

  // broadcast with a condition variable

  void c_tcvbdcast (void* tcv) {
    if (tcv == 0) return;
    pthread_cond_t* condv = (pthread_cond_t*) tcv;
    pthread_cond_signal (condv);
  }

  // destroy a condition variable

  void c_tcvdestroy (void* tcv) {
    pthread_cond_t* condv = (pthread_cond_t*) tcv;
    delete condv;
  }
}
