use super::*;

macro_rules! diff_list {
    () => {
        Solution {
            text1: Range::empty(),
            text2: Range::empty(),
            diffs: Vec::new(),
            utf8: true,
        }
    };
    ($($kind:ident($text:literal)),+ $(,)?) => {{
        macro_rules! text1 {
            (Insert, $s:literal) => { "" };
            (Delete, $s:literal) => { $s };
            (Equal, $s:literal) => { $s };
        }
        macro_rules! text2 {
            (Insert, $s:literal) => { $s };
            (Delete, $s:literal) => { "" };
            (Equal, $s:literal) => { $s };
        }
        let text1 = concat!($(text1!($kind, $text)),*);
        let text2 = concat!($(text2!($kind, $text)),*);
        let (_i, _j) = (&mut 0, &mut 0);
        macro_rules! range {
            (Insert, $s:literal) => {
                Diff::Insert(range(text2, _j, $s))
            };
            (Delete, $s:literal) => {
                Diff::Delete(range(text1, _i, $s))
            };
            (Equal, $s:literal) => {
                Diff::Equal(range(text1, _i, $s), range(text2, _j, $s))
            };
        }
        Solution {
            text1: Range::new(text1, ..),
            text2: Range::new(text2, ..),
            diffs: vec![$(range!($kind, $text)),*],
            utf8: true,
        }
    }};
}

fn range<'a>(doc: &'a str, offset: &mut usize, text: &str) -> Range<'a> {
    let range = Range {
        doc,
        offset: *offset,
        len: text.len(),
    };
    *offset += text.len();
    range
}

macro_rules! assert_diffs {
    ([$($kind:ident($text:literal)),* $(,)?], $solution:ident, $msg:expr $(,)?) => {
        let expected = &[$(Chunk::$kind($text)),*];
        assert!(
            same_diffs(expected, &$solution.diffs),
            concat!($msg, "\nexpected={:#?}\nactual={:#?}"),
            expected, $solution.diffs,
        );
    };
}

fn same_diffs(expected: &[Chunk], actual: &[Diff]) -> bool {
    expected.len() == actual.len()
        && expected.iter().zip(actual).all(|pair| match pair {
            (Chunk::Insert(expected), Diff::Insert(actual)) => *expected == str(*actual),
            (Chunk::Delete(expected), Diff::Delete(actual)) => *expected == str(*actual),
            (Chunk::Equal(expected), Diff::Equal(actual1, actual2)) => {
                *expected == str(*actual1) && *expected == str(*actual2)
            }
            (_, _) => false,
        })
}

#[test]
fn test_common_prefix() {
    let text1 = Range::new("abc", ..);
    let text2 = Range::new("xyz", ..);
    assert_eq!(0, common_prefix_bytes(text1, text2), "Null case");

    let text1 = Range::new("1234abcdef", ..);
    let text2 = Range::new("1234xyz", ..);
    assert_eq!(4, common_prefix_bytes(text1, text2), "Non-null case");

    let text1 = Range::new("1234", ..);
    let text2 = Range::new("1234xyz", ..);
    assert_eq!(4, common_prefix_bytes(text1, text2), "Whole case");
}

#[test]
fn test_common_suffix() {
    let text1 = Range::new("abc", ..);
    let text2 = Range::new("xyz", ..);
    assert_eq!(0, common_suffix(text1, text2), "Null case");
    assert_eq!(0, common_suffix_bytes(text1, text2), "Null case");

    let text1 = Range::new("abcdef1234", ..);
    let text2 = Range::new("xyz1234", ..);
    assert_eq!(4, common_suffix(text1, text2), "Non-null case");
    assert_eq!(4, common_suffix_bytes(text1, text2), "Non-null case");

    let text1 = Range::new("1234", ..);
    let text2 = Range::new("xyz1234", ..);
    assert_eq!(4, common_suffix(text1, text2), "Whole case");
    assert_eq!(4, common_suffix_bytes(text1, text2), "Whole case");
}

#[test]
fn test_common_overlap() {
    let text1 = Range::empty();
    let text2 = Range::new("abcd", ..);
    assert_eq!(0, common_overlap(text1, text2), "Null case");

    let text1 = Range::new("abc", ..);
    let text2 = Range::new("abcd", ..);
    assert_eq!(3, common_overlap(text1, text2), "Whole case");

    let text1 = Range::new("123456", ..);
    let text2 = Range::new("abcd", ..);
    assert_eq!(0, common_overlap(text1, text2), "No overlap");

    let text1 = Range::new("123456xxx", ..);
    let text2 = Range::new("xxxabcd", ..);
    assert_eq!(3, common_overlap(text1, text2), "Overlap");

    // Some overly clever languages (C#) may treat ligatures as equal to their
    // component letters. E.g. U+FB01 == 'fi'
    let text1 = Range::new("fi", ..);
    let text2 = Range::new("\u{fb01}i", ..);
    assert_eq!(0, common_overlap(text1, text2), "Unicode");
}

