// Interface C++ aux Thread POSIX  -   R. Ansari 02/2001
// LAL (Orsay) / IN2P3-CNRS  DAPNIA/SPP (Saclay) / CEA

#include "sopnamsp.h"
#include "zthread.h"
#include <stdlib.h>
#include <stdio.h>
#include <pthread.h>
#include <signal.h>

/*!
  \class SOPHYA::ZThread
  \ingroup SysTools
  \brief Simple class for creating and controlling threads.

  This class provides an interface for creating and controlling threads.
  The implementation uses the POSIX thread interface.
  The ZThread objects can be sub classed with the redefinition of 
  the \c run() method which then performs the task.
  The default \c run() method of the base class can be used directly
  to perform computation through a function (see \b setAction() method)
  \sa SOPHYA::ZMutex
  The following sample code shows the usage of ZThread object 
  to run simultaneously two functions to perform computation.

  \code
  // The functions to perform computing
  void fun1(void *arg) { }
  void fun2(void *arg) { }
  // ...
  ZThread zt1;
  zt1.setAction(fun1, arg[1]);
  ZThread zt2;
  zt2.setAction(fun2, arg[1]);
  cout << " Starting threads ... " << endl;
  zt1.start();
  zt2.start();
  cout << " Waiting for threads to end ... " << endl;  
  zt1.join();
  zt2.join();
  \endcode
*/

#define CheckSt(st_, strg_)  if (st_ != 0)  perror(strg_); 

/*
extern "C" {
  void * zthr_run( void * xthr);
  void zthr_sig_exit(int s);
}
*/

static int _RCR_ = 99;
static void * zthr_run(void * xthr)
{
  ZThread * thr = (ZThread *)xthr;
  thr->run_p();
  return &_RCR_;
}

static void zthr_cleanup(void * xthr)
{
  ZThread * thr = (ZThread *)xthr;
  if(thr->_status == 1) thr->_status = 2;
  return;
}

static int _RCS_ = 97;
static void zthr_sig_exit(int s)
{
  printf("zthr_sig_exit(int s=%d)  signal received -> pthread_exit()\n",s);
  pthread_exit(&_RCS_);
}

/* ------ Classe ZThread  -------  */
/*!
  Constructor, with optional specification of the thread stack size.
*/
ZThread::ZThread(size_t stacksize)
{
  _status = 0;
  _ssize = 0;
  _act = NULL;
  _usp = NULL;
  _rc = -99;
}

ZThread::~ZThread()
{
  // Que faut-il faire ?
}

/*!
  Method which starts the thread.
*/
void ZThread::start()
{
  if ( IfStarted() ) throw ZThreadExc("ZThread::Start() - Already started thread !");
  int rc;
  pthread_attr_t tha;
  rc = pthread_attr_init(&tha);
  CheckSt(rc,"ZThread::start() - Pb creating tha attribute object");
  if (_ssize > 0) 
    rc = pthread_attr_setstacksize(&tha, _ssize);
  rc = pthread_create(&_thr, &tha, zthr_run, this);
  CheckSt(rc,"ZThread::start() - Pb creating the thread object");     
  setRC(rc);      
}

/*!
  Calls the pthread_cancel. Can be used to stop a running thread.
*/
void ZThread::cancel()
{
  if ( !IfStarted() ) throw ZThreadExc("ZThread::cancel() - thread not started !");
  int rc = pthread_cancel(_thr);
  CheckSt(rc,"ZThread::cancel() - Pb pthread_cancel() ");  
  setRC(-77);      
}

/*!
  Calls the pthread_kill to deliver a signal to the corresponding thread
*/
void ZThread::kill(int sig)
{
  if ( !IfStarted() ) throw ZThreadExc("ZThread::kill() - thread not started !");
  int rc = pthread_kill(_thr, sig);
  CheckSt(rc,"ZThread::kill() - Pb pthread_kill() ");  
  setRC(-76);      
}


/*!
  \brief Waits for the thread to terminate (call pthread_join() )
*/
void ZThread::join()
{
  if ( !IfStarted() ) throw ZThreadExc("ZThread::join() - thread not started !");
  void * x;
  int rc = pthread_join(_thr, &x);
  CheckSt(rc,"ZThread::Join() - Pb pthread_join() ");
  return;  
}

/*!
  \brief Method which does the actual computation 
  This virtual method can be redefined in the derived class, in order 
  to perform the actual computation. The default implementation call
  the action function (if defined by setAction() )
  
*/
void ZThread::run()
{
  if (_act)  _act(_usp);
  setRC(0);      
  return;
}

/*!
  \brief Method called by the function passed to pthread_create().
  This method sets the clean-up handler, changes the status variable
  and call the run() method, and pthread_exit() at the end.
*/
void ZThread::run_p()
{
  _status = 1;
  pthread_cleanup_push(zthr_cleanup, (void *)this);
  run();
  _status = 2;
  pthread_exit(&(_rc));
  pthread_cleanup_pop(0);
  return;
}


void ZThread::ActivateExitOnSignal(int sig)
{
  struct sigaction ae, ad;

  ae.sa_handler = zthr_sig_exit;
  ad.sa_handler = SIG_DFL;
  memset( &(ae.sa_mask), 0, sizeof(sigset_t) ); 
  ae.sa_flags = 0;
  memset( &(ad.sa_mask), 0, sizeof(sigset_t) ); 
  ad.sa_flags = 0;
#ifdef  OSF1
  ae.sa_flags = SA_RESTART;
  ad.sa_mask = 0;
#endif
  printf(" Activating signal %d  handling for threads -> pthread_exit()\n",sig);
  sigaction(sig, &ae, NULL);
}


/* ------ Classe ZMutex -------  */
/*!
  \class SOPHYA::ZMutex
  \ingroup SysTools
  \brief Wrapper for Mutex objects.

  This class implements an interface to the Mutual Exclusion objects
  of the POSIX threads. 
  The ZMutex objects should be used to control acces from different threads 
  to common objects through the \b lock() and \b unlock() methods. 
  When the \b signal() method is called, one of the waiting threads
  on the corresponding mutex object (\b ZMutex::wait()) is awakened. 
  All waiting threads on the mutex object are awakened when \b broadcast()
  is called.
  \sa SOPHYA::ZSync
*/

/*!
  Constructor: Creates a associated pair of POSIX \c pthread_mutex_t 
  and \c pthread_cond_t objects
*/
ZMutex::ZMutex()
{
  _mutx = new pthread_mutex_t;
  _condv = new pthread_cond_t;
  int rc;
  rc = pthread_mutex_init(_mutx, NULL);
  CheckSt(rc,"ZMutex::ZMutex() Pb pthread_mutex_init");
  rc = pthread_cond_init(_condv, NULL);
  CheckSt(rc,"ZMutex::ZMutex() Pb pthread_cond_init");
}

ZMutex::~ZMutex()
{
  delete _mutx;
  delete _condv;
}

/*!
  \class SOPHYA::ZSync
  \ingroup SysTools
  \brief Wrapper/utility class ensuring synchronised execution of an instruction bloc.

  This class can be used to insure that the execution of a given 
  part of the code is synchronised, i.e. only a single thread 
  goes through this at a given time.
  The constructor acquires a lock on given \b ZMutex object,
  which is released by the destructor.
  The ZSync object should then be instanciated at the beginning of 
  the synchronised instruction bloc.
  \sa SOPHYA::ZMutex
*/
