use crate::Settings;
use anyhow::Result;
use std::{
    fs::OpenOptions,
    io::{BufReader, Read, Write},
    path::Path,
};
use zstd::stream::copy_encode;

macro_rules! u64_from_usize {
    ($value:expr) => {{
        let value: usize = $value;

        // Assuming we're on a 32-bit or 64-bit platform, converting from a usize to a u64 will
        // always succeed. However, we don't want to have to litter the code with `unwrap`,
        // `unreachable!()`, etc. Putting the conditional compilation only inside the error path
        // handles both generating a compiler error on unsupported platforms and eliminating any
        // need to handle the error.
        u64::try_from(value).unwrap_or_else(|_| {
            static_assertions_next::assert_cfg!(any(
                target_pointer_width = "32",
                target_pointer_width = "64"
            ));
            unreachable!();
        })
    }};
}

impl Settings {
    pub(crate) fn export_helper(&self, destination: &Path) -> Result<()> {
        let mut destination_file = OpenOptions::new()
            .create(true)
            .write(true)
            .truncate(true)
            .open(destination)?;

        let settings_file = self.open_mod_settings_file()?;
        export_file(settings_file, &mut destination_file)?;

        for entry in self.read_mod_files_dir()? {
            let entry = entry?;

            let file_name = entry.file_name();
            let extension = Path::new(&file_name).extension().unwrap_or_default();

            if !&extension.eq_ignore_ascii_case("pak") {
                continue;
            }

            todo!()

            // let mod_file = self.open_mod_file(&file_name)?;
            // export_file(mod_file, &mut destination_file)?;

            // let file_name_bytes = file_name.as_encoded_bytes();
            // destination_file.write_all(file_name_bytes)?;
            // destination_file.write_all(
            //     u64_from_usize!(file_name_bytes.len())
            //         .to_le_bytes()
            //         .as_slice(),
            // )?;
        }

        Ok(())
    }
}

struct CountWrite<W> {
    num_bytes_written: usize,
    inner: W,
}

impl<W> CountWrite<W> {
    const fn new(inner: W) -> Self {
        Self {
            num_bytes_written: 0,
            inner,
        }
    }
}

impl<W: Write> Write for CountWrite<W> {
    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
        let num_bytes_written = self.inner.write(buf)?;
        self.num_bytes_written += num_bytes_written;
        Ok(num_bytes_written)
    }

    fn flush(&mut self) -> std::io::Result<()> {
        self.inner.flush()
    }
}

fn export_file(file: impl Read, destination: impl Write) -> Result<()> {
    let mut file = BufReader::new(file);
    let mut destination = CountWrite::new(destination);

    copy_encode(&mut file, &mut destination, 0)?;

    destination.write_all(
        u64_from_usize!(destination.num_bytes_written)
            .to_le_bytes()
            .as_slice(),
    )?;

    Ok(())
}
