import pytest

from synthegrator.environments import ProjectWorkingDirectory
from synthegrator.memory_fs import ProjectDir, ProjectFile
from synthegrator.transformation_spec import (
    MarkElement,
    MarkText,
    StsEditable,
    StsInsert,
    StsInvisible,
    StsNormalText,
    StsPath,
    StsPrefixCommentSelector,
    StsSelectorCharSpan,
    StsSelectorLineSpan,
    StsSpecStatement,
    StsValueQuery,
    TransformationSpec,
    markup_path,
)


def test_glob_select1():
    selector = StsPath(path_glob="foo.py")
    assert selector.does_match_path("foo.py")


def test_glob_select2():
    selector = StsPath(path_glob="*.py")
    assert selector.does_match_path("foo.py")
    assert not selector.does_match_path("foo.java")


def test_glob_select3():
    selector = StsPath(path_glob="foo/*.py")
    assert selector.does_match_path("foo/bar.py")
    assert not selector.does_match_path("foo.py")


def test_split_text():
    text = "bar"
    node = MarkText(text, 0, len(text))
    assert node.text == text
    assert node.mark_start_original_char == 0
    assert node.mark_end_original_char == 3
    first, second = node.split(1)
    assert first == MarkText("b", 0, 1)
    assert second == MarkText("ar", 1, 3)
    assert second.mark_start_original_char == 1
    assert second.mark_end_original_char == 3


def test_split_text1():
    text = "bar"
    start_offset = 5
    node = MarkText(text, start_offset, start_offset + len(text))
    first, second = node.split(start_offset + 2)
    assert first == MarkText("ba", start_offset, start_offset + 2)
    assert second == MarkText("r", start_offset + 2, start_offset + 3)


def test_split_text2():
    text = "bar"
    start_offset = 0
    node = MarkText(text, start_offset, start_offset + len(text))
    first, second = node.split(0)
    assert first is None
    assert second == node


def test_split_text3():
    text = "bar"
    start_offset = 0
    node = MarkText(text, start_offset, start_offset + len(text))
    first, second = node.split(3)
    assert first == node
    assert second is None


def test_split_span():
    text = "bar"
    start_offset = 0
    node = MarkText(text, start_offset, start_offset + len(text))
    before, inner, after = node.split_span(1, 2)
    assert before == MarkText("b", start_offset, start_offset + 1)
    assert inner == MarkText("a", start_offset + 1, start_offset + 2)
    assert after == MarkText("r", start_offset + 2, start_offset + 3)


def test_split_span_before():
    text = "bar"
    start_offset = 5
    node = MarkText(text, start_offset, start_offset + len(text))
    before, inner, after = node.split_span(0, start_offset + 1)
    assert before is None
    assert inner == MarkText("b", start_offset, start_offset + 1)
    assert after == MarkText("ar", start_offset + 1, start_offset + 3)


def test_split_span_all():
    text = "bar"
    start_offset = 5
    node = MarkText(text, start_offset, start_offset + len(text))
    before, inner, after = node.split_span(0, 99)
    assert before is None
    assert inner == MarkText("bar", start_offset, start_offset + 3)
    assert after is None


def test_insert_node1():
    text = "bar"
    node = MarkElement(StsNormalText(), [MarkText(text, 0, 3)], 0, 3)
    node.insert_node(StsInvisible(), 1, 2)
    print("\n")
    print(node.as_xml())
    assert node.children[0] == MarkText("b", 0, 1)
    assert node.children[1] == MarkElement(
        StsInvisible(),
        [MarkText(text[1:2], 1, 2)],
        1,
        2,
    )
    assert node.children[2] == MarkText("r", 2, 3)


def test_insert_node_left_side():
    text = "bar"
    node = MarkElement(StsNormalText(), [MarkText(text, 0, 3)], 0, 3)
    node.insert_node(StsInvisible(), 0, 2)
    print(node.as_xml())
    assert len(node.children) == 2
    assert node.children[0] == MarkElement(
        StsInvisible(),
        [MarkText(text[0:2], 0, 2)],
        0,
        2,
    )
    assert node.children[1] == MarkText("r", 2, 3)


