include "primitives.alh"
include "constructors.alh"
include "object_operations.alh"
include "library.alh"
include "conformance_scd.alh"
include "io.alh"
include "metamodels.alh"
include "modelling.alh"
include "typing.alh"
include "compiler.alh"
include "utils.alh"

Boolean verbose = True

String function cmd_help_m(write : Boolean):
	String result
	result = ""
	result = result + "Allowed operations:\n"
	if (write):
		result = result + " == READ/WRITE ==\n"
		result = result + "  instantiate_node -- Create a new model element (node)\n"
		result = result + "  instantiate_edge -- Create a new model element (edge)\n"
		result = result + "  delete           -- Delete an existing element\n"
		result = result + "  attr_add         -- Add an attribute to an element\n"
		result = result + "  attr_add_code    -- Add a coded attribute to an element\n"
		result = result + "  attr_del         -- Delete an attribute of an element\n"
		result = result + "  attr_modify      -- Modify an attribute of an element\n"
		result = result + "  upload           -- Upload a completely new model\n"
	else:
		result = result + " == READ-ONLY ==\n"
	result = result + "  read_outgoing    -- Prints the list of outgoing links of an element\n"
	result = result + "  read_incoming    -- Prints the list of incoming links to an element\n"
	result = result + "  list             -- Prints the list of elements in the model\n"
	result = result + "  list_full        -- Prints the list of all elements in the model\n"
	result = result + "  types            -- Prints the list of elements that can be instantiated\n"
	result = result + "  read             -- Prints the current state of a model element\n"
	result = result + "  verify           -- Check whether the model conforms to the metamodel\n"
	result = result + "  exit             -- Leave the modification interface\n"

	return result!

String function cmd_instantiate_node(write : Boolean, model : Element, mm_type_name : String, element_name : String):
	if (write):
		if (dict_in(model["metamodel"]["model"], mm_type_name)):
			if (dict_in(model["model"], element_name)):
				return "Element exists: " + element_name!
			else:
				if (is_edge(model["metamodel"]["model"][mm_type_name])):
					return "Incorrect format: " + mm_type_name + " is an edge instead of a node"!

				element_name = instantiate_node(model, mm_type_name, element_name)
				return "Success: " + element_name!
		else:
			return "Element not found: " + mm_type_name!
	else:
		return "Write permission denied"!

String function cmd_instantiate_edge(write : Boolean, model : Element, mm_type_name : String, element_name : String, source_name : String, target_name : String):
	if (write):
		if (dict_in(model["metamodel"]["model"], mm_type_name)):
			if (dict_in(model["model"], element_name)):
				return "Element exists: " + element_name!
			else:
				if (is_edge(model["metamodel"]["model"][mm_type_name])):
					if (dict_in(model["model"], source_name)):
						if (dict_in(model["model"], target_name)):
							element_name = instantiate_link(model, mm_type_name, element_name, source_name, target_name)
							return "Success: " + element_name!
						else:
							return "Element not found: " + target_name!
					else:
						return "Element not found: " + source_name!
				else:
					return "Incorrect format: " + mm_type_name + " is a node instead of an edge"!
		else:
			return "Element not found: " + mm_type_name!
	else:
		return "Write permission denied"!

String function cmd_define_attribute(write : Boolean, model : Element, element_name : String, attr_name : String, type : String):
	if (write):
		if (dict_in(model["model"], element_name)):
			if (dict_in(model["model"], type)):
				//TODO automatically find name AttributeLink, as this might change with merging...
				if (dict_in(model["metamodel"]["model"], "AttributeLink")):
					Element attrs
					attrs = getInstantiatableAttributes(model, element_name, "AttributeLink")
					if (bool_not(set_in(dict_keys(attrs), attr_name))):
						model_define_attribute(model, element_name, attr_name, False, type)
						return "Success"!
					else:
						return "Attribute exists: " + attr_name!
				else:
					return "Not a SimpleClassDiagrams model!"!
			else:
				return "Element not found: " + type!
		else:
			return "Element not found: " + element_name!
	else:
		return "Write permission denied"!

