use super::*;

fn expected_union_difference(
    left: &ReferenceMap,
    middle: &ReferenceMap,
    right: &ReferenceMap,
    root: Option<TestPrefix>,
) -> Vec<(TestPrefix, (Option<i32>, Option<i32>))> {
    sorted(
        expected_union(left, middle, root, root)
            .into_iter()
            .filter(|(prefix, _)| !right.contains_key(prefix) || !in_scope(root, prefix))
            .collect(),
    )
}

fn expected_difference_intersection(
    left: &ReferenceMap,
    middle: &ReferenceMap,
    right: &ReferenceMap,
    root: Option<TestPrefix>,
) -> Vec<(TestPrefix, (i32, i32))> {
    sorted(
        left.iter()
            .filter(|(prefix, _)| in_scope(root, prefix))
            .filter(|(prefix, _)| !middle.contains_key(prefix) || !in_scope(root, prefix))
            .filter_map(|(prefix, left_value)| {
                right.get(prefix).and_then(|right_value| {
                    in_scope(root, prefix).then_some((*prefix, (*left_value, *right_value)))
                })
            })
            .collect(),
    )
}

fn check_union_difference_iter(
    left: Entries,
    middle: Entries,
    right: Entries,
    root: Option<TestPrefix>,
) -> bool {
    let (left_map, left_reference) = build_map(left);
    let (middle_map, middle_reference) = build_map(middle);
    let (right_map, right_reference) = build_map(right);
    let empty_left = PrefixMap::new();
    let empty_middle = PrefixMap::new();
    let empty_right = PrefixMap::new();

    let want =
        expected_union_difference(&left_reference, &middle_reference, &right_reference, root);
    let got = view_or_empty(&left_map, &empty_left, root)
        .union(view_or_empty(&middle_map, &empty_middle, root))
        .difference(view_or_empty(&right_map, &empty_right, root))
        .into_iter()
        .map(|(prefix, item)| (prefix, norm_union(item)))
        .collect::<Vec<_>>();
    got == want
}

fn check_union_difference_lpm(
    left: Entries,
    middle: Entries,
    right: Entries,
    root: Option<TestPrefix>,
    query: TestPrefix,
) -> bool {
    let (left_map, left_reference) = build_map(left);
    let (middle_map, middle_reference) = build_map(middle);
    let (right_map, right_reference) = build_map(right);
    let empty_left = PrefixMap::new();
    let empty_middle = PrefixMap::new();
    let empty_right = PrefixMap::new();

    let entries =
        expected_union_difference(&left_reference, &middle_reference, &right_reference, root);
    let want = lpm_in_view(&entries, &query);
    let got = view_or_empty(&left_map, &empty_left, root)
        .union(view_or_empty(&middle_map, &empty_middle, root))
        .difference(view_or_empty(&right_map, &empty_right, root))
        .find_lpm_value(&query)
        .map(|(prefix, item)| (prefix, norm_union(item)));
    got == want
}

fn check_union_difference_iter_mut(
    left: Entries,
    middle: Entries,
    right: Entries,
    root: Option<TestPrefix>,
) -> bool {
    let (mut left_map, left_reference) = build_map(left);
    let (mut middle_map, middle_reference) = build_map(middle);
    let (mut right_map, right_reference) = build_map(right);
    let mut empty_left = PrefixMap::new();
    let mut empty_middle = PrefixMap::new();
    let mut empty_right = PrefixMap::new();

    let want =
        expected_union_difference(&left_reference, &middle_reference, &right_reference, root);
    let got = view_mut_or_empty(&mut left_map, &mut empty_left, root)
        .union(view_mut_or_empty(&mut middle_map, &mut empty_middle, root))
        .difference(view_mut_or_empty(&mut right_map, &mut empty_right, root))
        .into_iter()
        .map(|(prefix, item)| (prefix, norm_union(item)))
        .collect::<Vec<_>>();
    got == want
}

fn check_union_difference_lpm_mut(
    left: Entries,
    middle: Entries,
    right: Entries,
    root: Option<TestPrefix>,
    query: TestPrefix,
) -> bool {
    let (mut left_map, left_reference) = build_map(left);
    let (mut middle_map, middle_reference) = build_map(middle);
    let (mut right_map, right_reference) = build_map(right);
    let mut empty_left = PrefixMap::new();
    let mut empty_middle = PrefixMap::new();
    let mut empty_right = PrefixMap::new();

    let entries =
        expected_union_difference(&left_reference, &middle_reference, &right_reference, root);
    let want = lpm_in_view(&entries, &query);
    let got = view_mut_or_empty(&mut left_map, &mut empty_left, root)
        .union(view_mut_or_empty(&mut middle_map, &mut empty_middle, root))
        .difference(view_mut_or_empty(&mut right_map, &mut empty_right, root))
        .find_lpm_value(&query)
        .map(|(prefix, item)| (prefix, norm_union(item)));
    got == want
}

