include "primitives.alh"
include "modelling.alh"
include "object_operations.alh"
include "utils.alh"
include "random.alh"
include "library.alh"
include "io.alh"

Boolean afap = False

Element function resolve_function(location : String, data : Element):
	if (bool_not(dict_in(data["cache_operations"], location))):
		dict_add(data["cache_operations"], location, get_func_AL_model(import_node(location)))
	return data["cache_operations"][location]!

Void function print_states(model : Element, data : Element):
	Element classes
	Element states
	Element class
	String state

	log("Current states:")
	classes = dict_keys(data["classes"])
	while (set_len(classes) > 0):
		class = set_pop(classes)
		class = data["classes"][class]
		log(string_join(string_join(string_join("  ", class["ID"]), " : "), read_attribute(model, class["type"], "name")))
		log("	Attributes: " + dict_to_string(class["attributes"]))

		states = set_copy(class["states"])
		log("	States:")
		while (set_len(states) > 0):
			state = set_pop(states)
			log(string_join("		", read_attribute(model, state, "name")))

	return!

Element function filter(model : Element, set : Element, attribute_name : String, attribute_value : Element):
	Element keys
	String key
	Element result

	result = set_create()
	while (set_len(set) > 0):
		key = set_pop(set)
		if (value_eq(read_attribute(model, key, attribute_name), attribute_value)):
			set_add(result, key)

	return result!

Element function filter_exists(model : Element, set : Element, attribute_name : String):
	Element keys
	String key
	Element result

	result = set_create()
	while (set_len(set) > 0):
		key = set_pop(set)
		if (element_neq(read_attribute(model, key, attribute_name), read_root())):
			set_add(result, key)

	return result!

Element function expand_current_state(model : Element, state : String, data : Element):
	// Find the hierarchy of all current states, and select those that contain the currently selected state
	Element result
	Element current_states
	result = set_create()
	current_states = set_copy(data["current_class_handle"]["states"])

	Element hierarchy
	String deep_state
	while (set_len(current_states) > 0):
		deep_state = set_pop(current_states)
		hierarchy = find_hierarchy(model, deep_state, data)
		// Got the hierarchy of one of the states

		if (list_in(hierarchy, state)):
			// This hierarchy contains the root state we are checking for, so add to set
			set_add(result, deep_state)

	return result!

Element function expand_initial_state(model : Element, state : String, data : Element):
	String t
	t = read_type(model, state)
	if (t == "SCCD/CompositeState"):
		// Recurse further in the composite
		return expand_composite_state(model, state, data)!
	elif (t == "SCCD/ParallelState"):
		// Split up all components
		return expand_parallel_state(model, state, data)!
	elif (t == "SCCD/HistoryState"):
		// Reset the history
		// This is not really an initial state, but it is called in exactly the same places
		return data["current_class_handle"]["history"][get_parent(model, state)]!
	else:
		// Probably just an atomic, so return this one only
		Element result
		result = set_create()
		set_add(result, state)
		return result!

Element function expand_composite_state(model : Element, composite_state : String, data : Element):
	// Resolve all initial states from a single composite state
	String initial

	// Fetch the initial state
	initial = set_pop(filter(model, allAssociationDestinations(model, composite_state, "SCCD/composite_children"), "isInitial", True))

	// Expand the initial state, depending on what it is
	return expand_initial_state(model, initial, data)!

Element function expand_parallel_state(model : Element, parallel_state : String, data : Element):
	// Resolve all initial states from a single parallel state
	Element children
	Element result
	Element expanded_children

	children = allAssociationDestinations(model, parallel_state, "SCCD/parallel_children")
	result = set_create()

	while (set_len(children) > 0):
		set_merge(result, expand_initial_state(model, set_pop(children), data))

	return result!

Void function delete_class(model : Element, data : Element, identifier : String):
	// Stop a specific class instance, with attached statechart, from executing
	dict_delete(data["classes"], identifier)
	return!

