# -*- coding: utf-8 -*-
"""
Adapted from Cython/Compiler/Visitor.py, see this module for detailed
explanations.
"""
from __future__ import print_function, division, absolute_import

import inspect

miniast = None # avoid circular import AttributeError for sphinx-apidoc
from . import treepath

class TreeVisitor(object):
    """
    Non-mutating visitor. Subclass and implement visit_MyNode methods.
    A user can traverse a foreign AST by implementing
    :py:class:`minivect.miniast.Context.getchildren`
    """

    want_access_path = False

    def __init__(self, context):
        self.context = context
        self.dispatch_table = {}
        if self.want_access_path:
            self.access_path = []
        else:
            self._visitchild = self.visit

    def _find_handler(self, obj):
        # to resolve, try entire hierarchy
        cls = type(obj)
        pattern = "visit_%s"
        mro = inspect.getmro(cls)
        handler_method = None
        for mro_cls in mro:
            handler_method = getattr(self, pattern % mro_cls.__name__, None)
            if handler_method is not None:
                return handler_method

        raise RuntimeError("Visitor %r does not accept object: %s" % (self, obj))

    def visit(self, obj, *args):
        "Visit a single child."
        try:
            handler_method = self.dispatch_table[type(obj)]
        except KeyError:
            handler_method = self._find_handler(obj)
            self.dispatch_table[type(obj)] = handler_method
        return handler_method(obj)

    def _visitchild(self, child, parent, attrname, idx):
        self.access_path.append((parent, attrname, idx))
        result = self.visit(child)
        self.access_path.pop()
        return result

    def visit_childlist(self, child, parent=None, attr=None):
        if isinstance(child, list):
            childretval = [self._visitchild(child_node, parent, attr, idx)
                               for idx, child_node in enumerate(child)]
        else:
            childretval = self._visitchild(child, parent, attr, None)
            if isinstance(childretval, list):
                raise RuntimeError(
                    'Cannot insert list here: %s in %r' % (attr, node))

        return childretval

    def visitchildren(self, parent, attrs=None):
        "Visits the children of the given node."
        if parent is None:
            return None

        if attrs is None:
            attrs = self.context.getchildren(parent)

        result = {}
        for attr in attrs:
            child = getattr(parent, attr)
            if child is not None:
                result[attr] = self.visit_childlist(child, parent, attr)

        return result

    def treepath(self, node, xpath_expr):
        return treepath.iterfind(node, xpath_expr)

    def treepath_first(self, node, xpath_expr):
        return treepath.find_first(node, xpath_expr)

    def p(self, node):
        node.print_tree(self.context)

class VisitorTransform(TreeVisitor):
    """
    Mutating transform. Each attribute is replaced by the result of the
    corresponding visit_MyNode method.
    """

    def visitchildren(self, parent, attrs=None):
        result = super(VisitorTransform, self).visitchildren(parent, attrs)
        for attr, newnode in result.items():
            if not isinstance(newnode, list):
                setattr(parent, attr, newnode)
            else:
                # Flatten the list one level and remove any None
                newlist = []
                for x in newnode:
                    if x is not None:
                        if isinstance(x, list):
                            newlist += x
                        else:
                            newlist.append(x)
                setattr(parent, attr, newlist)
        return result

class GenericVisitor(TreeVisitor):
    "Generic visitor that automatically visits children"

    def visit_Node(self, node):
        self.visitchildren(node)
        return node

class GenericTransform(VisitorTransform, GenericVisitor):
    "Generic transform that automatically visits children"

class MayErrorVisitor(TreeVisitor):
    """
    Determine whether code generated by an AST can raise exceptions.
    """

    may_error = False

    def visit_Node(self, node):
        self.visitchildren(node)

    def visit_NodeWrapper(self, node):
        self.may_error = (self.may_error or
                          self.context.may_error(node.opaque_node))

    def visit_ForNode(self, node):
        self.visit(node.init)
        self.visit(node.condition)
        self.visit(node.step)

class PrintTree(TreeVisitor):
    """
    Print an AST, see also :py:class:`minivect.miniast.Node.print_tree`.
    """

    indent = 0
    want_access_path = True

    def format_value(self, node):
        from . import miniast

        if node.is_temp:
            format_value = node.repr_name
        elif (isinstance(node, miniast.Variable) or
              isinstance(node, miniast.FuncNameNode) or
              node.is_funcarg):
            format_value = node.name
        elif node.is_binop or node.is_unop:
            format_value = node.operator
        elif node.is_constant:
            format_value = node.value
        elif node.is_sizeof:
            format_value = str(node.type)
        else:
            return None

        return format_value

    def format_node(self, node, want_type_info=True):
        result = type(node).__name__
        format_value = self.format_value(node)

        if node.is_expression and want_type_info:
            if format_value is not None:
                format_value = "%s, type=%s" % (format_value, node.type)
            else:
                format_value = "type=%s" % (node.type,)

        if format_value:
            return "%s(%s)" % (result, format_value)
        else:
            return result

    def visit_Node(self, node):
        if self.access_path:
            parent, attr, idx = self.access_path[-1]
        else:
            attr = "(root)"
            idx = None

        prefix = "%s%s" % (self.indent * "  ", attr)
        if idx is not None:
            prefix = "%s[%d]" % (prefix, idx)

        print(("%s: %s" % (prefix, self.format_node(node))))

        self.indent += 1
        self.visitchildren(node)
        self.indent -= 1
