pub mod archive;
pub mod diff;
pub mod time;

use std::{borrow::Cow, fs, io, path::Path};

#[derive(rust_embed::Embed)]
#[folder = "../resources/test"]
pub struct TestResources;

#[derive(rust_embed::Embed)]
#[folder = "../lib"]
pub struct LibSourceCode;

pub trait EmbedExt {
    fn extract_all(into: impl AsRef<Path>) -> io::Result<()>;
    fn extract_in(item: &str, into: impl AsRef<Path>) -> io::Result<()>;
    fn item_iter() -> impl Iterator<Item = (Cow<'static, str>, rust_embed::EmbeddedFile)>;
}

impl<T: rust_embed::Embed> EmbedExt for T {
    fn extract_all(into: impl AsRef<Path>) -> io::Result<()> {
        extract_all::<Self>(into)
    }
    fn extract_in(item: &str, into: impl AsRef<Path>) -> io::Result<()> {
        extract_in::<Self>(item, into)
    }
    fn item_iter() -> impl Iterator<Item = (Cow<'static, str>, rust_embed::EmbeddedFile)> {
        item_iter::<Self>()
    }
}

pub fn item_iter<T: rust_embed::Embed>()
-> impl Iterator<Item = (Cow<'static, str>, rust_embed::EmbeddedFile)> {
    T::iter().flat_map(|i| T::get(&i).map(|embedded| (i, embedded)))
}

pub fn extract_all<T: rust_embed::Embed>(into: impl AsRef<Path>) -> io::Result<()> {
    let path = into.as_ref();
    T::iter().try_for_each(|i| {
        let path = path.join(i.as_ref());
        if let Some(parent) = path.parent() {
            fs::create_dir_all(parent)?;
        }
        fs::write(path, T::get(&i).unwrap().data)
    })
}

pub fn extract_in<T: rust_embed::Embed>(item: &str, into: impl AsRef<Path>) -> io::Result<()> {
    let path = into.as_ref();
    if let Some(b) = T::get(item) {
        let path = path.join(item);
        if let Some(parent) = path.parent() {
            fs::create_dir_all(parent)?;
        }
        fs::write(path, b.data)?;
        return Ok(());
    }
    T::iter().try_for_each(|i| {
        if i.starts_with(item) {
            let path = path.join(i.as_ref());
            if let Some(parent) = path.parent() {
                fs::create_dir_all(parent)?;
            }
            fs::write(path, T::get(&i).unwrap().data)?;
        }
        Ok(())
    })
}

pub fn setup() {
    fs::create_dir_all(env!("CARGO_TARGET_TMPDIR")).expect("Failed to create working dir");
    std::env::set_current_dir(env!("CARGO_TARGET_TMPDIR")).expect("Failed to set current dir");
}

pub fn copy_dir_all(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> io::Result<()> {
    fs::create_dir_all(&dst)?;
    for entry in fs::read_dir(src)? {
        let entry = entry?;
        let ty = entry.file_type()?;
        if ty.is_dir() {
            copy_dir_all(entry.path(), dst.as_ref().join(entry.file_name()))?;
        } else {
            fs::copy(entry.path(), dst.as_ref().join(entry.file_name()))?;
        }
    }
    Ok(())
}

pub fn remove_with_empty_parents(path: impl AsRef<Path>) -> io::Result<()> {
    fn inner(path: &Path) -> io::Result<()> {
        pna::fs::remove_path_all(path)?;
        let mut current_path = path;
        while let Some(dir) = current_path.parent() {
            if fs::read_dir(dir)?.next().is_none() {
                fs::remove_dir(dir)?;
                current_path = dir;
            } else {
                break;
            }
        }
        Ok(())
    }
    inner(path.as_ref())
}
