@ 261104.1
current
use std::ffi::OsStr;

use anyhow::{Result, anyhow};
use globset::{Glob, GlobBuilder, GlobMatcher, GlobSet, GlobSetBuilder};
use memchr::memmem;
use regex::bytes::{RegexSet, RegexSetBuilder};

use crate::regex_helper::{self, pattern_matches_strings_with_leading_dot};

pub trait Matcher {
    fn matches_path(&self, path: &OsStr) -> bool;
}

pub type Patterns = Box<dyn Matcher + Send + Sync>;

#[derive(Eq, PartialEq, Copy, Clone)]
pub enum PatternType {
    Regex,
    Fixed,
    Glob,
    Exact,
}

impl Matcher for RegexSet {
    fn matches_path(&self, path: &OsStr) -> bool {
        let haystack = path.as_encoded_bytes();
        let matches = self.matches(haystack);
        // Return true if the number of regexes that matched
        // equals the total number of regexes.
        matches.iter().count() == self.len()
    }
}

impl Matcher for GlobSet {
    fn matches_path(&self, path: &OsStr) -> bool {
        self.matches_all(path)
    }
}

/// In the common case a single glob, it is simpler, and
/// faster to just use a single Glob instead of a GlobSet
impl Matcher for GlobMatcher {
    fn matches_path(&self, path: &OsStr) -> bool {
        self.is_match(path)
    }
}

/// Matcher that matches fixed strings
pub struct FixedStrings(pub Vec<String>);

impl Matcher for FixedStrings {
    fn matches_path(&self, path: &OsStr) -> bool {
        let path = path.as_encoded_bytes();
        self.0.iter().all(|f| bytes_contains(path, f.as_bytes()))
    }
}

pub struct ExactStrings(pub Vec<String>);

impl Matcher for ExactStrings {
    fn matches_path(&self, path: &OsStr) -> bool {
        self.0.iter().all(|s| s.as_str() == path)
    }
}

/// Matcher that matches everything
pub struct MatchAll;
impl Matcher for MatchAll {
    fn matches_path(&self, _path: &OsStr) -> bool {
        true
    }
}
pub fn build_patterns(
    mut patterns: Vec<String>,
    pattern_type: PatternType,
    ignore_case: bool,
) -> Result<Patterns> {
    if patterns.is_empty() || is_empty_pattern(&patterns, pattern_type) {
        return Ok(Box::new(MatchAll));
    }
    match pattern_type {
        PatternType::Glob => build_glob_matcher(patterns, ignore_case),
        PatternType::Fixed if !ignore_case => Ok(Box::new(FixedStrings(patterns))),
        PatternType::Exact if !ignore_case => Ok(Box::new(ExactStrings(patterns))),
        typ => {
            // TODO: is there a better way we could handle case insensitive fixed strings?
            if typ == PatternType::Fixed {
                for pattern in patterns.iter_mut() {
                    *pattern = regex::escape(pattern);
                }
            } else if typ == PatternType::Exact {
                // Would using eq_ignore_ascii case be good enough?
                for pattern in patterns.iter_mut() {
                    // Anchor the escaped pattern so the full filename (or path) must match exactly.
                    // Literal. No substring matching.
                    *pattern = format!("^{}$", regex::escape(pattern));
                }
            }
            Ok(Box::new(build_regex_matcher(patterns, ignore_case)?))
        }
    }
}

pub fn patterns_match_strings_with_leading_dots(
    patterns: &[String],
    pattern_type: PatternType,
) -> bool {
    let mut iter = patterns.iter();
    match pattern_type {
        PatternType::Regex => iter.any(|pat| pattern_matches_strings_with_leading_dot(pat)),
        // fixed strings aren't anchored so always false
        PatternType::Fixed => false,
        // globs and exact just check if it starts with a .
        PatternType::Glob | PatternType::Exact => patterns.iter().any(|pat| pat.starts_with(".")),
    }
}

pub fn pattern_has_uppercase_char(pattern_type: PatternType, pattern: &str) -> bool {
    match pattern_type {
        PatternType::Regex => regex_helper::pattern_has_uppercase_char(pattern),
        // For everything else it's sufficient to just check if there are any uppercase letters
        _ => pattern.chars().any(char::is_uppercase),
    }
}

fn build_glob_matcher(patterns: Vec<String>, ignore_case: bool) -> Result<Patterns> {
    Ok(if patterns.len() == 1 {
        Box::new(build_glob(&patterns[0], ignore_case)?.compile_matcher())
    } else {
        let mut builder = GlobSetBuilder::new();
        for pat in patterns {
            builder.add(build_glob(&pat, ignore_case)?);
        }
        Box::new(builder.build()?)
    })
}

fn build_glob(pattern: &str, ignore_case: bool) -> Result<Glob> {
    Ok(GlobBuilder::new(pattern)
        .literal_separator(true)
        .case_insensitive(ignore_case)
        .build()?)
}

fn build_regex_matcher(patterns: Vec<String>, ignore_case: bool) -> Result<RegexSet> {
    RegexSetBuilder::new(patterns)
        .case_insensitive(ignore_case)
        .dot_matches_new_line(true)
        .build()
        .map_err(|e| {
            anyhow!(
                "{}\n\nNote: You can search for literal substrings with '--fixed-strings' \
                 or literal strings with '--exact' options (instead of a regular expression). \
                 Alternatively, you can also use the '--glob' option to match on a glob pattern.",
                e,
            )
        })
}

fn is_empty_pattern(patterns: &[String], pattern_type: PatternType) -> bool {
    pattern_type != PatternType::Exact && patterns.len() == 1 && patterns[0].is_empty()
}

/// Test if the needle is a substring of the haystack
fn bytes_contains(haystack: &[u8], needle: &[u8]) -> bool {
    memmem::find(haystack, needle).is_some()
}

#[test]
fn empty_matches_all() {
    let patterns_none = build_patterns(Vec::new(), PatternType::Glob, false).unwrap();
    let patterns_empty_str =
        build_patterns(vec!["".to_string()], PatternType::Glob, false).unwrap();

    assert!(patterns_none.matches_path("abcd".as_ref()));
    assert!(patterns_empty_str.matches_path("abcd".as_ref()));
}