/* A compile-time-fixed-size queue implementation. */

use core::mem::{self, MaybeUninit};

pub struct Queue<T, const N: usize> {
    push_idx: usize,
    pop_idx: usize,
    entries: usize,
    store: [MaybeUninit<T>; N],
}

impl<T, const N: usize> Queue<T, N> {
    const INIT: MaybeUninit<T> = MaybeUninit::uninit();
    pub const fn new() -> Self {
        Queue {
            push_idx: 0,
            pop_idx: 0,
            entries: 0,
            store: [Self::INIT; N],
        }
    }

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

    pub fn is_empty(&self) -> bool {
        self.entries == 0
    }

    pub fn is_full(&self) -> bool {
        self.entries == N
    }

    pub fn push(&mut self, val: T) -> Result<(), T> {
        if self.is_full() {
            Err(val)
        } else {
            self.store[self.push_idx] = MaybeUninit::new(val);
            self.push_idx = (self.push_idx + 1) % N;
            self.entries += 1;

            Ok(())
        }
    }

    pub fn pop(&mut self) -> Option<T> {
        if self.is_empty() {
            None
        } else {
            let val = mem::replace(&mut self.store[self.pop_idx], MaybeUninit::uninit());
            self.pop_idx = (self.pop_idx + 1) % N;
            self.entries -= 1;
            // safety: we've validated that the queue is not empty, so this item is initialized.
            Some(unsafe { val.assume_init() })
        }
    }
}

impl<T, const N: usize> Drop for Queue<T, N> {
    fn drop(&mut self) {
        while let Some(_) = self.pop() {}
    }
}

pub struct Iter<'a, T, const N: usize> {
    queue: &'a Queue<T, N>,
    idx: usize,
}

pub struct IterMut<'a, T, const N: usize> {
    queue: &'a mut Queue<T, N>,
    idx: usize,
}

impl<T, const N: usize> Queue<T, N> {
    pub fn iter(&self) -> Iter<T, N> {
        Iter {
            queue: self,
            idx: 0,
        }
    }

    pub fn iter_mut(&mut self) -> IterMut<T, N> {
        IterMut {
            queue: self,
            idx: 0,
        }
    }
}

impl<'a, T, const N: usize> Iterator for Iter<'a, T, N> {
    type Item = &'a T;

    fn next(&mut self) -> Option<Self::Item> {
        if self.idx == self.queue.entries {
            None
        } else {
            let idx = (self.queue.pop_idx + self.idx) % N;
            let val = unsafe { self.queue.store[idx].assume_init_ref() };
            self.idx += 1;
            Some(val)
        }
    }
}

impl<'a, T, const N: usize> Iterator for IterMut<'a, T, N> {
    type Item = &'a mut T;

    fn next<'b>(&'b mut self) -> Option<&'a mut T> {
        if self.idx == self.queue.entries {
            None
        } else {
            let idx = (self.queue.pop_idx + self.idx) % N;
            /* As to why we have to do lifetime laundering here, see
             * https://users.rust-lang.org/t/itermut-and-lifetime-issue/14117/5
             */
            let elem = unsafe { &mut *(self.queue.store[idx].assume_init_mut() as *mut T) };
            self.idx += 1;
            Some(elem)
        }
    }
}