String function start_class(model : Element, data : Element, class : String, parameters : Element):
	// Start up the class and assign its initial state to it

	// First find an empty identifier
	String identifier
	identifier = cast_string(dict_len(data["classes"]))

	// Create the data structure for a running class
	Element class_handle
	class_handle = dict_create()
	dict_add(class_handle, "type", class)
	dict_add(class_handle, "ID", identifier)
	dict_add(class_handle, "events", set_create())
	dict_add(class_handle, "new_events", set_create())
	dict_add(class_handle, "timers", dict_create())
	dict_add(data["classes"], class_handle["ID"], class_handle)

	String prev_class
	prev_class = data["current_class"]
	dict_overwrite(data, "current_class", identifier)
	dict_overwrite(data, "current_class_handle", data["classes"][identifier])

	// Add the current state of the class
	String initial_state
	// Should only be one behaviour linked to it!
	initial_state = set_pop(allAssociationDestinations(model, class, "SCCD/behaviour"))
	dict_add(class_handle, "states", expand_initial_state(model, initial_state, class_handle))

	// Initialize history for all composite states
	Element history
	Element cstates
	String cstate

	history = dict_create()
	dict_add(class_handle, "history", history)
	cstates = allInstances(model, "SCCD/CompositeState")
	while (set_len(cstates) > 0):
		cstate = set_pop(cstates)
		dict_add(history, cstate, expand_initial_state(model, cstate, class_handle))

	// Add all attributes
	Element attributes
	attributes = dict_create()
	Element attrs
	attrs = allAssociationDestinations(model, class, "SCCD/class_attributes")
	while (set_len(attrs) > 0):
		dict_add(attributes, read_attribute(model, set_pop(attrs), "name"), read_root())
	dict_add(class_handle, "attributes", attributes)

	// Invoke constructor
	Element constructor
	constructor = read_attribute(model, class, "constructor_body")
	if (element_neq(constructor, read_root())):
		// Constructor, so execute
		constructor = resolve_function(constructor, data)
		constructor(attributes, parameters)

	// Execute all entry actions
	Element init
	init = set_create()
	set_add(init, "")
	// Initial state before initialization is the set with an empty hierarchy
	// Empty set would not find any difference between the source and target

	execute_actions(model, init, set_copy(class_handle["states"]), data, "")

	dict_overwrite(data, "current_class", prev_class)
	dict_overwrite(data, "current_class_handle", data["classes"][prev_class])

	return identifier!

Element function get_enabled_transitions(model : Element, state : String, data : Element):
	// Returns all enabled transitions
	Element result
	Element to_filter
	String attr
	String transition
	Element cond
	String evt_name
	Element evt
	Element events
	Element event_names
	Element event_parameters

	result = set_create()
	to_filter = allOutgoingAssociationInstances(model, state, "SCCD/transition")

	event_names = set_create()
	event_parameters = set_create()
	events = set_copy(data["current_class_handle"]["events"])
	while (set_len(events) > 0):
		evt = set_pop(events)
		evt_name = list_read(evt, 0)

		if (bool_not(set_in(event_names, evt_name))):
			// Not yet registered the event
			set_add(event_names, evt_name)
			dict_add(event_parameters, evt_name, set_create())
		// Add event parameters
		set_add_node(event_parameters[evt_name], list_read(evt, 1))

	while (set_len(to_filter) > 0):
		transition = set_pop(to_filter)

		// Check event
		attr = read_attribute(model, transition, "event")
		if (bool_not(bool_or(element_eq(attr, read_root()), set_in(event_names, attr)))):
			// At least one enabled event is found
			continue!

		// Check after
		// Only an after if there was no event!
		if (bool_and(element_eq(attr, read_root()), read_attribute(model, transition, "after"))):
			if (dict_in(data["current_class_handle"]["timers"], transition)):
				// Registered timer already, let's check if it has expired
				if (float_gt(data["current_class_handle"]["timers"][transition], data["time_sim"])):
					// Not enabled yet
					continue!
			else:
				// Not registered even, so not enabled either
				continue!

		// Check condition, but depends on whether there was an event or not
		cond = read_attribute(model, transition, "cond")
		if (element_neq(cond, read_root())):
			// Got a condition, so resolve
			cond = resolve_function(cond, data)

		if (element_neq(attr, read_root())):
			// We have an event to take into account!
			Element params
			Element param
			params = set_copy(event_parameters[attr])
			while (set_len(params) > 0):
				param = set_pop(params)
				if (element_neq(cond, read_root())):
					// Got a condition to check first
					if (bool_not(cond(data["current_class_handle"]["attributes"], param))):
						// Condition failed, so skip
						continue!
				// Fine to add this one with the specified parameters
				set_add_node(result, create_tuple(transition, param))
		else:
			// No event to think about, just add the transition
			if (element_neq(cond, read_root())):
				// Check the condition first
				if (bool_not(cond(data["current_class_handle"]["attributes"], read_root()))):
					// Condition false, so skip
					continue!
			// Fine to add this one without event parameters (no event)
			set_add_node(result, create_tuple(transition, read_root()))

	return result!

