use crate::{codec::Decode, util::PartialBuffer};
use std::{
    fmt,
    io::{Error, ErrorKind, Result},
};

use brotli2::raw::{DeStatus, Decompress};

pub struct BrotliDecoder {
    decompress: Decompress,
}

impl BrotliDecoder {
    pub(crate) fn new() -> Self {
        Self {
            decompress: Decompress::new(),
        }
    }

    fn decode(
        &mut self,
        input: &mut PartialBuffer<&[u8]>,
        output: &mut PartialBuffer<&mut [u8]>,
    ) -> Result<DeStatus> {
        let mut in_buf = input.unwritten();
        let mut out_buf = output.unwritten_mut();

        let original_input_len = in_buf.len();
        let original_output_len = out_buf.len();

        let status = self.decompress.decompress(&mut in_buf, &mut out_buf)?;

        let input_len = original_input_len - in_buf.len();
        let output_len = original_output_len - out_buf.len();

        input.advance(input_len);
        output.advance(output_len);

        Ok(status)
    }
}

impl Decode for BrotliDecoder {
    fn decode(
        &mut self,
        input: &mut PartialBuffer<&[u8]>,
        output: &mut PartialBuffer<&mut [u8]>,
    ) -> Result<bool> {
        match self.decode(input, output)? {
            DeStatus::Finished => Ok(true),
            DeStatus::NeedOutput | DeStatus::NeedInput => Ok(false),
        }
    }

    fn flush(&mut self, output: &mut PartialBuffer<&mut [u8]>) -> Result<bool> {
        match self.decode(&mut PartialBuffer::new(&[][..]), output)? {
            DeStatus::Finished | DeStatus::NeedInput => Ok(true),
            DeStatus::NeedOutput => Ok(false),
        }
    }

    fn finish(&mut self, output: &mut PartialBuffer<&mut [u8]>) -> Result<bool> {
        match self.decode(&mut PartialBuffer::new(&[][..]), output)? {
            DeStatus::Finished => Ok(true),
            DeStatus::NeedOutput => Ok(false),
            DeStatus::NeedInput => Err(Error::new(
                ErrorKind::UnexpectedEof,
                "reached unexpected EOF",
            )),
        }
    }
}

impl fmt::Debug for BrotliDecoder {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("BrotliDecoder")
            .field("decompress", &"<no debug>")
            .finish()
    }
}
