from hutn_compiler.visitor import Visitor
from hutn_compiler.compiler import main as do_compile
import os

def empty(s):
    return None

class ModelVisitor(Visitor):
    def __init__(self, args):
        Visitor.__init__(self, args)
        self.constructors = []
        self.free_id = 0
        self.names = set()
        self.current_element = []
        self.includes = []

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

    def __getattr__(self, attr):
        if attr.startswith("visit_"):
            return empty
        else:
            raise AttributeError()

    def visit_start(self, tree):
        for t in tree.get_tail():
            self.visit(t)

    def visit_include_files(self, tree):
        self.includes.append('include %s' % tree.get_children("STRVALUE")[0].get_text())

    def visit_model_element(self, tree):
        children = tree.get_children("MODEL_ID")
        element_type = children[0].get_text()
        if len(children) == 2 or len(children) == 4:
            element_name = children[1].get_text()
        else:
            element_name = "__%s" % self.free_id
            self.free_id += 1

        if element_name in self.names:
            raise Exception("Redefinition of element %s" % element_name)

        if len(children) > 2:
            # So we have a source and target; but aren't sure which is which, because the name is optional!
            source_name = children[-2].get_text()
            target_name = children[-1].get_text()
            if source_name not in self.names:
                raise Exception("Source of link %s unknown: %s" % (element_name, source_name))
            if target_name not in self.names:
                raise Exception("Target of link %s unknown: %s" % (element_name, target_name))

            self.constructors.extend(["instantiate_link", element_type, element_name, source_name, target_name])
        else:
            self.constructors.extend(["instantiate_node", element_type, element_name])

        self.names.add(element_name)
        self.current_element.append(element_name)

        if tree.get_children("inheritance"):
            self.visit(tree.get_children("inheritance")[0])
            
        for attr in tree.get_children("model_attribute"):
            self.visit(attr)

        self.current_element.pop()

        return element_name

    def visit_inheritance(self, tree):
        for token in tree.get_children("MODEL_ID"):
            superclass = token.get_text()
            if superclass not in self.names:
                raise Exception("Superclass %s is undefined" % superclass)

            self.constructors.extend(["instantiate_link", "Inheritance", "%s_inherits_from_%s" % (self.current_element[-1], superclass), self.current_element[-1], superclass])
            self.names.add("%s_inherits_from_%s" % (self.current_element[-1], superclass))

    def visit_model_attribute(self, tree):
        children = tree.get_children("MODEL_ID")
        is_definition = bool(tree.get_children("COLON"))
        is_assign = bool(tree.get_children("model_attr_instance"))
        is_nested = bool(tree.get_children("model_element"))

        if is_definition:
            attr_name = children[0].get_text()
            attr_optional = len(tree.get_children("OPTIONAL")) > 0
            attr_type = children[1].get_text()
            if attr_type not in self.names:
                raise Exception("Unknown Attribute type!")
            self.constructors.extend(["model_define_attribute", self.current_element[-1], attr_name, attr_optional, attr_type])
            full_attribute_name = self.current_element[-1] + "_" + attr_name

            if is_assign:
                # There are also some attributes to set!
                self.current_element.append(full_attribute_name)

                for f in tree.get_children("model_attr_instance"):
                    self.visit(f)

                self.current_element.pop()
        elif is_assign:
            self.visit(tree.get_children("model_attr_instance")[0])
        elif is_nested:
            if tree.get_children("MODEL_ID"):
                contains_link = tree.get_children("MODEL_ID")[0].get_text()
            else:
                contains_link = ""
            entry = self.visit(tree.get_children("model_element")[0])
            self.constructors.extend(["instantiate_link", contains_link, "__%s" % self.free_id, self.current_element[-1], entry])
            self.names.add("__%s" % self.free_id)
            self.free_id += 1

    def visit_model_attr_instance(self, tree):
        def constructors_compile(code):
            code_fragments = code.split("\n")
            code_fragments = [i for i in code_fragments if i.strip() != ""]
            code_fragments = [i.replace("    ", "\t") for i in code_fragments]
            initial_tabs = min([len(i) - len(i.lstrip("\t")) for i in code_fragments])
            code_fragments = self.includes + [i[initial_tabs:] for i in code_fragments]
            code = "\n".join(code_fragments)
            code += "\n"

            with open(".code.alc", 'w') as f:
                f.write(code)
                f.flush()
            directory = os.path.realpath(__file__).rsplit(os.sep, 1)[0]
            compiled = do_compile(".code.alc", directory + "/../grammars/actionlanguage.g", "CS")
            return compiled + [code]

        children = tree.get_children("MODEL_ID")
        attr_name = children[0].get_text()
        if tree.get_children("value"):
            # Value attribute
            attr_value = tree.get_children("value")[0].get_tail()[0]
            if attr_value.head == "STRVALUE":
                attr_value = attr_value.get_text()[1:-1]
            elif attr_value.head == "LONG_STR":
                attr_value = attr_value.get_text()[3:-3]
            elif attr_value.head == "TRUE":
                attr_value = True
            elif attr_value.head == "FALSE":
                attr_value = False
            elif attr_value.head == "DEC_NUMBER":
                attr_value = int(attr_value.get_text())
            elif attr_value.head == "FLOAT_NUMBER":
                attr_value = float(attr_value.get_text())
            else:
                raise Exception(attr_value.head)
            self.constructors.extend(["instantiate_attribute", self.current_element[-1], attr_name, attr_value])
        elif tree.get_children("DOLLAR"):
            # Coded attribute
            self.constructors.extend(["instantiate_attribute_code", self.current_element[-1], attr_name])
            self.constructors.extend(constructors_compile(tree.get_children("ANYTHING_EXCEPT_DOLLAR")[0].get_text()))
