
try:
    import cPickle as pickle
except ImportError:
    import pickle as pickle

from hutn_compiler.visitor import Visitor

class ConstructorsVisitor(Visitor):
    def __init__(self, args):
        Visitor.__init__(self, args)
        self.constructors = []
        self.free_id = 0

    def dump(self):
        return [len(self.constructors)] + self.constructors

    def add_constructors(self, *constructors):
        self.constructors.extend(constructors)

    # a visit_* method for each non-terminal in the grammar
    def visit_start(self, tree):
        # declare all functions
        for child in tree.get_children("funcdecl"):
            if self.pre_visit_funcdecl(child):
                self.add_constructors(True)

        tail = tree.get_tail_without(["newline"])
        # tail = tree.get_children("funcdecl") +\
        #     tree.get_tail_without(["newline", "funcdecl"])
        last = tail[-1]
        for child in tail[:-1]:
            if child.head in ["return", "break", "continue"]:
                last = child
                break
            else:
                # funcdecl may add no constructors
                new_constructors_were_added = self.visit(child)
                if child.head == "func_call":  # pop 'false'
                    self.constructors.pop()
                if new_constructors_were_added:
                    self.add_constructors(True)
        new_constructors_were_added = self.visit(last)
        if last.head == "func_call":  # pop 'false'
            self.constructors.pop()
        if new_constructors_were_added:
            if last.head not in ["return", "break", "continue"]:
                self.add_constructors(False)
        elif self.constructors:
            self.constructors.pop()  # pop 'true'
            self.add_constructors(False)

    def declare(self, symbol):
        if symbol.is_global:
            symbol.node = symbol.name
            self.add_constructors("global", symbol.node)
        else:
            symbol.node = str(self.free_id)
            self.add_constructors("declare", symbol.node)
            self.free_id += 1

    def visit_assignment(self, tree):
        self.add_constructors("assign")
        self.visit(tree.get_tail()[0])
        self.visit(tree.get_tail()[2])
        return True

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

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

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

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

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

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

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

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

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

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

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

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

    def visit_type_specifier(self, tree):
        self.add_constructors("const",
            {"value": tree.get_text()})

    def visit_actionname(self, tree):
        self.add_constructors("const",
            {"value": tree.get_tail()[0].get_text()[1:]})

    def visit_string(self, tree):
        v = tree.get_text()[1:-1]
        v = v.replace("\\n", "\n")
        v = v.replace("\\t", "\t")
        v = v.replace("\\\"", "\"")
        self.add_constructors("const", v)

    def visit_integer(self, tree):
        self.add_constructors("const", int(tree.get_text()))

    def visit_float(self, tree):
        self.add_constructors("const", float(tree.get_text()))

    def visit_rvalue(self, tree):
        self.add_constructors("access")
        self.visit_lvalue(tree)

    def visit_lvalue(self, tree):
        symbol = self.get_symbol(tree)
        # TODO: split visit_funcdecl in pre_visit_funcdecl and visit_funcdecl
        if symbol.node is None:
            raise Exception("Undefined variable: %s" % (symbol.name))
        self.add_constructors("resolve", symbol.node)

    def visit_func_call(self, tree):
        symbol = self.get_symbol(tree.get_tail()[0])
        self.add_constructors("call")
        if hasattr(symbol, "pathmv"):
            self.add_constructors("deref", symbol.pathmv)
        else:
            self.visit(tree.get_tail()[0])
        expressions = tree.get_children("expression")
        self.add_constructors(len(expressions))
        for expression in expressions:
            self.visit(expression)
        self.add_constructors(False)
        return True

    def visit_input(self, tree):
        self.add_constructors("input")
        return True

    def visit_output(self, tree):
        self.add_constructors("output")
        self.visit(tree.get_child("expression"))
        return True

    def visit_dictionary(self, tree):
        pass  # TODO: list and dictionary

    def visit_list(self, tree):
        pass  # TODO: list and dictionary

    def visit_dict_item(self, tree):
        pass  # TODO: list and dictionary

    def visit_ifelse(self, tree):
        self.add_constructors("if")
        expressions = tree.get_children("expression")
        blocks = tree.get_children("block")
        self.visit(expressions[0])  # condition
        self.visit(blocks[0])  # then-clause

        for e, b in zip(expressions[1:], blocks[1:]):
            self.add_constructors(True, "if")  # else-if-clause
            self.visit(e)
            self.visit(b)

        if len(expressions) != len(blocks):
            self.add_constructors(True)  # else-clause
            self.visit(blocks[-1])
        else:
            self.add_constructors(False)  # no else_clause

        for i in range(len(expressions)-1):
            self.add_constructors(False)  # no next

        return True

    def visit_while(self, tree):
        self.add_constructors("while")
        self.visit(tree.get_child("expression"))
        self.visit(tree.get_child("block"))
        return True

    def visit_block(self, tree):
        tail = tree.get_tail_without(["newline", "indent"])
        last = tail[-1]
        for child in tail[:-1]:
            if child.head in ["return", "break", "continue"]:
                last = child
                break
            else:
                self.visit(child)
                if child.head == "func_call":  # pop 'false'
                    self.constructors.pop()
                self.add_constructors(True)
        self.visit(last)
        if last.head == "func_call":  # pop 'false'
            self.constructors.pop()
        if last.head not in ["return", "break", "continue"]:
            self.add_constructors(False)

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

    def pre_visit_funcdecl(self, tree):
        func_body = tree.get_child("func_body")
        symbol = self.get_symbol(tree)
        symbol.node = symbol.name
        return False

    def visit_funcdecl(self, tree):
        func_body = tree.get_child("func_body")
        symbol = self.get_symbol(tree)
        if func_body:
            if symbol.name in ["input", "output"]:
                return False
            if tree.get_children("MUTABLE"):
                self.add_constructors("mutable_funcdef")
            else:
                self.add_constructors("funcdef")
            self.add_constructors(symbol.node,
                                  len(symbol.params))
            for p in tree.get_children("parameter"):
                self.visit(p)
            self.visit(func_body)
            return True
        elif tree.get_child("ASSIGN"):
            # TODO: fix funcdecl special case: "X function f(...) = ..."
            # Note: replicates "Element x; x = ?primiteves/a" behavior
            # Dangerous: SemanticsVisitor handles it as a function
            # pathmv is needed in visit_func_call(self, tree)
            symbol.pathmv = tree.get_child("ANYTHING").get_text()

            self.add_constructors("global", symbol.node, "deref", symbol.pathmv)

            # reason: "X function f(Y) = Z" adds no constructors
            return True
        else:
            # Just a declaration, so skip
            return False

    def visit_parameter(self, tree):
        symbol = self.get_symbol(tree)
        symbol.node = str(self.free_id)
        self.add_constructors(symbol.node)
        self.free_id += 1

    def visit_continue(self, tree):
        self.add_constructors("continue")
        return True

    def visit_break(self, tree):
        self.add_constructors("break")
        return True
        
    def visit_return(self, tree):
        self.add_constructors("return")
        if len(tree.get_tail()) > 2:
            self.add_constructors(True)
            self.visit(tree.get_tail()[1])
        else:
            self.add_constructors(False)
        return True

    def visit_bool(self, tree):
        if tree.get_text() == "True":
            self.add_constructors("const", True)
        else:
            self.add_constructors("const", False)

    def visit_definition(self, tree):
        # First declare it
        symbol = self.get_symbol(tree)
        self.declare(symbol)

        if tree.get_children("ASSIGN"):
            # Determine whether it is just a constant, or a deref
            atom = tree.get_child("atomvalue")
            if atom.get_child("deref"):
                # Deref
                dest = atom.get_child("deref").get_child("ANYTHING")
                if dest is None:
                    # Just an empty questionmark!
                    self.add_constructors("empty")
                else:
                    self.add_constructors("deref", dest.get_text())
            else:
                # Constant
                self.visit(atom)
        else:
            self.add_constructors("none")

        return True
