/*
   File: athread.hh

   By: Alex Theo de Jong
   Created: March 1996
   Version: 0.1

   Description:
   Alex' Threads (athread) and synchronization objects, based on pthreads, 
   Solaris threads, and Irix sprocs. This library is a thin shell around the 
   previous MT solutions to use multi-processing with a shared memory space. 
   The interface is similar to the POSIX interface. Feel free to change the 
   interface names to anything you like :-)

   Compiler variable (select one):

     SOLARIS
     SOLARIS_PTHREAD
     IRIX
     IRIX_PTHREAD
     LINUX

   Synchronization Objects:

     class MutexLock {
       int lock();
       int unlock();
     }

     class Semaphore {
       int P();
       int V();
     }

     class Condition {
       int wait(MutexLock* lock);
       int timedwait(MutexLock* lock);
       int signal();
       int broadcast();
     }

   Threads:

     int athr_create();
     int athr_kill();
     int athr_yield();
     int athr_equal(athr_t t1, athr_t t2);
     int athr_join();
     int athr_


   Notes:
   - No considerable time has been spent on performance. This is something that
     should be done;
   - Pthreads are not able to work with X11 and the GNU C++ stream classes. This
     seems to be caused by the pthread library; 
*/


#ifndef __athread_hh
#define __athread_hh

class Errors {
  static int errcount;        // Number of classes using Error for debugging
 protected:
  int errnumber;              // Last error number
  char errstr[50];            // Last error string
  Errors(int e=0) : errnumber(e) { errstr[0]='\0'; errcount++; }
 public:
  int geterrno() const { return errnumber; }
  const char* geterrstr() const { return (char*) errstr; }
};


#if (defined( SOLARIS_PTHREAD) || defined(LINUX)) 

#ifndef _REENTRANT
#define _REENTRANT
#endif

#include <pthread.h>

typedef pthread_t athr_t;

class MutexLock : public Errors {
  friend class Condition;
  pthread_mutex_t mlock;
 public:
  MutexLock(){ errnumber=pthread_mutex_init(&mlock, 0); }
  ~MutexLock(){ errnumber=pthread_mutex_destroy(&mlock); }
  int lock(){ return errnumber=pthread_mutex_lock(&mlock); }
//  int lock(){ return errnumber=pthread_mutex_unlock(&mlock); }  
  int unlock(){ return errnumber=pthread_mutex_unlock(&mlock); }
};

class Semaphore : public Errors {
  pthread_cond_t condition;
  pthread_mutex_t mlock;
  int count;
 public:
  Semaphore(int value=0) : count(value) {
    errnumber=pthread_cond_init(&condition, 0); 
    errnumber=pthread_mutex_init(&mlock, 0); 
  }
  ~Semaphore(){
    errnumber=pthread_cond_destroy(&condition); 
    errnumber=pthread_mutex_destroy(&mlock);
  }
  int P(){
    errnumber=pthread_mutex_unlock(&mlock);
    count--;
    while (count<0)
      errnumber=pthread_cond_wait(&condition, &mlock);
    errnumber=pthread_mutex_unlock(&mlock);
    return errnumber; 
  }
  int V(){ 
    errnumber=pthread_mutex_unlock(&mlock);
    count++;
    errnumber=pthread_cond_signal(&condition);
    errnumber=pthread_mutex_unlock(&mlock);
    return errnumber;
  }
};

class Condition : public Errors {
  pthread_cond_t condition;
 public:
  Condition(){ errnumber=pthread_cond_init(&condition, 0); }
  ~Condition(){ errnumber=pthread_cond_destroy(&condition); }
  int signal(){ return errnumber=pthread_cond_signal(&condition); }
  int broadcast(){ return errnumber=pthread_cond_broadcast(&condition); }
  int wait(MutexLock* l){ return errnumber=pthread_cond_wait(&condition, &l->mlock); }
  int timedwait(MutexLock* l, const timespec* time){ return errnumber=pthread_cond_timedwait(&condition, &l->mlock, time); }
};

inline int athr_create(void* (*function)(void*), void* arg, athr_t* id){
  return pthread_create(id, 0, function, arg);
}
inline int athr_join(athr_t id){ return pthread_join(id, 0); }
inline void athr_yield(){ sched_yield(); }
//inline void athr_yield(){ pthread_yield(); }
inline void athr_exit(void* code){ pthread_exit(code); }
inline int athr_suspend(athr_t& id){ return -1; }  // return pthread_suspend(id); }
inline int athr_continue(athr_t& id){ return -1; } // return pthread_continue(id); }
inline athr_t athr_self(){ return pthread_self(); }
inline int athr_kill(athr_t thread, int signal){ return pthread_kill(thread, signal); }
inline int athr_equal(athr_t thread1, athr_t thread2){ return pthread_equal(thread1, thread2); }
inline int athr_detach(athr_t thread){ return pthread_detach(thread); }
inline int athr_setschedparam(athr_t t, int p, sched_param* param){ return pthread_setschedparam(t, p, param); }
inline int athr_getschedparam(athr_t t, int* p, sched_param* param){ return pthread_getschedparam(t, p, param); }

