/*
 *
 *  Copyright (C) 2000-2022, OFFIS e.V.
 *  All rights reserved.  See COPYRIGHT file for details.
 *
 *  This software and supporting documentation were developed by
 *
 *    OFFIS e.V.
 *    R&D Division Health
 *    Escherweg 2
 *    D-26121 Oldenburg, Germany
 *
 *
 *  Module:  ofstd
 *
 *  Author:  Marco Eichelberg
 *
 *  Purpose: Define classes for Semaphore, Mutex and Read/Write Lock
 *           as used by most multithread implementations
 *
 *
 */

#include "dcmtk/config/osconfig.h"

#define OFTEST_OFSTD_ONLY
#include "dcmtk/ofstd/oftest.h"
#include "dcmtk/ofstd/ofthread.h"
#include "dcmtk/ofstd/ofstring.h"
#include "dcmtk/ofstd/ofstd.h"
#include "dcmtk/ofstd/ofdiag.h"

#define BAILOUT(msg) do { \
    OFCHECK_FAIL(msg); \
    return; \
} while (0)

static OFMutex *mutex=NULL;
static volatile int mtx_var=0;
static volatile int mtx_cond1=0;
static volatile int mtx_cond2=0;
static volatile int mtx_cond3=0;
static const int wait_timeout = 100;


class MutexT1: public OFThread
{
public:
  MutexT1(): OFThread() {}
  ~MutexT1() {}

  virtual void run()
  {
    if (OFMutex::busy == mutex->trylock()) mtx_cond1=1; // trylock works
    if (0 == mutex->lock())
    {
      mtx_var = 1;
      OFStandard::milliSleep(wait_timeout); // since I've got the mutex, nobody should write to mtx_var
      if ((mtx_var == 1)&&(0 == mutex->unlock())) mtx_cond2 = 1;
    }
  }
};

class MutexT2: public OFThread
{
public:
  MutexT2(): OFThread() {}
  ~MutexT2() {}

  virtual void run()
  {
    if (0 == mutex->lock())
    {
      mtx_var = 2;
      OFStandard::milliSleep(wait_timeout); // since I've got the mutex, nobody should write to mtx_var
      if ((mtx_var == 2)&&(0 == mutex->unlock())) mtx_cond3 = 1;
    }
  }
};

static void mutex_test()
{
  OFString errmsg;
  mutex = new OFMutex();
  if ((!mutex)||(! mutex->initialized())) BAILOUT("creation of mutex failed");
  OFMutex memory_barrier;
  if (! memory_barrier.initialized()) BAILOUT("creation of mutex failed");

  int condition = mutex->trylock();
  if (condition)
  {
    mutex->errorstr(errmsg, condition);
    errmsg = "mutex lock failed: " + errmsg;
    BAILOUT(errmsg);
  }

  mtx_var = -1;

  MutexT1 t1;
  if (0 != t1.start()) BAILOUT("unable to create thread, mutex test failed");

  MutexT2 t2;
  if (0 != t2.start()) BAILOUT("unable to create thread, mutex test failed");

  OFStandard::milliSleep(wait_timeout); // since I've got the mutex, nobody should write to mtx_var
  memory_barrier.lock(); // locking the mutex acts as a (inefficient, but portable) memory barrier
  memory_barrier.unlock();
  if (mtx_var != -1) BAILOUT("mutex test failed");

  int i=0;
  while ((i++<10) && (!mtx_cond1))
  {
    memory_barrier.lock(); // locking the mutex acts as a (inefficient, but portable) memory barrier
    memory_barrier.unlock();
    OFStandard::milliSleep(wait_timeout);
  }
  if (!mtx_cond1) BAILOUT("mutex trylock test failed");

  if (0 != (condition = mutex->unlock()))
  {
    mutex->errorstr(errmsg, condition);
    errmsg =  "mutex unlock failed: " + errmsg;
    BAILOUT(errmsg);
  }

  while ((i++<10) && ((!mtx_cond2)||(!mtx_cond3)))
  {
    memory_barrier.lock(); // locking the mutex acts as a (inefficient, but portable) memory barrier
    memory_barrier.unlock();
    OFStandard::milliSleep(wait_timeout);
  }
  if ((!mtx_cond2) || (!mtx_cond3)) BAILOUT("mutex lock/unlock test failed");

  if (0 != t1.join()) BAILOUT("unable to join thread, mutex test failed");
  if (0 != t2.join()) BAILOUT("unable to join thread, mutex test failed");

  delete mutex;
}

