import string
from visitor import Visitor

class PrimitivesVisitor(Visitor):
    def __init__(self, args):
        Visitor.__init__(self, args)
        self.output = []
        self.free_id = 0
        self.function_values = dict()
        self.debug_symbols = "--debug" in args

    def debug(self, node, tree, msg=""):
        if self.debug_symbols:
            self.dict(node, "__debug", self.value('"[%s] %s"' % (tree.get_reference_line(), msg)))

    def node(self):
        self.output.append(("N", self.free_id))
        self.free_id += 1
        return self.free_id - 1

    def dict(self, src, val, trgt):
        if src is None or trgt is None:
            raise Exception("Got None")
        if isinstance(val, str):
            val = "\"%s\"" % val
        self.output.append(("D", (src, val, trgt)))

    def value(self, value):
        self.output.append(("V", (self.free_id, value)))
        self.free_id += 1
        return self.free_id - 1

    def edge(self, source, target):
        self.output.append(("E", (self.free_id, source, target)))
        self.free_id += 1
        return self.free_id - 1

    def dump(self):
        output = []
        for t, data in self.output:
            if t == "N":
                output.append("N auto_%s\n" % data)
            elif t == "V":
                name, value = data
                name = name if self.first != name else "initial_IP"
                output.append("V auto_%s(%s)\n" % (name, value))
            elif t == "D":
                source, value, target = data
                source = source if self.first != source else "auto_initial_IP"
                target = target if self.first != target else "auto_initial_IP"

                source = "auto_%s" % source if isinstance(source, int) else source
                target = "auto_%s" % target if isinstance(target, int) else target
                output.append("D %s,%s,%s\n" % (source, value, target))
            elif t == "E":
                name, source, target = data
                source = source if self.first != source else "auto_initial_IP"
                target = target if self.first != target else "auto_initial_IP"
                name = "auto_%s" % name if isinstance(name, int) else name
                source = "auto_%s" % source if isinstance(source, int) else source
                target = "auto_%s" % target if isinstance(target, int) else target
                output.append("E %s(%s,%s)\n" % (name, source, target))
        return "".join(output)

    def set_primitive(self, tree, primitive):
        tree.primitive = primitive

    def get_primitive(self, tree):
        return getattr(tree, 'primitive', None)

    def forward_primitive_of_child(self, tree, i):
        self.visit_children(tree)
        self.set_primitive(tree, self.get_primitive(tree.get_tail()[i]))

    # a visit_* method for each non-terminal in the grammar
    def visit_start(self, tree):
        primitives = []
        for child in tree.get_children("funcdecl"):
            p = self.pre_visit_funcdecl(child)
            if p:
                # funcdecl returns a (global, assign) pair
                primitives.extend(p)
        for child in tree.get_tail():
            self.visit(child)
            p = self.get_primitive(child)
            if p is not None:
                if type(p) in (list, tuple):
                    # funcdecl returns a (global, assign) pair
                    primitives.extend(p)
                else:
                    primitives.append(p)
                if child.head == "return":
                    break
        self.first = primitives[0]
        for i in range(len(primitives)-1):
            self.dict(primitives[i], "next", primitives[i+1])
        self.last_instruction = primitives[-1]

    def visit_vardecl(self, tree):
        symbol = self.get_symbol(tree)

        if symbol.is_global:
            d = self.value("global")
            n = self.value('"%s"' % symbol.name)
        else:
            d = self.value("declare")
            n = self.node()

        symbol.node = n
        self.dict(d, "var", n)
        self.debug(d, tree)

        self.set_primitive(tree, d)

    def visit_assignment(self, tree):
        self.visit_children(tree)

        a = self.value("assign")
        var = self.get_primitive(tree.get_tail()[0])
        value = self.get_primitive(tree.get_tail()[-1])
        self.dict(a, "var", var)
        self.dict(a, "value", value)
        self.debug(a, tree)

        self.set_primitive(tree, a)

    def visit_expression(self, tree):
        self.forward_primitive_of_child(tree, 0)

    def visit_binary_operation(self, tree):
        self.forward_primitive_of_child(tree, 0)

    def visit_disjunction(self, tree):
        self.forward_primitive_of_child(tree, 0)

    def visit_conjunction(self, tree):
        self.forward_primitive_of_child(tree, 0)

    def visit_comparison(self, tree):
        self.forward_primitive_of_child(tree, 0)

    def visit_relation(self, tree):
        self.forward_primitive_of_child(tree, 0)

    def visit_sum(self, tree):
        self.forward_primitive_of_child(tree, 0)

    def visit_term(self, tree):
        self.forward_primitive_of_child(tree, 0)

    def visit_factor(self, tree):
        self.forward_primitive_of_child(tree, 0)

    def visit_primary(self, tree):
        self.forward_primitive_of_child(tree, 0)

    def visit_parenthesized(self, tree):
        self.forward_primitive_of_child(tree, 1)

    def visit_atomvalue(self, tree):
        self.forward_primitive_of_child(tree, 0)

    def visit_type_specifier(self, tree):
        self.visit_literal(tree)

    def visit_actionname(self, tree):
        self.visit_literal(tree.get_tail()[1])
        self.set_primitive(tree, self.get_primitive(tree.get_tail()[1]))

    def visit_string(self, tree):
        self.visit_literal(tree)

    # there is no such rule in the grammar, we just avoid code duplicates
    def visit_literal(self, tree):
        # TODO dictionary and list
        # if self.no_constant:
        #     n = self.new_value(tree.get_text())
        #     self.set_id(tree, n)
        # else:
        c = self.value("constant")
        n = self.value(tree.get_text())
        self.dict(c, "node", n)
        self.debug(c, tree)

        self.set_primitive(tree, c)

    def visit_integer(self, tree):
        self.visit_literal(tree)

    def visit_float(self, tree):
        self.visit_literal(tree)

    def visit_rvalue(self, tree):
        self.visit_lvalue(tree)
        r = self.get_primitive(tree)
        if r is None:
            return
        a = self.value("access")
        self.dict(a, "var", r)
        self.debug(a, tree)
        self.set_primitive(tree, a)

    def visit_lvalue(self, tree):
        symbol = self.get_symbol(tree)
        if symbol.name in ["input", "output"]:
            return
        r = self.value("resolve")
        # print symbol.name, symbol.is_func(), symbol.node
        self.dict(r, "var", symbol.node)
        self.debug(r, tree)
        self.set_primitive(tree, r)

    def visit_func_call(self, tree):
        self.visit_children(tree)

        symbol = self.get_symbol(tree.get_tail()[0])

        expressions = tree.get_children("expression")
        arg_nodes_reversed = []

        for i in reversed(range(len(expressions))):
            arg_name = '"{}"'.format(string.ascii_lowercase[i])

            arg_node = self.node()
            name_node = self.value(arg_name)
            self.dict(arg_node, "name", name_node)

            # print expressions[i].get_text()
            value_node = self.get_primitive(expressions[i])
            self.dict(arg_node, "value", value_node)

            if arg_nodes_reversed:
                next_node = arg_nodes_reversed[-1]
                self.dict(arg_node, "next_param", next_node)

            arg_nodes_reversed.append(arg_node)

        c = self.value("call")
        a = self.get_primitive(tree.get_tail()[0])
        self.dict(c, "func", a)
        self.debug(c, tree)

        if arg_nodes_reversed:
            self.dict(c, "params", arg_nodes_reversed[-1])
            self.dict(c, "last_param", arg_nodes_reversed[0])

        self.set_primitive(tree, c)

    def visit_input(self, tree):
        self.visit_children(tree)
        v = self.value("input")
        self.debug(v, tree)
        self.set_primitive(tree, v)

    def visit_output(self, tree):
        self.visit_children(tree)
        v = self.value("output")
        self.debug(v, tree)
        value = self.get_primitive(tree.get_child("expression"))
        self.dict(v, "value", value)
        self.set_primitive(tree, v)

    def visit_ifelse(self, tree):
        self.visit_children(tree)
        expressions = [self.get_primitive(e) for e in
                       tree.get_children("expression")]
        blocks = [self.get_primitive(b) for b in tree.get_children("block")]

        first = None
        prev = None
        for e, b in zip(expressions, blocks):
            v = self.value("if")
            self.debug(v, tree)
            if first is None:
                first = v
            self.dict(v, "cond", e)
            self.dict(v, "then", b)
            if prev:
                self.dict(prev, "else", v)
            prev = v

        if len(expressions) != len(blocks):
            self.dict(prev, "else", blocks[-1])

        self.set_primitive(tree, first)

    def visit_while(self, tree):
        self.visit_children(tree)

        w = self.value("while")
        self.debug(w, tree)
        c = self.get_primitive(tree.get_child("expression"))
        b = self.get_primitive(tree.get_child("block"))

        self.dict(w, "cond", c)
        self.dict(w, "body", b)

        self.set_primitive(tree, w)

    def visit_block(self, tree):
        self.visit_children(tree)
        primitives = []
        for child in tree.get_tail():
            p = self.get_primitive(child)
            if p:
                primitives.append(p)
                if child.head == "return":
                    break
        for i in range(len(primitives)-1):
            self.dict(primitives[i], "next", primitives[i+1])
        self.set_primitive(tree, primitives[0])

    def visit_func_body(self, tree):
        self.forward_primitive_of_child(tree, 0)

    def pre_visit_funcdecl(self, tree):
        symbol = self.get_symbol(tree)
        if symbol.name in ["input", "output"]:
            return

        # TODO: fix funcdecl special case: "X function f(...) = ..."
        # Note: replicates "Element x; x = ?primiteves/a" behavior
        # Dangerous: SemanticsVisitor handles it as a function
        if not tree.get_child("func_body"):
            return

        new_value = self.node()
        self.function_values[symbol.name] = new_value

        root = self.value('"%s"' % symbol.name)
        symbol.node = root
        declare = self.value("global")
        self.dict(declare, "var", root)

        resolve = self.value("resolve")
        self.dict(resolve, "var", root)

        assign = self.value("assign")
        self.dict(assign, "var", resolve)

        const = self.value("constant")
        self.dict(const, "node", new_value)
        self.dict(assign, "value", const)

        return declare, assign

    def visit_funcdecl(self, tree):
        symbol = self.get_symbol(tree)
        if symbol.name in ["input", "output"]:
            return

        func_body = tree.get_child("func_body")
        if func_body:
            self.visit_children(tree)
            vf = self.function_values[symbol.name]
            parameters = tree.get_children("parameter")
            if parameters:
                ps = self.node()
                self.dict(vf, "params", ps)
                for i in range(len(parameters)):
                    n = self.get_primitive(parameters[i])
                    self.dict(ps, string.ascii_lowercase[i], n)

                    # Add the name in case we want to pre-compile in the MvK
                    self.dict(n, "name", self.value('"%s"' % string.ascii_lowercase[i]))
            b = self.get_primitive(func_body)
            self.dict(vf, "body", b)
        else:
            # TODO: fix funcdecl special case: "X function f(...) = ..."
            # Note: replicates "Element x; x = ?primiteves/a" behavior
            # Dangerous: SemanticsVisitor handles it as a function
            root = self.value('"%s"' % symbol.name)
            symbol.node = root

            if tree.get_child("ASSIGN"):
                new_value = "?" + tree.get_child("ANYTHING").get_text()
            elif tree.get_child("ASSIGN") and not tree.get_child("ANYTHING"):
                new_value = self.node()
            else:
                return

            declare = self.value("global")
            self.dict(declare, "var", root)
            self.debug(declare, tree)

            resolve = self.value("resolve")
            self.dict(resolve, "var", root)

            assign = self.value("assign")
            self.dict(assign, "var", resolve)

            const = self.value("constant")
            self.dict(const, "node", new_value)
            self.dict(assign, "value", const)

            self.set_primitive(tree, (declare, assign))

    def visit_definition(self, tree):
        symbol = self.get_symbol(tree)
        root = self.value('"%s"' % symbol.name)

        declare = self.value("global")
        self.dict(declare, "var", root)
        self.debug(declare, tree)

        symbol.node = root

        resolve = self.value("resolve")
        self.dict(resolve, "var", root)

        assign = self.value("assign")
        self.dict(assign, "var", resolve)

        const = self.value("constant")

        self.visit_atomvalue(tree.get_tail()[-1])
        value = self.get_primitive(tree.get_tail()[-1])

        #TODO hack...
        if value is None:
            value = self.node()

        self.dict(const, "node", value)
        self.dict(assign, "value", const)

        self.set_primitive(tree, (declare, assign))

    def visit_parameter(self, tree):
        n = self.node()

        symbol = self.get_symbol(tree)
        symbol.node = n

        self.set_primitive(tree, n)

    def visit_return(self, tree):
        self.visit_children(tree)
        r = self.value("return")
        self.debug(r, tree)
        if len(tree.get_tail()) > 1:
            v = self.get_primitive(tree.get_tail()[1])
            self.dict(r, "value", v)
        self.set_primitive(tree, r)

    def visit_bool(self, tree):
        self.visit_literal(tree)
