use mousiki::decoder::Decoder;
use mousiki::oggreader::{OggRead, OggReader, OggReaderError, ReadError};

pub const MAX_PCM_BYTES: usize = 1920;
pub const OPUS_TAGS_SIGNATURE: &[u8] = b"OpusTags";
pub const TINY_OGG: &[u8] =
    include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/testdata/tiny.ogg"));

pub struct SliceReader<'a> {
    data: &'a [u8],
    position: usize,
}

impl<'a> SliceReader<'a> {
    pub fn new(data: &'a [u8]) -> Self {
        Self { data, position: 0 }
    }
}

impl<'a> OggRead for SliceReader<'a> {
    fn read(&mut self, buf: &mut [u8]) -> Result<usize, ReadError> {
        if self.position >= self.data.len() {
            return Ok(0);
        }

        let remaining = self.data.len() - self.position;
        let to_copy = remaining.min(buf.len());
        buf[..to_copy].copy_from_slice(&self.data[self.position..self.position + to_copy]);
        self.position += to_copy;
        Ok(to_copy)
    }
}

pub fn fuzz_decoder(data: &[u8]) {
    let _ = decode_stream(data);
}

pub fn decode_stream(data: &[u8]) -> Result<usize, ()> {
    let mut out = [0u8; MAX_PCM_BYTES];

    let (mut ogg, _) = OggReader::new_with(SliceReader::new(data)).map_err(|_| ())?;
    let mut decoder = Decoder::new();
    let mut decoded_frames = 0usize;

    loop {
        let (segments, _) = match ogg.parse_next_page() {
            Ok(result) => result,
            Err(OggReaderError::Read(ReadError::UnexpectedEof)) => break,
            Err(_) => return Err(()),
        };

        if let Some(first_segment) = segments.get(0)
            && first_segment.starts_with(OPUS_TAGS_SIGNATURE)
        {
            continue;
        }

        for segment in segments.into_iter() {
            if segment.is_empty() {
                continue;
            }

            match decoder.decode(segment, &mut out) {
                Ok(_) => decoded_frames += 1,
                Err(_) => return Err(()),
            }
        }
    }

    Ok(decoded_frames)
}
