// ---------------------------------------------------------------------------
// - cthr.cxx                                                                -
// - standard platform 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-2019 amaury darsch                                   -
// ---------------------------------------------------------------------------

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

namespace afnix {

  // -------------------------------------------------------------------------
  // - private section                                                       -
  // -------------------------------------------------------------------------

  // the maximum number of threads with 32 bits platform (hint)
  static const long THR_MAX_32 = 128;
  // the maximum number of threads with 64 bits platform (hint)
  static const long THR_MAX_64 = 32768;

  // the thread info structure
  struct s_thri {
    pthread_t d_tid;    // the thread id
    long      d_tgid;   // the thread group id
    t_thrf    p_func;   // the start function
    void*     p_args;   // the start function argument
    t_thrd    p_dtor;   // the object destructor
    t_thrn    p_thrn;   // the notifier function
    void*     p_thrs;   // the thread set object
    void*     p_result; // the thread object result
    bool      d_eflag;  // the end flag for this thread
    long      d_count;  // the reference count for this structure
    s_thri*   p_next;   // next thread in list
    s_thri*   p_prev;   // previous thread in list
    // simple constructor
    s_thri (const s_targ& targ) {
      d_tgid   = (targ.d_tgid < 0) ? 0 : targ.d_tgid;
      p_func   = targ.p_func;
      p_args   = targ.p_args;
      p_dtor   = targ.p_dtor;
      p_thrn   = targ.p_thrn;
      p_thrs   = targ.p_thrs;
      p_result = nullptr;
      d_eflag  = false;
      d_count  = 0;
      p_next   = nullptr;
      p_prev   = nullptr;
    }
    ~s_thri (void) {
      if (p_dtor != nullptr) p_dtor (p_result);
      if (p_dtor != nullptr) p_dtor (p_args);
      if (p_dtor != nullptr) p_dtor (p_thrs);
    }
  };

  // the main thread id
  static pthread_t       cthr_top;
  // the thread system is initialized
  static bool            cthr_sif = false;
  // the thread structure key
  static pthread_key_t   cthr_kid;
  // the control for one initialization
  static pthread_once_t  cthr_koc = AFNIX_PTHREAD_ONCE_INIT;
  // the threads linked list 
  static s_thri*         cthr_lst = nullptr;
  // the global lock for the thread list
  static pthread_mutex_t cthr_mtx = AFNIX_PTHREAD_MUTEX_INITIALIZER;
  // the condition variable during start
  static pthread_cond_t  cthr_cvs = AFNIX_PTHREAD_COND_INITIALIZER;
  // the condition variable during wait
  static pthread_cond_t  cthr_cvw = AFNIX_PTHREAD_COND_INITIALIZER;
  // the condition variable during nil test
  static pthread_cond_t  cthr_cvn = AFNIX_PTHREAD_COND_INITIALIZER;

  // this procedure marks the thread as finished
  static void cthr_mark_finished (s_thri* thr) {
    if ((thr == nullptr) || (thr->d_eflag == true)) return;
    // lock the thread list
    pthread_mutex_lock (&cthr_mtx);
    // mark as finished
    thr->d_eflag = true;
    // destroy the arguments - for circular reference when the argument
    // is an object which holds the thread object
    if (thr->p_dtor != nullptr) {
      thr->p_dtor (thr->p_args); 
      thr->p_args = nullptr;
    }
    // call the notifier if any
    if (thr->p_thrn != nullptr) thr->p_thrn (thr->p_thrs);
    // signal we have changed some status
    pthread_cond_broadcast (&cthr_cvw);
    // unlock the thread list
    pthread_mutex_unlock (&cthr_mtx);
  }

  // this procedure add a new thread in the thread list
  static void cthr_insert_list (s_thri* thr) {
    if (thr == nullptr) return;
    // get the thread list lock
    pthread_mutex_lock (&cthr_mtx);
    // increase the reference count and mark as started
    thr->d_count = 2;
    // insert in the list
    thr->p_next = cthr_lst;
    if (cthr_lst != nullptr) cthr_lst->p_prev = thr;
    cthr_lst = thr;
    // signal we are ready and unlock
    pthread_cond_signal  (&cthr_cvs);
    pthread_mutex_unlock (&cthr_mtx);
  }