fn check_difference_intersection_iter(
    left: Entries,
    middle: Entries,
    right: Entries,
    root: Option<TestPrefix>,
) -> bool {
    let (left_map, left_reference) = build_map(left);
    let (middle_map, middle_reference) = build_map(middle);
    let (right_map, right_reference) = build_map(right);
    let empty_left = PrefixMap::new();
    let empty_middle = PrefixMap::new();
    let empty_right = PrefixMap::new();

    let want = expected_difference_intersection(
        &left_reference,
        &middle_reference,
        &right_reference,
        root,
    );
    let got = view_or_empty(&left_map, &empty_left, root)
        .difference(view_or_empty(&middle_map, &empty_middle, root))
        .intersection(view_or_empty(&right_map, &empty_right, root))
        .map(|view| {
            view.into_iter()
                .map(|(prefix, (left, right))| (prefix, (*left, *right)))
                .collect::<Vec<_>>()
        })
        .unwrap_or_default();
    got == want
}

fn check_difference_intersection_lpm(
    left: Entries,
    middle: Entries,
    right: Entries,
    root: Option<TestPrefix>,
    query: TestPrefix,
) -> bool {
    let (left_map, left_reference) = build_map(left);
    let (middle_map, middle_reference) = build_map(middle);
    let (right_map, right_reference) = build_map(right);
    let empty_left = PrefixMap::new();
    let empty_middle = PrefixMap::new();
    let empty_right = PrefixMap::new();

    let entries = expected_difference_intersection(
        &left_reference,
        &middle_reference,
        &right_reference,
        root,
    );
    let want = lpm_in_view(&entries, &query);
    let got = view_or_empty(&left_map, &empty_left, root)
        .difference(view_or_empty(&middle_map, &empty_middle, root))
        .intersection(view_or_empty(&right_map, &empty_right, root))
        .and_then(|view| view.find_lpm_value(&query))
        .map(|(prefix, (left, right))| (prefix, (*left, *right)));
    got == want
}

fn check_difference_intersection_iter_mut(
    left: Entries,
    middle: Entries,
    right: Entries,
    root: Option<TestPrefix>,
) -> bool {
    let (mut left_map, left_reference) = build_map(left);
    let (mut middle_map, middle_reference) = build_map(middle);
    let (mut right_map, right_reference) = build_map(right);
    let mut empty_left = PrefixMap::new();
    let mut empty_middle = PrefixMap::new();
    let mut empty_right = PrefixMap::new();

    let want = expected_difference_intersection(
        &left_reference,
        &middle_reference,
        &right_reference,
        root,
    );
    let got = view_mut_or_empty(&mut left_map, &mut empty_left, root)
        .difference(view_mut_or_empty(&mut middle_map, &mut empty_middle, root))
        .intersection(view_mut_or_empty(&mut right_map, &mut empty_right, root))
        .map(|view| {
            view.into_iter()
                .map(|(prefix, (left, right))| (prefix, (*left, *right)))
                .collect::<Vec<_>>()
        })
        .unwrap_or_default();
    got == want
}

fn check_difference_intersection_lpm_mut(
    left: Entries,
    middle: Entries,
    right: Entries,
    root: Option<TestPrefix>,
    query: TestPrefix,
) -> bool {
    let (mut left_map, left_reference) = build_map(left);
    let (mut middle_map, middle_reference) = build_map(middle);
    let (mut right_map, right_reference) = build_map(right);
    let mut empty_left = PrefixMap::new();
    let mut empty_middle = PrefixMap::new();
    let mut empty_right = PrefixMap::new();

    let entries = expected_difference_intersection(
        &left_reference,
        &middle_reference,
        &right_reference,
        root,
    );
    let want = lpm_in_view(&entries, &query);
    let got = view_mut_or_empty(&mut left_map, &mut empty_left, root)
        .difference(view_mut_or_empty(&mut middle_map, &mut empty_middle, root))
        .intersection(view_mut_or_empty(&mut right_map, &mut empty_right, root))
        .and_then(|view| view.find_lpm_value(&query))
        .map(|(prefix, (left, right))| (prefix, (*left, *right)));
    got == want
}

macro_rules! root_case {
    ($iter:ident, $iter_fn:ident, $lpm:ident, $lpm_fn:ident, $check_iter:ident, $check_lpm:ident) => {
        qc!($iter, $iter_fn);
        fn $iter_fn((left, middle, right): (Entries, Entries, Entries)) -> bool {
            $check_iter(left, middle, right, None)
        }

        qc!($lpm, $lpm_fn);
        fn $lpm_fn((left, middle, right, query): (Entries, Entries, Entries, TestPrefix)) -> bool {
            $check_lpm(left, middle, right, None, query)
        }
    };
}

macro_rules! sub_case {
    ($iter:ident, $iter_fn:ident, $lpm:ident, $lpm_fn:ident, $check_iter:ident, $check_lpm:ident) => {
        qc!($iter, $iter_fn);
        fn $iter_fn(
            (left, middle, right, sub_root): (Entries, Entries, Entries, TestPrefix),
        ) -> bool {
            $check_iter(left, middle, right, Some(sub_root))
        }

        qc!($lpm, $lpm_fn);
        fn $lpm_fn(
            (left, middle, right, sub_root, query): (
                Entries,
                Entries,
                Entries,
                TestPrefix,
                TestPrefix,
            ),
        ) -> bool {
            $check_lpm(left, middle, right, Some(sub_root), query)
        }
    };
}

