# coding=utf8
from __future__ import unicode_literals, absolute_import, print_function

import functools
import arpeggio
import arpeggio.peg
from ..typedef import nodes


WHITESPACES = ' \t'

grammar = """
entry_code_pragma  <- nl* pragma* (typedef / nl)* pragma* nl* EOF;
entry_type_pragma  <- nl* pragma* nl* type? nl* pragma* nl* EOF;
entry_code         <- (typedef / nl)* EOF;
entry_type         <- nl* type nl* EOF;

typedef            <- block_comment? "data" type_name "=" type (line_comment / semi);

type               <- union_type
                    / non_union_type
                    ;
union_type         <- non_union_type ("|" non_union_type)+;
non_union_type     <- record_type
                    / array_type
                    / generic_instance_type
                    / named_type
                    ;
record_type        <- "{" (record_entry / nl)* "}";
record_entry       <- block_comment? symbol ":" type (line_comment / semi);

array_type         <- "[" type "]";
generic_instance_type <- type_name "[" type ("," type)* ","? "]";

named_type         <- type_name '';
type_name          <- r'[A-Z][_A-Za-z0-9]*';
symbol             <- r'[a-z][_A-Za-z0-9]*';

line_comment       <- r'--.*\n' '';
block_comment      <- !pragma r'{-([^-]|-[^}])*-}' nl;
pragma             <- r'{-#([^#]|#[^-]|#-[^}])*#-}' nl;

nl     <- '\n';
semi   <- ';' / nl;
"""


def node_pos(node):
    if isinstance(node, arpeggio.Terminal):
        return nodes.Position(node.position, node.position + len(node.value))
    elif isinstance(node, arpeggio.NonTerminal):
        return nodes.Position(node.position)
    else:
        raise TypeError


def _filter_node(l):
    return [n for n in l if isinstance(n, nodes.Node)]

def _first_or_else(l, default_lambda):
    return l[0] if len(l) > 0 else default_lambda()


class ParsingVisitor(arpeggio.PTNodeVisitor):

    def visit_entry_code_pragma(self, node, children):
        return nodes.Code(
            members=_filter_node(children),
            pos=node_pos(node),
        )

    def visit_entry_type_pragma(self, node, children):
        return nodes.Code(
            members=_filter_node(children),
            pos=node_pos(node),
        )

    def visit_entry_code(self, node, children):
        return nodes.Code(
            members=_filter_node(children),
            pos=node_pos(node),
        )

    def visit_entry_type(self, node, children):
        return nodes.Code(
            members=_filter_node(children),
            pos=node_pos(node),
        )

    def visit_typedef(self, node, children):
        return nodes.TypeDef(
            type_name=children.type_name[0],
            type_=children.type[0],
            block_comment=_first_or_else(
                children.block_comment,
                functools.partial(nodes.BlockComment.create, text=None)
            ),
            line_comment=_first_or_else(
                children.line_comment,
                functools.partial(nodes.LineComment.create, text=None)
            ),
            pos=node_pos(node)
        )

    def visit_union_type(self, node, children):
        return nodes.UnionType(
            choices=children.non_union_type,
            pos=node_pos(node)
        )


    def visit_record_type(self, node, children):
        entries = children.record_entry
        return nodes.RecordType(
            entries=entries,
            pos=node_pos(node)
        )

    def visit_record_entry(self, node, children):
        return nodes.RecordTypeEntry(
            symbol=children.symbol[0],
            element_type=children.type[0],
            block_comment=_first_or_else(
                children.block_comment,
                functools.partial(nodes.BlockComment.create, text=None)
            ),
            line_comment=_first_or_else(
                children.line_comment,
                functools.partial(nodes.LineComment.create, text=None)
            ),
            pos=node_pos(node)
        )

    def visit_array_type(self, node, children):
        return nodes.ArrayType(
            element_type=children.type[0],
            pos=node_pos(node)
        )

    def visit_generic_instance_type(self, node, children):
        return nodes.GenericInstanceType(
            type_name=children.type_name[0],
            type_arguments=children.type,
            pos=node_pos(node)
        )

    def visit_named_type(self, node, children):
        return nodes.NamedType(
            type_name=children.type_name[0],
            pos=node_pos(node)
        )

    def visit_type_name(self, node, children):
        assert isinstance(node, arpeggio.Terminal)
        return nodes.TypeName(
            name=node.value,
            pos=node_pos(node)
        )

    def visit_symbol(self, node, children):
        assert isinstance(node, arpeggio.Terminal)
        return nodes.Symbol(
            name=node.value,
            pos=node_pos(node)
        )

    def visit_line_comment(self, node, children):
        return nodes.LineComment(
            text=children[0],
            pos=node_pos(node)
        )

    def visit_block_comment(self, node, children):
        return nodes.BlockComment(
            text=children[0],
            pos=node_pos(node)
        )

    def visit_pragma(self, node, children):
        return nodes.Pragma(
            text=children[0],
            pos=node_pos(node)
        )


def _parser(entry):
    return arpeggio.peg.ParserPEG(grammar, entry, ws=WHITESPACES)


def _parse(entry, code):
    parser = _parser(entry)
    parsed = parser.parse(code)
    transformed = arpeggio.visit_parse_tree(parsed, ParsingVisitor())
    return transformed


parse_type = functools.partial(_parse, 'entry_type')
parse_code = functools.partial(_parse, 'entry_code')
parse_type_pragma = functools.partial(_parse, 'entry_type_pragma')
parse_code_pragma = functools.partial(_parse, 'entry_code_pragma')