Void function process_raised_event(model : Element, event : Element, parameter_action : Element, data : Element):
	String scope
	scope = read_attribute(model, event, "scope")
	if (scope == "cd"):
		// Is an event for us internally, so don't append
		// Instead, we process it directly
		String operation
		operation = read_attribute(model, event, "event")
		if (operation == "create_instance"):
			// Start up a new class of the desired type

			// Parameters of this call:
			// class -- type of the class to instantiate
			// identifier -- name of this instance, for future reference
			// parameters -- parameters for constructor
			String class
			String identifier
			Element parameters
			class = set_pop(filter(model, allInstances(model, "SCCD/Class"), "name", list_read(parameter_action, 1)))
			parameters = list_read(parameter_action, 2)
			identifier = start_class(model, data, class, parameters)

			// Notify the creator of the ID of the created instance
			Element lst
			lst = list_create()
			list_append(lst, identifier)
			set_add_node(data["current_class_handle"]["new_events"], create_tuple("instance_created", lst))

		elif (operation == "delete_instance"):
			// Delete the requested class
			String identifier
			identifier = list_read(parameter_action, 0)
			delete_class(model, data, identifier)
			set_add_node(data["current_class_handle"]["new_events"], create_tuple("instance_deleted", parameter_action))

	elif (scope == "broad"):
		// Send to all classes
		Element classes
		classes = dict_keys(data["classes"])
		while(set_len(classes) > 0):
			set_add_node(data["classes"][set_pop(classes)]["new_events"], create_tuple(read_attribute(model, event, "event"), parameter_action))

	elif (scope == "narrow"):
		// Send to the specified class only
		Element func
		func = resolve_function(read_attribute(model, event, "target"), data)
		String dest
		dest = func(data["current_class_handle"]["attributes"])
		set_add_node(data["classes"][dest]["new_events"], create_tuple(read_attribute(model, event, "event"), parameter_action))

	elif (scope == "output"):
		// Store it in the output event model
		String evt

		if (bool_not(afap)):
			Element val
			val = dict_create()
			dict_add(val, "timestamp", data["time_sim"])
			dict_add(val, "name", read_attribute(model, event, "event"))
			dict_add(val, "parameter", parameter_action)

			output("EVENT: " + dict_to_string(val))

		evt = instantiate_node(model, "trace/Event", "")
		instantiate_attribute(model, evt, "timestamp", data["time_sim"])
		instantiate_attribute(model, evt, "name", read_attribute(model, event, "event"))
		instantiate_attribute(model, evt, "parameter", parameter_action)

	else:
		// Same as local
		set_add_node(data["current_class_handle"]["new_events"], create_tuple(read_attribute(model, event, "event"), parameter_action))

	return !

Element function execute_transition(model : Element, data : Element, transition_tuple : Element):
	// Execute the script (if any)
	Element script
	String transition
	Element event_parameter
	transition = list_read(transition_tuple, 0)
	event_parameter = list_read(transition_tuple, 1)

	script = read_attribute(model, transition, "script")
	if (element_neq(script, read_root())):
		script = resolve_function(script, data)
		script(data["current_class_handle"]["attributes"], event_parameter)

	// Raise events (if any)
	Element events
	String event
	events = allAssociationDestinations(model, transition, "SCCD/transition_raises")
	while (set_len(events) > 0):
		event = set_pop(events)

		Element parameter_action
		parameter_action = read_attribute(model, event, "parameter")
		if (element_neq(parameter_action, read_root())):
			// Got a parameter to evaluate
			parameter_action = resolve_function(parameter_action, data)
			parameter_action = parameter_action(data["current_class_handle"]["attributes"], event_parameter)

		process_raised_event(model, event, parameter_action, data)

	// Find new set of states
	Element target_states
	Element source_states
	source_states = expand_current_state(model, readAssociationSource(model, transition), data)
	target_states = expand_initial_state(model, readAssociationDestination(model, transition), data)

	execute_actions(model, source_states, target_states, data, readAssociationSource(model, transition))

	return target_states!