String function cmd_attr_add(write : Boolean, model : Element, element_name : String, attr_name : String, value : Element):
	if (write):
		if (dict_in(model["model"], element_name)):
			Element attrs
			attrs = getAttributeList(model, element_name)
			if (set_in(dict_keys(attrs), attr_name)):
				instantiate_attribute(model, element_name, attr_name, value)
				return "Success"!
			else:
				return "Attribute not found: " + attr_name!
		else:
			return "Element not found: " + element_name!
	else:
		return "Write permission denied"!

String function cmd_attr_add_code(write : Boolean, model : Element, element_name : String, attr_name : String):
	if (write):
		if (dict_in(model["model"], element_name)):
			Element attrs
			attrs = getAttributeList(model, element_name)
			if (set_in(dict_keys(attrs), attr_name)):
				output("Waiting for code constructors...")
				Element compiled
				compiled = compile_code(input())
				if (is_physical_string(compiled)):
					return "Compilation error: " + cast_string(compiled)!

				instantiate_attribute_code(model, element_name, attr_name, compiled)
				return "Success"!
			else:
				return "Attribute not found: " + attr_name!
		else:
			return "Element not found: " + element_name!
	else:
		return "Write permission denied"!

String function cmd_attr_del(write : Boolean, model : Element, element_name : String, attr_name : String):
	if (write):
		if (dict_in(model["model"], element_name)):
			Element attrs
			attrs = getAttributeList(model, element_name)
			if (set_in(dict_keys(attrs), attr_name)):
				unset_attribute(model, element_name, attr_name)
				return "Success"!
			else:
				return "Attribute not found: " + attr_name!
		else:
			return "Element not found: " + element_name!
	else:
		return "Write permission denied"!

String function cmd_attr_name(write : Boolean, model : Element, element_name : String, attr_name : String, new_attr_name : String):
	if (write):
		if (dict_in(model["model"], element_name)):
			Element attrs
			// TODO automatically find AttributeLink name
			if (dict_in(model["metamodel"]["model"], "AttributeLink")):
				attrs = getInstantiatableAttributes(model, element_name, "AttributeLink")
				if (set_in(dict_keys(attrs), attr_name)):
					if (set_in(dict_keys(attrs), attr_name)):
						if (bool_not(set_in(dict_keys(attrs), new_attr_name))):
							if (dict_in(model["model"][element_name], attr_name)):
								Boolean optional
								String attr_edge
								attr_edge = reverseKeyLookup(model["model"], dict_read_edge(model["model"][element_name], attr_name))
								optional = read_attribute(model, attr_edge, "optional")
								model_undefine_attribute(model, element_name, attr_name)
								model_define_attribute_ID(model, element_name, new_attr_name, optional, attrs[attr_name], attr_edge)
								return "Success"!
							else:
								return "Attribute not defined here: " + new_attr_name!
						else:
							return "Attribute exists: " + new_attr_name!
					else:
						return "Attribute not found: " + attr_name!
				else:
					return "Attribute not found: " + attr_name!
			else:
				return "Not a SimpleClassDiagrams model!"!
		else:
			return "Element not found: " + element_name!
	else:
		return "Write permission denied"!

String function cmd_attr_type(write : Boolean, model : Element, element_name : String, attr_name : String, new_attr_type : String):
	if (write):
		if (dict_in(model["model"], element_name)):
			if (dict_in(model["model"], new_attr_type)):
				Element attrs
				// TODO automatically find AttributeLink name
				if (dict_in(model["metamodel"]["model"], "AttributeLink")):
					attrs = getInstantiatableAttributes(model, element_name, "AttributeLink")
					if (set_in(dict_keys(attrs), attr_name)):
						if (dict_in(model["model"][element_name], attr_name)):
							Boolean optional
							String attr_edge
							attr_edge = reverseKeyLookup(model["model"], dict_read_edge(model["model"][element_name], attr_name))
							optional = read_attribute(model, attr_edge, "optional")
							model_undefine_attribute(model, element_name, attr_name)
							model_define_attribute_ID(model, element_name, attr_name, optional, new_attr_type, attr_edge)
							return "Success"!
						else:
							return "Attribute not defined here: " + attr_name!
					else:
						return "Attribute not found: " + attr_name!
				else:
					return "Not a SimpleClassDiagrams model!"!
			else:
				return "Element not found: " + new_attr_type!
		else:
			return "Element not found: " + element_name!
	else:
		return "Write permission denied"!

