use std::io::Read;

use crate::error::CoreError;
use crate::error::CoreResult;

/// Fixed byte length of every q IPC message header.
pub const HEADER_LEN: usize = 8;

/// Endianness marker stored in the first q IPC header byte.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum Encoding {
    BigEndian,
    #[default]
    LittleEndian,
}

impl Encoding {
    fn decode_u32(self, bytes: [u8; 4]) -> u32 {
        match self {
            Self::BigEndian => u32::from_be_bytes(bytes),
            Self::LittleEndian => u32::from_le_bytes(bytes),
        }
    }

    fn encode_u32(self, value: u32) -> [u8; 4] {
        match self {
            Self::BigEndian => value.to_be_bytes(),
            Self::LittleEndian => value.to_le_bytes(),
        }
    }
}

impl From<Encoding> for u8 {
    fn from(value: Encoding) -> Self {
        match value {
            Encoding::BigEndian => 0,
            Encoding::LittleEndian => 1,
        }
    }
}

impl TryFrom<u8> for Encoding {
    type Error = CoreError;

    fn try_from(value: u8) -> CoreResult<Self> {
        match value {
            0 => Ok(Self::BigEndian),
            1 => Ok(Self::LittleEndian),
            _ => Err(CoreError::InvalidEncoding(value)),
        }
    }
}

/// q IPC message kind stored in the second q IPC header byte.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum MessageType {
    #[default]
    Asynchronous,
    Synchronous,
    Response,
}

impl From<MessageType> for u8 {
    fn from(value: MessageType) -> Self {
        match value {
            MessageType::Asynchronous => 0,
            MessageType::Synchronous => 1,
            MessageType::Response => 2,
        }
    }
}

impl TryFrom<u8> for MessageType {
    type Error = CoreError;

    fn try_from(value: u8) -> CoreResult<Self> {
        match value {
            0 => Ok(Self::Asynchronous),
            1 => Ok(Self::Synchronous),
            2 => Ok(Self::Response),
            _ => Err(CoreError::InvalidMessageType(value)),
        }
    }
}

/// q IPC compression marker stored in the third q IPC header byte.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum Compression {
    #[default]
    Uncompressed,
    Compressed,
    CompressedLarge,
}

impl From<Compression> for u8 {
    fn from(value: Compression) -> Self {
        match value {
            Compression::Uncompressed => 0,
            Compression::Compressed => 1,
            Compression::CompressedLarge => 2,
        }
    }
}

impl TryFrom<u8> for Compression {
    type Error = CoreError;

    fn try_from(value: u8) -> CoreResult<Self> {
        match value {
            0 => Ok(Self::Uncompressed),
            1 => Ok(Self::Compressed),
            2 => Ok(Self::CompressedLarge),
            _ => Err(CoreError::InvalidCompression(value)),
        }
    }
}

/// Decoded q IPC message header.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct MessageHeader {
    encoding: Encoding,
    message_type: MessageType,
    compression: Compression,
    size: usize,
}

impl MessageHeader {
    /// Builds a validated message header.
    pub fn new(
        encoding: Encoding,
        message_type: MessageType,
        compression: Compression,
        size: usize,
    ) -> CoreResult<Self> {
        if size < HEADER_LEN {
            return Err(CoreError::InvalidMessageLength(size));
        }

        Ok(Self {
            encoding,
            message_type,
            compression,
            size,
        })
    }

    /// Parses a message header from an exact 8-byte array.
    pub fn from_bytes(bytes: [u8; HEADER_LEN]) -> CoreResult<Self> {
        let encoding = Encoding::try_from(bytes[0])?;
        let message_type = MessageType::try_from(bytes[1])?;
        let compression = Compression::try_from(bytes[2])?;
        let size = encoding.decode_u32(bytes[4..8].try_into().expect("fixed-size slice")) as usize;
        Self::new(encoding, message_type, compression, size)
    }

    /// Parses a message header from a byte slice.
    pub fn parse(bytes: &[u8]) -> CoreResult<Self> {
        let header: [u8; HEADER_LEN] = bytes
            .get(..HEADER_LEN)
            .ok_or(CoreError::TruncatedHeader {
                actual: bytes.len(),
            })?
            .try_into()
            .expect("header slice length already checked");
        Self::from_bytes(header)
    }