def test_insert_node_right_side():
    text = "bar"
    node = MarkElement(StsNormalText(), [MarkText(text, 0, 3)], 0, 3)
    node.insert_node(StsInvisible(), 1, 3)
    print(node.as_xml())
    assert len(node.children) == 2
    assert node.children[0] == MarkText("b", 0, 1)
    assert node.children[1] == MarkElement(
        StsInvisible(),
        [MarkText(text[1:3], 1, 3)],
        1,
        3,
    )


def test_insert_node_full():
    text = "bar"
    node = MarkElement(StsNormalText(), [MarkText(text, 0, 3)], 0, 3)
    node.insert_node(StsInvisible(), 0, 3)
    print(node.as_xml())
    assert len(node.children) == 1
    assert node.children[0] == MarkElement(StsInvisible(), [MarkText(text, 0, 3)], 0, 3)


def test_too_far_past():
    text = "bar"
    node = MarkElement(StsNormalText(), [MarkText(text, 0, 3)], 0, 3)
    with pytest.raises(ValueError):
        node.insert_node(StsInvisible(), 0, 5)


def test_too_single():
    text = "bar"
    node = MarkElement(StsNormalText(), [MarkText(text, 0, 3)], 0, 3)
    node.insert_node(StsInvisible(), 1, 1)
    print("\n", node.as_xml())
    assert len(node.children) == 3
    assert node.children[0] == MarkText("b", 0, 1)
    assert node.children[1] == MarkElement(StsInvisible(), [], 1, 1)
    assert node.children[2] == MarkText("ar", 1, 3)


def test_too_single_end():
    text = "bar"
    node = MarkElement(StsNormalText(), [MarkText(text, 0, 3)], 0, 3)
    node.insert_node(StsInvisible(), 3, 3)
    print("\n", node.as_xml())
    assert len(node.children) == 2
    assert node.children[0] == MarkText("bar", 0, 3)
    assert node.children[1] == MarkElement(StsInvisible(), [], 3, 3)


def test_multi_element():
    node = MarkElement(
        StsNormalText(),
        [
            MarkText("foo", 0, 3),
            MarkElement(StsInvisible(), [MarkText("bar", 3, 6)], 3, 6),
            MarkText("baz", 6, 9),
        ],
        0,
        9,
    )
    node.insert_node(StsEditable(), 3, 6)
    print("\n", node.as_xml())
    assert len(node.children) == 3
    assert node.children[0] == MarkText("foo", 0, 3)
    val = node.children[1]
    expected = MarkElement(
        StsInvisible(),
        [MarkElement(StsEditable(), [MarkText("bar", 3, 6)], 3, 6)],
        3,
        6,
    )
    assert val.as_xml() == expected.as_xml()
    assert val == expected
    assert node.children[2] == MarkText("baz", 6, 9)


def test_multi_element_cut():
    node = MarkElement(
        StsNormalText(),
        [
            MarkText("foo", 0, 3),
            MarkElement(StsInvisible(), [MarkText("bar", 3, 6)], 3, 6),
            MarkText("baz", 6, 9),
        ],
        0,
        9,
    )
    node.insert_node(StsEditable(), 2, 6)
    print("\n", node.as_xml())
    assert len(node.children) == 3
    assert node.children[0] == MarkText("fo", 0, 2)
    val = node.children[1]
    expected = MarkElement(
        StsEditable(),
        [
            MarkText("o", 2, 3),
            MarkElement(StsInvisible(), [MarkText("bar", 3, 6)], 3, 6),
        ],
        2,
        6,
    )
    assert val.as_xml() == expected.as_xml()
    assert val == expected
    assert node.children[2] == MarkText("baz", 6, 9)