static OFSemaphore *semaphore=NULL;
static volatile int sem_cond1=0;
static volatile int sem_cond2=0;
static volatile int sem_cond3=0;
static volatile int sem_cond4=0;

class SemaT1: public OFThread
{
public:
  SemaT1(): OFThread() {}
  ~SemaT1() {}

  virtual void run()
  {
    if (0 == semaphore->wait())
    {
      sem_cond1 = 1; // acquired semaphore
      mutex->lock();
      mutex->unlock();
      if (0== semaphore->post()) sem_cond2=1;
    }
  }
};

class SemaT2: public OFThread
{
public:
  SemaT2(): OFThread() {}
  ~SemaT2() {}

  virtual void run()
  {
    if (OFSemaphore::busy == semaphore->trywait())
    {
      if (0 == semaphore->wait()) // block on semaphore
      {
        sem_cond3 = 1; // acquired semaphore
        if (0== semaphore->post()) sem_cond4=1;
      }
    }
    else
    {
        sem_cond3 = 1; // acquired semaphore
        if (0== semaphore->post()) sem_cond4=1;
    }
  }
};

static void semaphore_test()
{
  OFString errmsg;
  mutex = new OFMutex();
  if ((!mutex)||(! mutex->initialized())) BAILOUT("creation of mutex failed");
  semaphore = new OFSemaphore(2);
  if ((!semaphore)||(! semaphore->initialized())) BAILOUT("creation of semaphore failed");
  int condition = mutex->trylock();
  if (condition)
  {
    mutex->errorstr(errmsg, condition);
    errmsg = "mutex lock failed: " + errmsg;
    BAILOUT(errmsg);
  }
  condition = semaphore->trywait();
  if (condition)
  {
    semaphore->errorstr(errmsg, condition);
    errmsg = "semaphore acquisition failed: " + errmsg;
    BAILOUT(errmsg);
  }

  OFMutex memory_barrier;
  if (! memory_barrier.initialized()) BAILOUT("creation of mutex failed");

  SemaT1 t1;
  if (0 != t1.start()) BAILOUT("unable to create thread, semaphore test failed");

  int i=0;
  while ((i++<10) && (!sem_cond1))
  {
    memory_barrier.lock(); // locking the mutex acts as a (inefficient, but portable) memory barrier
    memory_barrier.unlock();
    OFStandard::milliSleep(wait_timeout);
  }
  if (!sem_cond1) BAILOUT("semaphore lock/unlock test failed");

  SemaT2 t2;
  if (0 != t2.start()) BAILOUT("unable to create thread, semaphore test failed");

  OFStandard::milliSleep(wait_timeout);
  if (sem_cond3) BAILOUT("semaphore lock/unlock test failed"); // make sure T2 is really blocked
  mutex->unlock();

  i=0;
  while ((i++<10) && ((!sem_cond2)||(!sem_cond3)||(!sem_cond4)))
  {
    memory_barrier.lock(); // locking the mutex acts as a (inefficient, but portable) memory barrier
    memory_barrier.unlock();
    OFStandard::milliSleep(wait_timeout);
  }

  if ((!sem_cond2)||(!sem_cond3)||(!sem_cond4)) BAILOUT("semaphore lock/unlock test failed: sem_cond2=" << sem_cond2 << ", sem_cond3=" << sem_cond3 << ", sem_cond4=" << sem_cond4);

  if (0 != t1.join()) BAILOUT("unable to join thread, semaphore test failed");
  if (0 != t2.join()) BAILOUT("unable to join thread, semaphore test failed");

  delete mutex;
  delete semaphore;
}

static OFReadWriteLock *rwlock=NULL;
static OFMutex *mutex2=NULL;
static volatile int rw_cond1=0;
static volatile int rw_cond2=0;
static volatile int rw_cond3=0;
static volatile int rw_cond4=0;
static volatile int rw_cond5=0;
static volatile int rw_cond6=0;
static volatile int rw_cond7=0;

class RWLockT2: public OFThread
{
public:
  RWLockT2(): OFThread() {}
  ~RWLockT2() {}

  virtual void run()
  {
    if ((0==mutex2->trylock())&&(OFReadWriteLock::busy == rwlock->trywrlock())) rw_cond5=1;
    if (0 == rwlock->wrlock())
    {
      rw_cond6=1;
      mutex2->unlock();
      OFStandard::milliSleep(wait_timeout);
      if (0==rwlock->wrunlock()) rw_cond7=1;
    }
    return;
  }
};

