//! # Common Ogg Container Parsing
//!
//! This module provides shared functionality for parsing Ogg containers,
//! used by both Ogg/Vorbis and Ogg/Opus decoders.
//!
//! ## Features
//!
//! - **Streaming packet assembly**: Reads Ogg pages and assembles multi-page packets
//! - **Optional CRC32 validation**: Can validate page integrity
//! - **Optional sync search**: Can search for "OggS" pattern in streams with garbage
//! - **Shared error type**: Uses `OggContainerError` for consistent error reporting
//!
//! ## Architecture
//!
//! The `OggPacketReader` reads Ogg pages incrementally:
//! 1. Optionally searches for "OggS" sync pattern
//! 2. Reads 27-byte page headers
//! 3. Optionally validates CRC32 checksums
//! 4. Reads segment tables and page data
//! 5. Assembles packets from segments (handling multi-page packets)
//! 6. Returns complete packets to the decoder

use std::{
    collections::VecDeque,
    io::{self, Read},
};

use crate::{common::ChannelReader, decoder_common::DecoderError};

/// Maximum number of bytes to scan when searching for Ogg sync pattern.
///
/// This prevents unbounded memory growth when processing streams with
/// large amounts of garbage data before the first valid Ogg page.
const MAX_SYNC_SEARCH: usize = 64 * 1024;

/// Configuration options for Ogg packet reader.
#[derive(Clone, Debug)]
pub struct OggReaderOptions {
    /// Whether to validate CRC32 checksums of Ogg pages.
    ///
    /// Vorbis typically validates CRC, Opus often doesn't.
    pub validate_crc: bool,

    /// Whether to search for "OggS" sync pattern at start of stream.
    ///
    /// Useful for streams that may have garbage bytes before valid data.
    pub find_sync: bool,

    /// Maximum bytes to search for sync pattern (only used if find_sync is true).
    pub max_sync_search: usize,
}

impl Default for OggReaderOptions {
    fn default() -> Self {
        Self {
            validate_crc: true,
            find_sync: true,
            max_sync_search: MAX_SYNC_SEARCH,
        }
    }
}

pub type OggContainerError = DecoderError;

/// Streaming Ogg packet reader that assembles packets from Ogg pages.
///
/// This reader parses Ogg pages manually without requiring seek operations,
/// making it suitable for truly streaming scenarios.
///
/// # Features
///
/// - Searches for Ogg sync pattern ("OggS")
/// - Parses page headers and segment tables
/// - Validates CRC32 checksums (optional)
/// - Assembles multi-page packets
/// - Detects end-of-stream
/// - Enforces single logical bitstream
pub struct OggPacketReader {
    reader: ChannelReader<OggContainerError>,
    current_packet: Vec<u8>,
    queue: VecDeque<Vec<u8>>,
    finished: bool,
    stream_serial: Option<u32>,
    sync_buffer: Vec<u8>,
    sync_bytes_read: usize,
    synced: bool,
    options: OggReaderOptions,
}

impl OggPacketReader {
    /// Creates a new Ogg packet reader with the given options.
    pub fn new(reader: ChannelReader<OggContainerError>, options: OggReaderOptions) -> Self {
        Self {
            reader,
            current_packet: Vec::new(),
            queue: VecDeque::new(),
            finished: false,
            stream_serial: None,
            sync_buffer: Vec::new(),
            sync_bytes_read: 0,
            synced: !options.find_sync, // If we don't need to find sync, we're already synced
            options,
        }
    }

    /// Returns the next complete packet, or None if the stream has ended.
    ///
    /// # Errors
    ///
    /// Returns an error if:
    /// - I/O error occurs
    /// - Ogg page structure is invalid
    /// - CRC32 validation fails (if enabled)
    /// - Multiple logical bitstreams detected
    pub fn next_packet(&mut self) -> Result<Option<Vec<u8>>, OggContainerError> {
        loop {
            if let Some(packet) = self.queue.pop_front() {
                return Ok(Some(packet));
            }
            if self.finished {
                return Ok(None);
            }
            self.read_page()?;
        }
    }