#[test]
fn test_cleanup_merge() {
    let mut solution = diff_list![];
    cleanup_merge(&mut solution);
    assert_diffs!([], solution, "Null case");

    let mut solution = diff_list![Equal("a"), Delete("b"), Insert("c")];
    cleanup_merge(&mut solution);
    assert_diffs!(
        [Equal("a"), Delete("b"), Insert("c")],
        solution,
        "No change case",
    );

    let mut solution = diff_list![Equal("a"), Equal("b"), Equal("c")];
    cleanup_merge(&mut solution);
    assert_diffs!([Equal("abc")], solution, "Merge equalities");

    let mut solution = diff_list![Delete("a"), Delete("b"), Delete("c")];
    cleanup_merge(&mut solution);
    assert_diffs!([Delete("abc")], solution, "Merge deletions");

    let mut solution = diff_list![Insert("a"), Insert("b"), Insert("c")];
    cleanup_merge(&mut solution);
    assert_diffs!([Insert("abc")], solution, "Merge insertions");

    let mut solution = diff_list![
        Delete("a"),
        Insert("b"),
        Delete("c"),
        Insert("d"),
        Equal("e"),
        Equal("f"),
    ];
    cleanup_merge(&mut solution);
    assert_diffs!(
        [Delete("ac"), Insert("bd"), Equal("ef")],
        solution,
        "Merge interweave",
    );

    let mut solution = diff_list![Delete("a"), Insert("abc"), Delete("dc")];
    cleanup_merge(&mut solution);
    assert_diffs!(
        [Equal("a"), Delete("d"), Insert("b"), Equal("c")],
        solution,
        "Prefix and suffix detection",
    );

    let mut solution = diff_list![
        Equal("x"),
        Delete("a"),
        Insert("abc"),
        Delete("dc"),
        Equal("y"),
    ];
    cleanup_merge(&mut solution);
    assert_diffs!(
        [Equal("xa"), Delete("d"), Insert("b"), Equal("cy")],
        solution,
        "Prefix and suffix detection with equalities",
    );

    let mut solution = diff_list![Equal("a"), Insert("ba"), Equal("c")];
    cleanup_merge(&mut solution);
    assert_diffs!([Insert("ab"), Equal("ac")], solution, "Slide edit left");

    let mut solution = diff_list![Equal("c"), Insert("ab"), Equal("a")];
    cleanup_merge(&mut solution);
    assert_diffs!([Equal("ca"), Insert("ba")], solution, "Slide edit right");

    let mut solution = diff_list![
        Equal("a"),
        Delete("b"),
        Equal("c"),
        Delete("ac"),
        Equal("x"),
    ];
    cleanup_merge(&mut solution);
    assert_diffs!(
        [Delete("abc"), Equal("acx")],
        solution,
        "Slide edit left recursive",
    );

    let mut solution = diff_list![
        Equal("x"),
        Delete("ca"),
        Equal("c"),
        Delete("b"),
        Equal("a"),
    ];
    cleanup_merge(&mut solution);
    assert_diffs!(
        [Equal("xca"), Delete("cba")],
        solution,
        "Slide edit right recursive",
    );

    let mut solution = diff_list![Delete("b"), Insert("ab"), Equal("c")];
    cleanup_merge(&mut solution);
    assert_diffs!([Insert("a"), Equal("bc")], solution, "Empty range");

    let mut solution = diff_list![Equal(""), Insert("a"), Equal("b")];
    cleanup_merge(&mut solution);
    assert_diffs!([Insert("a"), Equal("b")], solution, "Empty equality");
}

