use std::{
    fs::File,
    io::{Seek, Write},
    path::{Path, PathBuf},
};
use crate::*;
pub fn compress<W: Write + Seek>(src: impl AsRef<Path>, dest: W) -> Result<W, Error> {
    let mut z = SevenZWriter::new(dest)?;
    let parent = if src.as_ref().is_dir() {
        src.as_ref()
    } else {
        src.as_ref().parent().unwrap_or(src.as_ref())
    };
    compress_path(src.as_ref(), parent, &mut z)?;
    z.finish().map_err(Error::io)
}
#[cfg(feature = "aes256")]
pub fn compress_encypted<W: Write + Seek>(
    src: impl AsRef<Path>,
    dest: W,
    password: Password,
) -> Result<W, Error> {
    let mut z = SevenZWriter::new(dest)?;
    if !password.is_empty() {
        z.set_content_methods(vec![
            aes256sha256::AesEncoderOptions::new(password).into(),
            SevenZMethod::LZMA2.into(),
        ]);
    }
    let parent = if src.as_ref().is_dir() {
        src.as_ref()
    } else {
        src.as_ref().parent().unwrap_or(src.as_ref())
    };
    compress_path(src.as_ref(), parent, &mut z)?;
    z.finish().map_err(Error::io)
}
pub fn compress_to_path(src: impl AsRef<Path>, dest: impl AsRef<Path>) -> Result<(), Error> {
    if let Some(p) = dest.as_ref().parent() {
        if !p.exists() {
            std::fs::create_dir_all(p)
                .map_err(|e| Error::io_msg(e, format!("Create dir failed:{:?}", dest.as_ref())))?;
        }
    }
    compress(
        src,
        File::create(dest.as_ref())
            .map_err(|e| Error::file_open(e, dest.as_ref().to_string_lossy().to_string()))?,
    )?;
    Ok(())
}
#[cfg(feature = "aes256")]
pub fn compress_to_path_encrypted(
    src: impl AsRef<Path>,
    dest: impl AsRef<Path>,
    password: Password,
) -> Result<(), Error> {
    if let Some(p) = dest.as_ref().parent() {
        if !p.exists() {
            std::fs::create_dir_all(p)
                .map_err(|e| Error::io_msg(e, format!("Create dir failed:{:?}", dest.as_ref())))?;
        }
    }
    compress_encypted(
        src,
        File::create(dest.as_ref())
            .map_err(|e| Error::file_open(e, dest.as_ref().to_string_lossy().to_string()))?,
        password,
    )?;
    Ok(())
}
fn compress_path<W: Write + Seek, P: AsRef<Path>>(
    src: P,
    root: &Path,
    z: &mut SevenZWriter<W>,
) -> Result<(), Error> {
    let entry_name = src
        .as_ref()
        .strip_prefix(root)
        .map_err(|e| Error::other(e.to_string()))?
        .to_string_lossy()
        .to_string();
    let entry = SevenZArchiveEntry::from_path(src.as_ref(), entry_name);
    let path = src.as_ref();
    if path.is_dir() {
        for dir in path
            .read_dir()
            .map_err(|e| Error::io_msg(e, "error read dir"))?
        {
            let dir = dir.map_err(Error::io)?;
            let ftype = dir.file_type().map_err(Error::io)?;
            if ftype.is_dir() || ftype.is_file() {
                compress_path(dir.path(), root, z)?;
            }
        }
    } else {
        z.push_archive_entry(
            entry,
            Some(
                File::open(path)
                    .map_err(|e| Error::file_open(e, path.to_string_lossy().to_string()))?,
            ),
        )?;
    }
    Ok(())
}
impl<W: Write + Seek> SevenZWriter<W> {
    #[inline]
    pub fn push_source_path(
        &mut self,
        path: impl AsRef<Path>,
        filter: impl Fn(&Path) -> bool,
    ) -> Result<&mut Self, crate::Error> {
        let (entries, reader) = collect_entries_and_reader(&path, filter).map_err(|e| {
            crate::Error::io_msg(
                e,
                format!("Failed to collect entries from path:{:?}", path.as_ref()),
            )
        })?;
        self.push_archive_entries(entries, reader)
    }
}
fn collect_file_paths(
    src: impl AsRef<Path>,
    paths: &mut Vec<PathBuf>,
    filter: &dyn Fn(&Path) -> bool,
) -> std::io::Result<()> {
    let path = src.as_ref();
    if !filter(path) {
        return Ok(());
    }
    if path.is_dir() {
        for dir in path.read_dir()? {
            let dir = dir?;
            let ftype = dir.file_type()?;
            if ftype.is_file() || ftype.is_dir() {
                collect_file_paths(dir.path(), paths, filter)?;
            }
        }
    } else {
        paths.push(path.to_path_buf())
    }
    Ok(())
}
pub fn collect_entries_and_reader(
    src: impl AsRef<Path>,
    filter: impl Fn(&Path) -> bool,
) -> std::io::Result<(Vec<SevenZArchiveEntry>, SeqReader<SourceReader<File>>)> {
    let mut entries = Vec::new();
    let mut paths = Vec::new();
    collect_file_paths(&src, &mut paths, &filter)?;
    for ele in paths.iter() {
        let name = ele
            .strip_prefix(&src)
            .unwrap()
            .to_string_lossy()
            .to_string();
        entries.push(SevenZArchiveEntry::from_path(ele, name))
    }
    let files: Vec<SourceReader<_>> = paths
        .iter()
        .map(|p| File::open(p).unwrap().into())
        .collect();
    Ok((entries, SeqReader::new(files)))
}