use magic_buffer::MagicBuffer;
use std::cell::Cell;

/// Buffer is a FIFO queue for bytes, built on a ring buffer.
/// It always provides contiguous slices for both the readable and writable parts,
/// using an underlying buffer that is "mirrored" in virtual memory.
pub struct Buffer {
    buffer: MagicBuffer,
    /// first readable byte
    head: Cell<usize>,
    /// first writable byte
    tail: usize,
}

impl Buffer {
    /// Allocate a fresh buffer, with the specified capacity.
    /// The buffer can contain at most `capacity - 1` bytes.
    /// The capacity must be a power of two, and at least [Buffer::min_len].
    pub fn new(capacity: usize) -> Buffer {
        Buffer {
            // MagicBuffer::new verifies that `capacity` is a power of two,
            // and at least MagicBuffer::min_len().
            buffer: MagicBuffer::new(capacity).unwrap(),
            // `head == tail` means the buffer is empty.
            // In order to ensure that this remains unambiguous,
            // the buffer can only be filled with capacity-1 bytes.
            head: Cell::new(0),
            tail: 0,
        }
    }

    /// Returns the minimum buffer capacity.
    /// This depends on the operating system and architecture.
    pub fn min_capacity() -> usize {
        MagicBuffer::min_len()
    }

    /// Return the capacity of the buffer.
    /// This is equal to `self.data().len() + self.space().len() + 1`.
    pub fn capacity(&self) -> usize {
        self.buffer.len()
    }

    /// Return the valid, readable data in the buffer.
    pub fn data(&self) -> &[u8] {
        let len = self.buffer.len();
        let head = self.head.get();

        if head <= self.tail {
            &self.buffer[head..self.tail]
        } else {
            &self.buffer[head..self.tail + len]
        }
    }

    /// Mark `read_len` bytes of the readable data as consumed, freeing the space.
    pub fn consume(&self, read_len: usize) {
        debug_assert!(read_len <= self.data().len());
        let mut head = self.head.get();
        head += read_len;
        head &= self.buffer.len() - 1;
        self.head.set(head);
    }

    /// Return the empty, writable space in the buffer.
    pub fn space(&mut self) -> &mut [u8] {
        let len = self.buffer.len();
        let head = self.head.get();

        if head <= self.tail {
            &mut self.buffer[self.tail..head + len - 1]
        } else {
            &mut self.buffer[self.tail..head - 1]
        }
    }

    /// Mark `written_len` bytes of the writable space as valid, readable data.
    pub fn commit(&mut self, written_len: usize) {
        debug_assert!(written_len <= self.space().len());
        self.tail += written_len;
        self.tail &= self.buffer.len() - 1;
    }
}