  // this procedure removes a thread from the thread list
  static void cthr_remove_list (s_thri* thr) {
    if (thr == nullptr) return;
    // get the thread list lock
    pthread_mutex_lock (&cthr_mtx);
    if (thr->d_count > 1) {
      thr->d_count--;
      pthread_mutex_unlock (&cthr_mtx);
      return;
    }
    // remove from the list
    s_thri* prev = thr->p_prev;
    s_thri* next = thr->p_next;
    if ((thr == cthr_lst) || (prev == nullptr)) {
      cthr_lst = next;
      if (next != nullptr) next->p_prev = nullptr;
    } else {
      if (prev != nullptr) prev->p_next = next;
      if (next != nullptr) next->p_prev = prev;
    }
    thr->p_next = nullptr;
    thr->p_prev = nullptr;
    // decrement the reference count and eventually remove
    if (--thr->d_count == 0) delete thr;
    // signal we have removed a thread
    pthread_cond_signal  (&cthr_cvn);
    // release the lock
    pthread_mutex_unlock (&cthr_mtx);
  }

  // this procedure initialize the key when the first thread is started.
  // this procedure is called by pthread_once
  static void cthr_key_once (void) {
    // create the initial key
    pthread_key_create (&cthr_kid, nullptr);
    cthr_top = pthread_self ();
    cthr_sif = true;
    // set the unexpected handler
    c_errsetexpt (nullptr);
  }

  // this procedure is the start routine for the thread - it takes
  // the thread structure and register the key, insert the thread descriptor
  // in the thread list and finally start the function
  static void* cthr_start (void* args) {
    // finish to fill the structure
    s_thri* thri = (s_thri*) args;
    // map the key with the structure
    pthread_setspecific (cthr_kid, (void*) thri);
    // install the thread in the list
    cthr_insert_list (thri);
    // run the procedure
    try {
      // call the thread function
      thri->p_result = thri->p_func (thri->p_args);
      // mark as finished
      cthr_mark_finished (thri);
      // remove from the list
      cthr_remove_list (thri);
      // exit the thread
      return nullptr;
    } catch (...) {
      // mark as finished
      cthr_mark_finished (thri);
      // remove from the list
      cthr_remove_list   (thri);
      // exit the thread
      throw;
    }
  }

  // -------------------------------------------------------------------------
  // - public section                                                        -
  // -------------------------------------------------------------------------

  // return the maximum number of threads as a hint

  long c_thrmax (void) {
    return c_is32 () ? THR_MAX_32 : THR_MAX_64;
  }

  // check if the thread list is nil

  bool c_thrnullptr (void) {
    // get the thread list lock
    if (pthread_mutex_lock (&cthr_mtx) != 0) return false;
    // prepare result
    bool result = (cthr_lst == nullptr);
    // release the lock
    pthread_mutex_unlock (&cthr_mtx);
    return result;
  }

  // wait for the thread list to be nil

  void c_thrwnil (void) {
    // get the thread list lock
    if (pthread_mutex_lock (&cthr_mtx) != 0) return;
    // check for nil
    while (cthr_lst != nullptr) pthread_cond_wait (&cthr_cvn, &cthr_mtx);
    // release the lock
    pthread_mutex_unlock (&cthr_mtx);
  }
    
  // get a list of threads by running flag

  s_thrl* c_thrgetl (const bool rflg) {
    // get the thread list lock
    pthread_mutex_lock (&cthr_mtx);
    // initialize result
    s_thrl* result = nullptr;
    // loop in the list
    s_thri* thri = cthr_lst;
    while (thri != nullptr) {
      // check for terminated
      if ((rflg == true) && (thri->d_eflag == true)) {
	// link next element
	thri = thri->p_next;
	// continue
	continue;
      }
      // increment reference count
      thri->d_count++;
      // prepare list record
      s_thrl* thrl = new s_thrl;
      thrl->p_tid  = thri;
      thrl->p_next = result;
      result = thrl;
      // link next element
      thri = thri->p_next;
    }
    // release the lock
    pthread_mutex_unlock (&cthr_mtx);
    return result;
  }

  // get a list of threads by group id - carefull the calling
  // thread might be in the list and waiting with this list might not be
  // a good idea unless you remove the calling thread with the group id

  s_thrl* c_thrgetl (const long tgid, const bool rflg) {
    // get the thread list lock
    pthread_mutex_lock (&cthr_mtx);
    // initialize result
    s_thrl* result = nullptr;
    // loop in the list
    s_thri* thri = cthr_lst;
    while (thri != nullptr) {
      // check for terminated
      if ((rflg == true) && (thri->d_eflag == true)) {
	// link next element
	thri = thri->p_next;
	// continue
	continue;
      }
      // check the group id
      if (thri->d_tgid != tgid) {
	// link next element
	thri = thri->p_next;
	// continue
	continue;
      }
      // increment reference count
      thri->d_count++;
      // prepare list record
      s_thrl* thrl = new s_thrl;
      thrl->p_tid  = thri;
      thrl->p_next = result;
      result = thrl;
      // link next element
      thri = thri->p_next;
    }
    // release the lock
    pthread_mutex_unlock (&cthr_mtx);
    return result;
  }

