# DEVS Generator by Sam Pieters. Inspired on the generatic generator by Joeri Excelmans
#
# Visits SCCD-domain constructs (see sccd_constructs.py) and converts them
# to a DEVS language AST (see generic_language_constructs.py), that can
# then be visited by a target language writer (only supports python).

from sccd.compiler.utils import Enum, Logger
from sccd.compiler.visitor import Visitor
from sccd.compiler.sccd_constructs import FormalParameter
from sccd.compiler.stateful_writer import StatefulWriter
import sccd.compiler.generic_language_constructs as GLC

Platforms = Enum("Threads", "GameLoop", "EventLoop")

class DEVSGenerator(Visitor):
    def __init__(self, platform):
        self.platform = platform
        self.writer = StatefulWriter()

    def generic_visit(self, node):
        Logger.showWarning("GenericGenerator has no visit method for node of type '" + str(type(node)) + "'.")

    def get(self):
        return self.writer.get()

    def visit_ClassDiagram(self, class_diagram):
        header = ("Generated by Statechart compiler by Glenn De Jonghe, Joeri Exelmans, Simon Van Mierlo, "
                  "and Yentl Van Tendeloo (for the inspiration) and Sam Pieters (DEVS)\n")
        if class_diagram.name or class_diagram.author or class_diagram.description:
            header += "\n"
        if class_diagram.author:
            header += "Model author: " + class_diagram.author + "\n"
        if class_diagram.name:
            header += "Model name:   " + class_diagram.name + "\n"
        if class_diagram.description.strip():
            header += "Model description:\n"
            header += class_diagram.description.strip()

        self.writer.addMultiLineComment(header)
        self.writer.addVSpace()
        self.writer.addInclude(([GLC.RuntimeModuleIdentifier(), "DEVS_statecharts_core"]))

        if class_diagram.top.strip():
            self.writer.addRawCode(class_diagram.top)
        self.writer.addVSpace()

        self.writer.beginPackage(class_diagram.name)

        # visit children
        for c in class_diagram.classes:
            c.accept(self)


        ################################
        # Object Manager State
        ################################
        self.writer.beginClass("ObjectManagerState")
        self.writer.beginConstructor()
        self.writer.beginMethodBody()

        self.writer.addAssignment(GLC.SelfProperty("to_send"), f"[(\"{class_diagram.default_class.name}\", \"{class_diagram.default_class.name}\", Event(\"start_instance\", None, [\"{class_diagram.default_class.name}[0]\"], 0))]")

        self.writer.endMethodBody()
        self.writer.endConstructor()
        self.writer.endClass()

        ################################
        # Object Manager
        ################################
        self.writer.beginClass("ObjectManager", ["TheObjectManager"])
        self.writer.beginConstructor()
        self.writer.addFormalParameter("name")
        self.writer.beginMethodBody()
        self.writer.beginSuperClassConstructorCall("TheObjectManager")
        self.writer.addActualParameter("name")
        self.writer.endSuperClassConstructorCall()

        self.writer.addAssignment(GLC.SelfProperty("State"), GLC.FunctionCall("ObjectManagerState"))
        self.writer.addAssignment(GLC.SelfProperty("input"),
                                  GLC.FunctionCall(GLC.SelfProperty("addInPort"), [GLC.String("input")]))

        for class_name in class_diagram.class_names:
            self.writer.addAssignment(GLC.SelfProperty(f"output[\"{class_name}\"]"),
                                      GLC.FunctionCall(GLC.SelfProperty("addOutPort")))

        self.writer.endMethodBody()
        self.writer.endConstructor()
        self.writer.endClass()

        ################################
        # Controller
        ################################
        self.writer.beginClass("Controller", ["CoupledDEVS"])
        self.writer.beginConstructor()
        self.writer.addFormalParameter("name")
        for p in class_diagram.default_class.constructors[0].parameters:
            p.accept(self)

        self.writer.beginMethodBody()
        self.writer.beginSuperClassConstructorCall("CoupledDEVS")
        self.writer.addActualParameter("name")
        self.writer.endSuperClassConstructorCall()

        for i in class_diagram.inports:
            self.writer.addAssignment(GLC.SelfProperty("in_" + i), GLC.FunctionCall(GLC.SelfProperty("addInPort"), [GLC.String(i)]))
            self.writer.add(GLC.FunctionCall("Ports.addInputPort", [GLC.String(i)]))
        for o in class_diagram.outports:
            self.writer.addAssignment(GLC.SelfProperty("out_" + o), GLC.FunctionCall(GLC.SelfProperty("addOutPort"), [GLC.String(o)]))
            self.writer.add(GLC.FunctionCall("Ports.addOutputPort", [GLC.String(o)]))

        # Add AtomicDEVS models
        self.writer.addAssignment(GLC.SelfProperty("objectmanager"), (GLC.FunctionCall(GLC.SelfProperty("addSubModel"),
                                                                                       [GLC.FunctionCall(
                                                                                           "ObjectManager",
                                                                                           GLC.ActualParameters([
                                                                                               GLC.String(
                                                                                                   "ObjectManager")]))])))
        for (i, class_name) in enumerate(class_diagram.class_names):
            self.writer.addAssignment(GLC.SelfProperty(f"atomic{i}"),
                                      (GLC.FunctionCall(GLC.SelfProperty("addSubModel"), [GLC.FunctionCall(class_name,
                                                                                                           GLC.ActualParameters(
                                                                                                               [
                                                                                                                   GLC.String(
                                                                                                                       class_name)]))])))

        # Add links between the models
        for (i, the_class) in enumerate(class_diagram.classes):
            self.writer.add((GLC.FunctionCall(GLC.SelfProperty("connectPorts"),
                                              [GLC.SelfProperty(f"atomic{i}.obj_manager_out"),
                                               GLC.SelfProperty(f"objectmanager.input")])))
            self.writer.add((GLC.FunctionCall(GLC.SelfProperty("connectPorts"),
                                              [GLC.SelfProperty(f"objectmanager.output[\"{the_class.name}\"]"),
                                               GLC.SelfProperty(f"atomic{i}.obj_manager_in")])))

            for association in the_class.associations:
                temp = class_diagram.class_names.index(association.to_class)
                self.writer.add(GLC.FunctionCall(GLC.SelfProperty("connectPorts"),
                                                 [GLC.SelfProperty(f"atomic{i}.outputs[\"{association.name}\"]"),
                                                  GLC.SelfProperty(f"atomic{temp}.input")]))

        for (i, the_class) in enumerate(class_diagram.classes):
            for o in class_diagram.outports:
                self.writer.add(GLC.FunctionCall(GLC.SelfProperty("connectPorts"), [GLC.SelfProperty(f"atomic{i}.output"), GLC.SelfProperty(f"out_{o}")]))

        # TODO: What to do with "actual_parameters"?
        # actual_parameters = [p.getIdent() for p in class_diagram.default_class.constructors[0].parameters]
        # self.writer.add(GLC.FunctionCall(GLC.Property(GLC.SelfProperty("object_manager"), "createInstance"), [GLC.String(class_diagram.default_class.name), GLC.ArrayExpression(actual_parameters)]))
        self.writer.endMethodBody()
        self.writer.endConstructor()
        self.writer.endClass()
        # End Controller Class

        # visit test node if there is one
        if class_diagram.test:
            class_diagram.test.accept(self)

        self.writer.endPackage()

    # CLASS
    def visit_Class(self, class_node):
        """
        Generate code for Class construct
        """
        super_classes = []
        if not class_node.super_class_objs:
            if class_node.statechart:
                super_classes.append("ObjectManagerBase")
        if class_node.super_classes:
            for super_class in class_node.super_classes:
                super_classes.append(super_class)

        ################################
        # State Instance (statechart)
        ################################
        self.writer.beginClass(f"{class_node.name}Instance", ["RuntimeClassBase"])
        self.writer.beginConstructor()
        self.writer.addFormalParameter("atomdevs")

        for p in class_node.constructors[0].getParams():
            self.writer.addFormalParameter(p.getIdent(), p.getDefault())

        self.writer.beginMethodBody()

        self.writer.beginSuperClassConstructorCall("RuntimeClassBase")
        self.writer.addActualParameter("atomdevs")
        self.writer.endSuperClassConstructorCall()

        self.writer.addAssignment(GLC.SelfProperty("associations"), "{}")
        for association in class_node.associations:
            self.writer.addAssignment(
                GLC.SelfProperty(f"associations[\"{association.name}\"]"),
                GLC.FunctionCall("Association",
                                 [GLC.String(association.to_class), f"{association.min}", f"{association.max}"]))
            
        # TODO: Switch instance to write in constructor instead of the atomic devs because corresponds better
        constructor = class_node.constructors[0]
        if class_node.statechart:
            self.writer.addVSpace()
            if constructor.parent_class.statechart.big_step_maximality == "take_one":
                self.writer.addAssignment(GLC.Property(GLC.SelfProperty("semantics"), "big_step_maximality"), GLC.Property("StatechartSemantics", "TakeOne"))
            elif constructor.parent_class.statechart.big_step_maximality == "take_many":
                self.writer.addAssignment(GLC.Property(GLC.SelfProperty("semantics"), "big_step_maximality"), GLC.Property("StatechartSemantics", "TakeMany"))

            if constructor.parent_class.statechart.internal_event_lifeline == "queue":
                self.writer.addAssignment(GLC.Property(GLC.SelfProperty("semantics"), "internal_event_lifeline"), GLC.Property("StatechartSemantics", "Queue"))
            elif constructor.parent_class.statechart.internal_event_lifeline == "next_small_step":
                self.writer.addAssignment(GLC.Property(GLC.SelfProperty("semantics"), "internal_event_lifeline"), GLC.Property("StatechartSemantics", "NextSmallStep"))
            elif constructor.parent_class.statechart.internal_event_lifeline == "next_combo_step":
                self.writer.addAssignment(GLC.Property(GLC.SelfProperty("semantics"), "internal_event_lifeline"), GLC.Property("StatechartSemantics", "NextComboStep"))

            if constructor.parent_class.statechart.input_event_lifeline == "first_small_step":
                self.writer.addAssignment(GLC.Property(GLC.SelfProperty("semantics"), "input_event_lifeline"), GLC.Property("StatechartSemantics", "FirstSmallStep"))
            elif constructor.parent_class.statechart.input_event_lifeline == "first_combo_step":
                self.writer.addAssignment(GLC.Property(GLC.SelfProperty("semantics"), "input_event_lifeline"), GLC.Property("StatechartSemantics", "FirstComboStep"))
            elif constructor.parent_class.statechart.input_event_lifeline == "whole":
                self.writer.addAssignment(GLC.Property(GLC.SelfProperty("semantics"), "input_event_lifeline"), GLC.Property("StatechartSemantics", "Whole"))

            if constructor.parent_class.statechart.priority == "source_parent":
                self.writer.addAssignment(GLC.Property(GLC.SelfProperty("semantics"), "priority"), GLC.Property("StatechartSemantics", "SourceParent"))
            elif constructor.parent_class.statechart.priority == "source_child":
                self.writer.addAssignment(GLC.Property(GLC.SelfProperty("semantics"), "priority"), GLC.Property("StatechartSemantics", "SourceChild"))

            if constructor.parent_class.statechart.concurrency == "single":
                self.writer.addAssignment(GLC.Property(GLC.SelfProperty("semantics"), "concurrency"), GLC.Property("StatechartSemantics", "Single"))
            elif constructor.parent_class.statechart.concurrency == "many":
                self.writer.addAssignment(GLC.Property(GLC.SelfProperty("semantics"), "concurrency"), GLC.Property("StatechartSemantics", "Many"))
            
            self.writer.addVSpace()
            self.writer.addComment("build Statechart structure")
            self.writer.add(GLC.FunctionCall(GLC.SelfProperty("build_statechart_structure"), []))

        if constructor.parent_class.attributes:
            self.writer.addVSpace()
            self.writer.addComment("user defined attributes")
            for attribute in constructor.parent_class.attributes:
                if attribute.init_value is None :
                    self.writer.addAssignment(GLC.SelfProperty(attribute.name), GLC.NoneExpression())
                else :
                    self.writer.addAssignment(GLC.SelfProperty(attribute.name), attribute.init_value)

        self.writer.addVSpace()
        self.writer.addComment("call user defined constructor")
        self.writer.beginSuperClassMethodCall(f"{constructor.parent_class.name}Instance", "user_defined_constructor")
        for p in constructor.getParams():
            # we can't do p.accept(self) here because 'p' is a FormalParameter
            # and we want to write it as an actual parameter
            self.writer.addActualParameter(p.getIdent())
        self.writer.endSuperClassMethodCall()

        # TODO
        #if constructor.parent_class.name != constructor.parent_class.class_diagram.default_class.name:
        self.writer.addAssignment("port_name", GLC.FunctionCall("Ports.addInputPort", [GLC.String("<narrow_cast>"), "self"]))
        self.writer.add(GLC.FunctionCall("atomdevs.addInPort", ["port_name"]))

        for inp in class_node.inports:
            #self.writer.addAssignment(GLC.SelfProperty(f"inports[\"{inp}\"]"), f"(\'{inp}\', atomdevs.next_instance)")
            self.writer.addAssignment("port_name", GLC.FunctionCall("Ports.addInputPort", [GLC.String(inp), "self"]))
            self.writer.add(GLC.FunctionCall("atomdevs.addInPort", ["port_name"]))
            self.writer.addAssignment("atomdevs.port_mappings[port_name]", "atomdevs.next_instance")
            self.writer.addAssignment(f"self.inports[\"{inp}\"]", "port_name")


        self.writer.endMethodBody()
        self.writer.endConstructor()

        # visit constructor
                # user defined constructor
        self.writer.beginMethod("user_defined_constructor")
        for p in constructor.getParams():
            p.accept(self)
        self.writer.beginMethodBody()
        for super_class in constructor.parent_class.super_classes:
            # begin call
            if super_class in constructor.parent_class.super_class_objs:
                self.writer.beginSuperClassMethodCall(super_class, "user_defined_constructor")
            else:
                self.writer.beginSuperClassConstructorCall(super_class)
            # write actual parameters
            if super_class in constructor.super_class_parameters:
                for p in constructor.super_class_parameters[super_class]:
                    self.writer.addActualParameter(p)
            # end call
            if super_class in constructor.parent_class.super_class_objs:
                self.writer.endSuperClassMethodCall()
            else:
                self.writer.endSuperClassConstructorCall()
        self.writer.addRawCode(constructor.body)
        self.writer.endMethodBody()
        self.writer.endMethod()

        # visit destructor
        class_node.destructors[0].accept(self)

        # visit methods
        for i in class_node.methods:
            i.accept(self)

        # compile and initialize Statechart
        if class_node.statechart:
            class_node.statechart.accept(self)

            self.writer.beginMethod("initializeStatechart")
            self.writer.beginMethodBody()
            self.writer.addComment("enter default state")

            # get effective target of initial transition
            self.writer.addAssignment(
                GLC.SelfProperty("default_targets"),
                GLC.FunctionCall(
                    GLC.Property(
                        GLC.MapIndexedExpression(
                            GLC.SelfProperty("states"),
                            GLC.String(class_node.statechart.root.initial)
                        ),
                        "getEffectiveTargetStates"
                    )
                )
            )

            self.writer.add(GLC.SuperClassMethodCall(
                "RuntimeClassBase",
                "initializeStatechart",
                []
            ))

            self.writer.endMethodBody()
            self.writer.endMethod()

        self.writer.endClass()

        ################################
        # State Object (keeps list of all instances of that object + controller operations)
        ################################
        self.writer.beginClass(class_node.name, super_classes)

        self.writer.beginMethod("constructObject")

        parameters = [GLC.SelfExpression()]
        for i, _ in enumerate(constructor.parameters):
            parameters.append(f"parameters[{i+2}]")

        self.writer.addFormalParameter("parameters")
        self.writer.beginMethodBody()
        self.writer.addAssignment("new_instance", GLC.FunctionCall(f"{class_node.name}Instance", parameters))
        self.writer.add(GLC.ReturnStatement("new_instance"))
        self.writer.endMethodBody()
        self.writer.endMethod()

        # visit constructor
        class_node.constructors[0].name = class_node.name
        class_node.constructors[0].accept(self)

        self.writer.endClass()

    # CLASS -- CONSTRUCTOR
    def visit_Constructor(self, constructor):
        ################################
        # Constructor of State Object
        ################################

        self.writer.beginConstructor()
        self.writer.addFormalParameter("name")

        self.writer.beginMethodBody()  # constructor body
        self.writer.beginSuperClassConstructorCall("ObjectManagerBase")
        self.writer.addActualParameter("name")
        self.writer.endSuperClassConstructorCall()

        self.writer.addAssignment(GLC.SelfProperty("input"), GLC.FunctionCall(GLC.SelfProperty("addInPort"), [GLC.String("input")]))

        self.writer.addAssignment(GLC.SelfProperty("output"), GLC.FunctionCall(GLC.SelfProperty("addOutPort"), [GLC.String("ui")]))



        for association in constructor.parent_class.associations:
            self.writer.addAssignment(GLC.SelfProperty(f"outputs[\"{association.name}\"]"),
                                      GLC.FunctionCall(GLC.SelfProperty("addOutPort"), [GLC.String(association.name)]))

        for p in constructor.parent_class.inports:
            self.writer.addAssignment(GLC.SelfProperty(p),
                                      GLC.FunctionCall(GLC.SelfProperty("addInPort"), [GLC.String(p)]))

        for p in constructor.parent_class.outports:
            self.writer.addAssignment(
                GLC.MapIndexedExpression(GLC.SelfProperty("outports"), GLC.String(p)),
                GLC.FunctionCall(GLC.Property("controller", "addOutputPort"), [GLC.String(p), GLC.SelfExpression()]))

        if constructor.parent_class.name == constructor.parent_class.class_diagram.default_class.name:
            #self.writer.add(GLC.FunctionCall(GLC.SelfProperty("instances.append"), [f"{constructor.parent_class.name}Instance(self)"]))
            self.writer.addAssignment("self.instances[self.next_instance]", f"{constructor.parent_class.name}Instance(self)")
            self.writer.addAssignment("self.next_instance", "self.next_instance + 1")
            #self.writer.addAssignment("port_name", GLC.FunctionCall("Ports.addInputPort", [GLC.String("<narrow_cast>"), "0"]))
            #self.writer.add(GLC.FunctionCall("self.addInPort", ["port_name"]))

        self.writer.endMethodBody()
        self.writer.endConstructor()

    def visit_FormalParameter(self, formal_parameter):
        self.writer.addFormalParameter(formal_parameter.getIdent(), formal_parameter.getDefault())

    ### CLASS -- DESTRUCTOR
    def visit_Destructor(self, destructor):
        self.writer.beginMethod("user_defined_destructor")
        self.writer.beginMethodBody()
        if destructor.body.strip():
            self.writer.addRawCode(destructor.body)
        if destructor.parent_class.super_classes:
            self.writer.addComment("call super class destructors")
            for super_class in destructor.parent_class.super_classes:
                # begin call
                if super_class in destructor.parent_class.super_class_objs:
                    self.writer.beginSuperClassMethodCall(super_class, "user_defined_destructor")
                    self.writer.endSuperClassMethodCall()
                else:
                    self.writer.beginSuperClassDestructorCall(super_class)
                    self.writer.endSuperClassDestructorCall()
                    pass

                # self.writer.beginSuperClassMethodCall(super_class, "user_defined_destructor")
                # self.writer.endSuperClassMethodCall()
        self.writer.endMethodBody()
        self.writer.endMethod()

    # CLASS -- METHOD
    def visit_Method(self, method):
        self.writer.addVSpace()
        self.writer.beginMethod(method.name, "user defined method")
        for p in method.parameters:
            p.accept(self)
        self.writer.beginMethodBody()
        self.writer.addRawCode(method.body)
        self.writer.endMethodBody()
        self.writer.endMethod()

    # CLASS -- ASSOCIATION
    def visit_Association(self, association):
        self.writer.addAssignment(
            GLC.MapIndexedExpression(
                GLC.Property("instance", "associations"),
                GLC.String(association.name)),
            GLC.NewExpression("Association",
                              [GLC.String(association.to_class), str(association.min), str(association.max)]))

    # CLASS -- STATECHART
    def visit_StateChart(self, statechart):
        self.writer.addVSpace()
        self.writer.beginMethod("build_statechart_structure", "builds Statechart structure")
        self.writer.beginMethodBody()

        def writeState(s, i):
            self.writer.addVSpace()
            self.writer.addComment("state %s" % ("<root>" if s.is_root else s.new_full_name))
            index_expr = GLC.MapIndexedExpression(GLC.SelfProperty("states"), GLC.String(s.new_full_name))
            clazz = "State"
            if s.is_parallel_state:
                clazz = "ParallelState"
            elif s.is_history:
                if s.is_history_deep:
                    clazz = "DeepHistoryState"
                else:
                    clazz = "ShallowHistoryState"
            self.writer.addAssignment(
                index_expr,
                GLC.NewExpression(clazz, [str(i), GLC.String(s.new_full_name), GLC.SelfExpression()])
            )
            if not s.is_root:
                if s.enter_action.action or s.has_timers:
                    self.writer.add(
                        GLC.FunctionCall(
                            GLC.Property(
                                index_expr,
                                "setEnter"
                            ),
                            [GLC.SelfProperty(s.friendly_name + "_enter")]
                        )
                    )
                if s.exit_action.action or s.has_timers:
                    self.writer.add(
                        GLC.FunctionCall(
                            GLC.Property(
                                index_expr,
                                "setExit"
                            ),
                            [GLC.SelfProperty(s.friendly_name + "_exit")]
                        )
                    )

        # write all states
        for (i, s) in enumerate(statechart.states):
            writeState(s, i)

        # add children to composite states
        self.writer.addVSpace()
        self.writer.addComment("add children")
        for (i, s) in enumerate(statechart.composites):
            for c in s.children:
                self.writer.add(
                    GLC.FunctionCall(
                        GLC.Property(
                            GLC.MapIndexedExpression(
                                GLC.SelfProperty("states"),
                                GLC.String(s.new_full_name)
                            ),
                            "addChild"),
                        [GLC.MapIndexedExpression(GLC.SelfProperty("states"), GLC.String(c.new_full_name))]
                    )
                )

        # fix tree at root, such that 'descendants' and 'ancestors' fields are filled in
        self.writer.add(
            GLC.FunctionCall(
                GLC.Property(
                    GLC.MapIndexedExpression(
                        GLC.SelfProperty("states"),
                        GLC.String("")
                    ),
                    "fixTree"
                )
            )
        )

        # defaults
        for (i, s) in enumerate(statechart.composites):
            if not s.is_parallel_state:
                self.writer.addAssignment(
                    GLC.Property(
                        GLC.MapIndexedExpression(
                            GLC.SelfProperty("states"),
                            GLC.String(s.new_full_name)
                        ),
                        "default_state"
                    ),
                    GLC.MapIndexedExpression(
                        GLC.SelfProperty("states"),
                        GLC.String(s.initial)
                    )
                )

        # transitions
        for s in statechart.basics + statechart.composites:
            if s.transitions:
                self.writer.addVSpace()
                self.writer.addComment("transition %s" % s.new_full_name)
            for (i, t) in enumerate(s.transitions + s.else_transitions):
                # instantiate new Transition instance
                self.writer.addAssignment(
                    GLC.LocalVariableDeclaration(
                        "%s_%i" % (s.friendly_name, i)
                    ),
                    GLC.NewExpression(
                        "Transition",
                        [
                            GLC.SelfExpression(),
                            GLC.MapIndexedExpression(
                                GLC.SelfProperty("states"),
                                GLC.String(s.new_full_name),
                            ),
                            GLC.ArrayExpression(
                                [
                                    GLC.MapIndexedExpression(
                                        GLC.SelfProperty("states"),
                                        GLC.String(target_node.new_full_name)
                                    )
                                    for target_node in t.target.target_nodes
                                ]
                            )
                        ]
                    )
                )
                # if any action associated with transition: set executable_content to correct function (generated later)
                if t.action.sub_actions:
                    self.writer.add(
                        GLC.FunctionCall(
                            GLC.Property(
                                "%s_%i" % (s.friendly_name, i),
                                "setAction"
                            ),
                            [GLC.SelfProperty("%s_%i_exec" % (s.friendly_name, i))]
                        )
                    )
                # if any trigger associated with transition: instantiate correct Event instance
                trigger = None
                if t.trigger.is_after:
                    trigger = GLC.NewExpression("Event", [GLC.String("_%iafter" % (t.trigger.getAfterIndex()))])
                elif t.trigger.event:
                    trigger = GLC.NewExpression("Event",
                                                [
                                                    GLC.String(t.trigger.event),
                                                    GLC.NoneExpression() if t.trigger.port is None else GLC.FunctionCall(
                                                        GLC.SelfProperty("getInPortName"),
                                                        [GLC.String(t.trigger.port)]
                                                    )
                                                ]
                                                )
                else:
                    trigger = GLC.NoneExpression()
                if trigger:
                    self.writer.add(
                        GLC.FunctionCall(
                            GLC.Property(
                                "%s_%i" % (s.friendly_name, i),
                                "setTrigger"
                            ),
                            [trigger]
                        )
                    )
                # if any guard associated with transition: set guard to correct function (generated later)
                if t.guard:
                    self.writer.add(
                        GLC.FunctionCall(
                            GLC.Property(
                                "%s_%i" % (s.friendly_name, i),
                                "setGuard"
                            ),
                            [GLC.SelfProperty("%s_%i_guard" % (s.friendly_name, i))]
                        )
                    )
                self.writer.add(
                    GLC.FunctionCall(
                        GLC.Property(
                            GLC.MapIndexedExpression(
                                GLC.SelfProperty("states"),
                                GLC.String(s.new_full_name)
                            ),
                            "addTransition"
                        ),
                        ["%s_%i" % (s.friendly_name, i)]
                    )
                )

        self.writer.endMethodBody()
        self.writer.endMethod()

        # enter/exit actions
        for (i, s) in enumerate(statechart.composites + statechart.basics):
            if not s.is_root:
                if s.enter_action.action or s.has_timers:
                    s.enter_action.accept(self)
                if s.exit_action.action or s.has_timers:
                    s.exit_action.accept(self)

        # transition actions and guards
        for s in statechart.composites + statechart.basics:
            for (i, t) in enumerate(s.transitions):
                if t.action.sub_actions:
                    self.writeTransitionAction(t, i)
                if t.hasGuard():
                    self.writeTransitionGuard(t, i)

    def visit_FormalEventParameter(self, formal_event_parameter):
        self.writer.add(formal_event_parameter.name)

    def writeFormalEventParameters(self, transition):
        parameters = transition.getTrigger().getParameters()
        if (len(parameters) > 0):
            for index, parameter in enumerate(parameters):
                self.writer.startRecordingExpression()
                parameter.accept(self)
                parameter_expr = self.writer.stopRecordingExpression()
                self.writer.addAssignment(
                    GLC.LocalVariableDeclaration(parameter_expr),
                    GLC.ArrayIndexedExpression("parameters", str(index)))

    def writeTransitionAction(self, transition, index):
        self.writer.beginMethod("%s_%i_exec" % (transition.parent_node.friendly_name, index))

        # parameters, start method
        self.writer.addFormalParameter("parameters")
        self.writer.beginMethodBody()

        # handle parameters to actually use them
        self.writeFormalEventParameters(transition)

        # write action
        transition.getAction().accept(self)

        # end method
        self.writer.endMethodBody()
        self.writer.endMethod()

    def writeTransitionGuard(self, transition, index):
        self.writer.beginMethod("%s_%i_guard" % (transition.parent_node.friendly_name, index))

        # parameters, start method
        self.writer.addFormalParameter("parameters")
        self.writer.beginMethodBody()

        # handle parameters to actually use them
        self.writeFormalEventParameters(transition)

        # get guard condition
        self.writer.startRecordingExpression()
        transition.getGuard().accept(self)  # --> visit_Expression
        expr = self.writer.stopRecordingExpression()

        # return statement, end method
        self.writer.add(GLC.ReturnStatement(expr))
        self.writer.endMethodBody()
        self.writer.endMethod()

    def visit_EnterAction(self, enter_method):
        parent_node = enter_method.parent_node
        self.writer.beginMethod(parent_node.friendly_name + "_enter")
        self.writer.beginMethodBody()

        if enter_method.action:
            enter_method.action.accept(self)

        # take care of any AFTER events
        for transition in parent_node.transitions:
            trigger = transition.getTrigger()
            if trigger.isAfter():
                self.writer.startRecordingExpression()
                trigger.after.accept(self)
                after = self.writer.stopRecordingExpression()
                self.writer.add(GLC.FunctionCall(GLC.SelfProperty("addTimer"), [str(trigger.getAfterIndex()), after]))

        self.writer.endMethodBody()
        self.writer.endMethod()

    def visit_ExitAction(self, exit_method):
        parent_node = exit_method.parent_node
        self.writer.beginMethod(parent_node.friendly_name + "_exit")
        self.writer.beginMethodBody()

        # take care of any AFTER events
        for transition in parent_node.transitions:
            trigger = transition.getTrigger()
            if trigger.isAfter():
                self.writer.add(GLC.FunctionCall(GLC.SelfProperty("removeTimer"), [str(trigger.getAfterIndex())]))

        # execute user-defined exit action if present
        if exit_method.action:
            exit_method.action.accept(self)

        self.writer.endMethodBody()
        self.writer.endMethod()

        # helper method

    def writeEnterHistory(self, entered_node, is_deep):
        ### OLD CODE (TODO)
        self.writer.beginMethod("enterHistory" + ("Deep" if is_deep else "Shallow") + "_" + entered_node.full_name)
        self.writer.beginMethodBody()

        self.writer.beginIf(GLC.EqualsExpression(
            GLC.ArrayLength(
                GLC.MapIndexedExpression(
                    GLC.SelfProperty("history_state"),
                    GLC.SelfProperty(entered_node.full_name))),
            "0"))
        defaults = entered_node.defaults

        for node in defaults:
            if node.is_basic:
                self.writer.add(GLC.FunctionCall(GLC.SelfProperty("enter_" + node.full_name)))
            elif node.is_composite:
                self.writer.add(GLC.FunctionCall(GLC.SelfProperty("enterDefault_" + node.full_name)))

        self.writer.endIf()
        self.writer.beginElse()
        children = entered_node.children
        if entered_node.is_parallel_state:
            for child in children:
                if not child.is_history:
                    self.writer.add(GLC.FunctionCall(GLC.SelfProperty("enter_" + child.full_name)))
                    self.writer.add(GLC.FunctionCall(
                        GLC.SelfProperty("enterHistory" + ("Deep" if is_deep else "Shallow") + "_" + child.full_name)))
        else:
            for child in children:
                if not child.is_history:
                    self.writer.beginIf(GLC.ArrayContains(
                        GLC.MapIndexedExpression(
                            GLC.SelfProperty("history_state"),
                            GLC.SelfProperty(entered_node.full_name)),
                        GLC.SelfProperty(child.full_name)))
                    if child.is_composite:
                        if is_deep:
                            self.writer.add(GLC.FunctionCall(GLC.SelfProperty("enter_" + child.full_name)))
                            self.writer.add(GLC.FunctionCall(GLC.SelfProperty("enterHistoryDeep_" + child.full_name)))
                        else:
                            self.writer.add(GLC.FunctionCall(GLC.SelfProperty("enterDefault_" + child.full_name)))
                    else:
                        self.writer.add(GLC.FunctionCall(GLC.SelfProperty("enter_" + child.full_name)))
                    self.writer.endIf()
        self.writer.endElse()

        self.writer.endMethodBody()
        self.writer.endMethod()

    def visit_SelfReference(self, self_reference):
        self.writer.add(GLC.SelfExpression())

    def visit_StateReference(self, state_ref):
        self.writer.beginArray()
        for node in state_ref.getNodes():
            self.writer.add(GLC.SelfProperty(node.full_name))
        self.writer.endArray()

    def visit_InStateCall(self, in_state_call):
        self.writer.add(
            GLC.FunctionCall(
                GLC.SelfProperty("inState"),
                [
                    GLC.ArrayExpression(
                        [GLC.String(target_node.new_full_name) for target in in_state_call.targets for target_node in
                         target.target_nodes]
                    )
                ]
            )
        )

    def visit_ElseGuard(self, else_guard):
        self.writer.add(
            GLC.String("ELSE_GUARD")
        )

    def visit_Expression(self, expression):
        self.writer.startRecordingExpression()
        self.writer.beginGlue()
        for part in expression.expression_parts:
            part.accept(self)
        self.writer.endGlue()
        expr = self.writer.stopRecordingExpression()
        self.writer.add(expr)

    def visit_ExpressionPartString(self, e):
        self.writer.add(e.string)

    def visit_RaiseEvent(self, raise_event):
        self.writer.startRecordingExpression()
        self.writer.begin(GLC.NewExpression("Event"))

        self.writer.addActualParameter(GLC.String(raise_event.getEventName()))
        if raise_event.isOutput():
            self.writer.addActualParameter(
                GLC.FunctionCall(
                    GLC.SelfProperty("getOutPortName"),
                    [GLC.String(raise_event.getPort())]
                )
            )
        else:
            self.writer.addActualParameter(GLC.NoneExpression())

        self.writer.end()
        new_event_expr = self.writer.stopRecordingExpression()

        self.writer.startRecordingExpression()
        self.writer.beginArray()
        if raise_event.isCD():
            self.writer.add(GLC.SelfExpression())
        for param in raise_event.getParameters():
            param.accept(self)  # -> visit_Expression will cause expressions to be added to array
        self.writer.endArray()
        parameters_array_expr = self.writer.stopRecordingExpression()
        new_event_expr.getActualParameters().add(parameters_array_expr)

        if raise_event.isNarrow():
            self.writer.add(GLC.FunctionCall(
                GLC.Property(GLC.SelfProperty("big_step"), "outputEventOM"), [
                    GLC.NewExpression("Event", [
                        GLC.String("narrow_cast"),
                        GLC.NoneExpression(),
                        GLC.ArrayExpression([
                            GLC.SelfExpression(),
                            raise_event.getTarget(),
                            new_event_expr])])]))
        elif raise_event.isLocal():
            self.writer.add(GLC.FunctionCall(
                GLC.SelfProperty("raiseInternalEvent"),
                [new_event_expr]))
        elif raise_event.isOutput():
            self.writer.add(GLC.FunctionCall(
                GLC.Property(GLC.SelfProperty("big_step"), "outputEvent"),
                [new_event_expr]))
        elif raise_event.isCD():
            self.writer.add(GLC.FunctionCall(
                GLC.Property(GLC.SelfProperty("big_step"), "outputEventOM"),
                [new_event_expr]))
        elif raise_event.isBroad():
            self.writer.add(GLC.FunctionCall(
                GLC.Property(GLC.SelfProperty("big_step"), "outputEventOM"),
                [GLC.NewExpression("Event", [
                    GLC.String("broad_cast"),
                    GLC.NoneExpression(),
                    GLC.ArrayExpression([
                        GLC.SelfExpression(),
                        new_event_expr])])]))

    def visit_Script(self, script):
        self.writer.addRawCode(script.code)

    def visit_Log(self, log):
        self.writer.add(GLC.LogStatement(log.message))

    def visit_Assign(self, assign):
        self.writer.startRecordingExpression()
        assign.lvalue.accept(self)  # --> visit_Expression
        lvalue = self.writer.stopRecordingExpression()
        self.writer.startRecordingExpression()
        assign.expression.accept(self)  # --> visit_Expression
        rvalue = self.writer.stopRecordingExpression()
        self.writer.addAssignment(lvalue, rvalue)
