use core::cell::UnsafeCell;

use super::{ReadBufferInner, Snapshot};
use crate::{Error, ReadBuffer};

/// A write buffer with cursor, last write undo and error flag.
///
/// [`WriteBuffer`] is a wrapper over a mutably borrowed data which can be referenced as `&[u8]`.
/// Any data type implementing `AsMut<[u8]>` can be borrowed and wrapped into an instance of [`WriteBuffer`].
///
/// # Flagging
/// Every write access to the buffer is sized, i.e. users must request write access with a given length.
/// Before write access is granted, the buffer implementation checks if there is enough space left in the underlying slice in order to fulfill the request.
/// Write requests which would overflow the buffer will fail with [`Error::OutOfMemory`].
/// In addition, after each failed write request, the buffer is flagged, letting users know that a write attempt has been made which would have resulted in buffer overflow.
/// Users may check the flag status at any time and decide whether the buffer content is still valid or complete.
///
/// # Undo
/// Instances of [`WriteBuffer`] keep track of the cursor position before the most recent write request.
/// Thus, it is possible to undo the most recent write and return the cursor to the position it held before the respective request.
///
/// # Basic usage
///
/// ```
/// use petra::WriteBuffer;
///
/// let mut data = [0u8; 1024]; // The underlying data
/// let mut buffer = WriteBuffer::from(&mut data);
///
/// buffer.write(13, |buf| {
///     // `buf` is referencing the first 13 bytes of the buffer
///     assert_eq!(buf.len(), 13);
///
///     // Here we can copy data into `buf`, etc.
///     buf.copy_from_slice(b"hello, world!");
/// });
///
/// assert_eq!(buffer.written(), 13);
///
/// let actual_written_data = buffer.view();
/// assert_eq!(actual_written_data, b"hello, world!");
/// ```
pub struct WriteBuffer<'de> {
    data: &'de mut [u8],
    pos: usize,
    flag: bool,
}

impl<'de> WriteBuffer<'de> {
    /// Construct a new [`WriteBuffer`] from borrowed data.
    ///
    /// # Examples
    ///
    /// ```
    /// use petra::WriteBuffer;
    ///
    /// let mut boxed: Box<[u8]> = Box::new([0u8; 1024]);
    /// let buffer = WriteBuffer::from(&mut boxed);
    ///
    /// let mut array = [0u8; 1024];
    /// let buffer = WriteBuffer::from(&mut array);
    /// ```
    pub fn from<T: AsMut<[u8]>>(data: &'de mut T) -> Self {
        Self {
            data: data.as_mut(),
            pos: 0,
            flag: false,
        }
    }

