import itertools
from collections import defaultdict
from collections.abc import Set, Mapping, Callable
from typing import Iterable, MutableMapping, NamedTuple

from .grammar import GrammarBase
from .common import Word, Symbol, PATTERNS, Rule, EOD, Semantic, Parser, LRMethod, LLMethod
from .lr.automaton import LR0Automaton
from .lr.tables import LRTable, LRParser
from .ll1 import LL1Table, LL1Parser
from .semantic import DictSemantic

class GrammarParms(NamedTuple):
    terminals:Iterable[Symbol]
    axiom:Symbol
    rules_by_var:MutableMapping[Symbol, Set[Rule]]

class GrammarBuilder :  # static methods
    @staticmethod
    def parse_right(s) -> list[Word]:
        """
            s syntax : word [ | word ]*
        """
        return [Word.from_string(s) for s in PATTERNS.SEPARATOR.split(s)]

    @staticmethod
    def decompose_line(line: str) -> (Symbol | None, list[Word]):
        """
            decompose line
            line syntax : ( symbol -> )? word [ | word]*
        """
        parts = PATTERNS.ARROW.split(line)
        assert len(parts) <= 2, f"{line} : Error : line must contain at most arrow sign"
        return (Symbol(parts[0]) if len(parts)==2 else None), GrammarBuilder.parse_right(parts[-1])

    @staticmethod
    def parse_line(line) -> list[Rule]:
        """
            l : stripped string
            returns list of Rules (Rules have same left side)
        """
        left, right = GrammarBuilder.decompose_line(line)
        assert left is not None, f"{line} : Error : line must contain exactly on arrow sign"
        return [Rule(left, word) for word in right]

    @staticmethod
    def from_rules(rules : Iterable[Rule]) -> GrammarParms:
        """
            Static method
            Creates grammar from rules
            rules : Iterable of rules.
                    left variable of the first rule is regarded as axiom
            returns Grammar instance
        """
        dico: MutableMapping[Symbol, dict[Rule,None]] \
            = defaultdict(dict)
            # = defaultdict(lambda : defaultdict(lambda : None))
            # rulesets dictionary, indexed by left part
            # NB : dict[Rule,None] used as ordered set[Rule]
        symbols:set[Symbol] = set[Symbol]()  # symbols (terminals or variables)
        for rule in rules :
            dico[rule.left][rule] = None  # add rule in the pseudo set
            symbols.update(rule.right)
        assert EOD not in symbols, f"forbidden symbol : {EOD}"
        return GrammarParms(
            terminals = symbols - dico.keys(),
            axiom = next(iter(dico)),
            rules_by_var = {k:v.keys() for k,v in dico.items()}
        )


    @staticmethod
    def from_string(source : str) -> GrammarParms:
            """
                Static method
                returns new Grammar instance
                source : multiline string containing context-free grammar definition. e.g :
                    E -> E + T | T
                    T -> T * F | F
                    F -> * F  | ( E ) | i
                symbols must be separated by at least one blank
                the first used variable is regarded as axiom
            """
            lines = filter( bool,  map(str.strip, source.splitlines()) ) #iterator through non empty stripped lines
            rules = itertools.chain.from_iterable(map(GrammarBuilder.parse_line, lines)) #iterator through rules
            return GrammarBuilder.from_rules(rules)


class ParserBuilder:

    @staticmethod
    def from_dict(definitions: Mapping[str, Callable], method:LRMethod|LLMethod|str = LRMethod.LALR1) -> Parser:
        """
            create LALR(1) parser from dictionary :
                key : grammar multirule strings
                value : callable (synthetised attribute computation)

        """
        try:
            method = LRMethod(method)
        except ValueError:
            try:
                method = LLMethod(method)
            except ValueError:
                raise ValueError(f'{method} is not a correct method')

        dico = {rule: definitions[line] for line in definitions for rule in GrammarBuilder.parse_line(line)}
        grammar = GrammarBase(*GrammarBuilder.from_rules(dico.keys()))

        if isinstance(method,LRMethod):
            return LRParser(
                table=LRTable(LR0Automaton(grammar), method),
                default_semantic=DictSemantic(dico)
            )
        else:
            return LL1Parser(
                table = LL1Table(grammar),
                default_semantic= DictSemantic(dico)
            )



