#![cfg_attr(not(feature = "std"), no_std)]
#![doc = include_str!("../README.md")]
#![warn(missing_docs)]
extern crate alloc;

use alloc::{vec, vec::Vec};
use core::fmt;

pub mod escape;
pub use escape::{OnePassEscapeEngine, ShellDecodeMode, TrSedEngine, TwoPassEscapeEngine, TR_SED};

/// Error type returned by encoding and decoding operations.
#[derive(Debug, Clone, PartialEq, thiserror::Error)]
pub enum EscnulError {
    /// The output buffer was too small to hold the full result.
    #[error("Buffer too small")]
    BufferTooSmall(Progress),
    /// The input ended in the middle of an escape sequence.
    #[error("Truncated input")]
    TruncatedInput(Progress),
    /// The provided escape byte was invalid (must not be 0x00 or 0x01).
    #[error("Bad escape byte: {0:#04x}")]
    BadEscapes(u8),
}

/// Progress info returned by `EscnulError::BufferTooSmall` and
/// `EscnulError::TruncatedInput`.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Progress {
    /// Number of bytes processed from the input buffer.
    pub processed: usize,
    /// Number of bytes written to the output buffer.
    pub written: usize,
}

impl fmt::Display for Progress {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "processed={}, written={}", self.processed, self.written)
    }
}

/// Trait for encoding a byte stream into a pre-allocated output buffer.
pub trait Encode {
    /// Encode `input` into `output`, returning the number of bytes written.
    ///
    /// # Errors
    ///
    /// - [`EscnulError::BadEscapes`] if the engine’s escape byte is invalid.
    /// - [`EscnulError::BufferTooSmall`] if `output` is too small.  The
    ///   contained [`Progress`] tells you how many bytes were produced
    ///   before running out of space.
    fn encode_slice<T: AsRef<[u8]>>(
        &self,
        input: T,
        output: &mut [u8],
    ) -> Result<usize, EscnulError>;

    /// Encode `input` into a newly allocated `Vec<u8>`.
    fn encode<T: AsRef<[u8]>>(&self, input: T) -> Vec<u8> {
        let mut output = vec![0; input.as_ref().len() * 2];
        let n = self.encode_slice(input, &mut output).unwrap();
        output.truncate(n);
        output
    }
}

/// Trait for decoding an escaped byte stream into its original bytes.
pub trait Decode {
    /// Decode `input` into `output`, returning the number of bytes written.
    ///
    /// # Errors
    ///
    /// - [`EscnulError::BadEscapes`] if the engine’s escape byte is invalid.
    /// - [`EscnulError::BufferTooSmall`] if `output` is too small.
    /// - [`EscnulError::TruncatedInput`] if `input` ends mid-escape.
    fn decode_slice<T: AsRef<[u8]>>(
        &self,
        input: T,
        output: &mut [u8],
    ) -> Result<usize, EscnulError>;

    /// Decode `input` into a newly allocated `Vec<u8>`.
    fn decode<T: AsRef<[u8]>>(&self, input: T) -> Vec<u8> {
        let mut output = vec![0; input.as_ref().len()];
        let n = self.decode_slice(input, &mut output).unwrap();
        output.truncate(n);
        output
    }
}

/// Marker trait for types that implement both [`Encode`] and [`Decode`].
pub trait Engine: Encode + Decode {}

#[cfg(feature = "std")]
/// Error type returned by the stream-oriented encoding helpers.
#[derive(Debug, thiserror::Error)]
pub enum StreamEncodeError {
    /// An I/O error occurred while streaming input or output.
    #[error(transparent)]
    Io(#[from] std::io::Error),
    /// An escnul encoding error occurred while processing a chunk.
    #[error(transparent)]
    Escnul(#[from] EscnulError),
}
