#![no_main]

use fzf_oxide::{chars, Matcher, MatcherConfig, Utf32Str};
use libfuzzer_sys::arbitrary::Arbitrary;
use libfuzzer_sys::fuzz_target;

#[derive(Arbitrary, Debug)]
pub struct Input<'a> {
    haystack: &'a str,
    needle: &'a str,
    ignore_case: bool,
    normalize: bool,
}

fuzz_target!(|data: Input<'_>| {
    let mut data = data;
    let mut config = MatcherConfig::DEFAULT;
    config.ignore_case = data.ignore_case;
    config.normalize = data.normalize;
    let mut matcher = Matcher::new(config);
    let mut indices_optimal = Vec::new();
    let mut indices_greedy = Vec::new();
    let mut needle_buf = Vec::new();
    let mut haystack_buf = Vec::new();
    let normalize = |mut c: char| {
        if config.normalize {
            c = chars::normalize(c);
        }
        if config.ignore_case {
            c = chars::to_lower_case(c);
        }
        c
    };
    let needle: String = data.needle.chars().map(normalize).collect();
    let needle_chars: Vec<_> = needle.chars().collect();
    let needle = Utf32Str::new(&needle, &mut needle_buf);
    let haystack = Utf32Str::new(data.haystack, &mut haystack_buf);

    let greedy_score = matcher.fuzzy_indices_greedy(haystack, needle, &mut indices_greedy);
    if greedy_score.is_some() {
        let match_chars: Vec<_> = indices_greedy
            .iter()
            .map(|&i| normalize(haystack.get(i)))
            .collect();
        assert_eq!(
            match_chars, needle_chars,
            "failed match, found {indices_greedy:?} {match_chars:?} (greedy)"
        );
    }
    let optimal_score = matcher.fuzzy_indices(haystack, needle, &mut indices_optimal);
    if optimal_score.is_some() {
        let match_chars: Vec<_> = indices_optimal
            .iter()
            .map(|&i| normalize(haystack.get(i)))
            .collect();
        assert_eq!(
            match_chars, needle_chars,
            "failed match, found {indices_optimal:?} {match_chars:?}"
        );
    }
    match (greedy_score, optimal_score) {
        (None, Some(score)) => unreachable!("optimal matched {score} but greedy did not match"),
        (Some(score), None) => unreachable!("greedy matched {score} but optimal did not match"),
        (Some(greedy), Some(optimal)) => {
            assert!(
                greedy <= optimal,
                "optimal score must be at least the same as greedy score {greedy} {optimal}"
            );
            if indices_greedy == indices_optimal {
                assert_eq!(
                    greedy, optimal,
                    "if matching same char greedy and optimal score should be identical"
                )
            }
        }
        (None, None) => (),
    }
});
