use divan::Bencher;
use divan::counter::BytesCount;
use matcher_rs::{ProcessType, SimpleMatcher, text_process};
use std::collections::HashMap;
use std::hint::black_box;

const CN_PROCESS_TYPES: &[ProcessType] = &[
    ProcessType::None,
    ProcessType::Delete,
    ProcessType::Fanjian,
    ProcessType::FanjianDeleteNormalize,
];

const EN_PROCESS_TYPES: &[ProcessType] = &[
    ProcessType::None,
    ProcessType::Delete,
    ProcessType::DeleteNormalize,
];

const SIMPLE_WORD_MAP_SIZE_LIST: &[usize] = &[1000, 10000, 50000, 100000];
const COMBINED_TIMES_LIST: &[usize] = &[1, 2, 3, 4];

const DEFAULT_PROCESS_TYPE: ProcessType = ProcessType::None;
const DEFAULT_SIMPLE_WORD_MAP_SIZE: usize = 10000;
const DEFAULT_COMBINED_TIMES: usize = 1;

const CN_WORD_LIST: &str = include_str!("../../data/word/cn/jieba.txt");
const CN_HAYSTACK: &str = include_str!("../../data/text/cn/三体.txt");

const EN_WORD_LIST: &str = include_str!("../../data/word/en/dictionary.txt");
const EN_HAYSTACK: &str = include_str!("../../data/text/en/sherlock.txt");

/// Builds a simple word map deterministically.
///
/// * `en_or_cn`: "en" or "cn" dictating the vocabulary
/// * `simple_word_map_size`: Target number of combinations to generate
/// * `combined_times`: Target tokens per word (e.g., A & B & C)
/// * `match_scenario`: If false, the generated words are mangled so they will *never* match the haystack.
fn build_deterministic_map(
    en_or_cn: &str,
    simple_word_map_size: usize,
    combined_times: usize,
    match_scenario: bool,
) -> HashMap<u32, String> {
    let mut patterns: Vec<&str> = if en_or_cn == "cn" {
        CN_WORD_LIST.lines().collect()
    } else {
        EN_WORD_LIST.lines().collect()
    };
    patterns.sort_unstable();

    let mut simple_word_map = HashMap::new();
    let mut global_word_id = 0u32;
    let operators = ["&", "~"];

    for i in 0..simple_word_map_size {
        global_word_id += 1;
        let mut combined_word_list = Vec::with_capacity(combined_times);

        for j in 0..combined_times {
            // Deterministic word selection heavily distributed across the pattern list
            let word_idx = (i * combined_times + j * 997) % patterns.len();
            let mut word = patterns[word_idx].to_string();

            if !match_scenario {
                // Mangle the word so it cannot possibly match normal text.
                // For CN, append a Unicode PUA suffix to a real CJK word so the automaton
                // retains realistic CJK byte structure while the pattern is unachievable.
                // For EN, use a fully ASCII sentinel string.
                word = if en_or_cn == "cn" {
                    format!("{word}\u{E000}{i}")
                } else {
                    format!("__impossible_{word_idx}_match_{i}__")
                };
            }

            combined_word_list.push(word);
        }

        // Deterministic operator selection
        let op = operators[i % operators.len()];
        let combined_word = combined_word_list.join(op);
        simple_word_map.insert(global_word_id, combined_word);
    }
    simple_word_map
}

fn build_rule_shape_map(
    en_or_cn: &str,
    simple_word_map_size: usize,
    shape: &str,
) -> HashMap<u32, String> {
    let mut patterns: Vec<&str> = if en_or_cn == "cn" {
        CN_WORD_LIST.lines().collect()
    } else {
        EN_WORD_LIST.lines().collect()
    };
    patterns.sort_unstable();

    let mut simple_word_map = HashMap::with_capacity(simple_word_map_size);
    for i in 0..simple_word_map_size {
        let word_idx = (i * 997) % patterns.len();
        let word = patterns[word_idx];
        let shaped = match shape {
            "literal" => word.to_string(),
            "and" => {
                let a = patterns[word_idx];
                let b = patterns[(word_idx + 101) % patterns.len()];
                let c = patterns[(word_idx + 211) % patterns.len()];
                format!("{a}&{b}&{c}")
            }
            "not" => format!("{word}~__never_block_{i}__"),
            _ => unreachable!("unknown rule shape: {shape}"),
        };
        simple_word_map.insert((i + 1) as u32, shaped);
    }
    simple_word_map
}

