//! Slice writer.

use crate::{
    asn1::*, Encode, EncodeValue, ErrorKind, Header, Length, Result, Tag, TagMode, TagNumber,
    Tagged, Writer,
};

/// [`Writer`] which encodes DER into a mutable output byte slice.
#[derive(Debug)]
pub struct SliceWriter<'a> {
    /// Buffer into which DER-encoded message is written
    bytes: &'a mut [u8],

    /// Has the encoding operation failed?
    failed: bool,

    /// Total number of bytes written to buffer so far
    position: Length,
}

impl<'a> SliceWriter<'a> {
    /// Create a new encoder with the given byte slice as a backing buffer.
    pub fn new(bytes: &'a mut [u8]) -> Self {
        Self {
            bytes,
            failed: false,
            position: Length::ZERO,
        }
    }

    /// Encode a value which impls the [`Encode`] trait.
    pub fn encode<T: Encode>(&mut self, encodable: &T) -> Result<()> {
        if self.is_failed() {
            self.error(ErrorKind::Failed)?;
        }

        encodable.encode(self).map_err(|e| {
            self.failed = true;
            e.nested(self.position)
        })
    }

    /// Return an error with the given [`ErrorKind`], annotating it with
    /// context about where the error occurred.
    pub fn error<T>(&mut self, kind: ErrorKind) -> Result<T> {
        self.failed = true;
        Err(kind.at(self.position))
    }

    /// Did the decoding operation fail due to an error?
    pub fn is_failed(&self) -> bool {
        self.failed
    }

    /// Finish encoding to the buffer, returning a slice containing the data
    /// written to the buffer.
    pub fn finish(self) -> Result<&'a [u8]> {
        let position = self.position;

        if self.is_failed() {
            return Err(ErrorKind::Failed.at(position));
        }

        self.bytes
            .get(..usize::try_from(position)?)
            .ok_or_else(|| ErrorKind::Overlength.at(position))
    }

    /// Encode a `CONTEXT-SPECIFIC` field with the provided tag number and mode.
    pub fn context_specific<T>(
        &mut self,
        tag_number: TagNumber,
        tag_mode: TagMode,
        value: &T,
    ) -> Result<()>
    where
        T: EncodeValue + Tagged,
    {
        ContextSpecificRef {
            tag_number,
            tag_mode,
            value,
        }
        .encode(self)
    }

    /// Encode an ASN.1 `SEQUENCE` of the given length.
    ///
    /// Spawns a nested slice writer which is expected to be exactly the
    /// specified length upon completion.
    pub fn sequence<F>(&mut self, length: Length, f: F) -> Result<()>
    where
        F: FnOnce(&mut SliceWriter<'_>) -> Result<()>,
    {
        Header::new(Tag::Sequence, length).and_then(|header| header.encode(self))?;

        let mut nested_encoder = SliceWriter::new(self.reserve(length)?);
        f(&mut nested_encoder)?;

        if nested_encoder.finish()?.len() == usize::try_from(length)? {
            Ok(())
        } else {
            self.error(ErrorKind::Length { tag: Tag::Sequence })
        }
    }

    /// Reserve a portion of the internal buffer, updating the internal cursor
    /// position and returning a mutable slice.
    fn reserve(&mut self, len: impl TryInto<Length>) -> Result<&mut [u8]> {
        if self.is_failed() {
            return Err(ErrorKind::Failed.at(self.position));
        }

        let len = len
            .try_into()
            .or_else(|_| self.error(ErrorKind::Overflow))?;

        let end = (self.position + len).or_else(|e| self.error(e.kind()))?;
        let slice = self
            .bytes
            .get_mut(self.position.try_into()?..end.try_into()?)
            .ok_or_else(|| ErrorKind::Overlength.at(end))?;

        self.position = end;
        Ok(slice)
    }
}

impl<'a> Writer for SliceWriter<'a> {
    fn write(&mut self, slice: &[u8]) -> Result<()> {
        self.reserve(slice.len())?.copy_from_slice(slice);
        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use super::SliceWriter;
    use crate::{Encode, ErrorKind, Length};

    #[test]
    fn overlength_message() {
        let mut buffer = [];
        let mut writer = SliceWriter::new(&mut buffer);
        let err = false.encode(&mut writer).err().unwrap();
        assert_eq!(err.kind(), ErrorKind::Overlength);
        assert_eq!(err.position(), Some(Length::ONE));
    }
}