    /// Serializes the header back to its q IPC byte representation.
    pub fn to_bytes(self) -> CoreResult<[u8; HEADER_LEN]> {
        let size = u32::try_from(self.size).map_err(|_| CoreError::LengthOverflow(self.size))?;
        let mut bytes = [0_u8; HEADER_LEN];
        bytes[0] = self.encoding.into();
        bytes[1] = self.message_type.into();
        bytes[2] = self.compression.into();
        bytes[4..8].copy_from_slice(&self.encoding.encode_u32(size));
        Ok(bytes)
    }

    pub fn encoding(self) -> Encoding {
        self.encoding
    }

    pub fn message_type(self) -> MessageType {
        self.message_type
    }

    pub fn compression(self) -> Compression {
        self.compression
    }

    pub fn size(self) -> usize {
        self.size
    }

    pub fn body_len(self) -> usize {
        self.size - HEADER_LEN
    }
}

/// Borrowed validated q IPC frame.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Frame<'a> {
    header: MessageHeader,
    body: &'a [u8],
}

impl<'a> Frame<'a> {
    /// Validates a full q IPC frame and returns borrowed header/body views.
    pub fn parse(bytes: &'a [u8]) -> CoreResult<Self> {
        let header = MessageHeader::parse(bytes)?;
        if bytes.len() != header.size() {
            return Err(CoreError::FrameLengthMismatch {
                declared: header.size(),
                actual: bytes.len(),
            });
        }

        Ok(Self {
            header,
            body: &bytes[HEADER_LEN..],
        })
    }

    pub fn header(self) -> MessageHeader {
        self.header
    }

    pub fn body(self) -> &'a [u8] {
        self.body
    }
}

/// Decompresses a q IPC compressed body (follows the 8-byte header).
///
/// The first 4 bytes of the compressed body are a size prefix encoding the
/// total decompressed message length including the 8-byte header. The
/// remaining bytes are the compressed payload using q's LZW-style algorithm:
/// a flag byte drives 8 decisions — bit clear emits a literal byte, bit set
/// emits a back-reference (2 fixed bytes + n extra bytes) via a 256-entry
/// XOR-keyed lookup table.
pub fn decompress_ipc_body(compressed: &[u8], encoding: Encoding) -> CoreResult<Vec<u8>> {
    if compressed.len() < 4 {
        return Err(CoreError::InvalidStructure(format!(
            "compressed body must be at least 4 bytes for size prefix, got {}",
            compressed.len()
        )));
    }

    let size_with_header = match encoding {
        Encoding::LittleEndian => {
            i32::from_le_bytes(compressed[..4].try_into().expect("validated length"))
        }
        Encoding::BigEndian => {
            i32::from_be_bytes(compressed[..4].try_into().expect("validated length"))
        }
    };
    if size_with_header < 8 {
        return Err(CoreError::InvalidStructure(format!(
            "compressed size prefix {size_with_header} is less than minimum header size 8"
        )));
    }
    let size = (size_with_header - 8) as usize;

    let mut decompressed = vec![0_u8; size];
    let mut aa = [0_i32; 256];
    let mut n = 0_usize;
    let mut f = 0_usize;
    let mut s = 0_usize;
    let mut p = 0_usize;
    let mut i = 0_usize;
    let mut d = 4_usize; // skip the 4-byte size prefix

    while s < size {
        if i == 0 {
            if d >= compressed.len() {
                return Err(CoreError::InvalidStructure(
                    "unexpected end of compressed data while reading flag byte".to_string(),
                ));
            }
            f = compressed[d] as usize;
            d += 1;
            i = 1;
        }

        if (f & i) != 0 {
            // Back-reference: lookup key byte + extra count byte
            if d + 2 > compressed.len() {
                return Err(CoreError::InvalidStructure(
                    "insufficient data for back-reference (need 2 bytes)".to_string(),
                ));
            }
            let mut r = aa[compressed[d] as usize] as usize;
            d += 1;

            if r >= size {
                return Err(CoreError::InvalidStructure(format!(
                    "back-reference start {r} exceeds decompressed buffer size {size}"
                )));
            }
            if s >= size {
                return Err(CoreError::InvalidStructure(format!(
                    "write index {s} exceeds decompressed buffer size {size}"
                )));
            }
            decompressed[s] = decompressed[r];
            s += 1;
            r += 1;

            if r >= size {
                return Err(CoreError::InvalidStructure(format!(
                    "back-reference position {r} exceeds decompressed buffer size {size}"
                )));
            }
            if s >= size {
                return Err(CoreError::InvalidStructure(format!(
                    "write index {s} exceeds decompressed buffer size {size}"
                )));
            }
            decompressed[s] = decompressed[r];
            s += 1;
            r += 1;

            n = compressed[d] as usize;
            d += 1;

            if r + n > size {
                return Err(CoreError::InvalidStructure(format!(
                    "back-reference range {r}..{} exceeds decompressed buffer size {size}",
                    r + n
                )));
            }
            if s + n > size {
                return Err(CoreError::InvalidStructure(format!(
                    "write range {s}..{} exceeds decompressed buffer size {size}",
                    s + n
                )));
            }
            for m in 0..n {
                decompressed[s + m] = decompressed[r + m];
            }
        } else {
            // Literal byte
            if d >= compressed.len() {
                return Err(CoreError::InvalidStructure(
                    "unexpected end of compressed data while reading literal byte".to_string(),
                ));
            }
            decompressed[s] = compressed[d];
            s += 1;
            d += 1;
        }

        // Update the XOR lookup table with newly emitted bytes
        while p < s.saturating_sub(1) {
            aa[(decompressed[p] ^ decompressed[p + 1]) as usize] = p as i32;
            p += 1;
        }

        if (f & i) != 0 {
            s += n;
            p = s;
        }

        i *= 2;
        if i == 256 {
            i = 0;
        }
    }

    Ok(decompressed)
}