String function cmd_attr_optional(write : Boolean, model : Element, element_name : String, attr_name : String, optional : Boolean):
	if (write):
		if (dict_in(model["model"], element_name)):
			Element attrs
			// TODO automatically find AttributeLink name
			if (dict_in(model["metamodel"]["model"], "AttributeLink")):
				attrs = getInstantiatableAttributes(model, element_name, "AttributeLink")
				if (set_in(dict_keys(attrs), attr_name)):
					if (set_in(dict_keys(attrs), attr_name)):
						if (dict_in(model["model"][element_name], attr_name)):
							String attr_edge
							attr_edge = reverseKeyLookup(model["model"], dict_read_edge(model["model"][element_name], attr_name))
							model_undefine_attribute(model, element_name, attr_name)
							model_define_attribute_ID(model, element_name, attr_name, optional, attrs[attr_name], attr_edge)
							return "Success"!
						else:
							return "Attribute not defined here: " + attr_name!
					else:
						return "Attribute not defined: " + attr_name!
				else:
					return "Attribute not found: " + attr_name!
			else:
				return "Not a SimpleClassDiagrams model!"!
		else:
			return "Element not found: " + element_name!
	else:
		return "Write permission denied"!

String function cmd_undefine_attribute(write : Boolean, model : Element, element_name : String, attr_name : String):
	if (write):
		if (dict_in(model["model"], element_name)):
			//TODO automatically find name AttributeLink, as this might change with merging...
			if (dict_in(model["metamodel"]["model"], "AttributeLink")):
				Element attrs
				attrs = getInstantiatableAttributes(model, element_name, "AttributeLink")
				if (set_in(dict_keys(attrs), attr_name)):
					if (dict_in(model["model"][element_name], attr_name)):
						model_undefine_attribute(model, element_name, attr_name)
						return "Success"!
					else:
						return "Attribute not defined here: " + attr_name!
				else:
					return "Attribute not found: " + attr_name!
			else:
				return "Not a SimpleClassDiagrams model!"!
		else:
			return "Element not found: " + element_name!
	else:
		return "Write permission denied"!

String function cmd_delete(write : Boolean, model : Element, element_name : String):
	if (write):
		if (dict_in(model["model"], element_name)):
			model_delete_element(model, element_name)
			return "Success"!
		else:
			return "Element not found: " + element_name!
	else:
		return "Write permission denied"!

String function cmd_list_full(model : Element):
	Element keys_m
	String v_m
	String result
	String typename

	result = "Success: "
	keys_m = dict_keys(model["model"])
	while (set_len(keys_m) > 0):
		v_m = set_pop(keys_m)
		// Filter out anonymous objects
		typename = read_type(model, v_m)
		result = result + "  " + v_m + " : " + typename + "\n"
	
	return result!

String function cmd_read_outgoing(model : Element, element_name : String, type : String):
	String result
	Element elems

	if (dict_in(model["model"], element_name)):
		if (bool_or(dict_in(model["metamodel"]["model"], type), type == "")):
			result = "Success: "
			elems = allOutgoingAssociationInstances(model, element_name, type)
			while (set_len(elems) > 0):
				result = string_join(result, set_pop(elems)) + "\n"
			return result!
		else:
			return "Element not found: " + type!
	else:
		return "Element not found: " + element_name!

String function cmd_read_incoming(model : Element, element_name : String, type : String):
	String result
	Element elems

	if (dict_in(model["model"], element_name)):
		if (bool_or(dict_in(model["metamodel"]["model"], type), type == "")):
			result = "Success: "
			elems = allIncomingAssociationInstances(model, element_name, type)
			while (set_len(elems) > 0):
				result = string_join(result, set_pop(elems)) + "\n"
			return result!
		else:
			return "Element not found: " + type!
	else:
		return "Element not found: " + element_name!

String function cmd_connections_between(model : Element, source_name : String, target_name : String):
	String result
	Element options

	if (dict_in(model["model"], source_name)):
		if (dict_in(model["model"], target_name)):
			result = "Success: "
			options = allowedAssociationsBetween(model, source_name, target_name)
			while (set_len(options) > 0):
				result = string_join(result, set_pop(options)) + "\n"
			return result!
		else:
			return ("Element not found: " + target_name)!
	else:
		return ("Element not found: " + source_name)!

