use std::io;

pub(super) struct OutputQueue<const CAP: usize> {
    buffer: [u8; CAP],
    start: usize,
    len: usize,
}

impl<const CAP: usize> OutputQueue<CAP> {
    pub(super) const fn new() -> Self {
        Self {
            buffer: [0; CAP],
            start: 0,
            len: 0,
        }
    }

    pub(super) const fn is_empty(&self) -> bool {
        self.len == 0
    }

    pub(super) const fn len(&self) -> usize {
        self.len
    }

    pub(super) const fn capacity(&self) -> usize {
        self.len + self.available_capacity()
    }

    pub(super) fn push_slice(&mut self, input: &[u8]) -> io::Result<()> {
        if input.len() > self.available_capacity() {
            return Err(io::Error::other(
                "base64 stream output queue capacity exceeded",
            ));
        }

        let mut read = 0;
        while read < input.len() {
            let write = (self.start + self.len) % CAP;
            self.buffer[write] = input[read];
            self.len += 1;
            read += 1;
        }

        Ok(())
    }

    pub(super) fn copy_front(&self, output: &mut [u8]) -> usize {
        let count = core::cmp::min(self.len, output.len());
        let first = core::cmp::min(count, CAP - self.start);
        output[..first].copy_from_slice(&self.buffer[self.start..self.start + first]);

        let second = count - first;
        if second > 0 {
            output[first..first + second].copy_from_slice(&self.buffer[..second]);
        }

        count
    }

    pub(super) fn discard_front(&mut self, count: usize) {
        let count = core::cmp::min(count, self.len);
        let first = core::cmp::min(count, CAP - self.start);
        crate::wipe_bytes(&mut self.buffer[self.start..self.start + first]);

        let second = count - first;
        if second > 0 {
            crate::wipe_bytes(&mut self.buffer[..second]);
        }

        self.start = (self.start + count) % CAP;
        self.len -= count;
        if self.len == 0 {
            self.start = 0;
        }
    }

    pub(super) fn pop_slice(&mut self, output: &mut [u8]) -> usize {
        let count = self.copy_front(output);
        self.discard_front(count);
        count
    }

    pub(super) fn clear_all(&mut self) {
        crate::wipe_bytes(&mut self.buffer);
        self.start = 0;
        self.len = 0;
    }

    pub(super) const fn available_capacity(&self) -> usize {
        CAP - self.len
    }
}