def test_multi_element_cut_twice():
    node = MarkElement(
        StsNormalText(),
        [
            MarkText("foo", 0, 3),
            MarkElement(StsInvisible(), [MarkText("bar", 3, 6)], 3, 6),
            MarkText("baz", 6, 9),
        ],
        0,
        9,
    )
    node.insert_node(StsEditable(), 2, 7)
    print("\n", node.as_xml())
    assert len(node.children) == 3
    assert node.children[0] == MarkText("fo", 0, 2)
    val = node.children[1]
    expected = MarkElement(
        StsEditable(),
        [
            MarkText("o", 2, 3),
            MarkElement(StsInvisible(), [MarkText("bar", 3, 6)], 3, 6),
            MarkText("b", 6, 7),
        ],
        2,
        7,
    )
    assert val.as_xml() == expected.as_xml()
    assert val == expected
    assert node.children[2] == MarkText("az", 7, 9)


def test_multi_element_bad():
    node = MarkElement(
        StsNormalText(),
        [
            MarkText("foo", 0, 3),
            MarkElement(StsInvisible(), [MarkText("bar", 3, 6)], 3, 6),
            MarkText("baz", 6, 9),
        ],
        0,
        9,
    )
    with pytest.raises(ValueError):
        node.insert_node(StsEditable(), 2, 5)
        print("\n", node.as_xml())


def test_multi_element_many_elements():
    node = MarkElement(
        StsNormalText(),
        [
            MarkText("foo", 0, 3),
            MarkElement(StsInvisible(), [MarkText("bar", 3, 6)], 3, 6),
            MarkElement(StsInvisible(), [MarkText("pop", 6, 9)], 6, 9),
            MarkElement(StsInvisible(), [MarkText("bap", 9, 12)], 9, 12),
            MarkText("baz", 12, 15),
        ],
        0,
        15,
    )
    node.insert_node(StsEditable(), 3, 9)
    assert len(node.children) == 4
    assert node.children[0] == MarkText("foo", 0, 3)
    val = node.children[1]
    expected = MarkElement(
        StsEditable(),
        [
            MarkElement(StsInvisible(), [MarkText("bar", 3, 6)], 3, 6),
            MarkElement(StsInvisible(), [MarkText("pop", 6, 9)], 6, 9),
        ],
        3,
        9,
    )
    assert val.as_xml() == expected.as_xml()
    assert val == expected
    assert node.children[2] == MarkElement(
        StsInvisible(),
        [MarkText("bap", 9, 12)],
        9,
        12,
    )
    assert node.children[3] == MarkText("baz", 12, 15)


def test_markup_path():
    wd = ProjectWorkingDirectory(
        files=ProjectDir(
            {
                "foo.txt": ProjectFile("foo.txt", "0123456789"),
            },
        ),
    )
    spec = TransformationSpec(
        (
            StsSpecStatement(
                StsPath("*.txt"),
                StsSelectorCharSpan(1, 3),
                StsInvisible(),
            ),
            StsSpecStatement(
                StsPath("foo.py"),
                StsSelectorCharSpan(0, 9),
                StsInvisible(),
            ),  # Not match
            StsSpecStatement(StsPath("*.txt"), StsSelectorCharSpan(5, 5), StsInsert()),
        ),
    )
    node = markup_path(wd, "foo.txt", spec)
    node.pretty_print_xml()
    expected = MarkElement(
        StsNormalText(),
        [
            MarkText("0", 0, 1),
            MarkElement(StsInvisible(), [MarkText("12", 1, 3)], 1, 3),
            MarkText("34", 3, 5),
            MarkElement(StsInsert(), [], 5, 5),
            MarkText("56789", 5, 10),
        ],
        0,
        10,
    ).freeze()
    assert node.as_xml() == expected.as_xml()
    assert repr(node) == repr(expected)
    assert node == expected


def test_markup_path_double_sided_part():
    wd = ProjectWorkingDirectory(
        files=ProjectDir(
            {
                "foo.txt": ProjectFile("foo.txt", "0\n1\n2\n3\n4"),
            },
        ),
    )
    spec = TransformationSpec(
        (
            StsSpecStatement(
                StsPath("*.txt"),
                StsSelectorLineSpan(0, 1),
                StsInvisible(),
            ),
            StsSpecStatement(
                StsPath("*.txt"),
                StsSelectorLineSpan(4, 5),
                StsInvisible(),
            ),
            # StsSpecStatement(
            # ),
        ),
    )
    node = markup_path(wd, "foo.txt", spec)
    node.pretty_print_xml()
    expected = MarkElement(
        StsNormalText(),
        [
            MarkElement(StsInvisible(), [MarkText("0\n", 0, 2)], 0, 2),
            MarkText("1\n2\n3\n", 2, 8),
            MarkElement(StsInvisible(), [MarkText("4", 8, 9)], 8, 9),
        ],
        0,
        9,
    ).freeze()
    assert node.as_xml() == expected.as_xml()
    assert repr(node) == repr(expected)
    assert node == expected


