use super::helpers::edits::invert_edit;
use super::helpers::fixtures::get_language;
use crate::parse::{perform_edit, Edit};
use std::str;
use tree_sitter::{InputEdit, Parser, Point, Range, Tree};

#[test]
fn test_tree_edit() {
    let mut parser = Parser::new();
    parser.set_language(get_language("javascript")).unwrap();
    let tree = parser.parse("  abc  !==  def", None).unwrap();

    assert_eq!(
        tree.root_node().to_sexp(),
        "(program (expression_statement (binary_expression left: (identifier) right: (identifier))))"
    );

    // edit entirely within the tree's padding:
    // resize the padding of the tree and its leftmost descendants.
    {
        let mut tree = tree.clone();
        tree.edit(&InputEdit {
            start_byte: 1,
            old_end_byte: 1,
            new_end_byte: 2,
            start_position: Point::new(0, 1),
            old_end_position: Point::new(0, 1),
            new_end_position: Point::new(0, 2),
        });

        let expr = tree.root_node().child(0).unwrap().child(0).unwrap();
        let child1 = expr.child(0).unwrap();
        let child2 = expr.child(1).unwrap();

        assert!(expr.has_changes());
        assert_eq!(expr.start_byte(), 3);
        assert_eq!(expr.end_byte(), 16);
        assert!(child1.has_changes());
        assert_eq!(child1.start_byte(), 3);
        assert_eq!(child1.end_byte(), 6);
        assert!(!child2.has_changes());
        assert_eq!(child2.start_byte(), 8);
        assert_eq!(child2.end_byte(), 11);
    }

    // edit starting in the tree's padding but extending into its content:
    // shrink the content to compenstate for the expanded padding.
    {
        let mut tree = tree.clone();
        tree.edit(&InputEdit {
            start_byte: 1,
            old_end_byte: 4,
            new_end_byte: 5,
            start_position: Point::new(0, 1),
            old_end_position: Point::new(0, 5),
            new_end_position: Point::new(0, 5),
        });

        let expr = tree.root_node().child(0).unwrap().child(0).unwrap();
        let child1 = expr.child(0).unwrap();
        let child2 = expr.child(1).unwrap();

        assert!(expr.has_changes());
        assert_eq!(expr.start_byte(), 5);
        assert_eq!(expr.end_byte(), 16);
        assert!(child1.has_changes());
        assert_eq!(child1.start_byte(), 5);
        assert_eq!(child1.end_byte(), 6);
        assert!(!child2.has_changes());
        assert_eq!(child2.start_byte(), 8);
        assert_eq!(child2.end_byte(), 11);
    }

    // insertion at the edge of a tree's padding:
    // expand the tree's padding.
    {
        let mut tree = tree.clone();
        tree.edit(&InputEdit {
            start_byte: 2,
            old_end_byte: 2,
            new_end_byte: 4,
            start_position: Point::new(0, 2),
            old_end_position: Point::new(0, 2),
            new_end_position: Point::new(0, 4),
        });

        let expr = tree.root_node().child(0).unwrap().child(0).unwrap();
        let child1 = expr.child(0).unwrap();
        let child2 = expr.child(1).unwrap();

        assert!(expr.has_changes());
        assert_eq!(expr.start_byte(), 4);
        assert_eq!(expr.end_byte(), 17);
        assert!(child1.has_changes());
        assert_eq!(child1.start_byte(), 4);
        assert_eq!(child1.end_byte(), 7);
        assert!(!child2.has_changes());
        assert_eq!(child2.start_byte(), 9);
        assert_eq!(child2.end_byte(), 12);
    }

    // replacement starting at the edge of the tree's padding:
    // resize the content and not the padding.
    {
        let mut tree = tree.clone();
        tree.edit(&InputEdit {
            start_byte: 2,
            old_end_byte: 2,
            new_end_byte: 4,
            start_position: Point::new(0, 2),
            old_end_position: Point::new(0, 2),
            new_end_position: Point::new(0, 4),
        });

        let expr = tree.root_node().child(0).unwrap().child(0).unwrap();
        let child1 = expr.child(0).unwrap();
        let child2 = expr.child(1).unwrap();

        assert!(expr.has_changes());
        assert_eq!(expr.start_byte(), 4);
        assert_eq!(expr.end_byte(), 17);
        assert!(child1.has_changes());
        assert_eq!(child1.start_byte(), 4);
        assert_eq!(child1.end_byte(), 7);
        assert!(!child2.has_changes());
        assert_eq!(child2.start_byte(), 9);
        assert_eq!(child2.end_byte(), 12);
    }

    // deletion that spans more than one child node:
    // shrink subsequent child nodes.
    {
        let mut tree = tree.clone();
        tree.edit(&InputEdit {
            start_byte: 1,
            old_end_byte: 11,
            new_end_byte: 4,
            start_position: Point::new(0, 1),
            old_end_position: Point::new(0, 11),
            new_end_position: Point::new(0, 4),
        });

        let expr = tree.root_node().child(0).unwrap().child(0).unwrap();
        let child1 = expr.child(0).unwrap();
        let child2 = expr.child(1).unwrap();
        let child3 = expr.child(2).unwrap();

        assert!(expr.has_changes());
        assert_eq!(expr.start_byte(), 4);
        assert_eq!(expr.end_byte(), 8);
        assert!(child1.has_changes());
        assert_eq!(child1.start_byte(), 4);
        assert_eq!(child1.end_byte(), 4);
        assert!(child2.has_changes());
        assert_eq!(child2.start_byte(), 4);
        assert_eq!(child2.end_byte(), 4);
        assert!(child3.has_changes());
        assert_eq!(child3.start_byte(), 5);
        assert_eq!(child3.end_byte(), 8);
    }

    // insertion at the end of the tree:
    // extend the tree's content.
    {
        let mut tree = tree.clone();
        tree.edit(&InputEdit {
            start_byte: 15,
            old_end_byte: 15,
            new_end_byte: 16,
            start_position: Point::new(0, 15),
            old_end_position: Point::new(0, 15),
            new_end_position: Point::new(0, 16),
        });

        let expr = tree.root_node().child(0).unwrap().child(0).unwrap();
        let child1 = expr.child(0).unwrap();
        let child2 = expr.child(1).unwrap();
        let child3 = expr.child(2).unwrap();

        assert!(expr.has_changes());
        assert_eq!(expr.start_byte(), 2);
        assert_eq!(expr.end_byte(), 16);
        assert!(!child1.has_changes());
        assert_eq!(child1.end_byte(), 5);
        assert!(!child2.has_changes());
        assert_eq!(child2.end_byte(), 10);
        assert!(child3.has_changes());
        assert_eq!(child3.end_byte(), 16);
    }
}

