<class name="MvKController">
    <relationships>
        <association name="to_mvi" class="Server" min="1" max="1"/>
    </relationships>
    <constructor>
        <parameter name="params"/>
        <body>
            <![CDATA[
            self.mvs = ModelverseState("../bootstrap/bootstrap.m.gz")
            # Enable Garbage Collection
            self.mvs.GC = True
            self.root = self.mvs.read_root()[0]
            self.mvk = ModelverseKernel(self.root)
            self.all_failed = False
            self.timeout = False
            self.init_time = time.time()

            self.users = set()
            self.input_queue = defaultdict(list)
            self.output_queue = defaultdict(list)
            self.source = None
            self.port = int(sys.argv[1])
            self.count = 0
            
            self.debugged_users = set()
            self.debug_info = {}
            self.done_something = False

            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="username"/>
        <parameter name="operation"/>
        <parameter name="params"/>
        <body>
            <![CDATA[
            reply = None
            commands = []
            while 1:
                commands = self.mvk.execute_yields(username, operation, params, reply)
                if commands is None:
                    break
                reply = [self.mvs_operations[command[0]](*(command[1]))[0] for command in commands]
                #for c, r in zip(commands, reply):
                #    print("%s --> %s" % (c, r))
            ]]>
        </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="wait_for_requests">
                <state id="wait">
                    <transition event="from_mvi" target=".">
                        <parameter name="source"/>
                        <parameter name="data"/>
                        <script>
                            # No JSON encoding necessary, as it is not complex
                            try:
                                print 'from_mvi %s' % data
                                self.done_something = False
                                if data["op"] == "set_input":
                                    if "value" in data:
                                        value = [json.loads(data["value"])]
                                    else:
                                        value = json.loads(data["data"])
                                    self.input_queue[data["username"]].append((source, value))
                                elif data["op"] == "get_output":
                                    self.output_queue[data["username"]].append(source)
                                elif data["op"] == "attach_debugger":
                                    self.debugged_users.add(data["username"])
                                    self.done_something = True
                                    self.source = source
                                    self.debug_info[data["username"]] = {'state': 'running', 'breakpoints': []}
                                elif data["op"] == "detach_debugger":
                                    self.debugged_users.discard(data["username"])
                                    self.done_something = True
                                    self.source = source
                                    del self.debug_info[data["username"]]
                                elif data["op"] == "pause":
                                    if data["username"] in self.debugged_users:
                                        self.debug_info[data["username"]]['state'] = 'paused'
                                    self.done_something = True
                                    self.source = source
                                elif data["op"] == "resume":
                                    if data["username"] in self.debugged_users:
                                        self.debug_info[data["username"]]['state'] = 'running'
                                    self.done_something = True
                                    self.source = source
                                elif data["op"] == "step_over":
                                    pass
                                    self.done_something = True
                                    self.source = source
                                elif data["op"] == "step_into":
                                    pass
                                    self.done_something = True
                                    self.source = source
                                else:
                                    raise Exception("DROPPING unknown operation: " + str(data["op"]))
                            except ValueError:
                                print("Error when deserializing request: " + str(data))
                                raise
                        </script>
                    </transition>

                    <transition cond="self.done_something" target=".">
                        <raise event="HTTP_input" scope="narrow" target="'to_mvi/%s' % self.source">
                            <parameter expr="json.dumps(True)"/>
                        </raise>
                        <script>
                            self.done_something = False
                        </script>
                    </transition>
                </state>
            </state>

            <state id="execution">
                <state id="execution">
                    <onentry>
                        <script>
                            self.timeout = False
                            self.destination = None
                            if self.users:
                                user = self.users.pop()
                                if not user in self.debugged_users or self.debug_info[user]['state'] == 'running':
                                    # Check if there are values to input
                                    if self.input_queue[user]:
                                        source, args = self.input_queue[user].pop(0)
                                        for args_entry in args:
                                            self.execute_modelverse(user, "set_input", [args_entry])

                                        self.destination = source
                                        self.value = "OK"
                                        self.all_failed = False

                                    nr_of_steps = 1 if user in self.debugged_users else 100
                                    # Now process for some steps, or until we are again blocked for input
                                    for x in xrange(nr_of_steps):
                                        self.execute_modelverse(user, "execute_rule", [])

                                        if not self.mvk.success:
                                            # Blocking or broken, so quit already to stop wasting CPU
                                            break

                                        # Could at least execute one instruction, so mark it as "not failed"
                                        self.all_failed = False

                                    # Check that we don't have anything to output yet, otherwise we wait
                                    if self.destination is None:
                                        # Perform output if there is anything
                                        if self.output_queue[user]:
                                            self.execute_modelverse(user, "get_output", [])
                                            if self.mvk.success:
                                                self.destination = self.output_queue[user].pop(0)
                                                self.value = self.mvk.returnvalue
                                                self.all_failed = False

                            else:
                                if self.count >= 2000:
                                    self.count = 0
                                    self.mvs.purge()
                                else:
                                    self.count += 1
                                    self.mvs.garbage_collect()
                                out = self.mvs.read_outgoing(self.root)[0]
                                for m in out:
                                    src, user = self.mvs.read_edge(m)[0]
                                    outgoing = self.mvs.read_outgoing(m)[0]
                                    first = self.mvs.read_edge(outgoing[0])[0]
                                    dest = first[1]
                                    name = self.mvs.read_value(dest)[0]
                                    if name.startswith("__"):
                                        continue
                                    self.users.add(name)
                                self.timeout = self.all_failed
                                self.all_failed = True
                        </script>
                    </onentry>

                    <transition cond="self.destination is not None" after="self.sccd_yield()" target=".">
                        <raise event="HTTP_input" scope="narrow" target="'to_mvi/%s' % self.destination">
                            <parameter expr="json.dumps(self.value)"/>
                        </raise>
                    </transition>

                    <transition cond="self.timeout and self.destination is None" after="self.sccd_yield() + 0.10" target="."/>

                    <transition cond="not self.timeout and self.destination is None" after="self.sccd_yield()" target="."/>
                </state>
            </state>

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