use core::cell::UnsafeCell;

use super::Snapshot;
use crate::Error;

pub(super) struct ReadBufferInner {
    pub(super) pos: usize,
    pub(super) cap: usize,
}

pub(crate) struct ReadBufferBorrowed<'de> {
    data: &'de [u8],
    inner: *mut ReadBufferInner,
}

impl<'de> ReadBufferBorrowed<'de> {
    #[inline(always)]
    fn inner(&self) -> &ReadBufferInner {
        unsafe { &*self.inner }
    }

    #[inline(always)]
    fn inner_mut(&self) -> &mut ReadBufferInner {
        unsafe { &mut *self.inner }
    }

    #[inline(always)]
    pub(crate) fn len(&self) -> usize {
        self.inner().cap
    }

    #[inline(always)]
    pub(crate) fn remaining(&self) -> &'de [u8] {
        let inner = self.inner();

        // Safety: The following invariants are held:
        //         - inner.pos <= inner.cap (incrementing pos is protected by length checks)
        //         - inner.cap <= self.data.len() (guaranteed by how read buffers are created)
        unsafe { self.data.get_unchecked(inner.pos..inner.cap) }
    }

    #[inline(always)]
    pub(crate) fn consumed(&self) -> usize {
        self.inner().pos
    }

    pub(crate) fn read<R, F: FnOnce(&[u8]) -> R>(&self, len: usize, f: F) -> Result<R, Error> {
        let inner = self.inner_mut();

        let last = inner.pos + len;
        if last > inner.cap {
            return Err(Error::OutOfMemory);
        }

        // Safety: The following invariants are held:
        //         - inner.pos <= last <= inner.cap (checked above)
        //         - inner.cap <= self.data.len() (guaranteed by how read buffers are created)
        let buf = unsafe { self.data.get_unchecked(inner.pos..last) };
        let res = f(buf);

        inner.pos += len;
        Ok(res)
    }

    pub(crate) fn read_and_borrow(&self, len: usize) -> Result<&'de [u8], Error> {
        let inner = self.inner_mut();

        let last = inner.pos + len;
        if last > inner.cap {
            return Err(Error::OutOfMemory);
        }

        // Safety: The following invariants are held:
        //         - inner.pos <= last <= inner.cap (checked above)
        //         - inner.cap <= self.data.len() (guaranteed by how read buffers are created)
        let borrowed = unsafe { self.data.get_unchecked(inner.pos..last) };

        inner.pos += len;
        Ok(borrowed)
    }

    pub(crate) fn read_into_slice(&self, buf: &mut [u8]) -> Result<(), Error> {
        let inner = self.inner_mut();

        let last = inner.pos + buf.len();
        if last > inner.cap {
            return Err(Error::OutOfMemory);
        }

        // Safety: The following invariants are held:
        //         - inner.pos <= last <= inner.cap (checked above)
        //         - inner.cap <= self.data.len() (guaranteed by how read buffers are created)
        unsafe {
            let data = self.data.as_ptr().add(inner.pos);
            data.copy_to_nonoverlapping(buf.as_mut_ptr(), buf.len());
        };

        inner.pos = last;
        Ok(())
    }

    pub(crate) fn peek<R, F: FnOnce(&[u8]) -> R>(&self, len: usize, f: F) -> Result<R, Error> {
        let inner = self.inner();

        let last = inner.pos + len;
        if last > inner.cap {
            return Err(Error::OutOfMemory);
        }

        // Safety: The following invariants are held:
        //         - inner.pos <= last <= inner.cap (checked above)
        //         - inner.cap <= self.data.len() (guaranteed by how read buffers are created)
        let buf = unsafe { self.data.get_unchecked(inner.pos..last) };
        let res = f(buf);

        Ok(res)
    }

    pub(crate) fn discard(&self, count: usize) -> Result<(), Error> {
        let inner = self.inner_mut();

        let last = inner.pos + count;
        if last > inner.cap {
            return Err(Error::OutOfMemory);
        }

        inner.pos = last;
        Ok(())
    }

    pub(crate) fn snapshot(&self) -> Snapshot {
        let inner = self.inner();
        Snapshot(inner.pos)
    }

    pub(crate) fn restore(&self, snapshot: Snapshot) {
        let inner = self.inner_mut();

        if snapshot.0 > inner.pos {
            panic!()
        }

        inner.pos = snapshot.0;
    }
}