String function cmd_read(model : Element, element_name : String):
	String result
	Element attr_list
	Element attr_keys
	String attr_key

	result = "Success: "
	if (dict_in(model["model"], element_name)):
		result = result + "Type: " + read_type(model, element_name) + "\n"
		if (is_edge(model["model"][element_name])):
			result = result + "Source: " + reverseKeyLookup(model["model"], read_edge_src(model["model"][element_name])) + "\n"
			result = result + "Destination: " + reverseKeyLookup(model["model"], read_edge_dst(model["model"][element_name])) + "\n"
		if (has_value(model["model"][element_name])):
			result = result + "Value: " + cast_value(model["model"][element_name]) + "\n"
		return result!
	else:
		return "Element not found: " + element_name!

String function cmd_read_attrs(model : Element, element_name : String):
	String result
	Element attr_list
	Element attr_keys
	String attr_key
	String attr

	result = "Success: "
	if (dict_in(model["model"], element_name)):
		attr_list = getAttributeList(model, element_name)
		attr_keys = dict_keys(attr_list)
		while (0 < set_len(attr_keys)):
			attr_key = set_pop(attr_keys)
			attr = read_attribute(model, element_name, attr_key)
			if (read_type(model["metamodel"], attr_list[attr_key]) == "ActionLanguage"):
				if (element_eq(attr, read_root())):
					result = result + attr_key + " : " + cast_value(attr_list[attr_key]) + " = {\"AL\": \"\"}\n"
				else:
					result = result + attr_key + " : " + cast_value(attr_list[attr_key]) + " = {\"AL\": " + cast_value(attr) + "}\n"
			else:
				result = result + attr_key + " : " + cast_value(attr_list[attr_key]) + " = " + cast_value(attr) + "\n"
		return result!
	else:
		return "Element not found: " + element_name!

String function cmd_read_defined_attrs(model : Element, element_name : String):
	String result
	Element attr_list
	Element attr_keys
	String attr_key
	Element optionality

	result = "Success: "
	if (dict_in(model["model"], element_name)):
		//TODO automatically find name AttributeLink, as this might change with merging...
		if (dict_in(model["metamodel"]["model"], "AttributeLink")):
			attr_list = getInstantiatableAttributes(model, element_name, "AttributeLink")
			optionality = getAttributeOptionality(model, element_name, "AttributeLink")
			attr_keys = dict_keys(attr_list)
			while (0 < set_len(attr_keys)):
				attr_key = set_pop(attr_keys)
				if (optionality[attr_key]):
					result = string_join(result, attr_key) + " ?: " + cast_string(attr_list[attr_key]) + "\n"
				else:
					result = string_join(result, attr_key) + " : " + cast_string(attr_list[attr_key]) + "\n"
			return result!
		else:
			return "Not a SimpleClassDiagrams model!"!
	else:
		return "Element not found: " + element_name!

String function cmd_types(model : Element):
	Element keys_t
	String v_t
	String result

	keys_t = dict_keys(model["metamodel"]["model"])
	result = "Success: "
	while (set_len(keys_t) > 0):
		v_t = set_pop(keys_t)
		result = result + string_join("  " + v_t + " : ", read_type(model["metamodel"], v_t)) + "\n"

	return result!

String function cmd_read_association_source(write : Boolean, model : Element, element_name : String):
	if (dict_in(model["model"], element_name)):
		if (is_edge(model["model"][element_name])):
			return "Success: " + readAssociationSource(model, element_name)!
		else:
			return "Not an association: " + element_name!
	else:
		return "Element not found: " + element_name!

String function cmd_read_association_destination(write : Boolean, model : Element, element_name : String):
	if (dict_in(model["model"], element_name)):
		if (is_edge(model["model"][element_name])):
			return "Success: " + readAssociationDestination(model, element_name)!
		else:
			return "Not an association: " + element_name!
	else:
		return "Element not found: " + element_name!

