use std::path::PathBuf;
use tracing::*;

/// Recursively list all files inside a directory.
pub fn find_files(path: PathBuf) -> Result<FindFiles, std::io::Error> {
    debug!("find_files {:?}", path);
    let meta = std::fs::metadata(&path)?;
    Ok(FindFiles {
        stack: vec![(path, meta)],
    })
}

/// An iterator recursively listing all files inside a directory.
pub struct FindFiles {
    stack: Vec<(PathBuf, std::fs::Metadata)>,
}

impl Iterator for FindFiles {
    type Item = (PathBuf, std::fs::Metadata);
    fn next(&mut self) -> Option<Self::Item> {
        while let Some((p, meta)) = self.stack.pop() {
            debug!("find_files {:?}", p);
            if meta.is_symlink() || meta.is_file() {
                return Some((p, meta));
            } else if let Ok(dir) = std::fs::read_dir(&p) {
                for e in dir {
                    if let Ok(e) = e {
                        self.stack.push((e.path(), e.metadata().unwrap()));
                    }
                }
                return Some((p, meta));
            } else {
                error!("could not read {:?}", p);
                continue;
            }
        }
        None
    }
}

/// Recursively list all directories inside a directory.
pub fn find_dirs(path: PathBuf) -> Result<FindDirs, std::io::Error> {
    debug!("find_files {:?}", path);
    let meta = std::fs::metadata(&path)?;
    Ok(FindDirs {
        stack: vec![(path, meta)],
    })
}

/// An iterator recursively listing all directories inside a directory.
pub struct FindDirs {
    stack: Vec<(PathBuf, std::fs::Metadata)>,
}

impl Iterator for FindDirs {
    type Item = (PathBuf, std::fs::Metadata);
    fn next(&mut self) -> Option<Self::Item> {
        while let Some((p, meta)) = self.stack.pop() {
            if meta.is_symlink() {
                continue;
            }
            if let Ok(dir) = std::fs::read_dir(&p) {
                for e in dir {
                    if let Ok(e) = e {
                        self.stack.push((e.path(), e.metadata().unwrap()));
                    }
                }
                return Some((p, meta));
            }
        }
        None
    }
}