//===-- rosa/support/concurrent_queue.hpp -----------------------*- C++ -*-===//
//
//                                 The RoSA Framework
//
// Distributed under the terms and conditions of the Boost Software License 1.0.
// See accompanying file LICENSE.
//
// If you did not receive a copy of the license file, see
// http://www.boost.org/LICENSE_1_0.txt.
//
//===----------------------------------------------------------------------===//
///
/// \file rosa/support/concurrent_queue.hpp
///
/// \author David Juhasz (david.juhasz@tuwien.ac.at)
///
/// \date 2020
///
/// \brief Thread-safe queue.
///
//===----------------------------------------------------------------------===//

#ifndef ROSA_SUPPORT_CONCURRENT_QUEUE_HPP
#define ROSA_SUPPORT_CONCURRENT_QUEUE_HPP

#include <condition_variable>
#include <mutex>
#include <queue>

namespace rosa {

/// A thread-safe queue implementation for producer-consumer scenarios.
///
/// \tparam T element type
template <typename T> class concurrent_queue {
public:
  /// Enumeration type with values indicating outcome of popping from the queue.
  ///
  /// \see rosa::concurrent_queue::pop()
  enum pop_result {
    queue_empty,   ///< Could not pop a value because the queue is empty
    element_popped ///< Popped a value from the queue
  };

  /// Pops an element from the queue.
  ///
  /// \param Elem [out] variable to store the popped element
  /// \param Blocking whether to block until an element is available
  ///
  /// \note If \p Blocking is true, the function blocks until an element is
  /// available in the queue and returns only after having an element popped
  /// into \p Elem. Otherwise, the function returns with \c queue_empty
  /// immediately if there is no available element in the queue.
  ///
  /// \return whether an element was popped into \p Elem
  pop_result pop(T &Elem, const bool Blocking = true) {
    std::unique_lock<std::mutex> Lock(Mutex);
    if (Queue.empty()) {
      if (!Blocking) {
        return queue_empty;
      }
      Cond.wait(Lock, [&](void) { return !Queue.empty(); });
    }
    Elem = Queue.front();
    Queue.pop();
    return element_popped;
  }

  /// Puts an element into the queue.
  ///
  /// \param Elem element to put into the queue
  void push(const T &Elem) {
    std::unique_lock<std::mutex> Lock(Mutex);
    Queue.push(Elem);
    Lock.unlock();
    Cond.notify_one();
  }

  /// Puts an element into the queue.
  ///
  /// \param Elem element to put into the queue
  void push(T &&Elem) {
    std::unique_lock<std::mutex> Lock(Mutex);
    Queue.push(std::move(Elem));
    Lock.unlock();
    Cond.notify_one();
  }

private:
  /// Queue holding elements.
  std::queue<T> Queue;

  /// Mutex for providing mutual exclusion of accesses.
  std::mutex Mutex;

  /// Used by consumers to lock until data is available.
  std::condition_variable Cond;
};

} // End namespace rosa

#endif // ROSA_SUPPORT_CONCURRENT_QUEUE_HPP