#endif // SOLARIS_PTHREAD

#ifdef SOLARIS

#ifndef _REENTRANT
#define _REENTRANT
#endif

#include <thread.h>

typedef thread_t athr_t;

struct sched_param {   // copied from pthread in order to stay compatible
  int prio;
  void* no_data;
  sched_param() : prio(0) { no_data=0; }
};

class MutexLock : public Errors {
  friend class Condition;
  mutex_t mlock;
 public:
  MutexLock(){ errnumber=mutex_init(&mlock, USYNC_THREAD, 0); }
  ~MutexLock(){ errnumber=mutex_destroy(&mlock); }
  int lock(){ return errnumber=mutex_unlock(&mlock); }
  int unlock(){ return errnumber=mutex_unlock(&mlock); }
};

class Semaphore : public Errors {
  sema_t sema;
 public:
  Semaphore(int value=0){ errnumber=sema_init(&sema, value, USYNC_THREAD, 0); }
  ~Semaphore(){ errnumber=sema_destroy(&sema); }
  int P(){ return errnumber=sema_wait(&sema); }
  int V(){ return errnumber=sema_post(&sema); }
};

class Condition : public Errors {
  cond_t condition;
 public:
  Condition(){ errnumber=cond_init(&condition, USYNC_THREAD, 0); }
  ~Condition(){ errnumber=cond_destroy(&condition); }
  int signal(){ return errnumber=cond_signal(&condition); }
  int broadcast(){ return errnumber=cond_broadcast(&condition); }
  int wait(MutexLock* l){ return errnumber=cond_wait(&condition, &l->mlock); }
  int timedwait(MutexLock* l, const timespec* time){ return errnumber=cond_timedwait(&condition, &l->mlock, (timespec*) time); }
};

inline int athr_create(void* (*function)(void*), void* arg, athr_t* id){
  return thr_create(0, 0, function, arg, 0, id);
}
inline int athr_join(athr_t id){ return thr_join(id, 0, 0); }
inline void athr_yield(){ thr_yield(); }
inline void athr_exit(void* code){ thr_exit(code); }
inline int athr_suspend(athr_t& id){ return thr_suspend(id); }
inline int athr_continue(athr_t& id){ return thr_continue(id); }
inline athr_t athr_self(){ return thr_self(); }
inline int athr_kill(athr_t thread, int signal){ return thr_kill(thread, signal); }
inline int athr_equal(athr_t thread1, athr_t thread2){ return (thread1==thread2); }
inline int athr_detach(athr_t){ return -1; } // thr_detach(thread); }
inline int athr_setschedparam(athr_t t, int policy, sched_param* arg){ 
  return (policy!=0) ? -1 : thr_setprio(t, arg->prio); }
inline int athr_getschedparam(athr_t t, int* policy, sched_param* arg){
  *policy=0; return thr_getprio(t, &arg->prio); }

#endif // SOLARIS


#ifdef IRIX_PTHREAD

#ifndef _SGI_MP_SOURCE
#define _SGI_MP_SOURCE
#endif

#ifndef _REENTRANT
#define _REENTRANT
#endif

#include <pthread.h>

typedef pthread_t athr_t;

class MutexLock : public Errors {
  friend class Condition;
  pthread_mutex_t mlock;
 public:
  MutexLock(){ errnumber=pthread_mutex_init(&mlock, 0); }
  ~MutexLock(){ errnumber=pthread_mutex_destroy(&mlock); }
  int lock(){ return errnumber=pthread_mutex_lock(&mlock); }
  int unlock(){ return errnumber=pthread_mutex_unlock(&mlock); }
};

class Semaphore : public Errors {
  pthread_cond_t condition;
  pthread_mutex_t mlock;
  int count;
 public:
  Semaphore(int value=0) : count(value) { 
    errnumber=pthread_cond_init(&condition, 0); 
    errnumber=pthread_mutex_init(&mlock, 0); 
  }
  ~Semaphore(){
    errnumber=pthread_cond_destroy(&condition); 
    errnumber=pthread_mutex_destroy(&mlock);
  }
  int P(){
    errnumber=pthread_mutex_unlock(&mlock);
    count--;
    while (count<0)
      errnumber=pthread_cond_wait(&condition, &mlock);
    return errnumber=pthread_mutex_unlock(&mlock);
  }
  int V(){ 
    errnumber=pthread_mutex_unlock(&mlock);
    count++;
    errnumber=pthread_cond_signal(&condition);
    return errnumber=pthread_mutex_unlock(&mlock);
  }
};