Boolean function step_class(model : Element, data : Element, class : String):
	// Find enabled transitions in a class and execute it, updating the state
	// Iterate over all current states, searching for enabled transitions
	// Search for enabled transitions in higher levels as well!
	Element states
	Element new_states
	String state
	Element transitions
	String transition
	Boolean transitioned
	Element hierarchy
	String current_state
	Boolean found

	if (bool_not(dict_in(data["classes"], class))):
		// Seems like this class was removed, so stop execution
		return False!

	// Notify everyone of the current class
	dict_overwrite(data, "current_class", class)
	dict_overwrite(data, "current_class_handle", data["classes"][class])

	states = set_copy(data["current_class_handle"]["states"])
	new_states = set_create()
	transitioned = False

	while (set_len(states) > 0):
		state = set_pop(states)
		found = False

		// Loop over the hierarchy of this state and try to apply transitions
		hierarchy = find_hierarchy(model, state, data)
		while (list_len(hierarchy) > 0):
			current_state = list_pop(hierarchy, 0)
			transitions = get_enabled_transitions(model, current_state, data)

			if (set_len(transitions) > 0):
				// Found an enabled transition, so store that one
				transition = random_choice(set_to_list(transitions))
				
				// Execute transition
				set_merge(new_states, execute_transition(model, data, transition))

				// When leaving an orthogonal component, we must also pop all related states that might be processed in the future!
				Element leaving
				leaving = expand_current_state(model, current_state, data)
				set_subtract(states, leaving)

				transitioned = True
				found = True
				break!

		if (bool_not(found)):
			// Nothing found, so stay in the current state
			set_add(new_states, state)
			
	// Update states
	dict_overwrite(data["current_class_handle"], "states", new_states)

	return transitioned!

String function get_parent(model : Element, state : String):
	Element tmp_set
	tmp_set = allAssociationOrigins(model, state, "SCCD/composite_children")
	set_merge(tmp_set, allAssociationOrigins(model, state, "SCCD/parallel_children"))
	if (set_len(tmp_set) > 0):
		return set_pop(tmp_set)!
	else:
		return ""!

Element function find_hierarchy(model : Element, state : String, data : Element):
	// Try to cache as much as possible!
	if (bool_not(dict_in(data["cache_hierarchy"], state))):
		Element result
		if (state == ""):
			result = list_create()
		else:
			String parent
			parent = get_parent(model, state)
			// We have a parent, so take the parent list first
			result = find_hierarchy(model, parent, data)
			list_append(result, state)
		dict_add(data["cache_hierarchy"], state, result)
	return dict_copy(data["cache_hierarchy"][state])!