    /// Convert this [`WriteBuffer`] into a [`ReadBuffer`].
    ///
    /// Ownership of the underlying data is passed to the read buffer.
    /// The read buffer only gives access to the written portion of the data.
    ///
    /// # Examples
    /// ```
    /// use petra::{ReadBuffer, WriteBuffer};
    ///
    /// let mut data = [0u8; 1024];
    /// let mut wb = WriteBuffer::from(&mut data);
    /// wb.write(2, |buf| {
    ///     buf.copy_from_slice(b"ok");
    /// });
    ///
    /// assert_eq!(wb.written(), 2);
    ///
    /// let mut rb = wb.into_read_buffer();
    /// assert_eq!(rb.len(), 2);
    ///
    /// let data = rb.read_and_borrow(2);
    /// assert_eq!(data, Ok(b"ok" as _));
    /// ```
    pub fn into_read_buffer(self) -> ReadBuffer<'de> {
        ReadBuffer {
            data: &*self.data,
            inner: UnsafeCell::new(ReadBufferInner {
                pos: 0,
                cap: self.pos,
            }),
        }
    }

    /// Return the capacity of the buffer data, in bytes.
    ///
    /// # Examples
    /// ```
    /// use petra::WriteBuffer;
    ///
    /// let mut data = [0u8; 1024];
    /// let buffer = WriteBuffer::from(&mut data);
    /// assert_eq!(buffer.capacity(), 1024);
    ///
    /// let mut data = Box::new([0u8; 10]) as Box<[u8]>;
    /// let buffer = WriteBuffer::from(&mut data);
    /// assert_eq!(buffer.capacity(), 10);
    /// ```
    pub fn capacity(&self) -> usize {
        self.data.len()
    }

    /// Return a slice into the written part of the buffer.
    ///
    /// # Examples
    /// ```
    /// use petra::WriteBuffer;
    ///
    /// let mut data = [0u8; 1024];
    /// let mut buffer = WriteBuffer::from(&mut data);
    ///
    /// assert_eq!(buffer.view(), &[]);
    ///
    /// buffer.write(6, |buf| buf.copy_from_slice(b"hello,"));
    ///
    /// assert_eq!(buffer.view(), b"hello,");
    ///
    /// buffer.write(1, |buf| buf[0] = b' ');
    /// buffer.write(6, |buf| buf.copy_from_slice(b"world!"));
    ///
    /// assert_eq!(buffer.view(), b"hello, world!");
    /// ```
    pub fn view(&self) -> &[u8] {
        // Safety: The following invariants are held:
        //         - self.pos <= self.data.len() (incrementing pos is protected by length checks)
        unsafe { self.data.get_unchecked(..self.pos) }
    }

    /// Return the number of bytes written into the buffer.
    ///
    /// # Examples
    /// ```
    /// use petra::WriteBuffer;
    ///
    /// let mut data = [0u8; 1024];
    /// let mut buffer = WriteBuffer::from(&mut data);
    ///
    /// assert_eq!(buffer.view(), &[]);
    ///
    /// buffer.write(6, |buf| buf.copy_from_slice(b"hello,"));
    ///
    /// assert_eq!(buffer.written(), 6);
    ///
    /// buffer.write(1, |buf| buf[0] = b' ');
    /// buffer.write(6, |buf| buf.copy_from_slice(b"world!"));
    ///
    /// assert_eq!(buffer.written(), 13);
    /// ```
    pub fn written(&self) -> usize {
        self.pos
    }

    /// Write the next `len` bytes and advance the write cursor.
    ///
    /// The closure `f` receives a slice into the buffer, of length `len` and starting at the current write head position.
    /// After the closure execution, the cursor is advanced.
    ///
    /// # Errors
    /// Before executing the closure, a check is made if there is enough space left in the buffer.
    /// In case there is not enough space, [`Error::OutOfMemory`] is returned.
    ///
    /// # Examples
    /// ```
    /// use petra::{Error, WriteBuffer};
    ///
    /// let mut data = [0u8; 10];
    /// let mut buffer = WriteBuffer::from(&mut data);
    ///
    /// // Write the first 4 bytes.
    /// assert_eq!(buffer.write(4, |buf| buf.copy_from_slice(b"1234")), Ok(()));
    ///
    /// assert_eq!(buffer.written(), 4);
    /// assert_eq!(buffer.view(), b"1234");
    ///
    /// // Write the next 6 bytes, after this call the entire buffer is written.
    /// assert_eq!(buffer.write(6, |buf| buf.copy_from_slice(b"567890")), Ok(()));
    ///
    /// // No more space in the buffer, the closure is not called.
    /// assert_eq!(buffer.write(1, |_| assert!(false)), Err(Error::OutOfMemory));
    /// ```
    pub fn write<F: FnOnce(&mut [u8])>(&mut self, len: usize, f: F) -> Result<(), Error> {
        let last = self.pos + len;
        if last > self.capacity() {
            self.flag = true;
            return Err(Error::OutOfMemory);
        }

        // Safety: The following invariants are held:
        //         - self.pos <= last
        //         - last <= self.data.len() (checked above)
        let buf = unsafe { self.data.get_unchecked_mut(self.pos..last) };
        f(buf);

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

    /// Write the entire content of `buf` into the buffer and advance the write cursor.
    ///
    /// This method attempts to write the entire content of `buf`.
    /// No partial writes are possible.
    /// If there is not enough space available in the buffer, [`OutOfMemory`](Error::OutOfMemory) is returned.
    ///
    /// # Examples
    /// ```
    /// use petra::{Error, WriteBuffer};
    ///
    /// let mut data = [0u8; 10];
    /// let mut buffer = WriteBuffer::from(&mut data);
    ///
    /// assert_eq!(buffer.write_from_slice(b"works"), Ok(()));
    /// assert_eq!(buffer.written(), 5);
    ///
    /// assert_eq!(buffer.write_from_slice(b"too long"), Err(Error::OutOfMemory));
    /// ```
    pub fn write_from_slice(&mut self, buf: &[u8]) -> Result<(), Error> {
        let last = self.pos + buf.len();
        if last > self.capacity() {
            self.flag = true;
            return Err(Error::OutOfMemory);
        };

        // Safety: The following invariants are held:
        //         - self.pos <= last
        //         - last <= self.data.len() (checked above)
        unsafe {
            let data = self.data.as_mut_ptr().add(self.pos);
            data.copy_from_nonoverlapping(buf.as_ptr(), buf.len());
        };

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

    /// Discard the next `len` bytes and advance the write cursor.
    ///
    /// # Examples
    /// ```
    /// use petra::{Error, WriteBuffer};
    ///
    /// let mut data = [0u8; 10];
    /// let mut buffer = WriteBuffer::from(&mut data);
    ///
    /// // Discard the first 4 bytes.
    /// assert_eq!(buffer.discard(4), Ok(()));
    ///
    /// assert_eq!(buffer.written(), 4);
    /// assert_eq!(buffer.view(), b"\0\0\0\0");
    ///
    /// // Discard the next 6 bytes, after this call the entire buffer is written.
    /// assert_eq!(buffer.discard(6), Ok(()));
    ///
    /// // No more space in the buffer.
    /// assert_eq!(buffer.discard(1), Err(Error::OutOfMemory));
    /// ```
    pub fn discard(&mut self, len: usize) -> Result<(), Error> {
        let last = self.pos + len;
        if last > self.capacity() {
            self.flag = true;
            return Err(Error::OutOfMemory);
        }

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

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

    /// Restore the buffer cursor to the position recorded in `snapshot`.
    ///
    /// The `snapshot` must originate from a previous call to [`snapshot`](WriteBuffer::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`](WriteBuffer::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::WriteBuffer;
    ///
    /// let mut data = [0u8; 1024];
    /// let mut buffer = WriteBuffer::from(&mut data);
    ///
    /// buffer.write(7, |buf| buf.copy_from_slice(b"hello, "));
    ///
    /// // After this first write, the cursor is at position 7.
    /// assert_eq!(buffer.written(), 7);
    ///
    /// let snapshot = buffer.snapshot();
    ///
    /// buffer.write(6, |buf| buf.copy_from_slice(b"world!"));
    ///
    /// assert_eq!(buffer.view(), b"hello, world!");
    ///
    /// // We restore the cursor to position 7.
    /// buffer.restore(snapshot);
    /// assert_eq!(buffer.written(),7);
    ///
    /// // We write again
    /// buffer.write(6, |buf| buf.copy_from_slice(b"again!"));
    ///
    /// assert_eq!(buffer.view(), b"hello, again!");
    ///
    /// // The cursor position is 13 at this point.
    /// assert_eq!(buffer.written(), 13);
    /// ```
    pub fn restore(&mut self, snapshot: Snapshot) {
        if snapshot.0 > self.pos {
            panic!()
        }

        self.pos = snapshot.0;
    }

    /// Return `true` if the buffer is currently flagged.
    ///
    /// # Examples
    /// See [`WriteBuffer::unflag`] for examples.
    pub fn flagged(&self) -> bool {
        self.flag
    }

    /// Clear the error flag on the buffer.
    ///
    /// # Examples
    /// ```
    /// use petra::{Error, WriteBuffer};
    ///
    /// let mut data = [0u8; 10];
    /// let mut buffer = WriteBuffer::from(&mut data);
    ///
    /// assert!(!buffer.flagged());
    ///
    /// buffer.write(5, |_| {});
    /// assert!(!buffer.flagged());
    ///
    /// // This call has no effect since the buffer is not yet flagged.
    /// buffer.unflag();
    /// assert!(!buffer.flagged());
    ///
    /// // This call will generate an out-of-memory error because there are only 5 more bytes available in the buffer.
    /// assert_eq!(buffer.write(6, |_| {}), Err(Error::OutOfMemory));
    ///
    /// // At this point the buffer is flagged.
    /// assert!(buffer.flagged());
    ///
    /// // We unflag the buffer.
    /// buffer.unflag();
    /// assert!(!buffer.flagged());
    ///
    /// ```
    pub fn unflag(&mut self) {
        self.flag = false;
    }
}
