use super::*;

use ciborium_io::Read;

/// An error that occurred while decoding
#[derive(Debug)]
pub enum Error<T> {
    /// An error occurred while reading bytes
    ///
    /// Contains the underlying error reaturned while reading.
    Io(T),

    /// An error occurred while parsing bytes
    ///
    /// Contains the offset into the stream where the syntax error occurred.
    Syntax(usize),
}

impl<T> From<T> for Error<T> {
    #[inline]
    fn from(value: T) -> Self {
        Self::Io(value)
    }
}

/// A decoder for deserializing CBOR items
///
/// This decoder manages the low-level decoding of CBOR items into `Header`
/// objects. It also contains utility functions for parsing segmented bytes
/// and text inputs.
pub struct Decoder<R: Read> {
    reader: R,
    offset: usize,
    buffer: Option<Title>,
}

impl<R: Read> From<R> for Decoder<R> {
    #[inline]
    fn from(value: R) -> Self {
        Self {
            reader: value,
            offset: 0,
            buffer: None,
        }
    }
}

impl<R: Read> Read for Decoder<R> {
    type Error = R::Error;

    #[inline]
    fn read_exact(&mut self, data: &mut [u8]) -> Result<(), Self::Error> {
        assert!(self.buffer.is_none());
        self.reader.read_exact(data)?;
        self.offset += data.len();
        Ok(())
    }
}

impl<R: Read> Decoder<R> {
    #[inline]
    fn pull_title(&mut self) -> Result<Title, Error<R::Error>> {
        if let Some(title) = self.buffer.take() {
            self.offset += title.1.as_ref().len() + 1;
            return Ok(title);
        }

        let mut prefix = [0u8; 1];
        self.read_exact(&mut prefix[..])?;

        let major = match prefix[0] >> 5 {
            0 => Major::Positive,
            1 => Major::Negative,
            2 => Major::Bytes,
            3 => Major::Text,
            4 => Major::Array,
            5 => Major::Map,
            6 => Major::Tag,
            7 => Major::Other,
            _ => unreachable!(),
        };

        let mut minor = match prefix[0] & 0b00011111 {
            x if x < 24 => Minor::This(x),
            24 => Minor::Next1([0; 1]),
            25 => Minor::Next2([0; 2]),
            26 => Minor::Next4([0; 4]),
            27 => Minor::Next8([0; 8]),
            31 => Minor::More,
            _ => return Err(Error::Syntax(self.offset - 1)),
        };

        self.read_exact(minor.as_mut())?;
        Ok(Title(major, minor))
    }

    #[inline]
    fn push_title(&mut self, item: Title) {
        assert!(self.buffer.is_none());
        self.buffer = Some(item);
        self.offset -= item.1.as_ref().len() + 1;
    }

    /// Pulls the next header from the input
    #[inline]
    pub fn pull(&mut self) -> Result<Header, Error<R::Error>> {
        let offset = self.offset;
        self.pull_title()?
            .try_into()
            .map_err(|_| Error::Syntax(offset))
    }

    /// Push a single header into the input buffer
    ///
    /// # Panics
    ///
    /// This function panics if called while there is already a header in the
    /// input buffer. You should take care to call this function only after
    /// pulling a header to ensure there is nothing in the input buffer.
    #[inline]
    pub fn push(&mut self, item: Header) {
        self.push_title(Title::from(item))
    }

    /// Gets the current byte offset into the stream
    ///
    /// The offset starts at zero when the decoder is created. Therefore, if
    /// bytes were already read from the reader before the decoder was created,
    /// you must account for this.
    #[inline]
    pub fn offset(&mut self) -> usize {
        self.offset
    }

    /// Process an incoming bytes item
    ///
    /// In CBOR, bytes can be segmented. The logic for this can be a bit tricky,
    /// so we encapsulate that logic using this function. This function **MUST**
    /// be called immediately after first pulling a `Header::Bytes(len)` from
    /// the wire and `len` must be provided to this function from that value.
    ///
    /// The `buf` parameter provides a buffer used when reading in the segmented
    /// bytes. A large buffer will result in fewer calls to read incoming bytes
    /// at the cost of memory usage. You should consider this trade off when
    /// deciding the size of your buffer.
    #[inline]
    pub fn bytes(&mut self, len: Option<usize>) -> Segments<R, crate::seg::Bytes> {
        self.push(Header::Bytes(len));
        Segments::new(self, |header| match header {
            Header::Bytes(len) => Ok(len),
            _ => Err(()),
        })
    }

    /// Process an incoming text item
    ///
    /// In CBOR, text can be segmented. The logic for this can be a bit tricky,
    /// so we encapsulate that logic using this function. This function **MUST**
    /// be called immediately after first pulling a `Header::Text(len)` from
    /// the wire and `len` must be provided to this function from that value.
    ///
    /// The `buf` parameter provides a buffer used when reading in the segmented
    /// text. A large buffer will result in fewer calls to read incoming bytes
    /// at the cost of memory usage. You should consider this trade off when
    /// deciding the size of your buffer.
    #[inline]
    pub fn text(&mut self, len: Option<usize>) -> Segments<R, crate::seg::Text> {
        self.push(Header::Text(len));
        Segments::new(self, |header| match header {
            Header::Text(len) => Ok(len),
            _ => Err(()),
        })
    }
}