fn build_mixed_script_map(simple_word_map_size: usize) -> HashMap<u32, String> {
    let mut en_patterns: Vec<&str> = EN_WORD_LIST
        .lines()
        .filter(|word| word.is_ascii() && !word.is_empty())
        .collect();
    let mut cn_patterns: Vec<&str> = CN_WORD_LIST
        .lines()
        .filter(|word| !word.is_ascii() && !word.is_empty())
        .collect();
    en_patterns.sort_unstable();
    cn_patterns.sort_unstable();

    let mut simple_word_map = HashMap::with_capacity(simple_word_map_size);
    for i in 0..simple_word_map_size {
        let word = if i % 2 == 0 {
            en_patterns[(i * 997) % en_patterns.len()]
        } else {
            cn_patterns[(i * 991) % cn_patterns.len()]
        };
        simple_word_map.insert((i + 1) as u32, word.to_string());
    }
    simple_word_map
}

fn build_multi_process_table(
    simple_word_map_size: usize,
) -> HashMap<ProcessType, HashMap<u32, String>> {
    let slice = (simple_word_map_size / 4).max(1);
    HashMap::from([
        (
            ProcessType::None,
            build_deterministic_map("en", slice, DEFAULT_COMBINED_TIMES, true),
        ),
        (
            ProcessType::Delete,
            build_deterministic_map("en", slice, DEFAULT_COMBINED_TIMES, true),
        ),
        (
            ProcessType::Fanjian,
            build_deterministic_map("cn", slice, DEFAULT_COMBINED_TIMES, true),
        ),
        (
            ProcessType::FanjianDeleteNormalize,
            build_deterministic_map(
                "cn",
                simple_word_map_size - slice * 3,
                DEFAULT_COMBINED_TIMES,
                true,
            ),
        ),
    ])
}

mod build {
    use super::*;

    macro_rules! define_build_bench {
        ($lang:ident) => {
            paste::item! {
                #[divan::bench(args = [<$lang:upper _PROCESS_TYPES>], max_time = 5)]
                fn [<$lang:lower _by_process_type>](bencher: Bencher, process_type: ProcessType) {
                    let simple_word_map = build_deterministic_map(
                        stringify!($lang).to_lowercase().as_str(),
                        DEFAULT_SIMPLE_WORD_MAP_SIZE,
                        DEFAULT_COMBINED_TIMES,
                        true,
                    );
                    let simple_table = HashMap::from([(process_type, simple_word_map)]);
                    bencher.bench_local(|| {
                        let _ = black_box(SimpleMatcher::new(&simple_table));
                    });
                }

                #[divan::bench(args = SIMPLE_WORD_MAP_SIZE_LIST, max_time = 5)]
                fn [<$lang:lower _by_size>](bencher: Bencher, size: usize) {
                    let simple_word_map = build_deterministic_map(
                        stringify!($lang).to_lowercase().as_str(),
                        size,
                        DEFAULT_COMBINED_TIMES,
                        true,
                    );
                    let simple_table = HashMap::from([(DEFAULT_PROCESS_TYPE, simple_word_map)]);
                    bencher.bench_local(|| {
                        let _ = black_box(SimpleMatcher::new(&simple_table));
                    });
                }

                #[divan::bench(args = COMBINED_TIMES_LIST, max_time = 5)]
                fn [<$lang:lower _by_combinations>](bencher: Bencher, combined_times: usize) {
                    let simple_word_map = build_deterministic_map(
                        stringify!($lang).to_lowercase().as_str(),
                        DEFAULT_SIMPLE_WORD_MAP_SIZE,
                        combined_times,
                        true,
                    );
                    let simple_table = HashMap::from([(DEFAULT_PROCESS_TYPE, simple_word_map)]);
                    bencher.bench_local(|| {
                        let _ = black_box(SimpleMatcher::new(&simple_table));
                    });
                }
            }
        };
    }

    define_build_bench!(CN);
    define_build_bench!(EN);
}