Void function execute_actions(model : Element, source_states : Element, target_states : Element, data : Element, transition_source : String):
	Element exit
	Element entry
	exit = list_create()
	entry = list_create()

	source_states = set_copy(source_states)
	target_states = set_copy(target_states)

	// Add all exit and entry actions to the list of actions to execute
	// Do this by finding the common parent, and then doing all exit actions up to that node, and all entry actions up to the target_state

	// First, find the hierarchy!
	Element hierarchy_sources
	Element hierarchy_targets
	Element all_hierarchies

	hierarchy_sources = set_create()
	while (set_len(source_states) > 0):
		set_add_node(hierarchy_sources, find_hierarchy(model, set_pop(source_states), data))

	hierarchy_targets = set_create()
	while (set_len(target_states) > 0):
		set_add_node(hierarchy_targets, find_hierarchy(model, set_pop(target_states), data))

	all_hierarchies = set_copy(hierarchy_sources)
	set_merge(all_hierarchies, hierarchy_targets)

	// Difference these all lists, finding the first common entry
	Element iter_hierarchies
	Integer i
	String current
	Element hierarchy
	Boolean finished
	Integer transition_depth
	Integer lca_depth
	lca_depth = 0
	finished = False

	if (transition_source != ""):
		// Find out the level of the transition_source by fetching its hierarchy
		transition_depth = list_len(find_hierarchy(model, transition_source, data)) - 1

		// Now check for the least common ancestor
		while (bool_not(finished)):
			// Check the i-th element in both and see if they are equal
			current = ""
			iter_hierarchies = set_copy(all_hierarchies)
			while (set_len(iter_hierarchies) > 0):
				hierarchy = set_pop(iter_hierarchies)

				// Exhausted one of the lists
				if (lca_depth >= list_len(hierarchy)):
					finished = True
					break!

				// Reached the same level as transition depth already, so no need to increase
				if (lca_depth == transition_depth):
					finished = True
					break!

				// First entry, so read out value as reference
				if (current == ""):
					current = list_read(hierarchy, lca_depth)

				// Check with reference element
				if (bool_not(value_eq(list_read(hierarchy, lca_depth), current))):
					finished = True
					break!

			// i-th element equal for all hierarchies, so go to next element
			if (bool_not(finished)):
				lca_depth = lca_depth + 1

		if (lca_depth < transition_depth):
			i = lca_depth
		else:
			i = transition_depth

	else:
		// Initial, so just set i to zero
		i = 0

	// Found the first differing element at position i

	// All elements remaining in hierarchy_source are to be traversed in REVERSE order for the exit actions
	// All elements remaining in hierarchy_target are to be traversed in NORMAL order for the entry actions
	// This is not that simple either, as we need to consider that some actions might already have been added to the list...

	// Add hierarchy_sources actions
	String state
	Element visited
	Element action
	Element spliced_hierarchy
	Element hierarchy_source
	Element hierarchy_target
	visited = set_create()
	while (set_len(hierarchy_sources) > 0):
		// Get one of these hierarchies
		hierarchy_source = set_pop(hierarchy_sources)
		spliced_hierarchy = list_splice(hierarchy_source, i, list_len(hierarchy_source))
		while (list_len(spliced_hierarchy) > 0):
			state = list_pop(spliced_hierarchy, list_len(spliced_hierarchy) - 1)
			if (set_in(visited, state)):
				// Already added this state, so don't bother
				continue!
			else:
				// New state, so prepend it to the list
				// Prepend, instead of append, as we want to do these operations in reverse order!
				list_insert(exit, state, 0)

				// Add this state as visited
				set_add(visited, state)

	// Add hierarchy_targets actions
	// Clear visited, just to be safe, though it should not matter
	visited = set_create()
	while (set_len(hierarchy_targets) > 0):
		// Get one of these hierarchies
		hierarchy_target = set_pop(hierarchy_targets)
		spliced_hierarchy = list_splice(hierarchy_target, i, list_len(hierarchy_target))
		while (list_len(spliced_hierarchy) > 0):
			state = list_pop(spliced_hierarchy, list_len(spliced_hierarchy) - 1)
			if (set_in(visited, state)):
				// Already added this state, so don't bother
				continue!
			else:
				// New state, so append it to the list
				// Append, instead of prepend, as we want to do these operations in normal order!
				list_append(entry, state)

				// Add this state as visited, even though there might not have been an associated action
				set_add(visited, state)

	// Now we have a list of traversed states!
	// Start executing all their operations in order

	Element events
	String event
	// First do exit actions
	while (list_len(exit) > 0):
		state = list_pop(exit, 0)

		// Set history when leaving
		if (read_type(model, state) == "SCCD/CompositeState"):
			dict_overwrite(data["current_class_handle"]["history"], state, expand_current_state(model, state, data))

		// Do exit actions
		action = read_attribute(model, state, "onExitScript")
		if (element_neq(action, read_root())):
			// Got a script, so execute!
			action = resolve_function(action, data)
			action(data["current_class_handle"]["attributes"])

		// Raise events
		events = allAssociationDestinations(model, state, "SCCD/onExitRaise")
		while (set_len(events) > 0):
			event = set_pop(events)

			Element parameter_action
			parameter_action = read_attribute(model, event, "parameter")
			if (element_neq(parameter_action, read_root())):
				// Got a parameter to evaluate
				parameter_action = resolve_function(parameter_action, data)
				parameter_action = parameter_action(data["current_class_handle"]["attributes"])

			process_raised_event(model, event, parameter_action, data)

		// Unschedule after events
		Element timed_transitions
		timed_transitions = filter_exists(model, allOutgoingAssociationInstances(model, state, "SCCD/transition"), "after")
		while (set_len(timed_transitions) > 0):
			dict_delete(data["current_class_handle"]["timers"], set_pop(timed_transitions))

	// Then do entry actions
	while (list_len(entry) > 0):
		state = list_pop(entry, 0)

		// Do entry actions
		action = read_attribute(model, state, "onEntryScript")
		if (element_neq(action, read_root())):
			// Got a script, so execute!
			action = resolve_function(action, data)
			action(data["current_class_handle"]["attributes"])

		// Raise events
		events = allAssociationDestinations(model, state, "SCCD/onEntryRaise")
		while (set_len(events) > 0):
			event = set_pop(events)

			Element parameter_action
			parameter_action = read_attribute(model, event, "parameter")
			if (element_neq(parameter_action, read_root())):
				// Got a parameter to evaluate
				parameter_action = resolve_function(parameter_action, data)
				parameter_action = parameter_action(data["current_class_handle"]["attributes"])

			process_raised_event(model, event, parameter_action, data)

		// Schedule after events
		Element timed_transitions
		String transition
		Element after
		timed_transitions = filter_exists(model, allOutgoingAssociationInstances(model, state, "SCCD/transition"), "after")
		while (set_len(timed_transitions) > 0):
			transition = set_pop(timed_transitions)
			after = resolve_function(read_attribute(model, transition, "after"), data)
			dict_add(data["current_class_handle"]["timers"], transition, float_addition(data["time_sim"], after(data["current_class_handle"]["attributes"])))

	return !

