include "primitives.alh"
include "modelling.alh"
include "object_operations.alh"
include "library.alh"
include "conformance_scd.alh"
include "io.alh"
include "metamodels.alh"
include "compilation_manager.alh"

Element function retype_to_runtime(design_model : Element):
	Element runtime_model
	Element all_states
	Element all_links
	String mm_type_name
	String element_name
	String attr_name
	String attr_value
	String attribute
	String src
	String dst
	String time

	runtime_model = instantiate_model(import_node("models/FiniteStateAutomata_Runtime"))

	log("Retyping")
	all_states = allInstances(design_model, "State")
	while (list_len(all_states) > 0):
		element_name = set_pop(all_states)
		mm_type_name = reverseKeyLookup(design_model["metamodel"]["model"], dict_read_node(design_model["type_mapping"], design_model["model"][element_name]))
		element_name = instantiate_node(runtime_model, mm_type_name, element_name)
		instantiate_attribute(runtime_model, element_name, "name", read_attribute(design_model, element_name, "name"))

	// Don't merge this together with the block conversion, as the destination block might not exist yet!
	all_links = allInstances(design_model, "Transition")
	while (read_nr_out(all_links) > 0):
		element_name = set_pop(all_links)
		src = reverseKeyLookup(design_model["model"], read_edge_src(design_model["model"][element_name]))
		dst = reverseKeyLookup(design_model["model"], read_edge_dst(design_model["model"][element_name]))
		instantiate_link(runtime_model, "Transition", element_name, src, dst)
		instantiate_attribute(runtime_model, element_name, "event", read_attribute(design_model, element_name, "event"))

		if (element_neq(read_attribute(design_model, element_name, "raise"), read_root())):
			// There is a raise attribute
			instantiate_attribute(runtime_model, element_name, "raise", read_attribute(design_model, element_name, "raise"))

	log("DONE")
	return runtime_model!

Element function sanitize(new_runtime_model : Element, old_runtime_model : Element, auto : Boolean):
	String cstate
	String cstate_obj
	log("Start sanitize")

	cstate_obj = instantiate_node(new_runtime_model, "CurrentState", "")

	if (read_nr_out(allInstances(old_runtime_model, "CurrentStateLink")) > 0):
		cstate = readAssociationDestination(old_runtime_model, set_pop(allInstances(old_runtime_model, "CurrentStateLink")))
		if (bool_not(dict_in(new_runtime_model["model"], cstate))):
			// Current state removed, so fix
			log("Removed current state")
			if (auto):
				log("Auto-fixing the problem")
				cstate = set_pop(allInstances(new_runtime_model, "InitialState"))
			else:
				log("Please give new state")
				while (bool_not(dict_in(new_runtime_model["model"], cstate))):
					output("REQUEST_CURRENT_STATE")
					cstate = input()
	else:
		// Initialize for the first time
		cstate = set_pop(allInstances(new_runtime_model, "InitialState"))
	
	instantiate_link(new_runtime_model, "CurrentStateLink", "", cstate_obj, cstate)

	log("Sanitize OK")
	return new_runtime_model!

Void function do_transition(model : Element, start_time : Float, event : String):
	// Read out current state
	String cstate_link
	String cstate_obj
	String cstate
	String transition
	String new_state
	String raise

	log("Transition executing")
	cstate_link = set_pop(allInstances(model, "CurrentStateLink"))
	cstate = readAssociationDestination(model, cstate_link)
	cstate_obj = readAssociationSource(model, cstate_link)

	// Read out all outgoing transitions
	Element all_transitions
	all_transitions = allOutgoingAssociationInstances(model, cstate, "Transition")

	output("SIM_TIME " + cast_v2s(time() - start_time))
	output("SIM_EVENT " + cast_v2s(event))

	while (read_nr_out(all_transitions) > 0):
		transition = set_pop(all_transitions)
		if (value_eq(read_attribute(model, transition, "event"), event)):
			// Found a match
			new_state = readAssociationDestination(model, transition)
			log("Transition to " + new_state)

			// Delete current state
			model_delete_element(model, cstate_link)

			// Set destination of state
			instantiate_link(model, "CurrentStateLink", "", cstate_obj, new_state)

			log("Do state change")
			output("SIM_STATE " + cast_v2s(read_attribute(model, new_state, "name")))

			// Raise "raise" attribute of transition
			raise = read_attribute(model, transition, "raise")
			if (element_neq(raise, read_root())):
				// Raise the event
				output("SIM_RAISE " + cast_v2s(raise))

			return !

	return!