macro_rules! define_search_bench {
    ($lang:ident, $match_scenario:expr, $method:ident) => {
        paste::item! {
            #[divan::bench(args = [<$lang:upper _PROCESS_TYPES>], max_time = 5)]
            fn [<$lang:lower _by_process_type>](bencher: Bencher, process_type: ProcessType) {
                let mut simple_table = HashMap::new();
                let simple_word_map = build_deterministic_map(
                    stringify!($lang).to_lowercase().as_str(),
                    DEFAULT_SIMPLE_WORD_MAP_SIZE,
                    DEFAULT_COMBINED_TIMES,
                    $match_scenario,
                );
                simple_table.insert(process_type, simple_word_map);
                let matcher = SimpleMatcher::new(&simple_table);
                let haystack = [<$lang:upper _HAYSTACK>];

                let total_bytes = haystack.len();

                bencher
                    .counter(BytesCount::new(total_bytes))
                    .bench(|| {
                        for line in haystack.lines() {
                            let _ = black_box(matcher.$method(line));
                        }
                    });
            }

            #[divan::bench(args = SIMPLE_WORD_MAP_SIZE_LIST, max_time = 5)]
            fn [<$lang:lower _by_size>](bencher: Bencher, size: usize) {
                let mut simple_table = HashMap::new();
                let simple_word_map = build_deterministic_map(
                    stringify!($lang).to_lowercase().as_str(),
                    size,
                    DEFAULT_COMBINED_TIMES,
                    $match_scenario,
                );
                simple_table.insert(DEFAULT_PROCESS_TYPE, simple_word_map);
                let matcher = SimpleMatcher::new(&simple_table);
                let haystack = [<$lang:upper _HAYSTACK>];

                let total_bytes = haystack.len();

                bencher
                    .counter(BytesCount::new(total_bytes))
                    .bench(|| {
                        for line in haystack.lines() {
                            let _ = black_box(matcher.$method(line));
                        }
                    });
            }

            #[divan::bench(args = COMBINED_TIMES_LIST, max_time = 5)]
            fn [<$lang:lower _by_combinations>](bencher: Bencher, combined_times: usize) {
                let mut simple_table = HashMap::new();
                let simple_word_map = build_deterministic_map(
                    stringify!($lang).to_lowercase().as_str(),
                    DEFAULT_SIMPLE_WORD_MAP_SIZE,
                    combined_times,
                    $match_scenario,
                );
                simple_table.insert(DEFAULT_PROCESS_TYPE, simple_word_map);
                let matcher = SimpleMatcher::new(&simple_table);
                let haystack = [<$lang:upper _HAYSTACK>];

                let total_bytes = haystack.len();

                bencher
                    .counter(BytesCount::new(total_bytes))
                    .bench(|| {
                        for line in haystack.lines() {
                            let _ = black_box(matcher.$method(line));
                        }
                    });
            }
        }
    };
}

mod search_match {
    use super::*;

    define_search_bench!(CN, true, process);
    define_search_bench!(EN, true, process);
}

mod search_no_match {
    use super::*;

    define_search_bench!(CN, false, process);
    define_search_bench!(EN, false, process);
}

mod is_match_match {
    use super::*;
    define_search_bench!(CN, true, is_match);
    define_search_bench!(EN, true, is_match);
}

mod is_match_no_match {
    use super::*;

    define_search_bench!(CN, false, is_match);
    define_search_bench!(EN, false, is_match);
}

mod text_process {
    use super::*;

    const PROCESS_TYPES: &[ProcessType] = &[
        ProcessType::Fanjian,
        ProcessType::Delete,
        ProcessType::Normalize,
        ProcessType::PinYin,
        ProcessType::PinYinChar,
    ];

    #[divan::bench(args = PROCESS_TYPES, max_time = 5)]
    fn cn(bencher: Bencher, process_type: ProcessType) {
        let haystack = CN_HAYSTACK;
        let total_bytes = haystack.len();
        bencher.counter(BytesCount::new(total_bytes)).bench(|| {
            for line in haystack.lines() {
                let _ = black_box(text_process(process_type, line));
            }
        });
    }

    #[divan::bench(args = PROCESS_TYPES, max_time = 5)]
    fn en(bencher: Bencher, process_type: ProcessType) {
        let haystack = EN_HAYSTACK;
        let total_bytes = haystack.len();
        bencher.counter(BytesCount::new(total_bytes)).bench(|| {
            for line in haystack.lines() {
                let _ = black_box(text_process(process_type, line));
            }
        });
    }
}

