use core::mem::size_of;
pub struct Stack<T: Copy> {
    top: *mut T,
    cur: *mut T,
    bot: *mut T,
}
#[derive(Debug, PartialEq)]
pub enum StackError {
    StackEmpty,
    StackFull,
    OverwriteInvalid,
}
impl<T: Copy> Stack<T> {
    pub fn new(bottom: *mut T, items: usize) -> Self {
        let top = bottom.wrapping_add(items);
        debug_assert!(top >= bottom);
        Self {
            top,
            bot: bottom,
            cur: top,
        }
    }
    #[inline]
    pub fn push(&mut self, item: T) -> Result<(), StackError> {
        let next_cur = self.cur.wrapping_sub(1);
        if next_cur < self.bot {
            return Err(StackError::StackFull);
        }
        self.cur = next_cur;
        unsafe {
            self.cur.write(item);
        }
        Ok(())
    }
    #[inline]
    pub fn depth(&self) -> usize {
        ((self.top as usize) - (self.cur as usize)) / size_of::<T>()
    }
    #[inline]
    pub fn try_pop(&mut self) -> Result<T, StackError> {
        match self.pop() {
            Some(v) => Ok(v),
            None => Err(StackError::StackEmpty),
        }
    }
    #[inline]
    pub fn pop(&mut self) -> Option<T> {
        let next_cur = self.cur.wrapping_add(1);
        if next_cur > self.top {
            return None;
        }
        let val = unsafe { self.cur.read() };
        self.cur = next_cur;
        Some(val)
    }
    #[inline]
    pub fn try_peek(&self) -> Result<T, StackError> {
        if self.cur == self.top {
            Err(StackError::StackEmpty)
        } else {
            Ok(unsafe { self.cur.read() })
        }
    }
    #[inline]
    pub fn peek(&self) -> Option<T> {
        if self.cur == self.top {
            None
        } else {
            Some(unsafe { self.cur.read() })
        }
    }
    #[inline]
    pub fn peek_mut(&mut self) -> Option<&mut T> {
        if self.cur == self.top {
            None
        } else {
            Some(unsafe { &mut *self.cur })
        }
    }
    #[inline]
    pub fn peek_back_n(&self, n: usize) -> Option<T> {
        let request = self.cur.wrapping_add(n);
        if request >= self.top {
            None
        } else {
            unsafe { Some(request.read()) }
        }
    }
    #[inline]
    pub fn try_peek_back_n(&self, n: usize) -> Result<T, StackError> {
        let request = self.cur.wrapping_add(n);
        if request >= self.top {
            Err(StackError::StackEmpty)
        } else {
            unsafe { Ok(request.read()) }
        }
    }
    #[inline]
    pub fn peek_back_n_mut(&mut self, n: usize) -> Option<&mut T> {
        let request = self.cur.wrapping_add(n);
        if request >= self.top {
            None
        } else {
            unsafe { Some(&mut *request) }
        }
    }
    #[inline]
    pub fn try_peek_back_n_mut(&mut self, n: usize) -> Result<&mut T, StackError> {
        let request = self.cur.wrapping_add(n);
        if request >= self.top {
            Err(StackError::StackEmpty)
        } else {
            unsafe { Ok(&mut *request) }
        }
    }
    #[inline]
    pub fn overwrite_back_n(&mut self, n: usize, item: T) -> Result<(), StackError> {
        let request = self.cur.wrapping_add(n);
        if request >= self.top {
            Err(StackError::OverwriteInvalid)
        } else {
            unsafe {
                request.write(item);
            }
            Ok(())
        }
    }
    #[inline]
    pub fn clear(&mut self) {
        self.cur = self.top;
    }
    #[inline]
    pub fn is_empty(&self) -> bool {
        self.cur == self.top
    }
}
#[cfg(test)]
pub mod test {
    use super::Stack;
    use crate::leakbox::LeakBox;
    use crate::Word;
    #[test]
    fn stack() {
        const ITEMS: usize = 16;
        let payload: LeakBox<Word> = LeakBox::new(ITEMS);
        let mut stack = Stack::<Word>::new(payload.ptr(), payload.len());
        for _ in 0..3 {
            for i in 0..(ITEMS as i32) {
                assert!(stack.push(Word::data(i)).is_ok());
            }
            assert!(stack.push(Word::data(100)).is_err());
            for i in (0..(ITEMS as i32)).rev() {
                assert_eq!(stack.pop().unwrap().into_data(), i);
            }
            assert!(stack.pop().is_none());
        }
    }
}