Void function execute_fsa(design_model : Element):
	String verify_result
	Element runtime_model
	Element old_runtime_model
	String cmd
	Boolean running
	String conforming
	Float simulation_time
	Float start_time
	Boolean automatic_sanitization

	automatic_sanitization = True
	start_time = time()
	simulation_time = 0.0
	old_runtime_model = instantiate_model(import_node("models/FiniteStateAutomata_Runtime"))
	runtime_model = retype_to_runtime(design_model)

	conforming = conformance_scd(design_model)
	log("Conformance: " + conforming)
	if (conforming == "OK"):
		output("CONFORMANCE_OK")
	else:
		output("CONFORMANCE_FAIL")

	while (True):
		cmd = input()
		log("Do: " + cmd)

		if (cmd == "pause"):
			// Pausing merely stops a running simulation
			if (running):
				simulation_time = time() - start_time
				running = False
				output("PAUSED")

		elif (cmd == "simulate"):
			// Continue simulation, so reset the value
			if (bool_not(running)):
				start_time = time() - simulation_time
				running = True
				output("CONTINUE")
			
		elif (cmd == "auto_sanitize"):
			automatic_sanitization = input()

		elif (cmd == "event"):
			log("Got event")
			do_transition(runtime_model, start_time, input())

		elif (cmd == "read_available_attributes"):
			// Returns a list of all available attributes
			Element attr_list
			Element attrs
			Element attr
			attr_list = getAttributeList(design_model, input())
			attrs = dict_keys(attr_list)
			while (0 < read_nr_out(attrs)):
				attr = set_pop(attrs)
				output("AVAILABLE_ATTR_VALUE " + cast_v2s(attr))
				output("AVAILABLE_ATTR_TYPE " + cast_v2s(dict_read(attr_list, attr)))
			output("AVAILABLE_ATTR_END")

		elif (cmd == "read_attribute"):
			// Returns the value of an attribute
			output("ATTR_VALUE " + cast_v2s(read_attribute(design_model, input(), input())))

		elif (bool_or(cmd == "switch_initial", bool_or(bool_or(cmd == "set_attribute", cmd == "instantiate_node"), bool_or(cmd == "delete_element", cmd == "instantiate_association")))):
			// Modify the structure
			if (cmd == "set_attribute"):
				// Setting an attribute
				String element_name
				String attribute_name
				element_name = input()
				attribute_name = input()

				// Delete it if it exists already
				if (bool_not(element_eq(read_attribute(design_model, element_name, attribute_name), read_root()))):
					unset_attribute(design_model, element_name, attribute_name)

				// And finally set it
				instantiate_attribute(design_model, element_name, attribute_name, input())

			elif (cmd == "instantiate_node"):
				// Instantiate a node
				instantiate_node(design_model, input(), input())

			elif (cmd == "instantiate_association"):
				// Instantiate an association
				instantiate_link(design_model, input(), input(), input(), input())

			elif (cmd == "delete_element"):
				// Delete the provided element
				model_delete_element(design_model, input())

			elif (cmd == "switch_initial"):
				// Switch the initial state
				if (read_nr_out(allInstances(design_model, "InitialState")) > 0):
					retype(design_model, set_pop(allInstances(design_model, "InitialState")), "State")

				retype(design_model, input(), "InitialState")

			// After changes, we check whether or not the design model conforms
			if (conforming == "OK"):
				// Was correct, so store just to make sure
				simulation_time = time() - start_time
			conforming = conformance_scd(design_model)
			if (conforming == "OK"):
				// Conforming, so do the retyping and sanitization step
				runtime_model = retype_to_runtime(design_model)
				runtime_model = sanitize(runtime_model, old_runtime_model, automatic_sanitization)
				old_runtime_model = runtime_model
				start_time = time() - simulation_time
				output("CONFORMANCE_OK")
				log("Conformance became OK")
			else:
				// Not conforming, so stop simulation and block for input (preferably a modify to make everything consistent again)
				output("CONFORMANCE_FAIL " + conforming)
		else:
			log("Did not understand command: " + cmd)

		if (bool_and(conforming == "OK", running)):
			output("SIM_TIME " + cast_v2s(time() - start_time))
			output("SIM_STATE " + cast_v2s(read_attribute(runtime_model, readAssociationDestination(runtime_model, set_pop(allInstances(runtime_model, "CurrentStateLink"))), "name")))

Void function main():
	Element model
	String verify_result

	while (True):
		execute_fsa(instantiate_model(import_node("models/FiniteStateAutomata_Design")))

	return!