#[test]
fn test_tree_cursor() {
    let mut parser = Parser::new();
    parser.set_language(get_language("rust")).unwrap();

    let tree = parser
        .parse(
            "
                struct Stuff {
                    a: A;
                    b: Option<B>,
                }
            ",
            None,
        )
        .unwrap();

    let mut cursor = tree.walk();
    assert_eq!(cursor.node().kind(), "source_file");

    assert!(cursor.goto_first_child());
    assert_eq!(cursor.node().kind(), "struct_item");

    assert!(cursor.goto_first_child());
    assert_eq!(cursor.node().kind(), "struct");
    assert_eq!(cursor.node().is_named(), false);

    assert!(cursor.goto_next_sibling());
    assert_eq!(cursor.node().kind(), "type_identifier");
    assert_eq!(cursor.node().is_named(), true);

    assert!(cursor.goto_next_sibling());
    assert_eq!(cursor.node().kind(), "field_declaration_list");
    assert_eq!(cursor.node().is_named(), true);
}

#[test]
fn test_tree_cursor_fields() {
    let mut parser = Parser::new();
    parser.set_language(get_language("javascript")).unwrap();

    let tree = parser
        .parse("function /*1*/ bar /*2*/ () {}", None)
        .unwrap();

    let mut cursor = tree.walk();
    assert_eq!(cursor.node().kind(), "program");

    cursor.goto_first_child();
    assert_eq!(cursor.node().kind(), "function_declaration");
    assert_eq!(cursor.field_name(), None);

    cursor.goto_first_child();
    assert_eq!(cursor.node().kind(), "function");
    assert_eq!(cursor.field_name(), None);

    cursor.goto_next_sibling();
    assert_eq!(cursor.node().kind(), "comment");
    assert_eq!(cursor.field_name(), None);

    cursor.goto_next_sibling();
    assert_eq!(cursor.node().kind(), "identifier");
    assert_eq!(cursor.field_name(), Some("name"));

    cursor.goto_next_sibling();
    assert_eq!(cursor.node().kind(), "comment");
    assert_eq!(cursor.field_name(), None);

    cursor.goto_next_sibling();
    assert_eq!(cursor.node().kind(), "formal_parameters");
    assert_eq!(cursor.field_name(), Some("parameters"));
}