root_case!(
    combination_union_difference_root_iter,
    _combination_union_difference_root_iter,
    combination_union_difference_root_lpm,
    _combination_union_difference_root_lpm,
    check_union_difference_iter,
    check_union_difference_lpm
);
sub_case!(
    combination_union_difference_sub_iter,
    _combination_union_difference_sub_iter,
    combination_union_difference_sub_lpm,
    _combination_union_difference_sub_lpm,
    check_union_difference_iter,
    check_union_difference_lpm
);
root_case!(
    combination_union_difference_mut_root_iter,
    _combination_union_difference_mut_root_iter,
    combination_union_difference_mut_root_lpm,
    _combination_union_difference_mut_root_lpm,
    check_union_difference_iter_mut,
    check_union_difference_lpm_mut
);
sub_case!(
    combination_union_difference_mut_sub_iter,
    _combination_union_difference_mut_sub_iter,
    combination_union_difference_mut_sub_lpm,
    _combination_union_difference_mut_sub_lpm,
    check_union_difference_iter_mut,
    check_union_difference_lpm_mut
);

root_case!(
    combination_difference_intersection_root_iter,
    _combination_difference_intersection_root_iter,
    combination_difference_intersection_root_lpm,
    _combination_difference_intersection_root_lpm,
    check_difference_intersection_iter,
    check_difference_intersection_lpm
);
sub_case!(
    combination_difference_intersection_sub_iter,
    _combination_difference_intersection_sub_iter,
    combination_difference_intersection_sub_lpm,
    _combination_difference_intersection_sub_lpm,
    check_difference_intersection_iter,
    check_difference_intersection_lpm
);
root_case!(
    combination_difference_intersection_mut_root_iter,
    _combination_difference_intersection_mut_root_iter,
    combination_difference_intersection_mut_root_lpm,
    _combination_difference_intersection_mut_root_lpm,
    check_difference_intersection_iter_mut,
    check_difference_intersection_lpm_mut
);
sub_case!(
    combination_difference_intersection_mut_sub_iter,
    _combination_difference_intersection_mut_sub_iter,
    combination_difference_intersection_mut_sub_lpm,
    _combination_difference_intersection_mut_sub_lpm,
    check_difference_intersection_iter_mut,
    check_difference_intersection_lpm_mut
);

fn check_union_difference_iter_from(
    left: Entries,
    middle: Entries,
    right: Entries,
    root: Option<TestPrefix>,
    query: TestPrefix,
    inclusive: bool,
) -> bool {
    let (left_map, left_reference) = build_map(left);
    let (middle_map, middle_reference) = build_map(middle);
    let (right_map, right_reference) = build_map(right);
    let empty_left = PrefixMap::new();
    let empty_middle = PrefixMap::new();
    let empty_right = PrefixMap::new();

    let all = expected_union_difference(&left_reference, &middle_reference, &right_reference, root);
    let want: Vec<_> = all
        .into_iter()
        .filter(|(p, _)| if inclusive { *p >= query } else { *p > query })
        .collect();
    let got: Vec<_> = view_or_empty(&left_map, &empty_left, root)
        .union(view_or_empty(&middle_map, &empty_middle, root))
        .difference(view_or_empty(&right_map, &empty_right, root))
        .iter_from(&query, inclusive)
        .map(|(prefix, item)| (prefix, norm_union(item)))
        .collect();
    got == want
}

qc!(
    combination_union_difference_root_iter_from_inclusive,
    _combination_union_difference_root_iter_from_inclusive
);
fn _combination_union_difference_root_iter_from_inclusive(
    (left, middle, right, query): (Entries, Entries, Entries, TestPrefix),
) -> bool {
    check_union_difference_iter_from(left, middle, right, None, query, true)
}

qc!(
    combination_union_difference_root_iter_from_exclusive,
    _combination_union_difference_root_iter_from_exclusive
);
fn _combination_union_difference_root_iter_from_exclusive(
    (left, middle, right, query): (Entries, Entries, Entries, TestPrefix),
) -> bool {
    check_union_difference_iter_from(left, middle, right, None, query, false)
}

qc!(
    combination_union_difference_sub_iter_from_inclusive,
    _combination_union_difference_sub_iter_from_inclusive
);
fn _combination_union_difference_sub_iter_from_inclusive(
    (left, middle, right, root, query): (Entries, Entries, Entries, TestPrefix, TestPrefix),
) -> bool {
    check_union_difference_iter_from(left, middle, right, Some(root), query, true)
}

qc!(
    combination_union_difference_sub_iter_from_exclusive,
    _combination_union_difference_sub_iter_from_exclusive
);
fn _combination_union_difference_sub_iter_from_exclusive(
    (left, middle, right, root, query): (Entries, Entries, Entries, TestPrefix, TestPrefix),
) -> bool {
    check_union_difference_iter_from(left, middle, right, Some(root), query, false)
}