/// Serializes a q-encoded body as a complete q IPC message.
///
/// This mirrors the current rewrite contract: qroissant only emits
/// little-endian, uncompressed frames for now.
pub fn serialize_body_as_message(
    body: &[u8],
    encoding: Encoding,
    message_type: MessageType,
    compression: Compression,
) -> CoreResult<Vec<u8>> {
    if encoding != Encoding::LittleEndian {
        return Err(CoreError::UnsupportedEndianness(encoding));
    }
    if compression != Compression::Uncompressed {
        return Err(CoreError::UnsupportedCompression(compression));
    }

    let size = HEADER_LEN
        .checked_add(body.len())
        .ok_or(CoreError::LengthOverflow(usize::MAX))?;
    let header = MessageHeader::new(encoding, message_type, compression, size)?;
    let mut payload = Vec::with_capacity(size);
    payload.extend_from_slice(&header.to_bytes()?);
    payload.extend_from_slice(body);
    Ok(payload)
}

/// Reads the total q IPC frame length from an 8-byte header.
pub fn read_message_length(header: &[u8; HEADER_LEN]) -> CoreResult<usize> {
    Ok(MessageHeader::from_bytes(*header)?.size())
}

/// Reads one complete q IPC frame from an IO stream.
pub fn read_frame<R: Read>(reader: &mut R) -> CoreResult<Vec<u8>> {
    let mut header = [0_u8; HEADER_LEN];
    reader.read_exact(&mut header)?;
    let frame_len = read_message_length(&header)?;
    let mut frame = vec![0_u8; frame_len];
    frame[..HEADER_LEN].copy_from_slice(&header);
    reader.read_exact(&mut frame[HEADER_LEN..])?;
    Ok(frame)
}

/// Incremental q IPC decompressor that can be fed compressed bytes as they
/// arrive from the network, overlapping I/O with decompression work.
///
/// The q LZW algorithm reads compressed input forward-only — back-references
/// target the *output* buffer, not the input.  This means we can process
/// compressed bytes as soon as they arrive without buffering the entire
/// compressed payload first.
///
/// # Usage
///
/// ```ignore
/// let mut dec = StreamingDecompressor::new(size_prefix, Encoding::LittleEndian)?;
/// while !dec.is_complete() {
///     let chunk = read_from_network()?;
///     dec.feed(&chunk)?;
/// }
/// let body = dec.finish()?;
/// ```
pub struct StreamingDecompressor {
    decompressed: Vec<u8>,
    aa: [i32; 256],
    compressed_buf: Vec<u8>,
    d: usize,
    s: usize,
    p: usize,
    f: usize,
    i: usize,
    size: usize,
    read_ptr: usize,
}