#[test]
fn test_cleanup_semantic_lossless() {
    let mut solution = diff_list![];
    cleanup_semantic_lossless(&mut solution);
    assert_diffs!([], solution, "Null case");

    let mut solution = diff_list![
        Equal("AAA\r\n\r\nBBB"),
        Insert("\r\nDDD\r\n\r\nBBB"),
        Equal("\r\nEEE"),
    ];
    cleanup_semantic_lossless(&mut solution);
    assert_diffs!(
        [
            Equal("AAA\r\n\r\n"),
            Insert("BBB\r\nDDD\r\n\r\n"),
            Equal("BBB\r\nEEE"),
        ],
        solution,
        "Blank lines",
    );

    let mut solution = diff_list![Equal("AAA\r\nBBB"), Insert(" DDD\r\nBBB"), Equal(" EEE")];
    cleanup_semantic_lossless(&mut solution);
    assert_diffs!(
        [Equal("AAA\r\n"), Insert("BBB DDD\r\n"), Equal("BBB EEE")],
        solution,
        "Line boundaries",
    );

    let mut solution = diff_list![Equal("The c"), Insert("ow and the c"), Equal("at.")];
    cleanup_semantic_lossless(&mut solution);
    assert_diffs!(
        [Equal("The "), Insert("cow and the "), Equal("cat.")],
        solution,
        "Word boundaries",
    );

    let mut solution = diff_list![Equal("The-c"), Insert("ow-and-the-c"), Equal("at.")];
    cleanup_semantic_lossless(&mut solution);
    assert_diffs!(
        [Equal("The-"), Insert("cow-and-the-"), Equal("cat.")],
        solution,
        "Alphanumeric boundaries",
    );

    let mut solution = diff_list![Equal("a"), Delete("a"), Equal("ax")];
    cleanup_semantic_lossless(&mut solution);
    assert_diffs!([Delete("a"), Equal("aax")], solution, "Hitting the start");

    let mut solution = diff_list![Equal("xa"), Delete("a"), Equal("a")];
    cleanup_semantic_lossless(&mut solution);
    assert_diffs!([Equal("xaa"), Delete("a")], solution, "Hitting the end");

    let mut solution = diff_list![Equal("The xxx. The "), Insert("zzz. The "), Equal("yyy.")];
    cleanup_semantic_lossless(&mut solution);
    assert_diffs!(
        [Equal("The xxx."), Insert(" The zzz."), Equal(" The yyy.")],
        solution,
        "Sentence boundaries",
    );
}

#[test]
fn test_cleanup_semantic() {
    let mut solution = diff_list![];
    cleanup_semantic(&mut solution);
    assert_diffs!([], solution, "Null case");

    let mut solution = diff_list![Delete("ab"), Insert("cd"), Equal("12"), Delete("e")];
    cleanup_semantic(&mut solution);
    assert_diffs!(
        [Delete("ab"), Insert("cd"), Equal("12"), Delete("e")],
        solution,
        "No elimination #1",
    );

    let mut solution = diff_list![Delete("abc"), Insert("ABC"), Equal("1234"), Delete("wxyz")];
    cleanup_semantic(&mut solution);
    assert_diffs!(
        [Delete("abc"), Insert("ABC"), Equal("1234"), Delete("wxyz")],
        solution,
        "No elimination #2",
    );

    let mut solution = diff_list![Delete("a"), Equal("b"), Delete("c")];
    cleanup_semantic(&mut solution);
    assert_diffs!([Delete("abc"), Insert("b")], solution, "Simple elimination",);

    let mut solution = diff_list![
        Delete("ab"),
        Equal("cd"),
        Delete("e"),
        Equal("f"),
        Insert("g"),
    ];
    cleanup_semantic(&mut solution);
    assert_diffs!(
        [Delete("abcdef"), Insert("cdfg")],
        solution,
        "Backpass elimination",
    );

    let mut solution = diff_list![
        Insert("1"),
        Equal("A"),
        Delete("B"),
        Insert("2"),
        Equal("_"),
        Insert("1"),
        Equal("A"),
        Delete("B"),
        Insert("2"),
    ];
    cleanup_semantic(&mut solution);
    assert_diffs!(
        [Delete("AB_AB"), Insert("1A2_1A2")],
        solution,
        "Multiple elimination",
    );

    let mut solution = diff_list![Equal("The c"), Delete("ow and the c"), Equal("at.")];
    cleanup_semantic(&mut solution);
    assert_diffs!(
        [Equal("The "), Delete("cow and the "), Equal("cat.")],
        solution,
        "Word boundaries",
    );

    let mut solution = diff_list![Delete("abcxx"), Insert("xxdef")];
    cleanup_semantic(&mut solution);
    assert_diffs!(
        [Delete("abcxx"), Insert("xxdef")],
        solution,
        "No overlap elimination",
    );

    let mut solution = diff_list![Delete("abcxxx"), Insert("xxxdef")];
    cleanup_semantic(&mut solution);
    assert_diffs!(
        [Delete("abc"), Equal("xxx"), Insert("def")],
        solution,
        "Overlap elimination",
    );

    let mut solution = diff_list![Delete("xxxabc"), Insert("defxxx")];
    cleanup_semantic(&mut solution);
    assert_diffs!(
        [Insert("def"), Equal("xxx"), Delete("abc")],
        solution,
        "Reverse overlap elimination",
    );

    let mut solution = diff_list![
        Delete("abcd1212"),
        Insert("1212efghi"),
        Equal("----"),
        Delete("A3"),
        Insert("3BC"),
    ];
    cleanup_semantic(&mut solution);
    assert_diffs!(
        [
            Delete("abcd"),
            Equal("1212"),
            Insert("efghi"),
            Equal("----"),
            Delete("A"),
            Equal("3"),
            Insert("BC"),
        ],
        solution,
        "Two overlap eliminations",
    );
}

