#pragma once

#include <atomic>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <stdexcept>
#include <vector>

/**
 * Lock-free Single-Producer / Single-Consumer ring buffer.
 *
 * The writer (ingest thread) calls write(); the reader (render thread)
 * calls read_available() and consume(). Both pointers advance monotonically.
 * Capacity is rounded to the next power of two so indices wrap via bitmask.
 *
 * @tparam T  Sample type (float, int16_t, etc.)
 */
template <typename T>
class SPSCRingBuffer {
public:
    SPSCRingBuffer() = default;

    /**
     * @param capacity  Requested number of elements. Rounded up to power of two.
     */
    explicit SPSCRingBuffer(size_t capacity)
        : mask_(next_pow2(capacity) - 1)
        , buf_(mask_ + 1)
    {
        if (capacity == 0) throw std::invalid_argument("capacity must be > 0");
    }

    // ── Writer interface (single thread) ──────────────────────────

    /** Write one sample. Returns false on overflow (overwrites silently). */
    bool write(T sample) noexcept {
        const size_t head = head_.load(std::memory_order_relaxed);
        const size_t tail = tail_.load(std::memory_order_acquire);
        const size_t idx  = head & mask_;
        buf_[idx] = sample;
        if (head - tail >= capacity()) {
            overflow_++;
            tail_.store(tail + 1, std::memory_order_release); // advance tail
        }
        head_.store(head + 1, std::memory_order_release);
        return true;
    }

    /** Bulk-write n samples. Faster than n individual write() calls. */
    size_t write(const T* src, size_t n) noexcept {
        size_t written = 0;
        const size_t cap = capacity();
        const size_t head = head_.load(std::memory_order_relaxed);
        const size_t tail = tail_.load(std::memory_order_acquire);
        const size_t avail = cap - (head - tail);

        if (n > avail) {
            overflow_ += (n - avail);
            tail_.store(tail + (n - avail), std::memory_order_release);
        }

        size_t head_idx = head & mask_;
        const size_t to_end = cap - head_idx;
        const size_t first = (n < to_end) ? n : to_end;
        std::memcpy(&buf_[head_idx], src, first * sizeof(T));
        written += first;
        if (n > to_end) {
            std::memcpy(&buf_[0], src + first, (n - first) * sizeof(T));
            written += (n - first);
        }
        head_.store(head + written, std::memory_order_release);
        return written;
    }

    // ── Reader interface (single thread) ──────────────────────────

    /** Number of unread samples. */
    size_t read_available() const noexcept {
        return head_.load(std::memory_order_acquire) -
               tail_.load(std::memory_order_relaxed);
    }

    /**
     * Copy up to dst_cap samples into dst, starting at offset from the oldest
     * unread sample. Returns number of samples copied.
     */
    size_t read_range(T* dst, size_t dst_cap, size_t offset) const noexcept {
        const size_t tail = tail_.load(std::memory_order_acquire);
        const size_t head = head_.load(std::memory_order_relaxed);
        const size_t total = head - tail;
        if (offset >= total || dst_cap == 0) return 0;

        const size_t count = std::min(dst_cap, total - offset);
        const size_t start_idx = (tail + offset) & mask_;
        const size_t cap = capacity();
        const size_t to_end = cap - start_idx;
        const size_t first = (count < to_end) ? count : to_end;

        std::memcpy(dst, &buf_[start_idx], first * sizeof(T));
        if (count > to_end) {
            std::memcpy(dst + first, &buf_[0], (count - first) * sizeof(T));
        }
        return count;
    }

    /** Consume n oldest samples (advance tail). */
    void consume(size_t n) noexcept {
        tail_.store(tail_.load(std::memory_order_relaxed) + n,
                    std::memory_order_release);
    }

    // ── Queries ───────────────────────────────────────────────────

    size_t capacity() const noexcept { return mask_ + 1; }
    uint64_t overflow_count() const noexcept { return overflow_; }
    void reset_overflow() noexcept { overflow_ = 0; }

private:
    static size_t next_pow2(size_t v) noexcept {
        if (v == 0) return 1;
        v--;
        v |= v >> 1;  v |= v >> 2;  v |= v >> 4;
        v |= v >> 8;  v |= v >> 16; v |= v >> 32;
        return v + 1;
    }

    size_t              mask_ = 0;
    std::vector<T>      buf_;
    std::atomic<size_t> head_{0};
    std::atomic<size_t> tail_{0};
    uint64_t            overflow_ = 0;
};