String function cmd_all_instances(model : Element, type : String):
	String result
	Element elems

	if (dict_in(model["metamodel"]["model"], type)):
		result = "Success: "
		elems = allInstances(model, type)
		while (set_len(elems) > 0):
			result = string_join(result, set_pop(elems)) + "\n"
		return result!
	else:
		return "Element not found: " + type!

Boolean function modify(model : Element, write : Boolean):
	String cmd

	output("Model loaded, ready for commands!")

	while (True):
		cmd = input()
		if (cmd == "help"):
			output(cmd_help_m(write))
		elif (cmd == "exit"):
			return True!
		elif (cmd == "instantiate_node"):
			output(cmd_instantiate_node(write, model, single_input("Type?"), single_input("Name?")))
		elif (cmd == "instantiate_edge"):
			output(cmd_instantiate_edge(write, model, single_input("Type?"), single_input("Name?"), single_input("Source?"), single_input("Target?")))
		elif (cmd == "attr_add"):
			output(cmd_attr_add(write, model, single_input("Name?"), single_input("Attribute name?"), single_input("Value?")))
		elif (cmd == "attr_add_code"):
			output(cmd_attr_add_code(write, model, single_input("Name?"), single_input("Attribute name?")))
		elif (cmd == "attr_delete"):
			output(cmd_attr_del(write, model, single_input("Name?"), single_input("Attribute name?")))
		elif (cmd == "attr_name"):
			output(cmd_attr_name(write, model, single_input("Name?"), single_input("Attribute name?"), single_input("New name?")))
		elif (cmd == "attr_type"):
			output(cmd_attr_type(write, model, single_input("Name?"), single_input("Attribute name?"), single_input("Type name?")))
		elif (cmd == "attr_optional"):
			output(cmd_attr_optional(write, model, single_input("Name?"), single_input("Attribute name?"), input()))
		elif (cmd == "delete"):
			output(cmd_delete(write, model, single_input("Name?")))
		elif (cmd == "list_full"):
			output(cmd_list_full(model))
		elif (cmd == "JSON"):
			output("Success: " + JSON_print(model))
		elif (cmd == "read_outgoing"):
			output(cmd_read_outgoing(model, single_input("Name?"), single_input("Type?")))
		elif (cmd == "read_incoming"):
			output(cmd_read_incoming(model, single_input("Name?"), single_input("Type?")))
		elif (cmd == "read"):
			output(cmd_read(model, single_input("Name?")))
		elif (cmd == "read_attrs"):
			output(cmd_read_attrs(model, single_input("Name?")))
		elif (cmd == "read_defined_attrs"):
			output(cmd_read_defined_attrs(model, single_input("Name?")))
		elif (cmd == "types"):
			output(cmd_types(model))
		elif (cmd == "read_association_source"):
			output(cmd_read_association_source(write, model, single_input("Name?")))
		elif (cmd == "read_association_destination"):
			output(cmd_read_association_destination(write, model, single_input("Name?")))
		elif (cmd == "connections_between"):
			output(cmd_connections_between(model, single_input("Source element?"), single_input("Target element?")))
		elif (cmd == "all_instances"):
			output(cmd_all_instances(model, single_input("Type?")))
		elif (cmd == "define_attribute"):
			output(cmd_define_attribute(write, model, single_input("On which element?"), single_input("Attribute name?"), single_input("Type?")))
		elif (cmd == "undefine_attribute"):
			output(cmd_undefine_attribute(write, model, single_input("On which element?"), single_input("Attribute name?")))
		else:
			output("Unknown command while modelling: " + cast_value(cmd))
			output("Use command 'help' to get a list of available commands")

String function single_input(prompt : String):
	if (verbose):
		output(prompt)

	return input()!

Element function set_input(prompt : String):
	Element result
	Element inp

	if (verbose):
		output(prompt)
		output("-- Set input: empty string to terminate set")

	result = set_create()
	while (True):
		inp = input()
		if (value_eq(inp, "")):
			return result!
		else:
			set_add(result, inp)

Element function dict_input(prompt : String):
	Element result
	Element key

	if (verbose):
		output(prompt)
		output("-- Dict input: empty key to terminate dict")

	result = set_create()
	while (True):
		key = input()
		if (value_eq(key, "")):
			return result!
		else:
			dict_add(result, key, input())

Void function set_verbose(v : Boolean):
	verbose = v
	return!