#[test]
fn test_bisect() {
    let text1 = Range::new("cat", ..);
    let text2 = Range::new("map", ..);
    let solution = Solution {
        text1,
        text2,
        diffs: bisect(text1, text2),
        utf8: false,
    };
    assert_diffs!(
        [
            Delete("c"),
            Insert("m"),
            Equal("a"),
            Delete("t"),
            Insert("p"),
        ],
        solution,
        "Normal",
    );
}

#[test]
fn test_main() {
    let solution = main(Range::empty(), Range::empty());
    assert_diffs!([], solution, "Null case");

    let solution = main(Range::new("abc", ..), Range::new("abc", ..));
    assert_diffs!([Equal("abc")], solution, "Equality");

    let solution = main(Range::new("abc", ..), Range::new("ab123c", ..));
    assert_diffs!(
        [Equal("ab"), Insert("123"), Equal("c")],
        solution,
        "Simple insertion",
    );

    let solution = main(Range::new("a123bc", ..), Range::new("abc", ..));
    assert_diffs!(
        [Equal("a"), Delete("123"), Equal("bc")],
        solution,
        "Simple deletion",
    );

    let solution = main(Range::new("abc", ..), Range::new("a123b456c", ..));
    assert_diffs!(
        [
            Equal("a"),
            Insert("123"),
            Equal("b"),
            Insert("456"),
            Equal("c"),
        ],
        solution,
        "Two insertions",
    );

    let solution = main(Range::new("a123b456c", ..), Range::new("abc", ..));
    assert_diffs!(
        [
            Equal("a"),
            Delete("123"),
            Equal("b"),
            Delete("456"),
            Equal("c"),
        ],
        solution,
        "Two deletions",
    );

    let solution = main(Range::new("a", ..), Range::new("b", ..));
    assert_diffs!([Delete("a"), Insert("b")], solution, "Simple case #1");

    let solution = main(
        Range::new("Apples are a fruit.", ..),
        Range::new("Bananas are also fruit.", ..),
    );
    assert_diffs!(
        [
            Delete("Apple"),
            Insert("Banana"),
            Equal("s are a"),
            Insert("lso"),
            Equal(" fruit."),
        ],
        solution,
        "Simple case #2",
    );

    let solution = main(Range::new("ax\t", ..), Range::new("\u{0680}x\000", ..));
    assert_diffs!(
        [
            Delete("a"),
            Insert("\u{0680}"),
            Equal("x"),
            Delete("\t"),
            Insert("\000"),
        ],
        solution,
        "Simple case #3",
    );

    let solution = main(Range::new("1ayb2", ..), Range::new("abxab", ..));
    assert_diffs!(
        [
            Delete("1"),
            Equal("a"),
            Delete("y"),
            Equal("b"),
            Delete("2"),
            Insert("xab"),
        ],
        solution,
        "Overlap #1",
    );

    let solution = main(Range::new("abcy", ..), Range::new("xaxcxabc", ..));
    assert_diffs!(
        [Insert("xaxcx"), Equal("abc"), Delete("y")],
        solution,
        "Overlap #2",
    );

    let solution = main(
        Range::new("ABCDa=bcd=efghijklmnopqrsEFGHIJKLMNOefg", ..),
        Range::new("a-bcd-efghijklmnopqrs", ..),
    );
    assert_diffs!(
        [
            Delete("ABCD"),
            Equal("a"),
            Delete("="),
            Insert("-"),
            Equal("bcd"),
            Delete("="),
            Insert("-"),
            Equal("efghijklmnopqrs"),
            Delete("EFGHIJKLMNOefg"),
        ],
        solution,
        "Overlap #3",
    );

    let solution = main(
        Range::new("a [[Pennsylvania]] and [[New", ..),
        Range::new(" and [[Pennsylvania]]", ..),
    );
    assert_diffs!(
        [
            Insert(" "),
            Equal("a"),
            Insert("nd"),
            Equal(" [[Pennsylvania]]"),
            Delete(" and [[New"),
        ],
        solution,
        "Large equality",
    );
}