#[test]
fn test_tree_cursor_child_for_point() {
    let mut parser = Parser::new();
    parser.set_language(get_language("javascript")).unwrap();
    let source = &"
    [
        one,
        {
            two: tree
        },
        four, five, six
    ];"[1..];
    let tree = parser.parse(source, None).unwrap();

    let mut c = tree.walk();
    assert_eq!(c.node().kind(), "program");

    assert_eq!(c.goto_first_child_for_point(Point::new(7, 0)), None);
    assert_eq!(c.goto_first_child_for_point(Point::new(6, 7)), None);
    assert_eq!(c.node().kind(), "program");

    // descend to expression statement
    assert_eq!(c.goto_first_child_for_point(Point::new(6, 6)), Some(0));
    assert_eq!(c.node().kind(), "expression_statement");

    // step into ';' and back up
    assert_eq!(c.goto_first_child_for_point(Point::new(7, 0)), None);
    assert_eq!(c.goto_first_child_for_point(Point::new(6, 6)), Some(1));
    assert_eq!(
        (c.node().kind(), c.node().start_position()),
        (";", Point::new(6, 5))
    );
    assert!(c.goto_parent());

    // descend into array
    assert_eq!(c.goto_first_child_for_point(Point::new(6, 4)), Some(0));
    assert_eq!(
        (c.node().kind(), c.node().start_position()),
        ("array", Point::new(0, 4))
    );

    // step into '[' and back up
    assert_eq!(c.goto_first_child_for_point(Point::new(0, 4)), Some(0));
    assert_eq!(
        (c.node().kind(), c.node().start_position()),
        ("[", Point::new(0, 4))
    );
    assert!(c.goto_parent());

    // step into identifier 'one' and back up
    assert_eq!(c.goto_first_child_for_point(Point::new(1, 0)), Some(1));
    assert_eq!(
        (c.node().kind(), c.node().start_position()),
        ("identifier", Point::new(1, 8))
    );
    assert!(c.goto_parent());
    assert_eq!(c.goto_first_child_for_point(Point::new(1, 10)), Some(1));
    assert_eq!(
        (c.node().kind(), c.node().start_position()),
        ("identifier", Point::new(1, 8))
    );
    assert!(c.goto_parent());

    // step into first ',' and back up
    assert_eq!(c.goto_first_child_for_point(Point::new(1, 12)), Some(2));
    assert_eq!(
        (c.node().kind(), c.node().start_position()),
        (",", Point::new(1, 11))
    );
    assert!(c.goto_parent());

    // step into identifier 'four' and back up
    assert_eq!(c.goto_first_child_for_point(Point::new(5, 0)), Some(5));
    assert_eq!(
        (c.node().kind(), c.node().start_position()),
        ("identifier", Point::new(5, 8))
    );
    assert!(c.goto_parent());
    assert_eq!(c.goto_first_child_for_point(Point::new(5, 0)), Some(5));
    assert_eq!(
        (c.node().kind(), c.node().start_position()),
        ("identifier", Point::new(5, 8))
    );
    assert!(c.goto_parent());

    // step into ']' and back up
    assert_eq!(c.goto_first_child_for_point(Point::new(6, 0)), Some(10));
    assert_eq!(
        (c.node().kind(), c.node().start_position()),
        ("]", Point::new(6, 4))
    );
    assert!(c.goto_parent());
    assert_eq!(c.goto_first_child_for_point(Point::new(6, 0)), Some(10));
    assert_eq!(
        (c.node().kind(), c.node().start_position()),
        ("]", Point::new(6, 4))
    );
    assert!(c.goto_parent());

    // descend into object
    assert_eq!(c.goto_first_child_for_point(Point::new(2, 0)), Some(3));
    assert_eq!(
        (c.node().kind(), c.node().start_position()),
        ("object", Point::new(2, 8))
    );
}

#[test]
fn test_tree_node_equality() {
    let mut parser = Parser::new();
    parser.set_language(get_language("rust")).unwrap();
    let tree = parser.parse("struct A {}", None).unwrap();
    let node1 = tree.root_node();
    let node2 = tree.root_node();
    assert_eq!(node1, node2);
    assert_eq!(node1.child(0).unwrap(), node2.child(0).unwrap());
    assert_ne!(node1.child(0).unwrap(), node2);
}

