use std::{mem::MaybeUninit, ptr::null};

use monoio::buf::{IoBuf, IoBufMut};

/// RawBuf is not a real buf. It only hold the pointer of the buffer.
/// Users must make sure the buffer behind the pointer is always valid.
/// Which means, user must:
/// 1. await the future with RawBuf Ready before drop the real buffer
/// 2. make sure the pointer and length is valid before the future Ready
pub(crate) struct RawBuf {
    ptr: *const u8,
    len: usize,
}

impl RawBuf {
    pub(crate) fn uninit() -> Self {
        Self {
            ptr: null(),
            len: 0,
        }
    }

    pub(crate) fn new(ptr: *const u8, len: usize) -> Self {
        Self { ptr, len }
    }
}

unsafe impl IoBuf for RawBuf {
    fn read_ptr(&self) -> *const u8 {
        self.ptr
    }

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

unsafe impl IoBufMut for RawBuf {
    fn write_ptr(&mut self) -> *mut u8 {
        self.ptr as *mut u8
    }

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

    unsafe fn set_init(&mut self, _pos: usize) {}
}

/// Buf is a real buffer with data. It is used by "safe" TcpStreamCompat.
/// Read: If there is some data inside buf, copy it directly and return.
///       Otherwise, do async read and save the future. When the future
///       finished, init and offset will be reset.
/// Write: Copy data into our buffer if we not hold it. After that, we
///        will create future, save it and poll it. If we hold data,
///        we may check the buffer ptr and length(because user must make
///        sure it's the same slice). The saved future will be polled.
pub(crate) struct Buf {
    data: Box<[MaybeUninit<u8>]>,
    offset: usize,
    init: usize,
    capacity: usize,
}

unsafe impl IoBuf for Buf {
    fn read_ptr(&self) -> *const u8 {
        unsafe { self.data.as_ptr().add(self.offset) as *const u8 }
    }

    fn bytes_init(&self) -> usize {
        self.init - self.offset
    }
}

unsafe impl IoBufMut for Buf {
    fn write_ptr(&mut self) -> *mut u8 {
        self.data.as_ptr() as *mut u8
    }

    fn bytes_total(&mut self) -> usize {
        self.capacity
    }

    unsafe fn set_init(&mut self, init: usize) {
        self.offset = 0;
        self.init = init;
    }
}

impl Buf {
    pub(crate) fn new(size: usize) -> Self {
        let mut buf = Vec::with_capacity(size);
        unsafe { buf.set_len(size) };
        let data = buf.into_boxed_slice();
        Self {
            data,
            offset: 0,
            init: 0,
            capacity: size,
        }
    }

    pub(crate) fn uninit() -> Self {
        let buf = Vec::new();
        let data = buf.into_boxed_slice();
        Self {
            data,
            offset: 0,
            init: 0,
            capacity: 0,
        }
    }

    pub(crate) fn is_empty(&self) -> bool {
        self.offset == self.init
    }

    /// Return slice for copying data from Buf to user space.
    pub(crate) fn buf_to_read(&self, max: usize) -> &[u8] {
        let len = max.min(self.init - self.offset);
        unsafe { std::slice::from_raw_parts(self.read_ptr(), len) }
    }

    /// Advance offset.
    /// # Safety: User must ensure the cursor position after advancing is initialized.
    pub(crate) unsafe fn advance_offset(&mut self, len: usize) {
        self.offset += len;
        debug_assert!(self.offset <= self.init);
    }

    /// Return slice for copying data from user space to Buf.
    #[allow(clippy::mut_from_ref)]
    pub(crate) fn buf_to_write(&mut self) -> &mut [u8] {
        unsafe { std::slice::from_raw_parts_mut(self.write_ptr(), self.bytes_total()) }
    }
}

#[cfg(test)]
mod tests {
    use monoio::buf::IoBufMut;

    use super::Buf;

    #[test]
    fn test_buf_to_rw() {
        let mut buf = Buf::new(20);
        // write 0..10
        {
            let filled = buf.buf_to_write();
            for i in 0..10u8 {
                filled[i as usize] = i;
            }
            unsafe {
                buf.set_init(10);
            }
        }

        // read 0..5
        {
            let part = buf.buf_to_read(5);
            for i in 0..5u8 {
                assert_eq!(part[i as usize], i);
            }
            unsafe {
                buf.advance_offset(5);
            }
        }

        // read 5..10
        {
            let part = buf.buf_to_read(5);
            for i in 5..10u8 {
                assert_eq!(part[i as usize - 5], i);
            }
            unsafe {
                buf.advance_offset(5);
            }
        }

        assert!(buf.is_empty());
    }
}