impl StreamingDecompressor {
    /// Creates a new streaming decompressor from the 4-byte size prefix
    /// (the first 4 bytes of the compressed body after the 8-byte header).
    pub fn new(size_prefix: [u8; 4], encoding: Encoding) -> CoreResult<Self> {
        let size_with_header = match encoding {
            Encoding::LittleEndian => i32::from_le_bytes(size_prefix),
            Encoding::BigEndian => i32::from_be_bytes(size_prefix),
        };
        if size_with_header < 8 {
            return Err(CoreError::InvalidStructure(format!(
                "compressed size prefix {size_with_header} is less than minimum header size 8"
            )));
        }
        let size = (size_with_header - 8) as usize;

        Ok(Self {
            decompressed: vec![0_u8; size],
            aa: [0_i32; 256],
            compressed_buf: Vec::new(),
            d: 0,
            s: 0,
            p: 0,
            f: 0,
            i: 0,
            size,
            read_ptr: 0,
        })
    }

    pub fn feed(&mut self, chunk: &[u8]) -> CoreResult<usize> {
        self.compressed_buf.extend_from_slice(chunk);
        let prev_s = self.s;

        while self.s < self.size {
            if self.i == 0 {
                if self.d >= self.compressed_buf.len() {
                    break;
                }
                self.f = self.compressed_buf[self.d] as usize;
                self.d += 1;
                self.i = 1;
            }

            let is_backref = (self.f & self.i) != 0;
            let mut n = 0;

            if is_backref {
                if self.d + 2 > self.compressed_buf.len() {
                    break;
                }
                let mut r = self.aa[self.compressed_buf[self.d] as usize] as usize;
                self.d += 1;
                if r >= self.size || self.s + 2 > self.size {
                    return Err(CoreError::InvalidStructure(
                        "backref out of bounds".to_string(),
                    ));
                }
                self.decompressed[self.s] = self.decompressed[r];
                self.s += 1;
                r += 1;

                if r >= self.size || self.s + 1 > self.size {
                    return Err(CoreError::InvalidStructure(
                        "backref out of bounds".to_string(),
                    ));
                }
                self.decompressed[self.s] = self.decompressed[r];
                self.s += 1;
                r += 1;

                n = self.compressed_buf[self.d] as usize;
                self.d += 1;
                if r + n > self.size || self.s + n > self.size {
                    return Err(CoreError::InvalidStructure(
                        "backref out of bounds".to_string(),
                    ));
                }
                for m in 0..n {
                    self.decompressed[self.s + m] = self.decompressed[r + m];
                }
            } else {
                if self.d >= self.compressed_buf.len() {
                    break;
                }
                self.decompressed[self.s] = self.compressed_buf[self.d];
                self.s += 1;
                self.d += 1;
            }

            // Sync lookup table
            while self.p < self.s.saturating_sub(1) {
                self.aa[(self.decompressed[self.p] ^ self.decompressed[self.p + 1]) as usize] =
                    self.p as i32;
                self.p += 1;
            }

            if is_backref {
                self.s += n;
                self.p = self.s;
            }

            self.i *= 2;
            if self.i == 256 {
                self.i = 0;
            }
        }

        // Keep memory usage in check by draining processed bytes
        if self.d > 0 {
            self.compressed_buf.drain(0..self.d);
            self.d = 0;
        }

        Ok(self.s - prev_s)
    }

    /// Returns `true` when decompression is complete.
    pub fn is_complete(&self) -> bool {
        self.s >= self.size
    }

    /// Current number of decompressed bytes available.
    pub fn decompressed_len(&self) -> usize {
        self.s
    }

    /// Number of decompressed bytes that have not yet been read.
    pub fn unread_len(&self) -> usize {
        self.s - self.read_ptr
    }

    /// Returns a slice of the next available decompressed bytes.
    pub fn next_chunk(&self) -> &[u8] {
        &self.decompressed[self.read_ptr..self.s]
    }

    /// Advances the read pointer by `len` bytes.
    pub fn consume(&mut self, len: usize) {
        self.read_ptr = (self.read_ptr + len).min(self.s);
    }

