<?xml version="1.0" ?>
<diagram author="Simon Van Mierlo" name="NetLogo debugging-enhanced simulator">
    <description>
        A NetLogo simulator (enhanced with debugging support).
    </description>
    <top>
        import pyNetLogo
        import pprint, time

        class Breakpoint(object):
            def __init__(self, breakpoint_id, function, enabled, disable_on_trigger):
                self.id = breakpoint_id
                self.function = function
                self.enabled = enabled
                self.disable_on_trigger = disable_on_trigger
    </top>
    <inport name="request"/>
    <outport name="reply"/>
    <class name="NetLogoSimulator" default="true">
        <constructor>
            <parameter name="model"/>
            <body>
                <![CDATA[
                self.link = pyNetLogo.NetLogoLink(gui=True,
                             netlogo_home="C:\\Program Files\\NetLogo 6.0.3",
                             netlogo_version="6")
                self.link.load_model(model)
                self.attr_names = self.link.report('last last reflection:breeds')
                self.link.command("setup")
                self.ignored_attributes = set(["WHO", "HEADING", "SHAPE", "LABEL", "LABEL-COLOR", "BREED", "HIDDEN?", "PEN-SIZE", "PEN-MODE"])
                self.breakpoints = []
                self.realtime_scale = 1.0
                self.simulation_time = 0
                self.time_next = 1
                self.turtles = {}

                # Values to be set during simulation
                self.realtime_starttime = None
                ]]>
            </body>
        </constructor>
        <method name="turtles_created">
            <parameter name="new_turtles" />
            <body>
                return set(new_turtles.keys()) - set(self.turtles.keys())
            </body>
        </method>
        <method name="turtles_deleted">
            <parameter name="new_turtles" />
            <body>
                return set(self.turtles.keys()) - set(new_turtles.keys())
            </body>
        </method>
        <method name="get_all_turtles">
            <body>
                try:
                    all_turtle_attributes = self.link.report("reflection:turtles")
                except:
                    all_turtle_attributes = {}
                turtles = {}
                for turtle_attributes in all_turtle_attributes:
                    the_turtle = dict(zip(self.attr_names, turtle_attributes.getResultAsObject()))
                    turtles[int(float(the_turtle["WHO"]))] = the_turtle
                turtles = {k: {ak: av for ak, av in turtles[k].iteritems() if ak not in self.ignored_attributes} for k in turtles.keys()}
                return turtles
            </body>
        </method>
        <method name="breakpoint_triggers">
            <body>
                <![CDATA[
                breakpoint_id = -1
                for bp in self.breakpoints:
                    if not bp.enabled:
                        continue
                    # Include the function in the scope
                    exec(bp.function)
                    # And execute it, note that the breakpoint thus has to start with "def breakpoint("
                    if breakpoint(self.link.report("ticks"), self.turtles):
                        # Triggered!
                        breakpoint_id = bp.id
                    else:
                        # Not triggered, so continue
                        continue
                return breakpoint_id
                ]]>
            </body>
        </method>
        <method name="process_breakpoints">
            <body>
                <![CDATA[
                breakpoint_id = self.breakpoint_triggers()
                if (breakpoint_id != -1):
                    for breakpoint in self.breakpoints:
                        if breakpoint.id == breakpoint_id:
                            if breakpoint.disable_on_trigger:
                                breakpoint.enabled = False
                return breakpoint_id
                ]]>
            </body>
        </method>
        <!-- Calculate the time to wait before triggering the next transition.
             This method is also called in non-realtime simulation, so make sure that it returns infinity in that case. -->
        <method name="calculate_after">
            <body>
                <![CDATA[
                try:
                    # Process in parts of 100 milliseconds to repeatedly check the termination condition
                    nexttime = (self.time_next - (time.time() - self.realtime_starttime) / self.realtime_scale) * self.realtime_scale
                    x =  min(0.1, nexttime)
                    return x
                except (TypeError, AttributeError) as e:
                    # We are probably not simulating in realtime...
                    return float('inf')
                ]]>
            </body>
        </method>
        <!-- Parse a list of options that can be passed together with the request -->
        <method name="parse_options">
            <parameter name="configuration"/>
            <body>
                <![CDATA[
                self.realtime_scale = 1.0 if "realtime_scale" not in configuration else 1.0/configuration["realtime_scale"]
                # Subtract the current simulation time to allow for pausing
                self.realtime_starttime = (time.time() - self.simulation_time * self.realtime_scale)
                # Reset the time used in the waiting, as it might not get recomputed
                self.the_time = 0.00001
                ]]>
            </body>
        </method>
        <scxml initial="main">
            <parallel id="main">
                <state id="simulation_flow" initial="initialize">
                    <state id="initialize">
                        <transition target="../check_termination">
                            <script>
                                self.new_turtles = self.get_all_turtles()
                            </script>
                            <raise scope="output" port="reply" event="all_states">
                                <parameter expr="self.simulation_time" />
                                <parameter expr="self.new_turtles" />
                                <parameter expr="{'CREATED_TURTLES': self.turtles_created(self.new_turtles),
                                                  'DELETED_TURTLES': self.turtles_deleted(self.new_turtles)}" />
                            </raise>
                            <script>
                                self.turtles = self.new_turtles
                            </script>
                        </transition>
                    </state>
                    <state id="check_termination" initial="workaround">
                        <onexit>
                            <script>
                                self.simulation_time = self.time_next
                            </script>
                        </onexit>

                        <state id="workaround">
                            <transition after="self.sccd_yield()" target="../check_termination"/>
                        </state>

                        <state id="wait">
                            <onexit>
                                <script>
                                    diff = time.time() - self.realtime_starttime
                                    self.simulation_time = diff / self.realtime_scale
                                </script>
                            </onexit>
                            <transition after="self.calculate_after()" target="../check_termination"/>
                            <transition cond="INSTATE('../../../simulation_state/paused')" target="../check_termination"/>
                        </state>

                        <state id="check_termination">
                            <onentry>
                                <script>
                                    self.the_time = self.calculate_after()
                                </script>
                            </onentry>
                            <transition cond="(not INSTATE('../../../simulation_state/paused')) and (self.breakpoint_triggers() &gt; -1)" target="../workaround">
                                <script>
                                    breakpoint_id = self.process_breakpoints()
                                </script>
                                <raise scope="output" port="reply" event="breakpoint_triggered">
                                    <parameter expr="breakpoint_id"/>
                                </raise>
                                <raise event="termination_condition"/>
                            </transition>
                            <transition target="../../execute_step" cond="INSTATE('../../../simulation_state/continuous')" />
                            <transition target="../../execute_step" event="step" port="request" />
                            <transition target="../wait" cond="INSTATE('../../../simulation_state/realtime') and (self.the_time &gt; 0.0)"/>
                            <transition target="../../execute_step" cond="INSTATE('../../../simulation_state/realtime') and (self.the_time &lt;= 0.0)"/>
                            <transition cond="INSTATE('../../../simulation_state/paused')" port="request" event="god_event" target="../workaround">
                                <parameter name="configuration"/>
                                <script>
                                    turtle_id = configuration["turtle"]
                                    state_attribute = configuration["attribute"]
                                    new_value = configuration["value"]
                                    askstring = "ask turtle %i [set %s %s]" % (int(turtle_id), state_attribute, new_value)
                                    self.link.command(askstring)
                                    self.new_turtles = self.get_all_turtles()
                                </script>
                                <raise scope="output" port="reply" event="god_event_ok" />                                
                                <raise scope="output" port="reply" event="all_states">
                                    <parameter expr="self.simulation_time" />
                                    <parameter expr="self.new_turtles" />
                                    <parameter expr="{'CREATED_TURTLES': self.turtles_created(self.new_turtles),
                                                      'DELETED_TURTLES': self.turtles_deleted(self.new_turtles)}" />
                                </raise>
                                <script>
                                    self.turtles = self.new_turtles
                                </script>
                            </transition>
                        </state>
                    </state>
                    <state id="execute_step" initial="executing_step">
                        <onentry>
                            <script>
                                self.link.command("go")
                                self.new_turtles = self.get_all_turtles()
                            </script>
                        </onentry>
                        <state id="executing_step">
                            <transition target="../../check_termination" cond="INSTATE('../../../simulation_state/continuous')">
                                <script>
                                    self.time_next = self.time_next + 1
                                </script>
                            </transition>
                            <transition target="../../check_termination" cond="ELSE">
                                <raise scope="output" port="reply" event="all_states">
                                    <parameter expr="self.simulation_time" />
                                    <parameter expr="self.new_turtles" />
                                    <parameter expr="{'CREATED_TURTLES': self.turtles_created(self.new_turtles),
                                                      'DELETED_TURTLES': self.turtles_deleted(self.new_turtles)}" />
                                </raise>
                                <script>
                                    self.time_next = self.time_next + 1
                                    self.turtles = self.new_turtles
                                </script>
                            </transition>
                        </state>
                    </state>
                </state>
                <state id="simulation_state" initial="paused">
                    <state id="paused">
                        <transition target="../continuous" event="continuous" port="request" />
                        <transition target="../realtime" event="realtime" port="request">
                            <parameter name="configuration" />
                            <script>
                                self.parse_options(configuration)
                            </script>
                        </transition>
                    </state>
                    <state id="continuous">
                        <transition target="../paused" event="pause">
                            <raise scope="output" port="reply" event="all_states">
                                <parameter expr="self.simulation_time" />
                                <parameter expr="self.new_turtles" />
                                <parameter expr="{'CREATED_TURTLES': self.turtles_created(self.new_turtles),
                                                  'DELETED_TURTLES': self.turtles_deleted(self.new_turtles)}" />
                            </raise>
                            <script>
                                self.turtles = self.new_turtles
                            </script>
                        </transition>
                        <transition event="termination_condition" target="../paused">
                            <raise port="reply" event="terminate">
                                <parameter expr="self.simulation_time"/>
                            </raise>
                        </transition>
                    </state>
                    <state id="realtime">
                        <transition target="../paused" event="pause">
                            <script>
                                diff = time.time() - self.realtime_starttime
                                self.simulation_time = diff / self.realtime_scale
                                self.new_turtles = self.get_all_turtles()
                            </script>
                            <raise scope="output" port="reply" event="all_states">
                                <parameter expr="self.simulation_time" />
                                <parameter expr="self.new_turtles" />
                                <parameter expr="{'CREATED_TURTLES': self.turtles_created(self.new_turtles),
                                                  'DELETED_TURTLES': self.turtles_deleted(self.new_turtles)}" />
                            </raise>
                            <script>
                                self.turtles = self.new_turtles
                            </script>
                        </transition>
                        <transition event="termination_condition" target="../paused">
                            <raise port="reply" event="terminate">
                                <parameter expr="self.simulation_time"/>
                            </raise>
                        </transition>
                    </state>
                </state>
                <state id="reset_manager" initial="listening">
                    <state id="listening">
                        <transition port="request" event="reset" target="." cond="INSTATE('../../simulation_state/paused')">
                            <script>
                                self.link.command("setup")
                                self.new_turtles = self.get_all_turtles()
                                self.simulation_time = 0
                                self.realtime_scale = 1.0
                                self.realtime_starttime = None
                                self.time_next = 1
                            </script>
                            <raise scope="output" port="reply" event="all_states">
                                <parameter expr="self.simulation_time"/>
                                <parameter expr="self.new_turtles"/>
                                <parameter expr="{'CREATED_TURTLES': self.turtles_created(self.new_turtles),
                                                  'DELETED_TURTLES': self.turtles_deleted(self.new_turtles)}" />
                            </raise>
                            <script>
                                self.turtles = self.new_turtles
                            </script>
                        </transition>
                    </state>
                </state>
                <state id="breakpoint_manager">
                    <state id="breakpoint_manage">
                        <transition port="request" event="add_breakpoint" target=".">
                            <parameter name="breakpoint_id"/>
                            <parameter name="function"/>
                            <parameter name="enabled"/>
                            <parameter name="disable_on_trigger"/>
                            <script>
                                self.breakpoints.append(Breakpoint(breakpoint_id, function, enabled, disable_on_trigger))
                            </script>
                        </transition>
                        <transition port="request" event="del_breakpoint" target=".">
                            <parameter name="del_breakpoint_id"/>
                            <script>
                                self.breakpoints = [breakpoint for breakpoint in self.breakpoints if breakpoint.id != del_breakpoint_id]
                            </script>
                        </transition>
                        <transition port="request" event="toggle_breakpoint" target=".">
                            <parameter name="breakpoint_id"/>
                            <parameter name="enabled"/>
                            <script>
                                for breakpoint in self.breakpoints:
                                    if breakpoint.id == breakpoint_id:
                                        breakpoint.enabled = enabled
                                        break
                            </script>
                        </transition>
                    </state>
                </state>
            </parallel>
        </scxml>
    </class>
</diagram>