"""
A parser for ground ProbLog programs.
"""

from parsimonious.grammar import Grammar
from ground_problog.GroundProblogVisitor import GroundProblogVisitor


class GroundProblogParser:
    def __init__(self):
        self._visitor = GroundProblogVisitor(logging=False)

    def _grammar(self):
        # A PEG grammar for ground Problog. It is written in a very weird way
        # in order to make the visiting of the parse tree work.
        return Grammar(r"""
            program           = _ clauses
            clauses           = clause*
            clause            = predicate dot
            predicate         = prob_ann / rule / term
            
            rule              = term turnstile conjunction
            conjunction       = term conjunction_opt
            conjunction_opt   = conjunction_more?
            conjunction_more  = comma conjunction

            prob_ann          = prob_ann_heads prob_ann_rule_opt
            prob_ann_heads    = prob_fact prob_fact_opt
            prob_fact_opt     = prob_fact_more?
            prob_fact_more    = semicolon prob_ann_heads
            prob_ann_rule_opt = prob_ann_rule?
            prob_ann_rule     = turnstile conjunction
            prob_fact         = probability doublecolon term

            term              = negation_opt word_or_num arguments_opt
            word_or_num       = decimal_or_frac / word
            negation_opt      = negation?
            arguments_opt     = arguments?
            arguments         = lparen arguments_list rparen
            arguments_list    = term arguments_more_o
            arguments_more_o  = arguments_more?
            arguments_more    = comma arguments_list
            
            probability       = prob_num / prob_tunable_num / prob_tunable_none
            prob_num          = decimal_or_frac _
            prob_tunable_num  = _ tunable lparen decimal_or_frac rparen _
            prob_tunable_none = _ tunable_empty _
            decimal_or_frac   = decimal / fraction
            fraction          = number slash number
            # could to this in a better way, but this works and we are way over time already
            word              = ~r"([a-zA-Z0-9_\[\]]+|\"[a-zA-Z0-9_\-\'\.\/\=\<\>\+\[\]]*\"|\'[a-zA-Z0-9_\-\'\.\/\=\<\>\+\[\]]*\')"
            number            = ~r"[0-9]*"
            decimal           = ~r"[0-9]*\.[0-9]*"
            dot               = _ "." _
            comma             = _ "," _
            semicolon         = _ ";" _
            lparen            = _ "(" _
            rparen            = _ ")" _
            slash             = _ "/" _
            doublecolon       = _ "::" _
            turnstile         = _ ":-" _
            negation          = _ ~r"\\\+" _
            tunable           = _ "t" _
            tunable_empty     = "t(_)"
            
            _                 = meaninglessness*
            meaninglessness   = ~r"\s+"
        """)

    def parse(self, program):
        """ Parses a ground problog program and returns the root node of the parse tree. """
        return self._grammar().parse(program)

    def parse_tree_to_problog(self, root_node):
        """ Visits a ground problog program's parse tree and
        returns a GroundProblog object that represents the program. """
        return self._visitor.visit(root_node)

    def program_to_problog(self, program):
        """ Takes a ground problog program and returns a GroundProblog object that represents the program. """
        return self.parse_tree_to_problog(self.parse(program))
