#[cfg(feature = "gzip")]
use anyhow::Result;
#[cfg(feature = "gzip")]
use log::*;
#[cfg(feature = "gzip")]
use std::fs::File;
#[cfg(feature = "gzip")]
use std::io;
#[cfg(feature = "gzip")]
use std::io::{Read, Write};
#[cfg(feature = "gzip")]
use std::path::PathBuf;

#[cfg(feature = "gzip")]
use flate2::Compression;
#[cfg(feature = "gzip")]
use flate2::read::GzDecoder;
#[cfg(feature = "gzip")]
use flate2::write::GzEncoder;

#[cfg(feature = "gzip")]
use crate::compression_engine::CompressionEngine;

#[cfg(feature = "gzip")]
#[derive(Debug, Eq, PartialEq, Clone, Default)]
pub struct CompressionEngineGZip {}

#[cfg(feature = "gzip")]
impl CompressionEngineGZip {
    pub fn new() -> CompressionEngineGZip {
        CompressionEngineGZip {}
    }
}

#[cfg(feature = "gzip")]
impl CompressionEngine for CompressionEngineGZip {
    fn is_supported(&self) -> bool {
        true
    }

    fn get_status_info(&self) -> (String, String, String) {
        ("<INTERNAL>".to_string(), "".to_string(), "".to_string())
    }

    fn open(&self, file_path: PathBuf) -> Result<Box<dyn Read + Send>> {
        debug!("COMPRESSION: Opening {:?} using {:?}", file_path, *self);

        let file = File::open(file_path)?;
        Ok(Box::new(GzDecoder::new(file)))
    }

    fn create(&self, file_path: PathBuf) -> Result<Box<dyn Write>> {
        debug!("COMPRESSION: Writing to {:?} using {:?}", file_path, *self);

        let file = File::create(file_path)?;
        let gzip_write = GzEncoder::new(file, Compression::default());

        Ok(Box::new(AutoFinishGzEncoder::new(gzip_write)))
    }

    fn clone_box(&self) -> Box<dyn CompressionEngine> {
        Box::new(self.clone())
    }
}

#[cfg(feature = "gzip")]
#[derive(Debug)]
pub struct AutoFinishGzEncoder<W: Write> {
    encoder: Option<GzEncoder<W>>,
}

#[cfg(feature = "gzip")]
impl<W: Write> AutoFinishGzEncoder<W> {
    fn new(gz_encoder: GzEncoder<W>) -> AutoFinishGzEncoder<W> {
        AutoFinishGzEncoder {
            encoder: Some(gz_encoder),
        }
    }
}

#[cfg(feature = "gzip")]
impl<W: Write> Drop for AutoFinishGzEncoder<W> {
    fn drop(&mut self) {
        if let Some(encoder) = self.encoder.take() {
            debug!("COMPRESSION: Finishing");
            if let Err(e) = encoder.finish() {
                warn!("Failed to finish GZip encoder: {e}");
            }
        }
    }
}

#[cfg(feature = "gzip")]
impl<W: Write> Write for AutoFinishGzEncoder<W> {
    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
        self.encoder.as_mut().unwrap().write(buf)
    }

    fn flush(&mut self) -> io::Result<()> {
        self.encoder.as_mut().unwrap().flush()
    }
}