class Condition : public Errors {
  pthread_cond_t condition;
 public:
  Condition(){ errnumber=pthread_cond_init(&condition, 0); }
  ~Condition(){ errnumber=pthread_cond_destroy(&condition); }
  int signal(){ return errnumber=pthread_cond_signal(&condition); }
  int broadcast(){ return errnumber=pthread_cond_broadcast(&condition); }
  int wait(MutexLock* l){ return errnumber=pthread_cond_wait(&condition, &l->mlock); }
  int timedwait(MutexLock* l, const timespec* time){ return errnumber=pthread_cond_timedwait(&condition, &l->mlock, time); }
};

inline int athr_create(void* (*function)(void*), void* arg, athr_t* thread){
  return pthread_create(thread, 0, function, arg);
}
inline int athr_join(athr_t id){ return pthread_join(id, 0); }
inline void athr_yield(){ pthread_yield(); }
inline void athr_exit(void* code){ pthread_exit(code); }
inline int athr_suspend(athr_t& id){  return -1; } // return pthread_suspend(id); }
inline int athr_continue(athr_t& id){ return -1; } // return pthread_continue(id); }
inline athr_t athr_self(){ return pthread_self(); }
inline int athr_detach(athr_t thread){ return pthread_detach(thread); }
inline int athr_kill(athr_t thread, int signal){ return pthread_kill(thread, signal); }
inline int athr_equal(athr_t thread1, athr_t thread2){ return pthread_equal(thread1, thread2); }
inline int athr_setschedparam(athr_t t, int p, sched_param* param){ return pthread_setschedparam(t, p, param); }
inline int athr_getschedparam(athr_t t, int* p, sched_param* param){ return pthread_getschedparam(t, p, param); }

#endif // IRIX_PTHREAD

#ifdef IRIX

#ifndef _SGI_MP_SOURCE
#define _SGI_MP_SOURCE
#endif

#ifndef _REENTRANT
#define _REENTRANT
#endif

#include <sys/time.h>
#include <sys/types.h>
#include <sys/prctl.h>
#include <sys/schedctl.h>
#include <signal.h>
#include <wait.h>
#include <unistd.h>
#include <errno.h>
#include <ulocks.h>
#include <iostream.h>

struct sched_param {   // copied from pthread in order to stay compatible
  int prio;
  void* no_data;
  sched_param() : prio(0) { no_data=0; }
};

class SharedArena {
  friend class MutexLock;
  friend class Semaphore;
  friend class Condition;
  friend class athr_t;
  friend class _Thread_library;
 protected:
  char filename[50];  // filename of shared arena
  usptr_t* handle;
 public:
  SharedArena();
  ~SharedArena();
};

class SA : public Errors {
 protected:
  static SharedArena _sa;
};

class MutexLock : public SA {
  friend class Condition;
  ulock_t mlock;
 public:
  MutexLock(){ mlock=usnewlock(_sa.handle); errnumber=usinitlock(mlock); }
  ~MutexLock(){ usfreelock(mlock, _sa.handle); }
  int lock(){ return errnumber=ussetlock(mlock); }
  int unlock(){ return errnumber=usunsetlock(mlock); }
};

class Semaphore :  public SA {
  usema_t* sema;
 public:
  Semaphore(int value=0){ sema=usnewsema(_sa.handle, value); errnumber=usinitsema(sema, value); }
  ~Semaphore(){ usfreesema(sema, _sa.handle); }
  int P(){ return errnumber=uspsema(sema); }
  int V(){ return errnumber=usvsema(sema); }
};

class Condition : public SA {
  usema_t* sema;
 public:
  Condition(){ sema=usnewsema(_sa.handle, 0); errnumber=usinitsema(sema, 0); }
  ~Condition(){ usfreesema(sema, _sa.handle); }
  int signal(){ return (ustestsema(sema)<0) ? errnumber=usvsema(sema) : errnumber; }
  int broadcast(){
    int count=ustestsema(sema);
    while (count++<0) errnumber=usvsema(sema); 
    return errnumber;
  }
  int wait(MutexLock* l){
    errnumber=usunsetlock(l->mlock); 
    errnumber=uspsema(sema);     // this should be atomic
    return errnumber=ussetlock(l->mlock);
  }
  int timedwait(MutexLock* l, const timespec* time){ 
    errnumber=usunsetlock(l->mlock); 
    int gotit=0;
    if (!(gotit=uscpsema(sema))){
      timeval timevalue;
      timevalue.tv_sec=time->tv_sec;
      timevalue.tv_usec=time->tv_nsec * 1000;
      select(0, 0, 0, 0, &timevalue);  // primitive solution 
      gotit=uscpsema(sema);
    }
    errnumber=ussetlock(l->mlock); 
    return gotit;
  }
};

