#ifndef RING_BUFFER_H
#define RING_BUFFER_H

#include <mutex>
#include <optional>
#include <vector>

template <typename T> class RingBuffer {
  std::vector<T> buffer;
  size_t capacity;
  size_t head;
  size_t tail;
  bool full;

  mutable std::mutex mtx;
  bool empty_unlocked() const { return (!full && (head == tail)); }

public:
  explicit RingBuffer(size_t capacity)
      : buffer(capacity), capacity(capacity), head(0), tail(0), full(false) {}

  bool push(const T &item) {
    std::lock_guard<std::mutex> lock(mtx);
    if (full) {
      return false;
    }

    buffer[tail] = item;
    tail = (tail + 1) % capacity;
    full = (tail == head);
    return true;
  }

  std::optional<T> pop() {
    std::lock_guard<std::mutex> lock(mtx);

    if (empty_unlocked()) {
      return std::nullopt;
    }

    T item = buffer[head];
    full = false;
    head = (head + 1) % capacity;

    return item;
  }

  bool empty() const {
    std::lock_guard<std::mutex> lock(mtx);
    return empty_unlocked();
  }

  bool _full() const {
    std::lock_guard<std::mutex> lock(mtx);
    return full;
  }

  size_t size() const {
    std::lock_guard<std::mutex> lock(mtx);

    if (full) {
      return capacity;
    }

    if (tail >= head) {
      return tail - head;
    }

    return capacity + tail - head;
  }
};

#endif
