use anyhow::Context;
use std::path::{Path, PathBuf};
use structopt::StructOpt;
use summain::ManifestEntry;
use walkdir::WalkDir;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let opt = Opt::from_args();
    let mut files = vec![];
    for pathname in opt.pathnames {
        find(&pathname, &mut files)?;
    }
    files.sort();

    // Get metadata about all files, but don't compute checksum yet.
    //
    // This all runs in async worker threads, since it's theoretically
    // I/O heavy and benefits from async task context switching. We
    // create a task for each named file, since tasks are very cheap.
    // We keep the JoinHandle for each task in a vector: there's not
    // going to be so many handles that the vector becomes impossibly
    // large: Linux only allows 128 KiB of command line arguments.

    let mut handles = vec![];
    for filename in files.iter().cloned() {
        handles.push(tokio::spawn(async move { manifest(filename).await }));
    }

    // Compute checksums for regular files.
    //
    // This runs in blocking threads, since it's CPU heavy. We create
    // another vector of JoinHandles. Again, it won't become too large.

    let mut sumhandles = vec![];
    for h in handles {
        let mut m: ManifestEntry = h.await??;
        let h = tokio::task::spawn_blocking(move || match m.compute_checksum() {
            Err(e) => Err(e),
            Ok(_) => Ok(m),
        });
        sumhandles.push(h)
    }

    // Wait for checksums to be available and print manifest.
    //
    // Note how this iterates over the results in the right order.

    for h in sumhandles {
        let m: ManifestEntry = h.await??;
        print!("{}", serde_yaml::to_string(&m)?);
    }

    Ok(())
}

fn find(root: &Path, files: &mut Vec<PathBuf>) -> anyhow::Result<()> {
    let meta = std::fs::symlink_metadata(root)?;
    if meta.file_type().is_symlink() {
        // Walkdir doesn't handle dangling symlinks so we handle all
        // symlinks here.
        files.push(root.to_path_buf());
        return Ok(());
    }
    for e in WalkDir::new(root) {
        files.push(e?.path().to_path_buf());
    }
    Ok(())
}

#[derive(StructOpt, Debug)]
struct Opt {
    #[structopt(parse(from_os_str))]
    pathnames: Vec<PathBuf>,
}

async fn manifest(path: PathBuf) -> anyhow::Result<ManifestEntry> {
    ManifestEntry::new(&path)
        .await
        .with_context(|| format!("{}", path.display()))
}