class RWLockT1: public OFThread
{
private:
  RWLockT2 &t2;
public:
#include DCMTK_DIAGNOSTIC_PUSH
#include DCMTK_DIAGNOSTIC_IGNORE_SHADOW
  RWLockT1(RWLockT2 &t2) : OFThread(), t2(t2) {}
#include DCMTK_DIAGNOSTIC_POP
  ~RWLockT1() {}

  virtual void run()
  {
    if (0 == rwlock->rdlock())
    {
      t2.start();
      rw_cond1 = 1; // acquired read lock
      mutex->lock();
      mutex->unlock();
      if (0== rwlock->rdunlock()) rw_cond2=1;
      mutex2->lock();
      mutex2->unlock();
      if (OFReadWriteLock::busy == rwlock->tryrdlock()) rw_cond3=1;
      if ((0 == rwlock->rdlock())&&(0==rwlock->rdunlock())) rw_cond4=1;
    }
    return;
  }
};

static void rwlock_test()
{
  OFString errmsg;

  mutex = new OFMutex();
  if ((!mutex)||(! mutex->initialized())) BAILOUT("creation of mutex failed");
  mutex2 = new OFMutex();
  if ((!mutex2)||(! mutex2->initialized())) BAILOUT("creation of mutex failed");
  rwlock = new OFReadWriteLock();
  if ((!rwlock)||(! rwlock->initialized())) BAILOUT("creation of read/write lock failed");

  int condition = mutex->trylock();
  if (condition)
  {
    mutex->errorstr(errmsg, condition);
    errmsg = "mutex lock failed: " + errmsg;
    BAILOUT(errmsg);
  }

  condition = rwlock->tryrdlock();
  if (condition)
  {
    rwlock->errorstr(errmsg, condition);
    errmsg = "read lock failed: " + errmsg;
    BAILOUT(errmsg);
  }

  OFMutex memory_barrier;
  if (! memory_barrier.initialized()) BAILOUT("creation of mutex failed");

  RWLockT2 t2;
  RWLockT1 t1(t2);
  if (0 != t1.start()) BAILOUT("unable to create thread, read/write lock/unlock test failed");

  int i=0;
  while ((i++<10) && ((!rw_cond1)||(!rw_cond5)))
  {
    memory_barrier.lock(); // locking the mutex acts as a (inefficient, but portable) memory barrier
    memory_barrier.unlock();
    OFStandard::milliSleep(wait_timeout);
  }

  if ((!rw_cond1)||(!rw_cond5)) BAILOUT("read/write lock/unlock test failed");
  condition = rwlock->rdunlock();
  if (condition)
  {
    rwlock->errorstr(errmsg, condition);
    errmsg = "read lock failed: " + errmsg;
    BAILOUT(errmsg);
  }
  OFStandard::milliSleep(wait_timeout);
  memory_barrier.lock(); // locking the mutex acts as a (inefficient, but portable) memory barrier
  memory_barrier.unlock();
  if (rw_cond6) BAILOUT("read/write lock/unlock test failed");

  mutex->unlock();

  i=0;
  while ((i++<10) && ((!rw_cond2)||(!rw_cond3)||(!rw_cond4)||(!rw_cond5)||(!rw_cond6)||(!rw_cond7)))
  {
    memory_barrier.lock(); // locking the mutex acts as a (inefficient, but portable) memory barrier
    memory_barrier.unlock();
    OFStandard::milliSleep(wait_timeout);
  }

  if ((!rw_cond2)||(!rw_cond3)||(!rw_cond4)||(!rw_cond5)||(!rw_cond6)||(!rw_cond7)) BAILOUT("read/write lock/unlock test failed");

  if (0 != t1.join()) BAILOUT("unable to join thread, read/write lock/unlock test failed");
  if (0 != t2.join()) BAILOUT("unable to join thread, read/write lock/unlock test failed");

  delete mutex;
  delete mutex2;
  delete rwlock;
}

class RWLockerT2: public OFThread
{
public:
  RWLockerT2(): OFThread() {}
  ~RWLockerT2() {}

