#[cfg(any(
    feature = "zstd-compression",
    feature = "zlib-compression",
    feature = "snappy-compression"
))]
#[cfg(test)]
mod test;
#[cfg(feature = "zlib-compression")]
use flate2::{
    write::{ZlibDecoder, ZlibEncoder},
    Compression,
};
#[cfg(feature = "zlib-compression")]
use std::convert::TryInto;
#[cfg(any(
    feature = "zstd-compression",
    feature = "zlib-compression",
    feature = "snappy-compression"
))]
use std::io::Write;
use crate::error::{Error, ErrorKind, Result};
#[derive(Clone, Debug, PartialEq)]
pub(crate) enum CompressorId {
    Noop = 0,
    #[cfg(feature = "snappy-compression")]
    Snappy = 1,
    #[cfg(feature = "zlib-compression")]
    Zlib = 2,
    #[cfg(feature = "zstd-compression")]
    Zstd = 3,
}
impl CompressorId {
    pub(crate) fn from_u8(id: u8) -> Result<Self> {
        match id {
            0 => Ok(CompressorId::Noop),
            #[cfg(feature = "snappy-compression")]
            1 => Ok(CompressorId::Snappy),
            #[cfg(feature = "zlib-compression")]
            2 => Ok(CompressorId::Zlib),
            #[cfg(feature = "zstd-compression")]
            3 => Ok(CompressorId::Zstd),
            other => Err(ErrorKind::InvalidResponse {
                message: format!("Invalid compressor id: {}", other),
            }
            .into()),
        }
    }
}
#[derive(Clone, Debug, PartialEq)]
#[non_exhaustive]
pub enum Compressor {
    #[cfg(feature = "zstd-compression")]
    Zstd {
        level: Option<i32>,
    },
    #[cfg(feature = "zlib-compression")]
    Zlib {
        level: Option<i32>,
    },
    #[cfg(feature = "snappy-compression")]
    Snappy,
}
impl Compressor {
    #[allow(unused_variables)]
    pub(crate) fn write_zlib_level(&mut self, level: i32) {
        #[cfg(feature = "zlib-compression")]
        if let Compressor::Zlib {
            level: ref mut zlib_level,
        } = *self
        {
            *zlib_level = if level == -1 { None } else { Some(level) }
        }
    }
    pub(crate) fn parse_str(s: &str) -> Result<Self> {
        match s.to_lowercase().as_str() {
            #[cfg(feature = "zlib-compression")]
            "zlib" => Ok(Compressor::Zlib { level: None }),
            #[cfg(feature = "zstd-compression")]
            "zstd" => Ok(Compressor::Zstd { level: None }),
            #[cfg(feature = "snappy-compression")]
            "snappy" => Ok(Compressor::Snappy),
            other => Err(Error::from(ErrorKind::InvalidArgument {
                message: format!("Invalid compressor: {} was supplied but is invalid", other),
            })),
        }
    }
    pub(crate) fn name(&self) -> &'static str {
        match *self {
            #[cfg(feature = "zstd-compression")]
            Compressor::Zstd { .. } => "zstd",
            #[cfg(feature = "zlib-compression")]
            Compressor::Zlib { .. } => "zlib",
            #[cfg(feature = "snappy-compression")]
            Compressor::Snappy => "snappy",
        }
    }
    pub(crate) fn id(&self) -> CompressorId {
        match *self {
            #[cfg(feature = "zstd-compression")]
            Compressor::Zstd { level: _ } => CompressorId::Zstd,
            #[cfg(feature = "zlib-compression")]
            Compressor::Zlib { level: _ } => CompressorId::Zlib,
            #[cfg(feature = "snappy-compression")]
            Compressor::Snappy => CompressorId::Snappy,
        }
    }
    pub(crate) fn validate(&self) -> Result<()> {
        #[allow(unreachable_patterns)]
        match *self {
            #[cfg(feature = "zstd-compression")]
            Compressor::Zstd { level: Some(level) }
                if !zstd::compression_level_range().contains(&level) =>
            {
                Err(Error::from(ErrorKind::InvalidArgument {
                    message: format!("invalid zstd level: {}", level),
                }))
            }
            #[cfg(feature = "zlib-compression")]
            Compressor::Zlib { level: Some(level) } if !(-1..10).contains(&level) => {
                Err(Error::from(ErrorKind::InvalidArgument {
                    message: format!("invalid zlib level: {}", level),
                }))
            }
            _ => Ok(()),
        }
    }
    pub(crate) fn to_encoder(&self) -> Result<Encoder> {
        match *self {
            #[cfg(feature = "zstd-compression")]
            Compressor::Zstd { level } => {
                let encoder =
                    zstd::Encoder::new(vec![], level.unwrap_or(zstd::DEFAULT_COMPRESSION_LEVEL))
                        .map_err(|e| {
                            Error::from(ErrorKind::Internal {
                                message: format!(
                                    "an error occurred getting a new zstd encoder: {}",
                                    e
                                ),
                            })
                        })?;
                Ok(Encoder::Zstd { encoder })
            }
            #[cfg(feature = "zlib-compression")]
            Compressor::Zlib { level } => {
                let level = match level {
                    Some(level) => Compression::new(level.try_into().map_err(|e| {
                        Error::from(ErrorKind::Internal {
                            message: format!("an invalid zlib compression level was given: {}", e),
                        })
                    })?),
                    _ => Compression::default(),
                };
                let encoder = ZlibEncoder::new(vec![], level);
                Ok(Encoder::Zlib { encoder })
            }
            #[cfg(feature = "snappy-compression")]
            Compressor::Snappy => Ok(Encoder::Snappy { bytes: vec![] }),
        }
    }
}
pub(crate) enum Encoder {
    #[cfg(feature = "zstd-compression")]
    Zstd {
        encoder: zstd::Encoder<'static, Vec<u8>>,
    },
    #[cfg(feature = "zlib-compression")]
    Zlib { encoder: ZlibEncoder<Vec<u8>> },
    #[cfg(feature = "snappy-compression")]
    Snappy { bytes: Vec<u8> },
}
#[allow(unused_variables)]
impl Encoder {
    pub(crate) fn write_all(&mut self, buf: &[u8]) -> Result<()> {
        match *self {
            #[cfg(feature = "zstd-compression")]
            Encoder::Zstd { ref mut encoder } => encoder.write_all(buf).map_err(|e| {
                ErrorKind::Internal {
                    message: format!("an error occurred writing to the zstd encoder: {}", e),
                }
                .into()
            }),
            #[cfg(feature = "zlib-compression")]
            Encoder::Zlib { ref mut encoder } => encoder.write_all(buf).map_err(|e| {
                ErrorKind::Internal {
                    message: format!("an error occurred writing to the zlib encoder: {}", e),
                }
                .into()
            }),
            #[cfg(feature = "snappy-compression")]
            Encoder::Snappy { ref mut bytes } => bytes.write_all(buf).map_err(|e| {
                ErrorKind::Internal {
                    message: format!("an error occurred writing to the snappy encoder: {}", e),
                }
                .into()
            }),
        }
    }
    pub(crate) fn finish(self) -> Result<Vec<u8>> {
        match self {
            #[cfg(feature = "zstd-compression")]
            Encoder::Zstd { encoder } => encoder.finish().map_err(|e| {
                ErrorKind::Internal {
                    message: format!("an error occurred finishing zstd encoder: {}", e),
                }
                .into()
            }),
            #[cfg(feature = "zlib-compression")]
            Encoder::Zlib { encoder } => encoder.finish().map_err(|e| {
                ErrorKind::Internal {
                    message: format!("an error occurred finishing zlib encoder: {}", e),
                }
                .into()
            }),
            #[cfg(feature = "snappy-compression")]
            Encoder::Snappy { bytes } => {
                let mut compressor = snap::raw::Encoder::new();
                compressor.compress_vec(bytes.as_slice()).map_err(|e| {
                    ErrorKind::Internal {
                        message: format!("an error occurred finishing snappy encoder: {}", e),
                    }
                    .into()
                })
            }
        }
    }
}
#[derive(Clone, Debug)]
pub(crate) enum Decoder {
    #[cfg(feature = "zstd-compression")]
    Zstd,
    #[cfg(feature = "zlib-compression")]
    Zlib,
    #[cfg(feature = "snappy-compression")]
    Snappy,
    Noop,
}
impl Decoder {
    pub(crate) fn decode(self, source: &[u8]) -> Result<Vec<u8>> {
        match self {
            #[cfg(feature = "zstd-compression")]
            Decoder::Zstd => {
                let mut ret = Vec::new();
                zstd::stream::copy_decode(source, &mut ret).map_err(|e| {
                    Error::from(ErrorKind::Internal {
                        message: format!("Could not decode using zstd decoder: {}", e),
                    })
                })?;
                Ok(ret)
            }
            #[cfg(feature = "zlib-compression")]
            Decoder::Zlib => {
                let mut decoder = ZlibDecoder::new(vec![]);
                decoder.write_all(source)?;
                decoder.finish().map_err(|e| {
                    ErrorKind::Internal {
                        message: format!("Could not decode using zlib decoder: {}", e),
                    }
                    .into()
                })
            }
            #[cfg(feature = "snappy-compression")]
            Decoder::Snappy => {
                let mut decompressor = snap::raw::Decoder::new();
                decompressor.decompress_vec(source).map_err(|e| {
                    ErrorKind::Internal {
                        message: format!("Could not decode using snappy decoder: {}", e),
                    }
                    .into()
                })
            }
            Decoder::Noop => Ok(source.to_vec()),
        }
    }
    pub(crate) fn from_u8(id: u8) -> Result<Self> {
        let compressor_id = CompressorId::from_u8(id)?;
        match compressor_id {
            CompressorId::Noop => Ok(Decoder::Noop),
            #[cfg(feature = "snappy-compression")]
            CompressorId::Snappy => Ok(Decoder::Snappy),
            #[cfg(feature = "zlib-compression")]
            CompressorId::Zlib => Ok(Decoder::Zlib),
            #[cfg(feature = "zstd-compression")]
            CompressorId::Zstd => Ok(Decoder::Zstd),
        }
    }
}