    /// Total expected decompressed size.
    pub fn total_size(&self) -> usize {
        self.size
    }

    /// Borrows the decompressed output produced so far.
    pub fn decompressed(&self) -> &[u8] {
        &self.decompressed[..self.s]
    }

    /// Consumes the decompressor and returns the completed output buffer.
    ///
    /// Returns an error if decompression is not yet complete.
    pub fn finish(self) -> CoreResult<Vec<u8>> {
        if !self.is_complete() {
            return Err(CoreError::InvalidStructure(format!(
                "streaming decompress: incomplete — {}/{} bytes decompressed",
                self.s, self.size
            )));
        }
        Ok(self.decompressed)
    }
}

#[cfg(test)]
mod tests {
    use std::io::Cursor;

    use super::*;

    #[test]
    fn encoding_round_trips_from_u8() {
        assert_eq!(Encoding::try_from(0).unwrap(), Encoding::BigEndian);
        assert_eq!(Encoding::try_from(1).unwrap(), Encoding::LittleEndian);
        assert!(matches!(
            Encoding::try_from(9),
            Err(CoreError::InvalidEncoding(9))
        ));
    }

    #[test]
    fn compression_supports_compressed_large() {
        assert_eq!(Compression::try_from(0).unwrap(), Compression::Uncompressed);
        assert_eq!(Compression::try_from(1).unwrap(), Compression::Compressed);
        assert_eq!(
            Compression::try_from(2).unwrap(),
            Compression::CompressedLarge
        );
    }

    #[test]
    fn header_parses_little_endian_payloads() {
        let header = MessageHeader::from_bytes([1, 2, 2, 0, 24, 0, 0, 0]).unwrap();

        assert_eq!(header.encoding(), Encoding::LittleEndian);
        assert_eq!(header.message_type(), MessageType::Response);
        assert_eq!(header.compression(), Compression::CompressedLarge);
        assert_eq!(header.size(), 24);
        assert_eq!(header.body_len(), 16);
    }

    #[test]
    fn header_parses_big_endian_lengths() {
        let header = MessageHeader::from_bytes([0, 1, 0, 0, 0, 0, 0, 16]).unwrap();

        assert_eq!(header.encoding(), Encoding::BigEndian);
        assert_eq!(header.message_type(), MessageType::Synchronous);
        assert_eq!(header.size(), 16);
    }

    #[test]
    fn header_rejects_lengths_smaller_than_header() {
        assert!(matches!(
            MessageHeader::from_bytes([1, 2, 0, 0, 7, 0, 0, 0]),
            Err(CoreError::InvalidMessageLength(7))
        ));
    }

    #[test]
    fn header_to_bytes_round_trips() {
        let header = MessageHeader::new(
            Encoding::LittleEndian,
            MessageType::Response,
            Compression::Compressed,
            64,
        )
        .unwrap();

        let bytes = header.to_bytes().unwrap();
        assert_eq!(MessageHeader::from_bytes(bytes).unwrap(), header);
    }

    #[test]
    fn frame_parse_validates_declared_length() {
        let frame = [1, 2, 0, 0, 10, 0, 0, 0, 42, 43];
        let parsed = Frame::parse(&frame).unwrap();

        assert_eq!(parsed.header().size(), 10);
        assert_eq!(parsed.body(), &[42, 43]);
    }

    #[test]
    fn frame_parse_rejects_length_mismatch() {
        let frame = [1, 2, 0, 0, 11, 0, 0, 0, 42, 43];
        assert!(matches!(
            Frame::parse(&frame),
            Err(CoreError::FrameLengthMismatch {
                declared: 11,
                actual: 10
            })
        ));
    }

    #[test]
    fn serialize_body_wraps_uncompressed_little_endian_body() {
        let payload = serialize_body_as_message(
            &[10, 20, 30],
            Encoding::LittleEndian,
            MessageType::Synchronous,
            Compression::Uncompressed,
        )
        .unwrap();

        assert_eq!(payload, vec![1, 1, 0, 0, 11, 0, 0, 0, 10, 20, 30]);
    }

    #[test]
    fn serialize_body_rejects_big_endian_for_now() {
        assert!(matches!(
            serialize_body_as_message(
                &[1],
                Encoding::BigEndian,
                MessageType::Asynchronous,
                Compression::Uncompressed,
            ),
            Err(CoreError::UnsupportedEndianness(Encoding::BigEndian))
        ));
    }