  virtual void run()
  {
    OFReadWriteLocker locker(*rwlock);
    if ((0==mutex2->trylock())&&(OFReadWriteLock::busy == locker.trywrlock())) rw_cond5=1;
    if (0 == locker.wrlock())
    {
      rw_cond6=1;
      mutex2->unlock();
      OFStandard::milliSleep(wait_timeout);
      // Explicit unlock(), check if this causes one unlock() too much
      if (0==locker.unlock()) rw_cond7=1;
    }
    return;
  }
};

class RWLockerT1: public OFThread
{
private:
  RWLockerT2 &t2;
public:
#include DCMTK_DIAGNOSTIC_PUSH
#include DCMTK_DIAGNOSTIC_IGNORE_SHADOW
  RWLockerT1(RWLockerT2 &t2): OFThread(), t2(t2) {}
#include DCMTK_DIAGNOSTIC_POP
  ~RWLockerT1() {}

  virtual void run()
  {
    OFReadWriteLocker locker(*rwlock);
    if (0 == locker.rdlock())
    {
      t2.start();
      rw_cond1 = 1; // acquired read lock
      mutex->lock();
      mutex->unlock();
      if (0== locker.unlock()) rw_cond2=1;
      mutex2->lock();
      mutex2->unlock();
      if (OFReadWriteLock::busy == locker.tryrdlock()) rw_cond3=1;
      if (0 == locker.rdlock()) rw_cond4=1;
      // Implicit unlock() at the end
    }
    return;
  }
};

static void rwlocker_test()
{
  OFString errmsg;

  // Reset global state
  rwlock=NULL;
  mutex2=NULL;
  rw_cond1=0;
  rw_cond2=0;
  rw_cond3=0;
  rw_cond4=0;
  rw_cond5=0;
  rw_cond6=0;
  rw_cond7=0;

  mutex = new OFMutex();
  if ((!mutex)||(! mutex->initialized())) BAILOUT("creation of mutex failed");
  mutex2 = new OFMutex();
  if ((!mutex2)||(! mutex2->initialized())) BAILOUT("creation of mutex failed");
  rwlock = new OFReadWriteLock();
  if ((!rwlock)||(! rwlock->initialized())) BAILOUT("creation of read/write lock failed");

  int condition = mutex->trylock();
  if (condition)
  {
    mutex->errorstr(errmsg, condition);
    errmsg = "mutex lock failed: " + errmsg;
    BAILOUT(errmsg);
  }

  OFReadWriteLocker rwlockLocker(*rwlock);
  condition = rwlockLocker.tryrdlock();
  if (condition)
  {
    rwlock->errorstr(errmsg, condition);
    errmsg = "read lock failed: " + errmsg;
    BAILOUT(errmsg);
  }

  OFMutex memory_barrier;
  if (! memory_barrier.initialized()) BAILOUT("creation of mutex failed");

  RWLockerT2 t2;
  RWLockerT1 t1(t2);
  if (0 != t1.start()) BAILOUT("unable to create thread, read/write lock test failed");

  int i=0;
  while ((i++<10) && ((!rw_cond1)||(!rw_cond5)))
  {
    memory_barrier.lock(); // locking the mutex acts as a (inefficient, but portable) memory barrier
    memory_barrier.unlock();
    OFStandard::milliSleep(wait_timeout);
  }

  if ((!rw_cond1)||(!rw_cond5)) BAILOUT("read/write lock test failed");
  condition = rwlockLocker.unlock();
  if (condition)
  {
    rwlock->errorstr(errmsg, condition);
    errmsg = "read lock failed: " + errmsg;
    BAILOUT(errmsg);
  }
  OFStandard::milliSleep(wait_timeout);
  memory_barrier.lock(); // locking the mutex acts as a (inefficient, but portable) memory barrier
  memory_barrier.unlock();
  if (rw_cond6) BAILOUT("read/write lock test failed");

  mutex->unlock();

  i=0;
  while ((i++<10) && ((!rw_cond2)||(!rw_cond3)||(!rw_cond4)||(!rw_cond5)||(!rw_cond6)||(!rw_cond7)))
  {
    memory_barrier.lock(); // locking the mutex acts as a (inefficient, but portable) memory barrier
    memory_barrier.unlock();
    OFStandard::milliSleep(wait_timeout);
  }

  if ((!rw_cond2)||(!rw_cond3)||(!rw_cond4)||(!rw_cond5)||(!rw_cond6)||(!rw_cond7)) BAILOUT("read/write lock test failed");

  if (0 != t1.join()) BAILOUT("unable to join thread, read/write lock test failed");
  if (0 != t2.join()) BAILOUT("unable to join thread, read/write lock test failed");

  delete mutex;
  delete mutex2;
  delete rwlock;
}