  // create a new thread of control

  void* c_thrstart (const s_targ& targ) {
    // initialize the tid key
    pthread_once (&cthr_koc, cthr_key_once);
    // set the thread type as detached
    pthread_attr_t attr;
    if (pthread_attr_init (&attr) != 0) return nullptr;
    if (pthread_attr_setdetachstate (&attr,PTHREAD_CREATE_DETACHED) != 0) {
      pthread_attr_destroy (&attr);
      return nullptr;
    }
    // create the thread data structure
    s_thri* thri = new s_thri (targ);
    // 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 (&cthr_mtx);
    // run the thread 
    int status = pthread_create (&(thri->d_tid), &attr, cthr_start, 
				 (void*) thri);
    // destroy the attributes
    pthread_attr_destroy (&attr);
    // check for status
    if (status != 0) {
      pthread_mutex_unlock (&cthr_mtx);
      // if the reference count is zero - the thread did not started
      // correctly and the thread structure is not yet in the list
      if (thri->d_count == 0) {
	delete thri;
      } else {
	thri->d_count--;
	cthr_remove_list (thri);
      }
      return nullptr;
    }
    // wait until the thread is started
    pthread_cond_wait    (&cthr_cvs, &cthr_mtx);
    pthread_mutex_unlock (&cthr_mtx);
    return (void*) thri;
  }

  // return true if the thread system is initialized

  bool c_thralive (void) {
    return cthr_sif;
  }

  // return the thread id

  void* c_thrself (void) {
    return cthr_sif ? pthread_getspecific (cthr_kid) : nullptr;
  }

  // return true if two thread are equals

  bool c_threqual (void* thr) {
    if (cthr_sif == false) return true;
    pthread_t tid = (thr == nullptr) ? cthr_top : ((s_thri*) 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 (nullptr);
  }

  // return the thread group id

  long c_thrgetgid (void* thr) {
    if (thr == nullptr) return 0;
    s_thri* thri = (s_thri*) thr;
    return thri->d_tgid;
  }

  // return true if the thread has ended

  bool c_thrisend (void* thr) {
    if (thr == nullptr) return true;
    s_thri* thri = (s_thri*) thr;
    return thri->d_eflag;
  }

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

  // wait for a thread to terminate
  
  void c_thrwait (void* thr) {
    if (thr == nullptr) return;
    // do not wait on ourself
    if (c_threqual (thr) == true) return;
    // map the thread structure
    s_thri* thri = (s_thri*) thr;
    // lock the thread list
    if (pthread_mutex_lock (&cthr_mtx) != 0) return;
    // eventually check if it is finished
    if (thri->d_eflag == true) {
      pthread_mutex_unlock (&cthr_mtx);
      return;
    }      
    // wait for the thread to be marked as finished
    while (thri->d_eflag == false) {
      pthread_cond_wait (&cthr_cvw, &cthr_mtx);
      if (thri->d_eflag == true) break;
    }
    pthread_mutex_unlock (&cthr_mtx);
  }

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

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

  // destroys a thread record
  
  void c_thrdestroy (void* thr) {
    if (thr == nullptr) return;
    // do not destroy ourself
    if (c_threqual (thr) == true) return;
    // map the thread structure
    s_thri* thri = (s_thri*) thr;
    // remove from the list
    cthr_remove_list (thri);
  }

  // create a new mutex in an unlocked state

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

  // destroy the mutex argument 

  void c_mtxdestroy (void* mtx) {
    if (mtx == nullptr) 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 == nullptr) 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 == nullptr) 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 == nullptr) 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);
    // create the condition variable
    pthread_cond_t* tcv = new pthread_cond_t;
    if (tcv != nullptr) pthread_cond_init (tcv, &attr);
    // destroy the condition attributes
    pthread_condattr_destroy (&attr);
    return tcv;
  }

  // wait on a condition variable

  void c_tcvwait (void* tcv, void* mtx) {
    if ((tcv == nullptr) || (mtx == nullptr)) 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 == nullptr) 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 == nullptr) 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;
  }
}
