#pragma once

#include <cstddef>
#include <condition_variable>
#include <deque>
#include <mutex>
#include <optional>
#include <stop_token>
#include <utility>

namespace smartcut::core {
template <typename T>
class BoundedQueue {
 public:
  explicit BoundedQueue(std::size_t capacity) : capacity_(capacity) {}
  bool push(T value) { std::lock_guard lock(mutex_); if (closed_ || items_.size() >= capacity_) return false; items_.push_back(std::move(value)); not_empty_.notify_one(); return true; }
  bool wait_push(T value, std::stop_token stop = {}) {
    std::unique_lock lock(mutex_);
    if (!not_full_.wait(lock, stop, [this] { return closed_ || items_.size() < capacity_; }) || closed_) return false;
    items_.push_back(std::move(value)); not_empty_.notify_one(); return true;
  }
  std::optional<T> pop() { std::lock_guard lock(mutex_); if (items_.empty()) return std::nullopt; T value = std::move(items_.front()); items_.pop_front(); not_full_.notify_one(); return value; }
  std::optional<T> wait_pop(std::stop_token stop = {}) {
    std::unique_lock lock(mutex_);
    if (!not_empty_.wait(lock, stop, [this] { return closed_ || !items_.empty(); }) || items_.empty()) return std::nullopt;
    T value = std::move(items_.front()); items_.pop_front(); not_full_.notify_one(); return value;
  }
  void close() { std::lock_guard lock(mutex_); closed_ = true; not_empty_.notify_all(); not_full_.notify_all(); }
  void clear() { std::lock_guard lock(mutex_); items_.clear(); not_full_.notify_all(); }
  bool closed() const { std::lock_guard lock(mutex_); return closed_; }
  std::size_t size() const { std::lock_guard lock(mutex_); return items_.size(); }
 private:
  const std::size_t capacity_;
  mutable std::mutex mutex_;
  std::condition_variable_any not_empty_;
  std::condition_variable_any not_full_;
  std::deque<T> items_;
  bool closed_{};
};
}  // namespace smartcut::core
