//! Bundle your distribution as a TAR archive.

use std::io;
use std::path::Path;
use tracing::debug;

use crate::fs;

use super::{Bundler, BundleMetadata};


/// Provides a TAR archive of the distribution files,
/// typically compressed as `.tar.gz` file.
///
/// To be used with [`Distribution::bundle`](crate::Distribution::bundle).
#[derive(Default)]
pub struct TarBundler {
    /// The compression algorithm to use.
    pub compression: TarCompression,
}

/// Supported compression algorithms.
pub enum TarCompression {
    /// Gzip compression.
    Gz {
        /// The compression level to use.
        level: TarGzCompressionLevel
    },
}
impl Default for TarCompression {
    fn default() -> Self {
        TarCompression::Gz { level: TarGzCompressionLevel::default() }
    }
}

/// Compression level to use, offering a trade-off between compression speed and file size.
pub type TarGzCompressionLevel = flate2::Compression;


impl Bundler for TarBundler {
    fn file_extension(&self) -> &str {
        match self.compression {
            TarCompression::Gz { level: _ } => ".tar.gz",
        }
    }

    fn bundle(&self, in_dir: &Path, out_path: &Path, metadata: &BundleMetadata) -> cicero_core::Result<()> {
        debug!("Bundling archive: {out_path:?}");

        bundle(in_dir, out_path, &metadata.distribution_name, &self.compression)?;
        Ok(())
    }
}

fn bundle(in_dir: &Path, out_file: &Path, distribution_name: &str, compression: &TarCompression) -> io::Result<()> {

    let mut tar_file = {
        let tar_file = fs::File::create(out_file)?;

        tar::Builder::new(
            match compression {
                TarCompression::Gz { level } => flate2::write::GzEncoder::new(tar_file, *level)
            }
        )
    };

    tar_file.append_dir_all(distribution_name, in_dir)?;

    tar_file.finish()
}


#[cfg(test)]
mod tests {
    use crate::{distribution::tests::DistributionFixture, DistributionOptions};

    use super::*;

    use cicero_core::error::Context as _;


    #[test]
    fn should_bundle_as_tar_gz() -> cicero_core::Result<()> {
        let fixture = DistributionFixture::create()?;
        let distribution = fixture.testee(DistributionOptions::default())?;

        distribution.root
            .add_file("file1", |file| fs::write(file, "Hello").context("Adding file 'file1'"))?;

        let tar_file = distribution.bundle(TarBundler {
            compression: TarCompression::Gz { level: TarGzCompressionLevel::none() },
        })?;

        assert!(tar_file.to_string_lossy().ends_with(".tar.gz"), "Expected file path to end with '.tar.gz', but got: {tar_file:?}");
        assert!(fs::metadata(tar_file)?.len() > 0);

        Ok(())
    }
}