def test_markup_path_double_sided_full():
    wd = ProjectWorkingDirectory(
        files=ProjectDir(
            {
                "foo.txt": ProjectFile("foo.txt", "0\n1\n2\n3\n4"),
            },
        ),
    )
    spec = TransformationSpec(
        (
            StsSpecStatement(
                StsPath("*.txt"),
                StsSelectorLineSpan(0, 1),
                StsInvisible(),
            ),
            StsSpecStatement(
                StsPath("*.txt"),
                StsSelectorLineSpan(4, 5),
                StsInvisible(),
            ),
            StsSpecStatement(
                StsPath("*.txt"),
                StsSelectorLineSpan(2, 3),
                StsEditable(),
            ),
        ),
    )
    node = markup_path(wd, "foo.txt", spec)
    node.pretty_print_xml()
    expected = MarkElement(
        StsNormalText(),
        [
            MarkElement(StsInvisible(), [MarkText("0\n", 0, 2)], 0, 2),
            MarkText("1\n", 2, 4),
            MarkElement(StsEditable(), [MarkText("2\n", 4, 6)], 4, 6),
            MarkText("3\n", 6, 8),
            MarkElement(StsInvisible(), [MarkText("4", 8, 9)], 8, 9),
        ],
        0,
        9,
    ).freeze()
    assert node.as_xml() == expected.as_xml()
    assert repr(node) == repr(expected)
    assert node == expected


def test_line_selector():
    selector = StsSelectorLineSpan(1, 3)
    text = "foo\nbar\nbaz\npop"
    spans = selector.get_matching_spans(text, path="foo.txt")
    assert len(spans) == 1
    start, end = spans[0]
    assert text[start:end] == "bar\nbaz\n"


def test_line_selector2():
    selector = StsSelectorLineSpan(0, 2)
    text = "foob\nbarss\nbaz\npop"
    spans = selector.get_matching_spans(text, path="foo.txt")
    assert len(spans) == 1
    start, end = spans[0]
    assert text[start:end] == "foob\nbarss\n"


def test_line_selector3():
    selector = StsSelectorLineSpan(3, 4)
    text = "foob\nbarss\nbaz\npop"
    spans = selector.get_matching_spans(text, path="foo.txt")
    assert len(spans) == 1
    start, end = spans[0]
    assert text[start:end] == "pop"


def test_static_verb():
    verb = StsValueQuery()
    assert verb.operation_is_static()
    statement = StsSpecStatement(
        StsPath("*.py"),
        StsPrefixCommentSelector(),
        verb=verb,
    )
    assert statement.operation_is_static()
    spec = TransformationSpec((statement,))
    file = (
        "def foo():\n"
        '    """my comment"""\n'
        "    pass\n"
        "\n"
        "def bar():\n"
        '    """my comment"""\n'
        "    pass\n"
    )
    wd = ProjectWorkingDirectory(
        files=ProjectDir.construct_with_files([("foo.py", file)]),
    )
    node = markup_path(wd, "foo.py", spec)
    print(node.as_xml())
    # This is copied from whatever was printed. Things
    #   like the random hash might reasonably change.
    #   The main thing to confirm is that the two ids of the
    #   Value Queries are the same.
    assert (
        node.as_xml()
        == '''<StsNormalText id="FsD6y" _original_start_char="0" _original_end_char="83">def foo():
<StsValueQuery id="s_3Ypr9A" _original_start_char="11" _original_end_char="32">    """my comment"""
</StsValueQuery>    pass

def bar():
<StsValueQuery id="s_3Ypr9A" _original_start_char="53" _original_end_char="74">    """my comment"""
</StsValueQuery>    pass
</StsNormalText>'''
    )
