/*=========================================================================

  Program:   Visualization Toolkit
  Module:    vtkConditionVariable.h

  Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
  All rights reserved.
  See Copyright.txt or http://www.kitware.com/Copyright.htm for details.

     This software is distributed WITHOUT ANY WARRANTY; without even
     the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
     PURPOSE.  See the above copyright notice for more information.

=========================================================================*/
/**
 * @class   vtkConditionVariable
 * @brief   mutual exclusion locking class
 *
 * vtkConditionVariable allows the locking of variables which are accessed
 * through different threads.  This header file also defines
 * vtkSimpleConditionVariable which is not a subclass of vtkObject.
 *
 * The win32 implementation is based on notes provided by
 * Douglas C. Schmidt and Irfan Pyarali,
 * Department of Computer Science,
 * Washington University, St. Louis, Missouri.
 * http://www.cs.wustl.edu/~schmidt/win32-cv-1.html
 */

#ifndef vtkConditionVariable_h
#define vtkConditionVariable_h

#include "vtkCommonCoreModule.h" // For export macro
#include "vtkObject.h"

#include "vtkMutexLock.h" // Need for friend access to vtkSimpleMutexLock

#if defined(VTK_USE_PTHREADS)
#include <pthread.h> // Need POSIX thread implementation of mutex (even win32 provides mutexes)
typedef pthread_cond_t vtkConditionType;
#endif

// Typically a top level windows application sets _WIN32_WINNT. If it is not set we set it to
// 0x0501 (Windows XP)
#ifdef VTK_USE_WIN32_THREADS
#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0501 // 0x0501 means target Windows XP or later
#endif
#include "vtkWindows.h" // Needed for win32 CRITICAL_SECTION, HANDLE, etc.
#endif

#ifdef VTK_USE_WIN32_THREADS
#if 1
typedef struct
{
  // Number of threads waiting on condition.
  int WaitingThreadCount;

  // Lock for WaitingThreadCount
  CRITICAL_SECTION WaitingThreadCountCritSec;

  // Semaphore to block threads waiting for the condition to change.
  vtkWindowsHANDLE Semaphore;

  // An event used to wake up thread(s) waiting on the semaphore
  // when pthread_cond_signal or pthread_cond_broadcast is called.
  vtkWindowsHANDLE DoneWaiting;

  // Was pthread_cond_broadcast called?
  size_t WasBroadcast;
} pthread_cond_t;

typedef pthread_cond_t vtkConditionType;
#else  // 0
typedef struct
{
  // Number of threads waiting on condition.
  int WaitingThreadCount;

  // Lock for WaitingThreadCount
  CRITICAL_SECTION WaitingThreadCountCritSec;

  // Number of threads to release when pthread_cond_broadcast()
  // or pthread_cond_signal() is called.
  int ReleaseCount;

  // Used to prevent one thread from decrementing ReleaseCount all
  // by itself instead of letting others respond.
  int NotifyCount;

  // A manual-reset event that's used to block and release waiting threads.
  vtkWindowsHANDLE Event;
} pthread_cond_t;

typedef pthread_cond_t vtkConditionType;
#endif // 0
#endif // VTK_USE_WIN32_THREADS

#ifndef VTK_USE_PTHREADS
#ifndef VTK_USE_WIN32_THREADS
typedef int vtkConditionType;
#endif
#endif

// Condition variable that is not a vtkObject.
class VTKCOMMONCORE_EXPORT vtkSimpleConditionVariable
{
public:
  vtkSimpleConditionVariable();
  ~vtkSimpleConditionVariable();

  static vtkSimpleConditionVariable* New();

  void Delete() { delete this; }

  /**
   * Wake one thread waiting for the condition to change.
   */
  void Signal();

  /**
   * Wake all threads waiting for the condition to change.
   */
  void Broadcast();

  /**
   * Wait for the condition to change.
   * Upon entry, the mutex must be locked and the lock held by the calling thread.
   * Upon exit, the mutex will be locked and held by the calling thread.
   * Between entry and exit, the mutex will be unlocked and may be held by other threads.

   * @param mutex The mutex that should be locked on entry and will be locked on exit (but not in
   between)
   * @retval Normally, this function returns 0. Should a thread be interrupted by a signal, a
   non-zero value may be returned.
   */
  int Wait(vtkSimpleMutexLock& mutex);

protected:
  vtkConditionType ConditionVariable;

private:
  vtkSimpleConditionVariable(const vtkSimpleConditionVariable& other) = delete;
  vtkSimpleConditionVariable& operator=(const vtkSimpleConditionVariable& rhs) = delete;
};

class VTKCOMMONCORE_EXPORT vtkConditionVariable : public vtkObject
{
public:
  static vtkConditionVariable* New();
  vtkTypeMacro(vtkConditionVariable, vtkObject);
  void PrintSelf(ostream& os, vtkIndent indent) override;

  /**
   * Wake one thread waiting for the condition to change.
   */
  void Signal();

  /**
   * Wake all threads waiting for the condition to change.
   */
  void Broadcast();

  /**
   * Wait for the condition to change.
   * Upon entry, the mutex must be locked and the lock held by the calling thread.
   * Upon exit, the mutex will be locked and held by the calling thread.
   * Between entry and exit, the mutex will be unlocked and may be held by other threads.

   * @param mutex The mutex that should be locked on entry and will be locked on exit (but not in
   between)
   * @retval Normally, this function returns 0. Should a thread be interrupted by a signal, a
   non-zero value may be returned.
   */
  int Wait(vtkMutexLock* mutex);

protected:
  vtkConditionVariable() {}

  vtkSimpleConditionVariable SimpleConditionVariable;

private:
  vtkConditionVariable(const vtkConditionVariable&) = delete;
  void operator=(const vtkConditionVariable&) = delete;
};

inline void vtkConditionVariable::Signal()
{
  this->SimpleConditionVariable.Signal();
}

inline void vtkConditionVariable::Broadcast()
{
  this->SimpleConditionVariable.Broadcast();
}

inline int vtkConditionVariable::Wait(vtkMutexLock* lock)
{
  return this->SimpleConditionVariable.Wait(lock->SimpleMutexLock);
}

#endif // vtkConditionVariable_h
