use ignore::{WalkBuilder, WalkState};
use std::{
    fs::File,
    path::{Path, PathBuf},
    sync::{
        atomic::{AtomicUsize, Ordering},
        Arc,
    },
};

// This function visits each file in the given directory and calls the given callback with the path
// and the file. It skips files which cannot be read (e.g., due to lack of permissions). It also
// skips over symlinks. The number of files traversed is returned.
pub fn walk<T: 'static + Clone + Send + FnMut(&Path, File)>(
    paths: &[PathBuf],
    callback: T,
) -> usize {
    // Keep track of the number of files traversed, and allow multiple threads to update it.
    let files_scanned = Arc::new(AtomicUsize::new(0));

    // Scan each of the given paths.
    for path in paths {
        // Traverse the filesystem in parallel.
        WalkBuilder::new(path).build_parallel().run(|| {
            // These clones will be moved into the closure below, and that closure will be sent to a
            // new thread.
            let mut callback = callback.clone();
            let files_scanned = files_scanned.clone();

            // This closure will be sent to a new thread.
            Box::new(move |result| {
                // Proceed if we have access to the path.
                if let Ok(dir_entry) = result {
                    // Here, `file_type()` should always return a `Some`. It could only return
                    // `None` if the file represents STDIN, and that isn't the case here.
                    if dir_entry.file_type().unwrap().is_file() {
                        // Try to open the file.
                        let possible_file = File::open(dir_entry.path());
                        if let Ok(file) = possible_file {
                            // Process the file and increment the counter.
                            callback(dir_entry.path(), file);
                            files_scanned.fetch_add(1, Ordering::SeqCst);
                        }
                    }
                }

                // Don't stop...believing!
                WalkState::Continue
            })
        });
    }

    // Return the number of files traversed.
    files_scanned.load(Ordering::SeqCst)
}
