//===-- Predicate.h ---------------------------------------------*- C++ -*-===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

#ifndef liblldb_Predicate_h_
#define liblldb_Predicate_h_

// C Includes
#include <stdint.h>
#include <time.h>

// C++ Includes
#include <condition_variable>
#include <mutex>

// Other libraries and framework includes
// Project includes
#include "lldb/lldb-defines.h"

//#define DB_PTHREAD_LOG_EVENTS

//----------------------------------------------------------------------
/// Enumerations for broadcasting.
//----------------------------------------------------------------------
namespace lldb_private {

typedef enum {
  eBroadcastNever,   ///< No broadcast will be sent when the value is modified.
  eBroadcastAlways,  ///< Always send a broadcast when the value is modified.
  eBroadcastOnChange ///< Only broadcast if the value changes when the value is
                     ///modified.
} PredicateBroadcastType;

//----------------------------------------------------------------------
/// @class Predicate Predicate.h "lldb/Host/Predicate.h"
/// @brief A C++ wrapper class for providing threaded access to a value
/// of type T.
///
/// A templatized class that provides multi-threaded access to a value
/// of type T. Threads can efficiently wait for bits within T to be set
/// or reset, or wait for T to be set to be equal/not equal to a
/// specified values.
//----------------------------------------------------------------------
template <class T> class Predicate {
public:
  //------------------------------------------------------------------
  /// Default constructor.
  ///
  /// Initializes the mutex, condition and value with their default
  /// constructors.
  //------------------------------------------------------------------
  Predicate() : m_value(), m_mutex(), m_condition() {}

  //------------------------------------------------------------------
  /// Construct with initial T value \a initial_value.
  ///
  /// Initializes the mutex and condition with their default
  /// constructors, and initializes the value with \a initial_value.
  ///
  /// @param[in] initial_value
  ///     The initial value for our T object.
  //------------------------------------------------------------------
  Predicate(T initial_value)
      : m_value(initial_value), m_mutex(), m_condition() {}

  //------------------------------------------------------------------
  /// Destructor.
  ///
  /// Destroy the condition, mutex, and T objects.
  //------------------------------------------------------------------
  ~Predicate() = default;

  //------------------------------------------------------------------
  /// Value get accessor.
  ///
  /// Copies the current \a m_value in a thread safe manor and returns
  /// the copied value.
  ///
  /// @return
  ///     A copy of the current value.
  //------------------------------------------------------------------
  T GetValue() const {
    std::lock_guard<std::mutex> guard(m_mutex);
    T value = m_value;
    return value;
  }

  //------------------------------------------------------------------
  /// Value set accessor.
  ///
  /// Set the contained \a m_value to \a new_value in a thread safe
  /// way and broadcast if needed.
  ///
  /// @param[in] value
  ///     The new value to set.
  ///
  /// @param[in] broadcast_type
  ///     A value indicating when and if to broadcast. See the
  ///     PredicateBroadcastType enumeration for details.
  ///
  /// @see Predicate::Broadcast()
  //------------------------------------------------------------------
  void SetValue(T value, PredicateBroadcastType broadcast_type) {
    std::lock_guard<std::mutex> guard(m_mutex);
#ifdef DB_PTHREAD_LOG_EVENTS
    printf("%s (value = 0x%8.8x, broadcast_type = %i)\n", __FUNCTION__, value,
           broadcast_type);
#endif
    const T old_value = m_value;
    m_value = value;

    Broadcast(old_value, broadcast_type);
  }

  //------------------------------------------------------------------
  /// Set some bits in \a m_value.
  ///
  /// Logically set the bits \a bits in the contained \a m_value in a
  /// thread safe way and broadcast if needed.
  ///
  /// @param[in] bits
  ///     The bits to set in \a m_value.
  ///
  /// @param[in] broadcast_type
  ///     A value indicating when and if to broadcast. See the
  ///     PredicateBroadcastType enumeration for details.
  ///
  /// @see Predicate::Broadcast()
  //------------------------------------------------------------------
  void SetValueBits(T bits, PredicateBroadcastType broadcast_type) {
    std::lock_guard<std::mutex> guard(m_mutex);
#ifdef DB_PTHREAD_LOG_EVENTS
    printf("%s (bits = 0x%8.8x, broadcast_type = %i)\n", __FUNCTION__, bits,
           broadcast_type);
#endif
    const T old_value = m_value;
    m_value |= bits;

    Broadcast(old_value, broadcast_type);
  }

  //------------------------------------------------------------------
  /// Reset some bits in \a m_value.
  ///
  /// Logically reset (clear) the bits \a bits in the contained
  /// \a m_value in a thread safe way and broadcast if needed.
  ///
  /// @param[in] bits
  ///     The bits to clear in \a m_value.
  ///
  /// @param[in] broadcast_type
  ///     A value indicating when and if to broadcast. See the
  ///     PredicateBroadcastType enumeration for details.
  ///
  /// @see Predicate::Broadcast()
  //------------------------------------------------------------------
  void ResetValueBits(T bits, PredicateBroadcastType broadcast_type) {
    std::lock_guard<std::mutex> guard(m_mutex);
#ifdef DB_PTHREAD_LOG_EVENTS
    printf("%s (bits = 0x%8.8x, broadcast_type = %i)\n", __FUNCTION__, bits,
           broadcast_type);
#endif
    const T old_value = m_value;
    m_value &= ~bits;

    Broadcast(old_value, broadcast_type);
  }

  //------------------------------------------------------------------
  /// Wait for bits to be set in \a m_value.
  ///
  /// Waits in a thread safe way for any bits in \a bits to get
  /// logically set in \a m_value. If any bits are already set in
  /// \a m_value, this function will return without waiting.
  ///
  /// It is possible for the value to be changed between the time
  /// the bits are set and the time the waiting thread wakes up.
  /// If the bits are no longer set when the waiting thread wakes
  /// up, it will go back into a wait state.  It may be necessary
  /// for the calling code to use additional thread synchronization
  /// methods to detect transitory states.
  ///
  /// @param[in] bits
  ///     The bits we are waiting to be set in \a m_value.
  ///
  /// @param[in] abstime
  ///     If non-nullptr, the absolute time at which we should stop
  ///     waiting, else wait an infinite amount of time.
  ///
  /// @return
  ///     Any bits of the requested bits that actually were set within
  ///     the time specified. Zero if a timeout or unrecoverable error
  ///     occurred.
  //------------------------------------------------------------------
  T WaitForSetValueBits(T bits, const std::chrono::microseconds &timeout =
                                    std::chrono::microseconds(0)) {
    // pthread_cond_timedwait() or pthread_cond_wait() will atomically
    // unlock the mutex and wait for the condition to be set. When either
    // function returns, they will re-lock the mutex. We use an auto lock/unlock
    // class (std::lock_guard) to allow us to return at any point in this
    // function and not have to worry about unlocking the mutex.
    std::unique_lock<std::mutex> lock(m_mutex);
#ifdef DB_PTHREAD_LOG_EVENTS
    printf("%s (bits = 0x%8.8x, timeout = %llu), m_value = 0x%8.8x\n",
           __FUNCTION__, bits, timeout.count(), m_value);
#endif
    while ((m_value & bits) == 0) {
      if (timeout == std::chrono::microseconds(0)) {
        m_condition.wait(lock);
      } else {
        std::cv_status result = m_condition.wait_for(lock, timeout);
        if (result == std::cv_status::timeout)
          break;
      }
    }
#ifdef DB_PTHREAD_LOG_EVENTS
    printf("%s (bits = 0x%8.8x), m_value = 0x%8.8x, returning 0x%8.8x\n",
           __FUNCTION__, bits, m_value, m_value & bits);
#endif

    return m_value & bits;
  }

  //------------------------------------------------------------------
  /// Wait for bits to be reset in \a m_value.
  ///
  /// Waits in a thread safe way for any bits in \a bits to get
  /// logically reset in \a m_value. If all bits are already reset in
  /// \a m_value, this function will return without waiting.
  ///
  /// It is possible for the value to be changed between the time
  /// the bits are reset and the time the waiting thread wakes up.
  /// If the bits are no set when the waiting thread wakes up, it will
  /// go back into a wait state.  It may be necessary for the calling
  /// code to use additional thread synchronization methods to detect
  /// transitory states.
  ///
  /// @param[in] bits
  ///     The bits we are waiting to be reset in \a m_value.
  ///
  /// @param[in] abstime
  ///     If non-nullptr, the absolute time at which we should stop
  ///     waiting, else wait an infinite amount of time.
  ///
  /// @return
  ///     Zero on successful waits, or non-zero if a timeout or
  ///     unrecoverable error occurs.
  //------------------------------------------------------------------
  T WaitForResetValueBits(T bits, const std::chrono::microseconds &timeout =
                                      std::chrono::microseconds(0)) {
    // pthread_cond_timedwait() or pthread_cond_wait() will atomically
    // unlock the mutex and wait for the condition to be set. When either
    // function returns, they will re-lock the mutex. We use an auto lock/unlock
    // class (std::lock_guard) to allow us to return at any point in this
    // function and not have to worry about unlocking the mutex.
    std::unique_lock<std::mutex> lock(m_mutex);

#ifdef DB_PTHREAD_LOG_EVENTS
    printf("%s (bits = 0x%8.8x, timeout = %llu), m_value = 0x%8.8x\n",
           __FUNCTION__, bits, timeout.count(), m_value);
#endif
    while ((m_value & bits) != 0) {
      if (timeout == std::chrono::microseconds(0)) {
        m_condition.wait(lock);
      } else {
        std::cv_status result = m_condition.wait_for(lock, timeout);
        if (result == std::cv_status::timeout)
          break;
      }
    }

#ifdef DB_PTHREAD_LOG_EVENTS
    printf("%s (bits = 0x%8.8x), m_value = 0x%8.8x, returning 0x%8.8x\n",
           __FUNCTION__, bits, m_value, m_value & bits);
#endif
    return m_value & bits;
  }

  //------------------------------------------------------------------
  /// Wait for \a m_value to be equal to \a value.
  ///
  /// Waits in a thread safe way for \a m_value to be equal to \a
  /// value. If \a m_value is already equal to \a value, this
  /// function will return without waiting.
  ///
  /// It is possible for the value to be changed between the time
  /// the value is set and the time the waiting thread wakes up.
  /// If the value no longer matches the requested value when the
  /// waiting thread wakes up, it will go back into a wait state.  It
  /// may be necessary for the calling code to use additional thread
  /// synchronization methods to detect transitory states.
  ///
  /// @param[in] value
  ///     The value we want \a m_value to be equal to.
  ///
  /// @param[in] abstime
  ///     If non-nullptr, the absolute time at which we should stop
  ///     waiting, else wait an infinite amount of time.
  ///
  /// @param[out] timed_out
  ///     If not null, set to true if we return because of a time out,
  ///     and false if the value was set.
  ///
  /// @return
  ///     @li \b true if the \a m_value is equal to \a value
  ///     @li \b false otherwise
  //------------------------------------------------------------------
  bool WaitForValueEqualTo(T value, const std::chrono::microseconds &timeout =
                                        std::chrono::microseconds(0),
                           bool *timed_out = nullptr) {
    // pthread_cond_timedwait() or pthread_cond_wait() will atomically
    // unlock the mutex and wait for the condition to be set. When either
    // function returns, they will re-lock the mutex. We use an auto lock/unlock
    // class (std::lock_guard) to allow us to return at any point in this
    // function and not have to worry about unlocking the mutex.
    std::unique_lock<std::mutex> lock(m_mutex);

#ifdef DB_PTHREAD_LOG_EVENTS
    printf("%s (value = 0x%8.8x, timeout = %llu), m_value = 0x%8.8x\n",
           __FUNCTION__, value, timeout.count(), m_value);
#endif
    if (timed_out)
      *timed_out = false;

    while (m_value != value) {
      if (timeout == std::chrono::microseconds(0)) {
        m_condition.wait(lock);
      } else {
        std::cv_status result = m_condition.wait_for(lock, timeout);
        if (result == std::cv_status::timeout) {
          if (timed_out)
            *timed_out = true;
          break;
        }
      }
    }

    return m_value == value;
  }

  //------------------------------------------------------------------
  /// Wait for \a m_value to be equal to \a value and then set it to
  /// a new value.
  ///
  /// Waits in a thread safe way for \a m_value to be equal to \a
  /// value and then sets \a m_value to \a new_value. If \a m_value
  /// is already equal to \a value, this function will immediately
  /// set \a m_value to \a new_value and return without waiting.
  ///
  /// It is possible for the value to be changed between the time
  /// the value is set and the time the waiting thread wakes up.
  /// If the value no longer matches the requested value when the
  /// waiting thread wakes up, it will go back into a wait state.  It
  /// may be necessary for the calling code to use additional thread
  /// synchronization methods to detect transitory states.
  ///
  /// @param[in] value
  ///     The value we want \a m_value to be equal to.
  ///
  /// @param[in] new_value
  ///     The value to which \a m_value will be set if \b true is
  ///     returned.
  ///
  /// @param[in] abstime
  ///     If non-nullptr, the absolute time at which we should stop
  ///     waiting, else wait an infinite amount of time.
  ///
  /// @param[out] timed_out
  ///     If not null, set to true if we return because of a time out,
  ///     and false if the value was set.
  ///
  /// @return
  ///     @li \b true if the \a m_value became equal to \a value
  ///     @li \b false otherwise
  //------------------------------------------------------------------
  bool WaitForValueEqualToAndSetValueTo(
      T wait_value, T new_value,
      const std::chrono::microseconds &timeout = std::chrono::microseconds(0),
      bool *timed_out = nullptr) {
    // pthread_cond_timedwait() or pthread_cond_wait() will atomically
    // unlock the mutex and wait for the condition to be set. When either
    // function returns, they will re-lock the mutex. We use an auto lock/unlock
    // class (std::lock_guard) to allow us to return at any point in this
    // function and not have to worry about unlocking the mutex.
    std::unique_lock<std::mutex> lock(m_mutex);

#ifdef DB_PTHREAD_LOG_EVENTS
    printf("%s (wait_value = 0x%8.8x, new_value = 0x%8.8x, timeout = %llu), "
           "m_value = 0x%8.8x\n",
           __FUNCTION__, wait_value, new_value, timeout.count(), m_value);
#endif
    if (timed_out)
      *timed_out = false;

    while (m_value != wait_value) {
      if (timeout == std::chrono::microseconds(0)) {
        m_condition.wait(lock);
      } else {
        std::cv_status result = m_condition.wait_for(lock, timeout);
        if (result == std::cv_status::timeout) {
          if (timed_out)
            *timed_out = true;
          break;
        }
      }
    }

    if (m_value == wait_value) {
      m_value = new_value;
      return true;
    }

    return false;
  }

  //------------------------------------------------------------------
  /// Wait for \a m_value to not be equal to \a value.
  ///
  /// Waits in a thread safe way for \a m_value to not be equal to \a
  /// value. If \a m_value is already not equal to \a value, this
  /// function will return without waiting.
  ///
  /// It is possible for the value to be changed between the time
  /// the value is set and the time the waiting thread wakes up.
  /// If the value is equal to the test value when the waiting thread
  /// wakes up, it will go back into a wait state.  It may be
  /// necessary for the calling code to use additional thread
  /// synchronization methods to detect transitory states.
  ///
  /// @param[in] value
  ///     The value we want \a m_value to not be equal to.
  ///
  /// @param[out] new_value
  ///     The new value if \b true is returned.
  ///
  /// @param[in] abstime
  ///     If non-nullptr, the absolute time at which we should stop
  ///     waiting, else wait an infinite amount of time.
  ///
  /// @return
  ///     @li \b true if the \a m_value is equal to \a value
  ///     @li \b false otherwise
  //------------------------------------------------------------------
  bool WaitForValueNotEqualTo(
      T value, T &new_value,
      const std::chrono::microseconds &timeout = std::chrono::microseconds(0)) {
    // pthread_cond_timedwait() or pthread_cond_wait() will atomically
    // unlock the mutex and wait for the condition to be set. When either
    // function returns, they will re-lock the mutex. We use an auto lock/unlock
    // class (std::lock_guard) to allow us to return at any point in this
    // function and not have to worry about unlocking the mutex.
    std::unique_lock<std::mutex> lock(m_mutex);
#ifdef DB_PTHREAD_LOG_EVENTS
    printf("%s (value = 0x%8.8x, timeout = %llu), m_value = 0x%8.8x\n",
           __FUNCTION__, value, timeout.count(), m_value);
#endif
    while (m_value == value) {
      if (timeout == std::chrono::microseconds(0)) {
        m_condition.wait(lock);
      } else {
        std::cv_status result = m_condition.wait_for(lock, timeout);
        if (result == std::cv_status::timeout)
          break;
      }
    }

    if (m_value != value) {
      new_value = m_value;
      return true;
    }
    return false;
  }

protected:
  //----------------------------------------------------------------------
  // pthread condition and mutex variable to control access and allow
  // blocking between the main thread and the spotlight index thread.
  //----------------------------------------------------------------------
  T m_value; ///< The templatized value T that we are protecting access to
  mutable std::mutex m_mutex; ///< The mutex to use when accessing the data
  std::condition_variable m_condition; ///< The pthread condition variable to
                                       ///use for signaling that data available
                                       ///or changed.

private:
  //------------------------------------------------------------------
  /// Broadcast if needed.
  ///
  /// Check to see if we need to broadcast to our condition variable
  /// depending on the \a old_value and on the \a broadcast_type.
  ///
  /// If \a broadcast_type is eBroadcastNever, no broadcast will be
  /// sent.
  ///
  /// If \a broadcast_type is eBroadcastAlways, the condition variable
  /// will always be broadcast.
  ///
  /// If \a broadcast_type is eBroadcastOnChange, the condition
  /// variable be broadcast if the owned value changes.
  //------------------------------------------------------------------
  void Broadcast(T old_value, PredicateBroadcastType broadcast_type) {
    bool broadcast =
        (broadcast_type == eBroadcastAlways) ||
        ((broadcast_type == eBroadcastOnChange) && old_value != m_value);
#ifdef DB_PTHREAD_LOG_EVENTS
    printf("%s (old_value = 0x%8.8x, broadcast_type = %i) m_value = 0x%8.8x, "
           "broadcast = %u\n",
           __FUNCTION__, old_value, broadcast_type, m_value, broadcast);
#endif
    if (broadcast)
      m_condition.notify_all();
  }

  DISALLOW_COPY_AND_ASSIGN(Predicate);
};

} // namespace lldb_private

#endif // liblldb_Predicate_h_