static OFThreadSpecificData *tsdata=NULL;
static volatile int tsd_cond1=0;
static volatile int tsd_cond2=0;
static volatile int tsd_cond3=0;
static volatile int tsd_cond4=0;

class TSDataT1: public OFThread
{
public:
  TSDataT1(): OFThread() {}
  ~TSDataT1() {}

  virtual void run()
  {
    if (0 == tsdata->set(this))
    {
      tsd_cond1 = 1; // acquired read lock
      mutex->lock();
      mutex->unlock();
      void *result = NULL;
      if (0== tsdata->get(result))
      {
        if (result == OFstatic_cast(void *, this)) tsd_cond3 = 1;
      }
    }
  }
};

class TSDataT2: public OFThread
{
public:
  TSDataT2(): OFThread() {}
  ~TSDataT2() {}

  virtual void run()
  {
    if (0 == tsdata->set(this))
    {
      tsd_cond2 = 1; // acquired read lock
      mutex2->lock();
      mutex2->unlock();
      void *result = NULL;
      if (0== tsdata->get(result))
      {
        if (result == OFstatic_cast(void *, this)) tsd_cond4 = 1;
      }
    }
  }
};


static void tsdata_test()
{
  OFString errmsg;

  mutex = new OFMutex();
  if ((!mutex)||(! mutex->initialized())) BAILOUT("creation of mutex failed");
  mutex2 = new OFMutex();
  if ((!mutex2)||(! mutex2->initialized())) BAILOUT("creation of mutex failed");
  tsdata = new OFThreadSpecificData();
  if ((!tsdata)||(! tsdata->initialized())) BAILOUT("creation of thread specific data failed");

  int condition = mutex->trylock();
  if (condition)
  {
    mutex->errorstr(errmsg, condition);
    errmsg = "mutex lock failed: " + errmsg;
    BAILOUT(errmsg);
  }
  condition = mutex2->trylock();
  if (condition)
  {
    mutex2->errorstr(errmsg, condition);
    errmsg = "mutex lock failed: " + errmsg;
    BAILOUT(errmsg);
  }

  OFMutex memory_barrier;
  if (! memory_barrier.initialized()) BAILOUT("creation of mutex failed");

  TSDataT1 t1;
  if (0 != t1.start()) BAILOUT("unable to create thread, thread specific data test failed");

  TSDataT2 t2;
  if (0 != t2.start()) BAILOUT("unable to create thread, thread specific data test failed");


  int i=0;
  while ((i++<10) && ((!tsd_cond1)||(!tsd_cond2)))
  {
    memory_barrier.lock(); // locking the mutex acts as a (inefficient, but portable) memory barrier
    memory_barrier.unlock();
    OFStandard::milliSleep(wait_timeout);
  }

  if ((!tsd_cond1)||(!tsd_cond2)) BAILOUT("thread specific data write test failed");

  condition = mutex->unlock();
  if (condition)
  {
    mutex->errorstr(errmsg, condition);
    errmsg = "mutex unlock failed: " + errmsg;
    BAILOUT(errmsg);
  }
  condition = mutex2->unlock();
  if (condition)
  {
    mutex2->errorstr(errmsg, condition);
    errmsg = "mutex unlock failed: " + errmsg;
    BAILOUT(errmsg);
  }

  i=0;
  while ((i++<10) && ((!tsd_cond3)||(!tsd_cond4)))
  {
    memory_barrier.lock(); // locking the mutex acts as a (inefficient, but portable) memory barrier
    memory_barrier.unlock();
    OFStandard::milliSleep(wait_timeout);
  }

  if ((!tsd_cond3)||(!tsd_cond4)) BAILOUT("thread specific data read test failed");

  if (0 != t1.join()) BAILOUT("unable to create thread, thread specific data test failed");
  if (0 != t2.join()) BAILOUT("unable to create thread, thread specific data test failed");

  delete mutex;
  delete mutex2;
  delete tsdata;
}


OFTEST(ofstd_thread)
{
  // This makes sure tests are executed in the expected order
  mutex_test();
  semaphore_test(); // may assume that mutexes work correctly
  rwlock_test();    // may assume that mutexes and semaphores work correctly
  rwlocker_test();  // may assume that mutexes, semaphores and read/write locks work correctly
  tsdata_test();
}