class athr_t : public SA {
  friend class _Thread_library;
 protected:
  usema_t* done;
  int terminated;
  int id;
  int exit(){
    terminated=1;
    while (ustestsema(done)<0) errnumber=usvsema(done); 
//    cerr << "athr - thread "<< id << " terminated\n";
    return errnumber;
  }
  int wait(){
//    cerr << "athr - waiting for thread " << id << " to terminate ... ";
    return (terminated) ? 0 : uspsema(done);
  }
 public:
  athr_t(int pid=0) : terminated(0), id(pid){ done=usnewsema(_sa.handle, 0); usinitsema(done, 0); }
  ~athr_t(){ /*  usfreesema(done, _sa.handle); */ }
};

// Globals for conversion of standard void return argument of sproc for void* for threads
const int _thread_list_max=20;  // Maximum number of threads

class _Thread_library : public SA {
  static athr_t** _thread_list;
  static ulock_t mlock;
  static usema_t* funct_sema;
  static void* (*function_call)(void*);
  static void sproc_function_call(void* arg){
/*
    if (prctl(PR_SETEXITSIG)<0)
      cerr << "could not set PR_SETEXITSIG after sproc\n";
*/
    void* (*call)(void*)=function_call;
    usvsema(funct_sema);  // done with using function_call
    if (call) call(arg);
    athr_exit(0);
  }
 public:
  static int init();
  static int athr_create(void* (*function)(void*), void* arg, athr_t* thread){
    uspsema(funct_sema);   // we are using function_call here
    function_call=function;
    ussetlock(mlock);
    for (int i=0; _thread_list[i]!=0 && i<_thread_list_max; i++);
    if (i>=_thread_list_max){
      cerr << "thread overflow!\n";
      return -1;
    }                         // add entry to thread list in order to do proper clean-up
    _thread_list[i]=thread;
    usunsetlock(mlock);
    return (thread->id=sproc(sproc_function_call, PR_SFDS | PR_SADDR, arg)); 
    // PR_SALL
  }
  static int athr_join(athr_t& thread){ return thread.wait(); }
  static void athr_yield(){ schedctl(getpid(), DL_BLOCK); }
  static athr_t athr_self(){ return getpid(); }
  static int athr_detach(athr_t /* thread */ ){ return 0; }
  static int athr_suspend(athr_t& /* id */ ){  return -1; } // return ; }
  static int athr_continue(athr_t& /* id */ ){ return -1; } // ; }
  static int athr_kill(athr_t& thread, int signal){ return kill(thread.id, signal); }
  static void athr_exit(void* /* code */){
    ussetlock(mlock);
    for (int i=0; i<_thread_list_max; i++)
      if (_thread_list[i] && _thread_list[i]->id==getpid()){
	_thread_list[i]->exit();      // remove entry from thread list
	_thread_list[i]=0;
	break;
      }
    usunsetlock(mlock);
    ::exit(0);
  }
  static int athr_equal(athr_t& thread1, athr_t& thread2){ return (thread1.id==thread2.id); }
  static int athr_setschedparam(athr_t& /* t */, int /* p */ , sched_param* /* param */ ){ 
/* I really don't know how to use this stuff properly
    if (schedctl(GETNDPRI, t.id)<param->prio)
      return schedctl(NDPRI, t.id, NDPNORMMAX);
    if (schedctl(GETNDPRI, t.id)>param->prio)
      return schedctl(NDPRI, t.id, NDPNORMMIN);
    if (schedctl(GETNDPRI, t.id)==param->prio)
    return -1;
*/
    return 0;
  }
  static int athr_getschedparam(athr_t& t, int* /* p */, sched_param* param){
    return (param->prio=schedctl(GETNDPRI, t.id));
  }
};

#define athr_create        _Thread_library::athr_create
#define athr_join          _Thread_library::athr_join
#define athr_suspend       _Thread_library::athr_suspend
#define athr_continue      _Thread_library::athr_continue
#define athr_yield         _Thread_library::athr_yield
#define athr_self          _Thread_library::athr_self
#define athr_detach        _Thread_library::athr_detach
#define athr_kill          _Thread_library::athr_kill
#define athr_exit          _Thread_library::athr_exit
#define athr_equal         _Thread_library::athr_equal
#define athr_setschedparam _Thread_library::athr_setschedparam
#define athr_getschedparam _Thread_library::athr_getschedparam

#endif // IRIX

#endif // __athread_hh