mod rule_shapes {
    use super::*;

    const RULE_SHAPES: &[&str] = &["literal", "and", "not"];

    #[divan::bench(args = RULE_SHAPES, max_time = 5)]
    fn is_match_en(bencher: Bencher, shape: &str) {
        let simple_word_map = build_rule_shape_map("en", DEFAULT_SIMPLE_WORD_MAP_SIZE, shape);
        let matcher = SimpleMatcher::new(&HashMap::from([(ProcessType::None, simple_word_map)]));
        let haystack = EN_HAYSTACK;
        let total_bytes = haystack.len();

        bencher.counter(BytesCount::new(total_bytes)).bench(|| {
            for line in haystack.lines() {
                let _ = black_box(matcher.is_match(line));
            }
        });
    }

    #[divan::bench(args = RULE_SHAPES, max_time = 5)]
    fn process_en(bencher: Bencher, shape: &str) {
        let simple_word_map = build_rule_shape_map("en", DEFAULT_SIMPLE_WORD_MAP_SIZE, shape);
        let matcher = SimpleMatcher::new(&HashMap::from([(ProcessType::None, simple_word_map)]));
        let haystack = EN_HAYSTACK;
        let total_bytes = haystack.len();

        bencher.counter(BytesCount::new(total_bytes)).bench(|| {
            for line in haystack.lines() {
                let _ = black_box(matcher.process(line));
            }
        });
    }
}

mod mixed_scripts {
    use super::*;

    #[divan::bench(args = SIMPLE_WORD_MAP_SIZE_LIST, max_time = 5)]
    fn is_match_cn(bencher: Bencher, size: usize) {
        let matcher = SimpleMatcher::new(&HashMap::from([(
            ProcessType::None,
            build_mixed_script_map(size),
        )]));
        let haystack = CN_HAYSTACK;
        let total_bytes = haystack.len();

        bencher.counter(BytesCount::new(total_bytes)).bench(|| {
            for line in haystack.lines() {
                let _ = black_box(matcher.is_match(line));
            }
        });
    }

    #[divan::bench(args = SIMPLE_WORD_MAP_SIZE_LIST, max_time = 5)]
    fn is_match_en(bencher: Bencher, size: usize) {
        let matcher = SimpleMatcher::new(&HashMap::from([(
            ProcessType::None,
            build_mixed_script_map(size),
        )]));
        let haystack = EN_HAYSTACK;
        let total_bytes = haystack.len();

        bencher.counter(BytesCount::new(total_bytes)).bench(|| {
            for line in haystack.lines() {
                let _ = black_box(matcher.is_match(line));
            }
        });
    }
}

mod process_heavy {
    use super::*;

    #[divan::bench(args = SIMPLE_WORD_MAP_SIZE_LIST, max_time = 5)]
    fn is_match_cn(bencher: Bencher, size: usize) {
        let matcher = SimpleMatcher::new(&build_multi_process_table(size));
        let haystack = CN_HAYSTACK;
        let total_bytes = haystack.len();

        bencher.counter(BytesCount::new(total_bytes)).bench(|| {
            for line in haystack.lines() {
                let _ = black_box(matcher.is_match(line));
            }
        });
    }

    #[divan::bench(args = SIMPLE_WORD_MAP_SIZE_LIST, max_time = 5)]
    fn process_cn(bencher: Bencher, size: usize) {
        let matcher = SimpleMatcher::new(&build_multi_process_table(size));
        let haystack = CN_HAYSTACK;
        let total_bytes = haystack.len();

        bencher.counter(BytesCount::new(total_bytes)).bench(|| {
            for line in haystack.lines() {
                let _ = black_box(matcher.process(line));
            }
        });
    }
}

fn main() {
    println!(
        "Current default simple match type: {:?}",
        DEFAULT_PROCESS_TYPE
    );
    println!(
        "Current default simple word map size: {:?}",
        DEFAULT_SIMPLE_WORD_MAP_SIZE
    );
    println!(
        "Current default combined times: {:?}",
        DEFAULT_COMBINED_TIMES
    );

    divan::main()
}