    #[test]
    fn serialize_body_rejects_compressed_frames_for_now() {
        assert!(matches!(
            serialize_body_as_message(
                &[1],
                Encoding::LittleEndian,
                MessageType::Asynchronous,
                Compression::CompressedLarge,
            ),
            Err(CoreError::UnsupportedCompression(
                Compression::CompressedLarge
            ))
        ));
    }

    #[test]
    fn read_frame_reads_complete_payload() {
        let mut cursor = Cursor::new(vec![1, 2, 0, 0, 10, 0, 0, 0, 42, 43]);
        let frame = read_frame(&mut cursor).unwrap();

        assert_eq!(frame, vec![1, 2, 0, 0, 10, 0, 0, 0, 42, 43]);
    }

    // -----------------------------------------------------------------------
    // StreamingDecompressor tests
    // -----------------------------------------------------------------------

    /// Helper: compress a body using the batch decompressor, then verify the
    /// streaming decompressor produces identical output.
    ///
    /// Since we don't have an encoder for compression, we test by creating
    /// compressed data that the batch decompressor can handle and verifying
    /// the streaming variant matches.  We use decompress_ipc_body as the
    /// reference implementation.
    fn assert_streaming_matches_batch(compressed_body: &[u8]) {
        let batch_result = decompress_ipc_body(compressed_body, Encoding::LittleEndian).unwrap();

        // Feed all at once
        let size_prefix: [u8; 4] = compressed_body[..4].try_into().unwrap();
        let mut dec = StreamingDecompressor::new(size_prefix, Encoding::LittleEndian).unwrap();
        dec.feed(&compressed_body[4..]).unwrap();
        assert!(dec.is_complete());
        let streaming_result = dec.finish().unwrap();
        assert_eq!(streaming_result, batch_result, "all-at-once mismatch");

        // Feed byte-by-byte
        let mut dec = StreamingDecompressor::new(size_prefix, Encoding::LittleEndian).unwrap();
        for &byte in &compressed_body[4..] {
            dec.feed(&[byte]).unwrap();
        }
        assert!(dec.is_complete());
        let streaming_result = dec.finish().unwrap();
        assert_eq!(streaming_result, batch_result, "byte-by-byte mismatch");
    }

    #[test]
    fn streaming_decompressor_empty_body() {
        // Size prefix says 8 bytes total (header only), so decompressed size = 0
        let size_prefix = 8_i32.to_le_bytes();
        let dec = StreamingDecompressor::new(size_prefix, Encoding::LittleEndian).unwrap();
        // No data to feed — already complete
        assert!(dec.is_complete());
        assert_eq!(dec.decompressed_len(), 0);
        let result = dec.finish().unwrap();
        assert!(result.is_empty());
    }

    #[test]
    fn streaming_decompressor_rejects_small_size() {
        let size_prefix = 4_i32.to_le_bytes();
        assert!(StreamingDecompressor::new(size_prefix, Encoding::LittleEndian).is_err());
    }

    #[test]
    fn streaming_decompressor_finish_before_complete() {
        // Size says 16 bytes decompressed (24 total - 8 header)
        let size_prefix = 24_i32.to_le_bytes();
        let dec = StreamingDecompressor::new(size_prefix, Encoding::LittleEndian).unwrap();
        assert!(!dec.is_complete());
        assert!(dec.finish().is_err());
    }

    #[test]
    fn streaming_decompressor_literal_only() {
        // Build a compressed payload that's all literals (no back-references).
        // Flag byte 0x00 means all 8 bits are "literal".
        // For a 3-byte decompressed output:
        // size_prefix = (8 + 3) = 11
        // compressed: [flag=0x00] [lit1] [lit2] [lit3]
        let size_prefix = 11_i32.to_le_bytes();
        let mut compressed = Vec::new();
        compressed.extend_from_slice(&size_prefix);
        compressed.push(0x00); // flag: 8 literals
        compressed.push(0x41); // 'A'
        compressed.push(0x42); // 'B'
        compressed.push(0x43); // 'C'

        assert_streaming_matches_batch(&compressed);
    }
}