#[test]
fn test_get_changed_ranges() {
    let source_code = b"{a: null};\n".to_vec();

    let mut parser = Parser::new();
    parser.set_language(get_language("javascript")).unwrap();
    let tree = parser.parse(&source_code, None).unwrap();

    assert_eq!(
        tree.root_node().to_sexp(),
        "(program (expression_statement (object (pair key: (property_identifier) value: (null)))))"
    );

    // Updating one token
    {
        let mut tree = tree.clone();
        let mut source_code = source_code.clone();

        // Replace `null` with `nothing` - that token has changed syntax
        let edit = Edit {
            position: index_of(&source_code, "ull"),
            deleted_length: 3,
            inserted_text: b"othing".to_vec(),
        };
        let inverse_edit = invert_edit(&source_code, &edit);
        let ranges = get_changed_ranges(&mut parser, &mut tree, &mut source_code, edit);
        assert_eq!(ranges, vec![range_of(&source_code, "nothing")]);

        // Replace `nothing` with `null` - that token has changed syntax
        let ranges = get_changed_ranges(&mut parser, &mut tree, &mut source_code, inverse_edit);
        assert_eq!(ranges, vec![range_of(&source_code, "null")]);
    }

    // Changing only leading whitespace
    {
        let mut tree = tree.clone();
        let mut source_code = source_code.clone();

        // Insert leading newline - no changed ranges
        let edit = Edit {
            position: 0,
            deleted_length: 0,
            inserted_text: b"\n".to_vec(),
        };
        let inverse_edit = invert_edit(&source_code, &edit);
        let ranges = get_changed_ranges(&mut parser, &mut tree, &mut source_code, edit);
        assert_eq!(ranges, vec![]);

        // Remove leading newline - no changed ranges
        let ranges = get_changed_ranges(&mut parser, &mut tree, &mut source_code, inverse_edit);
        assert_eq!(ranges, vec![]);
    }

    // Inserting elements
    {
        let mut tree = tree.clone();
        let mut source_code = source_code.clone();

        // Insert a key-value pair before the `}` - those tokens are changed
        let edit1 = Edit {
            position: index_of(&source_code, "}"),
            deleted_length: 0,
            inserted_text: b", b: false".to_vec(),
        };
        let inverse_edit1 = invert_edit(&source_code, &edit1);
        let ranges = get_changed_ranges(&mut parser, &mut tree, &mut source_code, edit1);
        assert_eq!(ranges, vec![range_of(&source_code, ", b: false")]);

        let edit2 = Edit {
            position: index_of(&source_code, ", b"),
            deleted_length: 0,
            inserted_text: b", c: 1".to_vec(),
        };
        let inverse_edit2 = invert_edit(&source_code, &edit2);
        let ranges = get_changed_ranges(&mut parser, &mut tree, &mut source_code, edit2);
        assert_eq!(ranges, vec![range_of(&source_code, ", c: 1")]);

        // Remove the middle pair
        let ranges = get_changed_ranges(&mut parser, &mut tree, &mut source_code, inverse_edit2);
        assert_eq!(ranges, vec![]);

        // Remove the second pair
        let ranges = get_changed_ranges(&mut parser, &mut tree, &mut source_code, inverse_edit1);
        assert_eq!(ranges, vec![]);
    }

    // Wrapping elements in larger expressions
    {
        let mut tree = tree.clone();
        let mut source_code = source_code.clone();

        // Replace `null` with the binary expression `b === null`
        let edit1 = Edit {
            position: index_of(&source_code, "null"),
            deleted_length: 0,
            inserted_text: b"b === ".to_vec(),
        };
        let inverse_edit1 = invert_edit(&source_code, &edit1);
        let ranges = get_changed_ranges(&mut parser, &mut tree, &mut source_code, edit1);
        assert_eq!(ranges, vec![range_of(&source_code, "b === null")]);

        // Undo
        let ranges = get_changed_ranges(&mut parser, &mut tree, &mut source_code, inverse_edit1);
        assert_eq!(ranges, vec![range_of(&source_code, "null")]);
    }
}

fn index_of(text: &Vec<u8>, substring: &str) -> usize {
    str::from_utf8(text.as_slice())
        .unwrap()
        .find(substring)
        .unwrap()
}

fn range_of(text: &Vec<u8>, substring: &str) -> Range {
    let start_byte = index_of(text, substring);
    let end_byte = start_byte + substring.as_bytes().len();
    Range {
        start_byte,
        end_byte,
        start_point: Point::new(0, start_byte),
        end_point: Point::new(0, end_byte),
    }
}

fn get_changed_ranges(
    parser: &mut Parser,
    tree: &mut Tree,
    source_code: &mut Vec<u8>,
    edit: Edit,
) -> Vec<Range> {
    perform_edit(tree, source_code, &edit);
    let new_tree = parser.parse(&source_code, Some(tree)).unwrap();
    let result = tree.changed_ranges(&new_tree).collect();
    *tree = new_tree;
    result
}
