<class name="MvKController">
    <relationships>
        <association name="to_mvi" class="Server" min="1" max="1"/>
        <association name="tasks" class="Task"/>
        <association name="services" class="Service"/>
    </relationships>
    <constructor>
        <parameter name="params"/>
        <body>
            <![CDATA[
            self.mvs = ModelverseState("../bootstrap/bootstrap.m.gz")
            # Enable Garbage Collection
            self.mvs.GC = False

            self.root = self.mvs.read_root()

            # Instantiate the kernel.
            self.mvk = ModelverseKernel(self.root)

            # Parse kernel-related options
            default_kernel_type = 'adaptive-jit'
            kernel_opts = [default_kernel_type]
            for parameter in params:
                if parameter.startswith('--kernel='):
                    kernel_opts = parameter[len('--kernel='):].split(',')

            self.port = int(params[0])

            self.sc_map = {}
            self.forward = None
            self.HTTP_reply = None
            self.input_queue = defaultdict(list)
            self.output_queue = defaultdict(list)
            
            self.mvs_operations = {
                    "CN": self.mvs.create_node,
                    "CE": self.mvs.create_edge,
                    "CNV": self.mvs.create_nodevalue,
                    "CD": self.mvs.create_dict,

                    "RV": self.mvs.read_value,
                    "RO": self.mvs.read_outgoing,
                    "RI": self.mvs.read_incoming,
                    "RE": self.mvs.read_edge,
                    "RD": self.mvs.read_dict,
                    "RDN": self.mvs.read_dict_node,
                    "RDNE": self.mvs.read_dict_node_edge,
                    "RDE": self.mvs.read_dict_edge,
                    "RRD": self.mvs.read_reverse_dict,
                    "RR": self.mvs.read_root,
                    "RDK": self.mvs.read_dict_keys,

                    "DE": self.mvs.delete_edge,
                    "DN": self.mvs.delete_node,
                }

            self.execute_modelverse("", "load_primitives", [])
            ]]>
        </body>
    </constructor>

    <method name="execute_modelverse">
        <parameter name="taskname"/>
        <parameter name="operation"/>
        <parameter name="params"/>
        <body>
            <![CDATA[
            reply = None
            commands = []
            mvk = self.mvk
            mvs_operations = self.mvs_operations
            try:
                while 1:
                    commands = mvk.execute_yields(taskname, operation, params, reply)
                    if commands is None:
                        break
                    reply = [mvs_operations[command[0]](*(command[1])) for command in commands]
            except:
                print("ERROR: " + str(self.mvk.debug_info.get(taskname, "Unknown taskname")))
                raise
            ]]>
        </body>
    </method>

    <scxml initial="init_server">
        <state id="init_server">
            <onentry>
                <raise scope="cd" event="create_instance">
                    <parameter expr="'to_mvi'"/>
                    <parameter expr="'Server'"/>
                    <parameter expr="''"/>
                    <parameter expr="self.port"/>
                </raise>
            </onentry>
            <transition event="instance_created" target="../running">
                <parameter name="instancename"/>
                <raise scope="cd" event="start_instance">
                    <parameter expr="instancename"/>
                </raise>
            </transition>
        </state>

        <parallel id="running">
            <state id="forward_inputs">
                <state id="forward">
                    <transition cond="set(self.input_queue) &amp; set(self.sc_map)" target=".">
                        <script>
                            self.input_task = (set(self.input_queue) &amp; set(self.sc_map)).pop()
                            value = self.input_queue[self.input_task].pop(0)
                            if not self.input_queue[self.input_task]:
                                del self.input_queue[self.input_task]
                        </script>
                        <raise event="input" scope="narrow" target="self.sc_map[self.input_task]">
                            <parameter expr="value"/>
                        </raise>
                    </transition>
                </state>
            </state>

            <state id="forward_outputs">
                <state id="forward">
                    <transition cond="set(self.output_queue) &amp; set(self.sc_map)" target=".">
                        <script>
                            self.output_task = (set(self.output_queue) &amp; set(self.sc_map)).pop()
                            value = self.output_queue[self.output_task].pop(0)
                            if not self.output_queue[self.output_task]:
                                del self.output_queue[self.output_task]
                        </script>
                        <raise event="output" scope="narrow" target="self.sc_map[self.output_task]">
                            <parameter expr="value"/>
                        </raise>
                    </transition>
                </state>
            </state>

            <state id="wait_for_requests">
                <state id="wait">
                    <transition event="from_mvi" target=".">
                        <parameter name="source"/>
                        <parameter name="data"/>
                        <script>
                            self.source = source
                            # No JSON encoding necessary, as it is not complex
                            try:
                                if data["op"] == "set_input":
                                    if "value" in data:
                                        value = [json.loads(data["value"])]
                                    else:
                                        value = json.loads(data["data"])
                                    self.forward = (data["taskname"], "input", value)
                                    self.HTTP_reply = "OK"
                                elif data["op"] == "get_output":
                                    self.forward = (data["taskname"], "output", self.source)
                                    self.HTTP_reply = None
                                elif data["op"] == "pause":
                                    #TODO
                                    self.HTTP_reply = "NotImplemented"
                                elif data["op"] == "resume":
                                    #TODO
                                    self.HTTP_reply = "NotImplemented"
                                else:
                                    self.HTTP_reply = "Unknown command: %s" % data["op"]
                            except ValueError as e:
                                self.HTTP_reply = "Error when deserializing request: %s" % traceback.format_exc()
                            except Exception as e:
                                self.HTTP_reply = "Unknown Modelverse Error: %s" % traceback.format_exc()
                        </script>
                    </transition>

                    <transition cond="not self.forward and self.HTTP_reply is not None" target=".">
                        <raise event="HTTP_input" scope="narrow" target="'to_mvi/%s' % self.source">
                            <parameter expr="json.dumps(self.HTTP_reply)"/>
                        </raise>
                        <script>
                            self.HTTP_reply = None
                            self.forward = None
                        </script>
                    </transition>

                    <transition cond="self.forward and self.forward[1] == 'input' and self.forward[0] in self.sc_map" target=".">
                        <raise event="input" scope="narrow" target="self.sc_map[self.forward[0]]">
                            <parameter expr="self.forward[2]"/>
                        </raise>
                        <raise event="HTTP_input" scope="narrow" target="'to_mvi/%s' % self.source">
                            <parameter expr="json.dumps(self.HTTP_reply)"/>
                        </raise>
                        <script>
                            self.HTTP_reply = None
                            self.forward = None
                        </script>
                    </transition>

                    <transition cond="self.forward and self.forward[1] == 'input' and self.forward[0] not in self.sc_map" target=".">
                        <raise event="HTTP_input" scope="narrow" target="'to_mvi/%s' % self.source">
                            <parameter expr="json.dumps(self.HTTP_reply)"/>
                        </raise>
                        <script>
                            self.input_queue[self.forward[0]].append(self.forward[2])
                            self.HTTP_reply = None
                            self.forward = None
                        </script>
                    </transition>

                    <transition cond="self.forward and self.forward[1] == 'output' and self.forward[0] in self.sc_map" target=".">
                        <raise event="output" scope="narrow" target="self.sc_map[self.forward[0]]">
                            <parameter expr="self.forward[2]"/>
                        </raise>
                        <script>
                            self.HTTP_reply = None
                            self.forward = None
                        </script>
                    </transition>

                    <transition cond="self.forward and self.forward[1] == 'output' and self.forward[0] not in self.sc_map" target=".">
                        <script>
                            self.output_queue[self.forward[0]].append(self.forward[2])
                            self.HTTP_reply = None
                            self.forward = None
                        </script>
                    </transition>
                </state>
            </state>

            <parallel id="execution">
                <state id="refresh_tasks" initial="refresh_tasks">
                    <state id="refresh_tasks">
                        <onentry>
                            <script>
                                out = self.mvs.read_outgoing(self.root)
                                tasks = set()
                                for m in out:
                                    src, task = self.mvs.read_edge(m)
                                    outgoing = self.mvs.read_outgoing(m)
                                    first = self.mvs.read_edge(outgoing[0])
                                    dest = first[1]
                                    name = self.mvs.read_value(dest)
                                    if name == "__hierarchy":
                                        # Skip the special case, as this is neither data, nor a task
                                        continue
                                    tasks.add(name)

                                self.new_tasks = set([i for i in tasks if i not in self.sc_map])
                                self.old_tasks = set([i for i in self.sc_map if i not in tasks])
                            </script>
                        </onentry>

                        <transition cond="self.new_tasks" target="../create_SC"/>
                        <transition cond="not self.new_tasks and self.old_tasks" target="../remove_SC"/>
                        <transition after="1.0" target="."/>
                    </state>

                    <state id="create_SC" initial="creating">
                        <state id="creating">
                            <onentry>
                                <script>
                                    self.task = self.new_tasks.pop()
                                </script>
                                <raise event="create_instance" scope="cd">
                                    <parameter expr="'services' if self.task.startswith('__') else 'tasks'"/>
                                    <parameter expr="'Service' if self.task.startswith('__') else 'Task'"/>
                                    <parameter expr="self.task"/>
                                    <parameter expr="self.mvs_operations"/>
                                    <parameter expr="self.mvk"/>
                                </raise>
                            </onentry>

                            <transition event="instance_created" target="../created">
                                <parameter name="instancename"/>
                                <raise scope="cd" event="start_instance">
                                    <parameter expr="instancename" />
                                </raise>
                                <script>
                                    self.sc_map[self.task] = instancename
                                </script>
                            </transition>
                        </state>
                        <state id="created">
                            <transition cond="self.new_tasks" target="../creating"/>
                            <transition cond="not self.new_tasks and self.old_tasks" target="../../remove_SC"/>
                            <transition cond="not self.new_tasks and not self.old_tasks" target="../../refresh_tasks"/>
                        </state>
                    </state>

                    <state id="remove_SC">
                        <transition cond="self.old_tasks" target=".">
                            <script>
                                task = self.old_tasks.pop()
                            </script>
                            <raise scope="cd" event="delete_instance">
                                <parameter expr="self.sc_map[task]"/>
                            </raise>
                            <script>
                                del self.sc_map[task]
                            </script>
                        </transition>

                        <transition cond="not self.old_tasks" target="../refresh_tasks"/>
                    </state>
                </state>

                <state id="mvs_GC" initial="suspend_tasks">
                    <state id="suspend_tasks">
                        <onentry>
                            <raise scope="broad" event="pause_task"/>
                        </onentry>

                        <transition target="../mvs_GC"/>
                    </state>

                    <state id="mvs_GC">
                        <onentry>
                            <script>
                                print("Modelverse Garbage Collection")
                                self.execute_modelverse("", "protect_temporary_variables", [])
                                self.mvs.purge()
                                self.execute_modelverse("", "unprotect_temporary_variables", [])

                                # Clean up all used memory now, as this is rather much due to the copy of the MvS
                                import gc
                                gc.collect()
                            </script>
                            <raise scope="broad" event="resume_task"/>
                        </onentry>
                        <transition after="self.sccd_yield() + 100" target="."/>
                    </state>
                </state>
            </parallel>

            <state id="remove_sockets">
                <state id="remove_sockets">
                    <transition event="delete_socket" target=".">
                        <parameter name="socket"/>
                        <script>
                            for task in self.output_queue.keys():
                                self.output_queue[task] = [s for s in self.output_queue[task] if s != socket]
                        </script>
                    </transition>
                </state>
            </state>
        </parallel>
    </scxml>
</class>
