from muppet.parser_combinator import (
    MatchObject,
    ParseDirective,
    ParseError,
    ParserCombinator,
    all_,
    char,
    complement,
    count,
    digit,
    hexdig,
    line_comment,
    many,
    many1,
    name,
    nop,
    not_,
    optional,
    s,
    tag,
    ws,
    delimited,
    discard,
    space,
)


def test_partial():
    data = "123 Hello"

    parser = ParserCombinator(data)

    assert ["123"] == parser.get(many(digit))
    assert " Hello" == parser.remaining()


def test_char():
    parser = ParserCombinator("Hello!")
    assert ['H'] == parser.get(char)
    assert ['e'] == parser.get(char)
    assert "llo!" == parser.remaining()


def test_nop():
    parser = ParserCombinator("Hello!")
    assert [] == parser.get(nop)
    assert "Hello!" == parser.remaining()


def test_digit():
    p1 = ParserCombinator("123")
    assert ['1'] == p1.get(digit)
    p2 = ParserCombinator("Hello")
    try:
        p2.get(digit)
        assert False, "Parser should have failed, but didn't"
    except ParseError:
        assert "Hello" == p2.remaining()


def test_consume():
    p = ParserCombinator("Hello")
    try:
        p.get([char, digit])
        assert False, "Parser should have failed, but didn't"
    except ParseError:
        assert "ello" == p.remaining()


def test_hexdig():
    pass


def test_space():
    pass

# --------------------------------------------------


def test_many():
    p1 = ParserCombinator("Hello, World!")

    assert ["Hello, World!"] == p1.get(many(char))

    p2 = ParserCombinator("")
    assert [] == p2.get(many(char))


def test_many1():
    p1 = ParserCombinator("Hello, World!")

    assert ["Hello, World!"] == p1.get(many1(char))

    p2 = ParserCombinator("")
    try:
        p2.get(many1(char))
        assert False, "Parser should have failed, but didn't"
    except ParseError:
        assert True


def test_count():
    p1 = ParserCombinator("ABCDE")
    assert ["A"] == p1.get(count(s("A"), 1, 3))

    p2 = ParserCombinator("AAAAA")
    assert ["AAA"] == p2.get(count(s("A"), 1, 3))

    p3 = ParserCombinator("BBBBB")
    assert [] == p3.get(count(s("A"), 3))

    p4 = ParserCombinator("AAAAA")
    assert ["AAA"] == p4.get(count(s("A"), 3))


def test_optional():
    p1 = ParserCombinator("ABC")
    assert ["A"] == p1.get(optional(s("A")))
    assert "BC" == p1.remaining()

    p2 = ParserCombinator("ABC")
    assert [] == p2.get(optional(s("B")))
    assert "ABC" == p2.remaining()


def test_ws():
    p1 = ParserCombinator("Hello")
    assert [] == p1.get(ws)

    p2 = ParserCombinator("\t \n\r Hello")
    assert ["\t \n\r "] == p2.get(ws)


def test_discard():
    p1 = ParserCombinator("Hello")
    assert [] == p1.get(discard(s("He")))
    assert "llo" == p1.remaining()

    p2 = ParserCombinator("Hello!")
    assert ["ll"] == p2.get(discard(s("He")) & s("ll") & discard(s("o")))
    assert "!" == p2.remaining()


def handle_int(xs: list[str]) -> list[int]:
    """Convert matched to an integer."""
    return [int(xs[0])]


def test_delimited():
    number = many(digit) @ handle_int

    p1 = ParserCombinator("1,20,2")
    assert [1, ",", 20, ",", 2] == p1.get(delimited(s(","), number))

    p2 = ParserCombinator("1,20,2")
    assert [1, 20, 2] == p2.get(delimited(discard(s(",")), number))


def test_all():
    p1 = ParserCombinator("123")
    assert ["1"] == p1.get(all_(char, digit))


def test_complement_1():
    p1 = ParserCombinator("Hello, World!")
    assert ["H"] == p1.get(all_(~ space, char))


def test_complement_2():
    p1 = ParserCombinator("Hello, World!")
    assert ["Hello,"] == p1.get(many(all_(~ space, char)))


def test_complement():
    p1 = ParserCombinator("Hello")
    assert ["H"] == p1.get(complement('e'))

    p2 = ParserCombinator("Hello")
    assert ["He"] == p2.get(many(complement("l")))


def test_stringifiers():
    assert "'a'" == str(s("a"))
    assert "~ 'a'" == repr(~ s("a"))
    assert "x" == str(name("x", space & space))
    assert "('a' & 'b')" == str(s('a') & s('b'))
    assert "('a' | 'b')" == str(s('a') | s('b'))
    assert "char" == str(char)
    assert "nop" == str(nop)