Float function step(model : Element, data : Element):
	// Step through all classes
	Element classes
	Element class
	Float t_min
	Float t_current
	Boolean transitioned
	Element keys
	String key

	t_min = 999999.0
	classes = dict_keys(data["classes"])

	transitioned = False
	while (set_len(classes) > 0):
		class = set_pop(classes)
		if (step_class(model, data, class)):
			transitioned = True

		if (bool_not(transitioned)):
			// Find minimum timer for this class, and store that
			keys = dict_keys(data["classes"][class]["timers"])
			while (set_len(keys) > 0):
				key = set_pop(keys)
				t_current = data["classes"][class]["timers"][key]
				if (t_current < t_min):
					t_min = t_current

	if (transitioned):
		// Do another step, as we can transition
		return data["time_sim"]!
	else:
		return t_min!

Boolean function main(model : Element):
	// Executes the provided SCCD model
	Element data
	data = dict_create()
	dict_add(data, "classes", dict_create())
	dict_add(data, "cache_operations", dict_create())
	dict_add(data, "cache_hierarchy", dict_create())
	dict_add(data, "current_class", "")
	
	Float time_0
	Float time_sim
	Float time_wallclock
	time_0 = time()
	time_sim = 0.0
	dict_add(data, "time_sim", 0.0)

	// Prepare for input
	output("Ready for input!")

	// Find initial
	String default_class
	default_class = set_pop(filter(model, allInstances(model, "SCCD/Class"), "default", True))

	// Start up the default class
	String identifier
	identifier = start_class(model, data, default_class, read_root())

	// Notify this class itself of its ID
	Element lst
	lst = list_create()
	list_append(lst, identifier)
	set_add_node(data["current_class_handle"]["new_events"], create_tuple("instance_created", lst))

	Float timeout
	Element interrupt
	timeout = 0.0
	while (True):
		if (afap):
			timeout = 0.0
		interrupt = input_timeout(timeout)

		if (value_eq(interrupt, "#EXIT#")):
			// Stop execution
			return True!

		if (element_neq(interrupt, read_root())):
			// Send out, as otherwise the client doesn't get a dialog
			output("Processed event, ready for more!")

			// Update the simulated time to the time of interrupt
			time_sim = time() - time_0

		Element classes
		classes = dict_keys(data["classes"])
		while(set_len(classes) > 0):
			String class
			class = set_pop(classes)
			dict_overwrite(data["classes"][class], "events", data["classes"][class]["new_events"])
			dict_overwrite(data["classes"][class], "new_events", set_create())

			if (element_neq(interrupt, read_root())):
				// Got interrupt, so append it already
				set_add_node(data["classes"][class]["events"], create_tuple(interrupt, read_root()))

		// Else we timeout, and thus keep the time_sim
		dict_overwrite(data, "time_sim", time_sim)

		time_sim = step(model, data)

		if (float_gt(time_sim, data["time_sim"])):
			print_states(model, data)

		if (dict_len(data["classes"]) == 0):
			// No more active classes left: terminate!
			log("Finished SCCD execution")
			break!

		time_wallclock = time() - time_0
		timeout = time_sim - time_wallclock

	// We have finished without error
	return True!
