/*
 * Beat Rat -- lock-free SPSC ring buffer
 *
 * This is the bridge between the real-time audio thread and everything
 * slower (disk I/O, eventually the UI). Exactly one thread may push,
 * exactly one thread may pop -- that constraint is what lets this be
 * lock-free at all. Never call rb_push from more than one thread, or
 * rb_pop from more than one thread, concurrently.
 *
 * head/tail are ever-increasing counters (not wrapped to buffer size);
 * the buffer index is (counter % capacity). This avoids the classic
 * "one slot wasted" ambiguity between empty and full.
 */

#include "ring_buffer.h"

#include <stdlib.h>

int rb_init(RingBuffer *rb, size_t capacity) {
    rb->buf = malloc(capacity * sizeof(int16_t));
    if (!rb->buf)
        return -1;
    rb->capacity = capacity;
    atomic_init(&rb->head, 0);
    atomic_init(&rb->tail, 0);
    atomic_init(&rb->producer_finished, false);
    return 0;
}

void rb_free(RingBuffer *rb) {
    free(rb->buf);
    rb->buf = NULL;
    rb->capacity = 0;
}

size_t rb_push(RingBuffer *rb, const int16_t *data, size_t count) {
    size_t head = atomic_load_explicit(&rb->head, memory_order_relaxed);
    size_t tail = atomic_load_explicit(&rb->tail, memory_order_acquire);
    size_t used = head - tail;
    size_t free_space = rb->capacity - used;
    size_t n = count < free_space ? count : free_space;

    for (size_t i = 0; i < n; i++)
        rb->buf[(head + i) % rb->capacity] = data[i];

    atomic_store_explicit(&rb->head, head + n, memory_order_release);
    return n;
}

size_t rb_pop(RingBuffer *rb, int16_t *data, size_t count) {
    size_t tail = atomic_load_explicit(&rb->tail, memory_order_relaxed);
    size_t head = atomic_load_explicit(&rb->head, memory_order_acquire);
    size_t available = head - tail;
    size_t n = count < available ? count : available;

    for (size_t i = 0; i < n; i++)
        data[i] = rb->buf[(tail + i) % rb->capacity];

    atomic_store_explicit(&rb->tail, tail + n, memory_order_release);
    return n;
}

size_t rb_available_to_read(const RingBuffer *rb) {
    size_t head = atomic_load_explicit((atomic_size_t *)&rb->head, memory_order_acquire);
    size_t tail = atomic_load_explicit((atomic_size_t *)&rb->tail, memory_order_acquire);
    return head - tail;
}

size_t rb_available_to_write(const RingBuffer *rb) {
    return rb->capacity - rb_available_to_read(rb);
}