/// A read buffer with cursor, peaking, borrowing and last read undo.
pub struct ReadBuffer<'de> {
    pub(super) data: &'de [u8],
    pub(super) inner: UnsafeCell<ReadBufferInner>,
}

impl<'de> ReadBuffer<'de> {
    /// Construct a new [`ReadBuffer`] from borrowed data.
    ///
    /// # Examples
    /// ```
    /// use petra::ReadBuffer;
    ///
    /// let boxed: Box<[u8]> = Box::new([0u8; 1024]);
    /// let buffer = ReadBuffer::from(&boxed);
    ///
    /// let array = [0u8; 1024];
    /// let buffer = ReadBuffer::from(&array);
    /// ```
    pub fn from<T: AsRef<[u8]>>(data: &'de T) -> Self {
        Self {
            data: data.as_ref(),
            inner: UnsafeCell::new(ReadBufferInner {
                pos: 0,
                cap: data.as_ref().len(),
            }),
        }
    }

    // Safety: The borrow method is only used behind public interface methods which enforce strict borrowing rules.
    //         The data borrowed here is never mutated when the caller borrows &self immutably.
    //         Mutating the data happens only when the caller borrows &mut self mutably.
    pub(crate) fn borrow(&self) -> ReadBufferBorrowed<'de> {
        ReadBufferBorrowed {
            data: self.data,
            inner: self.inner.get(),
        }
    }

    /// Return the number of bytes allocated into the buffer, which may be read.
    ///
    /// If the buffer was constructed from a [`WriteBuffer`](crate::WriteBuffer), this method returns the number of written bytes, regardless of the allocated capacity of the underlying data.
    ///
    /// # Examples
    /// ```
    /// use petra::{ReadBuffer, WriteBuffer};
    ///
    /// let buffer = ReadBuffer::from(&[0u8; 1024]);
    /// assert_eq!(buffer.len(), 1024);
    ///
    /// let mut data = [0u8; 1024];
    /// let mut buffer = WriteBuffer::from(&mut data);
    /// buffer.write(2, |buf| buf.copy_from_slice(b"ok"));
    ///
    /// let buffer = buffer.into_read_buffer();
    /// assert_eq!(buffer.len(), 2);
    /// ```
    #[inline(always)]
    pub fn len(&self) -> usize {
        self.borrow().len()
    }

    /// Return a slice into the remaining, not-yet-read portion of the buffer.
    ///
    /// # Examples
    /// ```
    /// use petra::ReadBuffer;
    ///
    /// let mut buffer = ReadBuffer::from(&[1, 2, 3, 4, 5]);
    /// assert_eq!(buffer.remaining(), &[1, 2, 3, 4, 5]);
    ///
    /// buffer.read(3, |_| {});
    /// assert_eq!(buffer.remaining(), &[4, 5]);
    ///
    /// buffer.read(2, |_| {});
    /// assert_eq!(buffer.remaining(), &[]);
    /// ```
    #[inline(always)]
    pub fn remaining(&self) -> &'de [u8] {
        self.borrow().remaining()
    }

    /// Return the number of bytes already read and consumed from the buffer.
    ///
    /// # Examples
    /// ```
    /// use petra::ReadBuffer;
    ///
    /// let mut buffer = ReadBuffer::from(&[1, 2, 3, 4, 5]);
    /// assert_eq!(buffer.consumed(), 0);
    ///
    /// buffer.read(3, |_| {});
    /// assert_eq!(buffer.consumed(), 3);
    ///
    /// buffer.read(2, |_| {});
    /// assert_eq!(buffer.consumed(), 5);
    /// ```
    #[inline(always)]
    pub fn consumed(&self) -> usize {
        self.borrow().consumed()
    }

    /// Read the next `len` bytes of the buffer and advance the read cursor.
    ///
    /// The closure `f` receives a slice into the buffer, of length `len` and starting at the current read head position.
    /// After the closure execution, the cursor is advanced.
    ///
    /// # Errors
    /// Before executing the closure, a check is made if there are enough available bytes in the buffer for reading.
    /// In case there are not enough bytes available, [`OutOfMemory`](Error::OutOfMemory) is returned.
    ///
    /// # Examples
    /// ```
    /// use petra::{Error, ReadBuffer};
    ///
    /// let mut buffer = ReadBuffer::from(&[1, 2, 3, 4, 5]);
    /// assert_eq!(buffer.read(2, |buf| buf[1]), Ok(2));
    /// assert_eq!(buffer.read(3, |buf| buf[1]), Ok(4));
    ///
    /// // At this point the entire buffer was read.
    /// assert_eq!(buffer.consumed(), 5);
    ///
    /// // No more bytes available for reading, the closure is not executed.
    /// assert_eq!(buffer.read(1, |_| assert!(false)), Err(Error::OutOfMemory));
    /// ```
    #[inline(always)]
    pub fn read<R, F: FnOnce(&[u8]) -> R>(&mut self, len: usize, f: F) -> Result<R, Error> {
        self.borrow().read(len, f)
    }

    /// Read the next 'len` bytes of the buffer and borrow a slice into them.
    ///
    /// # Errors
    /// In case there are not enough bytes available, [`OutOfMemory`](Error::OutOfMemory) is returned.
    ///
    /// # Examples
    /// ```
    /// use petra::{Error, ReadBuffer};
    ///
    /// let mut buffer = ReadBuffer::from(&[1, 2, 3, 4, 5]);
    /// assert_eq!(buffer.read_and_borrow(2), Ok(&[1u8, 2] as _));
    /// assert_eq!(buffer.read_and_borrow(3), Ok(&[3u8, 4, 5] as _));
    ///
    /// // At this point the entire buffer was read.
    /// assert_eq!(buffer.consumed(), 5);
    ///
    /// // No more bytes available for reading.
    /// assert_eq!(buffer.read_and_borrow(1), Err(Error::OutOfMemory));
    /// ```
    #[inline(always)]
    pub fn read_and_borrow(&mut self, len: usize) -> Result<&'de [u8], Error> {
        self.borrow().read_and_borrow(len)
    }

    /// Read as many bytes from the buffer as to fill up `buf` entirely.
    ///
    /// This method attempts to read as many bytes as available in `buf`.
    /// No partial reas are possible.
    /// If the buffer would be drained before filling up `buf`, [`OutOfMemory`](Error::OutOfMemory) is returned.
    ///
    /// # Examples
    /// ```
    /// use petra::{Error, ReadBuffer};
    ///
    /// let mut buffer = ReadBuffer::from(&[1, 2, 3, 4, 5]);
    ///
    /// // The destination slice of length 3.
    /// let mut dest = [0u8; 3];
    ///
    /// assert_eq!(buffer.read_into_slice(&mut dest), Ok(()));
    /// assert_eq!(buffer.consumed(), 3);
    /// assert_eq!(&dest, &[1, 2, 3]);
    ///
    /// // Attempting to read another 3 bytes fails, since there are only 2 bytes left.
    /// assert_eq!(buffer.read_into_slice(&mut dest), Err(Error::OutOfMemory));
    /// assert_eq!(buffer.remaining(), &[4, 5]);
    /// ```
    #[inline(always)]
    pub fn read_into_slice(&mut self, buf: &mut [u8]) -> Result<(), Error> {
        self.borrow().read_into_slice(buf)
    }

    /// Peek into the next `len` bytes of the buffer, without advancing the read cursor.
    ///
    /// The closure `f` receives a slice into the buffer, of length `len` and starting at the current read head position.
    /// After the closure execution, the cursor is not advanced.
    ///
    /// # Errors
    /// Before executing the closure, a check is made if there are enough bytes available for reading in the buffer.
    /// In case there are not enough bytes available, [`OutOfMemory`](Error::OutOfMemory) is returned.
    ///
    /// # Examples
    /// ```
    /// use petra::{Error, ReadBuffer};
    ///
    /// let buffer = ReadBuffer::from(&[1, 2, 3, 4, 5]);
    /// assert_eq!(buffer.peek(1, |buf| buf[0]), Ok(1));
    /// assert_eq!(buffer.peek(5, |buf| buf[3]), Ok(4));
    ///
    /// // The cursor is not advanced after any call to peek.
    /// assert_eq!(buffer.consumed(), 0);
    ///
    /// // The closure is not executed, we peek over the length of the buffer.
    /// assert_eq!(buffer.peek(6, |_| assert!(false)), Err(Error::OutOfMemory));
    /// ```
    #[inline(always)]
    pub fn peek<R, F: FnOnce(&[u8]) -> R>(&self, len: usize, f: F) -> Result<R, Error> {
        self.borrow().peek(len, f)
    }

    /// Discard the next `len` bytes and advance the read cursor.
    ///
    /// # Examples
    /// ```
    /// use petra::{Error, ReadBuffer};
    ///
    /// let mut buffer = ReadBuffer::from(&[1, 2, 3, 4, 5]);
    ///
    /// // Discard the first 2 bytes.
    /// assert_eq!(buffer.discard(2), Ok(()));
    ///
    /// assert_eq!(buffer.consumed(), 2);
    /// assert_eq!(buffer.remaining(), &[3, 4, 5]);
    ///
    /// // Discard the next 3 bytes, after this call the entire buffer is consumed.
    /// assert_eq!(buffer.discard(3), Ok(()));
    ///
    /// // No more bytes available for reading.
    /// assert_eq!(buffer.discard(1), Err(Error::OutOfMemory));
    /// ```
    #[inline(always)]
    pub fn discard(&mut self, count: usize) -> Result<(), Error> {
        self.borrow().discard(count)
    }

    /// Record a snapshot of the current buffer cursor.
    ///
    /// Buffer snapshots can be easily abused.
    /// Please see the documentation of [`restore`](ReadBuffer::restore) for more details and examples.
    #[inline(always)]
    pub fn snapshot(&self) -> Snapshot {
        self.borrow().snapshot()
    }

    /// Restore the buffer cursor to the position recorded in `snapshot`.
    ///
    /// The `snapshot` must originate from a previous call to [`snapshot`](ReadBuffer::snapshot) on the same buffer instance.
    /// Note that it is fairly easy to abuse [`Snapshot`]s since there is no sanity check performed during the restoration.
    /// Users must be able to guarantee that calls to [`restore`](ReadBuffer::restore) act on snapshots created on the same buffer instances.
    ///
    /// The cursor is restored to the position recorded in the `snaphost`.
    ///
    /// # Panics
    /// This call panics if the cursor position recorded in `snapshot` points beyond the current cursor position of the buffer.
    ///
    /// # Examples
    /// ```
    /// use petra::ReadBuffer;
    ///
    /// let mut buffer = ReadBuffer::from(&[1, 2, 3, 4, 5]);
    ///
    /// // Write 1 byte.
    /// buffer.read(1, |_| {});
    ///
    /// // Record a snapshot of the buffer cursor.
    /// let snapshot = buffer.snapshot();
    ///
    /// // Write two more bytes.
    /// buffer.read(2, |_| {});
    ///
    /// // At this point, the cursor is at position 3.
    /// assert_eq!(buffer.consumed(), 3);
    ///
    /// // Undo the last read of len 2.
    /// buffer.restore(snapshot);
    ///
    /// // Now, the cursor has been restored to position 1.
    /// assert_eq!(buffer.consumed(), 1);
    /// ```
    #[inline(always)]
    pub fn restore(&mut self, snapshot: Snapshot) {
        self.borrow().restore(snapshot)
    }
}