    /// Reads bytes, first from sync_buffer then from the underlying reader.
    fn read_bytes(&mut self, buf: &mut [u8]) -> io::Result<usize> {
        if buf.is_empty() {
            return Ok(0);
        }

        let mut total = 0;

        // First, consume from sync_buffer
        if !self.sync_buffer.is_empty() {
            let to_copy = buf.len().min(self.sync_buffer.len());
            buf[..to_copy].copy_from_slice(&self.sync_buffer[..to_copy]);
            self.sync_buffer.drain(..to_copy);
            total += to_copy;
            if total == buf.len() {
                return Ok(total);
            }
        }

        // Then read from underlying reader
        while total < buf.len() {
            match Read::read(&mut self.reader, &mut buf[total..])? {
                0 => break,
                n => total += n,
            }
        }

        Ok(total)
    }

    /// Reads exactly buf.len() bytes or returns error.
    fn read_exact_from_source(&mut self, buf: &mut [u8]) -> Result<bool, OggContainerError> {
        let mut offset = 0;
        while offset < buf.len() {
            let n = self
                .read_bytes(&mut buf[offset..])
                .map_err(OggContainerError::from)?;
            if n == 0 {
                return if offset == 0 {
                    Ok(false) // Clean EOF
                } else {
                    Err(OggContainerError::Decode(
                        "unexpected EOF while reading page".into(),
                    ))
                };
            }
            offset += n;
        }
        Ok(true)
    }

    /// Searches for the Ogg sync pattern ("OggS") in the stream.
    ///
    /// This is called before reading the first page to handle streams that
    /// have garbage bytes at the beginning (e.g., HTTP headers, ID3 tags).
    /// It buffers up to max_sync_search bytes while searching.
    fn find_sync(&mut self) -> Result<(), OggContainerError> {
        if self.synced {
            return Ok(());
        }

        while self.sync_bytes_read < self.options.max_sync_search {
            let mut chunk = [0u8; 1024];
            let remaining = self.options.max_sync_search - self.sync_bytes_read;
            let to_read = remaining.min(chunk.len());
            if to_read == 0 {
                break;
            }
            let n = Read::read(&mut self.reader, &mut chunk[..to_read])
                .map_err(OggContainerError::from)?;
            if n == 0 {
                return Err(OggContainerError::Decode(
                    "EOF reached while searching for Ogg sync pattern".into(),
                ));
            }
            self.sync_bytes_read += n;
            self.sync_buffer.extend_from_slice(&chunk[..n]);

            // Search for "OggS" pattern
            if let Some(pos) = self
                .sync_buffer
                .windows(4)
                .position(|window| window == b"OggS")
            {
                // Found sync! Remove garbage bytes before it
                self.sync_buffer.drain(..pos);
                self.synced = true;
                return Ok(());
            }

            // Keep the buffer compact to avoid unbounded growth.
            if self.sync_buffer.len() > 3 {
                let keep_len = 3.min(self.sync_buffer.len());
                self.sync_buffer.drain(..self.sync_buffer.len() - keep_len);
            }
        }

        Err(OggContainerError::Decode(format!(
            "No Ogg sync pattern found in first {} bytes",
            self.options.max_sync_search
        )))
    }

