from itertools import chain

from prosodia.core.grammar import Language
from ...core import grammar as g

ALLOWED_SYMBOLS = r'| !#$%&()*+,-./:;>=<?@[\]^_`{}~'


def create_language() -> Language:
    lang = g.Language.create('Syntax')
    lang.add_rule(
        g.Rule(
            'Syntax',
            g.Syntax.create(
                g.TermGroup.create(
                    g.RuleReference('Rules'),
                    g.RuleReference('EOF')
                )
            )
        )
    )
    lang.add_rule(
        g.Rule(
            'Rules',
            g.Syntax.create(
                g.TermGroup.create(
                    g.RuleReference('Rule')
                ),
                g.TermGroup.create(
                    g.RuleReference('Rule'),
                    g.RuleReference('Rules')
                )
            )
        )
    )
    lang.add_rule(
        g.Rule(
            'Rule',
            g.Syntax.create(
                g.TermGroup.create(
                    g.RuleReference('OptWhitespace'),
                    g.Literal('<'),
                    g.RuleReference('RuleName'),
                    g.Literal('>'),
                    g.RuleReference('OptWhitespace'),
                    g.Literal('::='),
                    g.RuleReference('OptWhitespace'),
                    g.RuleReference('Expression'),
                    g.RuleReference('LineEnd')
                )
            )
        )
    )
    lang.add_rule(
        g.Rule(
            'OptWhitespace',
            g.Syntax.create(
                g.TermGroup.create(
                    g.Literal(' '),
                    g.RuleReference('OptWhitespace')
                ),
                g.TermGroup.create(
                    g.Literal('')
                )
            )
        )
    )
    lang.add_rule(
        g.Rule(
            'Expression',
            g.Syntax.create(
                g.TermGroup.create(
                    g.RuleReference('List')
                ),
                g.TermGroup.create(
                    g.RuleReference('List'),
                    g.RuleReference('OptWhitespace'),
                    g.Literal('|'),
                    g.RuleReference('OptWhitespace'),
                    g.RuleReference('Expression')
                )
            )
        )
    )
    lang.add_rule(
        g.Rule(
            'LineEnd',
            g.Syntax.create(
                g.TermGroup.create(
                    g.RuleReference('SingleLineEnd'),
                ),
                g.TermGroup.create(
                    g.RuleReference('SingleLineEnd'),
                    g.RuleReference('LineEnd'),
                )
            )
        )
    )
    lang.add_rule(
        g.Rule(
            'SingleLineEnd',
            g.Syntax.create(
                g.TermGroup.create(
                    g.RuleReference('OptWhitespace'),
                    g.RuleReference('EOL')
                )
            )
        )
    )
    lang.add_rule(
        g.Rule(
            'List',
            g.Syntax.create(
                g.TermGroup.create(
                    g.RuleReference('Term')
                ),
                g.TermGroup.create(
                    g.RuleReference('Term'),
                    g.RuleReference('OptWhitespace'),
                    g.RuleReference('List')
                )
            )
        )
    )
    lang.add_rule(
        g.Rule(
            'Term',
            g.Syntax.create(
                g.TermGroup.create(
                    g.RuleReference('Literal')
                ),
                g.TermGroup.create(
                    g.Literal('<'),
                    g.RuleReference('RuleName'),
                    g.Literal('>'),
                )
            )
        )
    )
    lang.add_rule(
        g.Rule(
            'Literal',
            g.Syntax.create(
                g.TermGroup.create(
                    g.Literal('"'),
                    g.RuleReference('Text1'),
                    g.Literal('"'),
                ),
                g.TermGroup.create(
                    g.Literal("'"),
                    g.RuleReference('Text2'),
                    g.Literal("'"),
                )
            )
        )
    )
    lang.add_rule(
        g.Rule(
            'Text1',
            g.Syntax.create(
                g.TermGroup.create(
                    g.Literal('')
                ),
                g.TermGroup.create(
                    g.RuleReference('Character1'),
                    g.RuleReference('Text1'),
                )
            )
        )
    )
    lang.add_rule(
        g.Rule(
            'Text2',
            g.Syntax.create(
                g.TermGroup.create(
                    g.Literal('')
                ),
                g.TermGroup.create(
                    g.RuleReference('Character2'),
                    g.RuleReference('Text2'),
                )
            )
        )
    )
    lang.add_rule(
        g.Rule(
            'Character',
            g.Syntax.create(
                g.TermGroup.create(
                    g.RuleReference('Letter')
                ),
                g.TermGroup.create(
                    g.RuleReference('Digit')
                ),
                g.TermGroup.create(
                    g.RuleReference('Symbol')
                ),
            )
        )
    )
    lang.add_rule(
        g.Rule(
            'Letter',
            g.Syntax.create(
                *(
                    g.TermGroup.create(
                        g.Literal(letter)
                    ) for letter in chain(
                        (chr(ord('A') + offset) for offset in range(26)),
                        (chr(ord('a') + offset) for offset in range(26))
                    )
                )
            )
        )
    )
    lang.add_rule(
        g.Rule(
            'Digit',
            g.Syntax.create(
                *(
                    g.TermGroup.create(
                        g.Literal(chr(ord('0') + offset))
                    ) for offset in range(10)
                )
            )
        )
    )
    lang.add_rule(
        g.Rule(
            'Symbol',
            g.Syntax.create(
                *(
                    g.TermGroup.create(
                        g.Literal(symbol)
                    ) for symbol in ALLOWED_SYMBOLS
                )
            )
        )
    )
    lang.add_rule(
        g.Rule(
            'Character1',
            g.Syntax.create(
                g.TermGroup.create(
                    g.RuleReference('Character')
                ),
                g.TermGroup.create(
                    g.Literal("'")
                )
            )
        )
    )
    lang.add_rule(
        g.Rule(
            'Character2',
            g.Syntax.create(
                g.TermGroup.create(
                    g.RuleReference('Character')
                ),
                g.TermGroup.create(
                    g.Literal('"')
                )
            )
        )
    )
    lang.add_rule(
        g.Rule(
            'RuleName',
            g.Syntax.create(
                g.TermGroup.create(
                    g.RuleReference('Letter')
                ),
                g.TermGroup.create(
                    g.RuleReference('Letter'),
                    g.RuleReference('RuleEnd')
                )
            )
        )
    )
    lang.add_rule(
        g.Rule(
            'RuleEnd',
            g.Syntax.create(
                g.TermGroup.create(
                    g.RuleReference('OneRuleEnd')
                ),
                g.TermGroup.create(
                    g.RuleReference('OneRuleEnd'),
                    g.RuleReference('RuleEnd')
                )
            )
        )
    )
    lang.add_rule(
        g.Rule(
            'OneRuleEnd',
            g.Syntax.create(
                g.TermGroup.create(
                    g.RuleReference('Letter')
                ),
                g.TermGroup.create(
                    g.RuleReference('Digit')
                ),
                g.TermGroup.create(
                    g.Literal('-'),
                    g.RuleReference('Letter')
                ),
                g.TermGroup.create(
                    g.Literal('-'),
                    g.RuleReference('Digit')
                )
            )
        )
    )
    lang.add_rule(
        g.Rule(
            'EOL',
            g.Syntax.create(
                g.TermGroup.create(
                    g.Literal('\n')
                )
            )
        )
    )
    lang.add_rule(
        g.Rule(
            'EOF',
            g.Syntax.create(
                g.TermGroup.create(
                    g.EOFTerm()
                )
            )
        )
    )
    return lang
