#![cfg(feature = "alloc")]

pub mod rb;
pub mod vmem_helper;

use core::ops::Index;

use crate::iterators::{ConsIter, ProdIter, WorkIter};
use crate::ring_buffer::storage::{MRBIndex, Storage};
use crate::{MutRB, UnsafeSyncCell};
use alloc::boxed::Box;
use alloc::vec::Vec;

/// Heap-allocated storage.
pub struct HeapStorage<T> {
    inner: *mut UnsafeSyncCell<T>,
    len: usize,
}

impl<T> Drop for HeapStorage<T> {
    fn drop(&mut self) {
        unsafe {
            #[cfg(feature = "vmem")]
            {
                let p = core::ptr::slice_from_raw_parts_mut(self.inner, self.len);
                core::ptr::drop_in_place(p);

                let size = self.len * size_of::<T>();

                // glibc manual says that it is fine to unmap two mapped blocks at the same
                // time. Nevertheless, I don't trust such a guarantee, as everything can change
                // at any time, so the blocks get unmapped one after the other.
                libc::munmap(self.inner.byte_add(size) as _, size);
                libc::munmap(self.inner as _, size);
            }

            #[cfg(not(feature = "vmem"))]
            let _ = Box::from_raw(core::ptr::slice_from_raw_parts_mut(self.inner, self.len));
        }
    }
}

impl<T> HeapStorage<T> {
    #[cfg(feature = "vmem")]
    fn new(value: Box<[UnsafeSyncCell<T>]>) -> Self {
        let r = vmem_helper::new(&value);

        Self {
            inner: r,
            len: value.len(),
        }
    }

    #[cfg(not(feature = "vmem"))]
    fn new(value: Box<[UnsafeSyncCell<T>]>) -> Self {
        let len = value.len();

        let v = Box::into_raw(value);

        unsafe {
            Self {
                inner: (*v).as_mut_ptr(),
                len,
            }
        }
    }
}

impl<T> From<Box<[T]>> for HeapStorage<T> {
    fn from(value: Box<[T]>) -> Self {
        Self::new(unsafe { core::mem::transmute::<Box<[T]>, Box<[UnsafeSyncCell<T>]>>(value) })
    }
}

impl<T> From<Box<[UnsafeSyncCell<T>]>> for HeapStorage<T> {
    fn from(value: Box<[UnsafeSyncCell<T>]>) -> Self {
        Self::new(value)
    }
}

impl<T> From<Vec<T>> for HeapStorage<T> {
    fn from(value: Vec<T>) -> Self {
        Self::from(value.into_boxed_slice())
    }
}

impl<T> From<Vec<UnsafeSyncCell<T>>> for HeapStorage<T> {
    fn from(value: Vec<UnsafeSyncCell<T>>) -> Self {
        Self::new(value.into_boxed_slice())
    }
}

impl<T> Index<usize> for HeapStorage<T> {
    type Output = UnsafeSyncCell<T>;

    #[inline]
    fn index(&self, index: usize) -> &Self::Output {
        assert!(
            index < self.len,
            "index out of bounds: the len is {} but the index is {index}",
            self.len
        );
        unsafe { &*self.inner.add(index) }
    }
}

impl<T> MRBIndex<usize> for HeapStorage<T> {
    type Output = UnsafeSyncCell<T>;

    #[inline]
    fn _index(&self, index: usize) -> &Self::Output {
        unsafe { &*self.inner.add(index) }
    }
}

impl<T> Storage for HeapStorage<T> {
    type Item = T;

    #[inline]
    fn as_ptr(&self) -> *const UnsafeSyncCell<Self::Item> {
        self.inner as _
    }

    #[inline]
    fn as_mut_ptr(&mut self) -> *mut UnsafeSyncCell<Self::Item> {
        self.inner
    }

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

/// Trait needed to call `split` method on a heap-allocated buffer.
#[cfg(feature = "alloc")]
pub trait HeapSplit<B: MutRB> {
    /// Consumes the buffer, yielding two iterators. See:
    /// - [`ProdIter`];
    /// - [`ConsIter`].
    fn split<'buf>(self) -> (ProdIter<'buf, B>, ConsIter<'buf, B, false>);

    /// Consumes the buffer, yielding three iterators. See:
    /// - [`ProdIter`];
    /// - [`WorkIter`];
    /// - [`ConsIter`].
    fn split_mut<'buf>(
        self,
    ) -> (
        ProdIter<'buf, B>,
        WorkIter<'buf, B>,
        ConsIter<'buf, B, true>,
    );
}

pub mod test {
    #[test]
    fn from_tests() {
        use crate::{HeapStorage, UnsafeSyncCell};
        use alloc::vec;

        #[cfg(feature = "vmem")]
        let buf_len = crate::vmem_helper::page_size();
        #[cfg(not(feature = "vmem"))]
        let buf_len = 100;

        let _ = HeapStorage::from(vec![0; buf_len]);
        let _ = HeapStorage::from(vec![0; buf_len].into_boxed_slice());
        let _: HeapStorage<i32> = HeapStorage::from(vec![UnsafeSyncCell::new(0i32); buf_len]);
        let _: HeapStorage<i32> =
            HeapStorage::from(vec![UnsafeSyncCell::new(0i32); buf_len].into_boxed_slice());
    }
}