    /// Reads a single Ogg page and processes its packets.
    ///
    /// This method:
    /// 1. Ensures we're synced to "OggS" pattern (if enabled)
    /// 2. Reads the 27-byte page header
    /// 3. Validates the CRC32 checksum (if enabled)
    /// 4. Reads the segment table
    /// 5. Reads the page data
    /// 6. Assembles packets from segments
    fn read_page(&mut self) -> Result<(), OggContainerError> {
        // Ensure we've found the sync pattern (if required)
        if self.options.find_sync {
            self.find_sync()?;
        }

        // Read 27-byte page header
        let mut header = [0u8; 27];
        if !self.read_exact_from_source(&mut header)? {
            self.finished = true;
            return Ok(());
        }

        // Validate Ogg page header
        if &header[0..4] != b"OggS" {
            return Err(OggContainerError::Decode(
                "invalid Ogg capture pattern".into(),
            ));
        }
        if header[4] != 0 {
            return Err(OggContainerError::Decode("unsupported Ogg version".into()));
        }

        let header_type = header[5];
        let bitstream_serial = u32::from_le_bytes([header[14], header[15], header[16], header[17]]);

        // Enforce single bitstream
        if let Some(serial) = self.stream_serial {
            if serial != bitstream_serial {
                return Err(OggContainerError::Decode(
                    "multiple logical streams are not supported".into(),
                ));
            }
        } else {
            self.stream_serial = Some(bitstream_serial);
        }

        // Read segment table
        let page_segments = header[26] as usize;
        let mut segment_table = vec![0u8; page_segments];
        self.read_exact_from_source(&mut segment_table)?;

        // Calculate page data length
        let data_len: usize = segment_table.iter().map(|&v| v as usize).sum();
        let mut data = vec![0u8; data_len];
        self.read_exact_from_source(&mut data)?;

        // Validate CRC32 if enabled
        if self.options.validate_crc {
            let expected_crc = u32::from_le_bytes([header[22], header[23], header[24], header[25]]);
            let mut crc_header = header;
            crc_header[22..26].copy_from_slice(&[0, 0, 0, 0]); // Zero out CRC field

            let mut crc = crc::vorbis_crc32_update(0, &crc_header);
            crc = crc::vorbis_crc32_update(crc, &segment_table);
            crc = crc::vorbis_crc32_update(crc, &data);

            if crc != expected_crc {
                return Err(OggContainerError::Decode(format!(
                    "CRC32 mismatch: expected 0x{expected_crc:08x}, got 0x{crc:08x}"
                )));
            }
        }

        // Validate continuation flags
        if header_type & 0x01 != 0 && self.current_packet.is_empty() {
            return Err(OggContainerError::Decode(
                "unexpected continuation flag without existing packet".into(),
            ));
        }
        if header_type & 0x01 == 0 && !self.current_packet.is_empty() {
            return Err(OggContainerError::Decode(
                "dangling packet without continuation flag".into(),
            ));
        }

        // Assemble packets from segments
        let mut offset: usize = 0;
        for &seg_len in &segment_table {
            let len = seg_len as usize;
            let end = offset
                .checked_add(len)
                .ok_or_else(|| OggContainerError::Decode("segment length overflow".into()))?;
            if end > data.len() {
                return Err(OggContainerError::Decode(
                    "segment exceeds page data".into(),
                ));
            }
            self.current_packet.extend_from_slice(&data[offset..end]);
            offset = end;

            // Packet complete when segment is less than 255 bytes
            if seg_len < 255 {
                let packet = std::mem::take(&mut self.current_packet);
                self.queue.push_back(packet);
            }
        }

        if offset != data.len() {
            return Err(OggContainerError::Decode(
                "page data not fully consumed".into(),
            ));
        }

        // Check for end-of-stream
        if header_type & 0x04 != 0 && self.current_packet.is_empty() {
            self.finished = true;
        }

        Ok(())
    }
}

/// CRC32 calculation for Ogg pages.
///
/// This module implements the CRC32 algorithm used by the Ogg container format.
/// The polynomial is 0x04c11db7 with initial value 0 and no final XOR.
pub(crate) mod crc {
    /// Precomputed CRC32 lookup table for Ogg.
    ///
    /// Generated using the polynomial 0x04c11db7.
    const fn get_tbl_elem(idx: u32) -> u32 {
        let mut r: u32 = idx << 24;
        let mut i = 0;
        while i < 8 {
            r = (r << 1) ^ (-(((r >> 31) & 1) as i32) as u32 & 0x04c11db7);
            i += 1;
        }
        r
    }

    const fn lookup_array() -> [u32; 0x100] {
        let mut lup_arr: [u32; 0x100] = [0; 0x100];
        let mut i = 0;
        while i < 0x100 {
            lup_arr[i] = get_tbl_elem(i as u32);
            i += 1;
        }
        lup_arr
    }

    static CRC_LOOKUP_ARRAY: &[u32] = &lookup_array();

    /// Updates the CRC32 value with new data.
    ///
    /// # Arguments
    ///
    /// * `cur` - Current CRC32 value (use 0 for initial call)
    /// * `array` - Data to include in CRC calculation
    ///
    /// # Returns
    ///
    /// Updated CRC32 value
    pub fn vorbis_crc32_update(cur: u32, array: &[u8]) -> u32 {
        let mut ret: u32 = cur;
        for av in array {
            ret = (ret << 8) ^ CRC_LOOKUP_ARRAY[(*av as u32 ^ (ret >> 24)) as usize];
        }
        ret
    }
}
