include "primitives.alh"
include "io.alh"
include "object_operations.alh"
include "constructors.alh"
include "metamodels.alh"
include "library.alh"
include "modelling.alh"

Element function model_fuse(models : Element):
	Element new_model
	Element tagged_model
	String model_name
	Element model
	Element keys
	String key
	Element selected_MM
	String type

	// Read out some data first
	tagged_model = set_pop(models)
	set_add(models, tagged_model)
	new_model = instantiate_model(tagged_model[1]["metamodel"])

	// Do the iteration
	while (read_nr_out(models)):
		tagged_model = set_pop(models)
		model_name = string_join(list_read(tagged_model, 0), "/")
		model = list_read(tagged_model, 1)

		// Add all elements from 'model', but prepend it with the 'model_name'
		keys = set_to_list(dict_keys(model["model"]))
		while (read_nr_out(keys) > 0):
			key = list_pop(keys, 0)
			type = reverseKeyLookup(model["metamodel"]["model"], dict_read_node(model["type_mapping"], model["model"][key]))

			if (is_edge(model["model"][key])):
				String src
				String dst
				src = model_name + reverseKeyLookup(model["model"], read_edge_src(model["model"][key]))
				dst = model_name + reverseKeyLookup(model["model"], read_edge_dst(model["model"][key]))
				if (bool_and(dict_in(new_model["model"], src), dict_in(new_model["model"], dst))):
					instantiate_link(new_model, type, model_name + key, src, dst)
				else:
					list_append(keys, key)
			elif (has_value(model["model"][key])):
				instantiate_value(new_model, type, model_name + key, model["model"][key])
			else:
				instantiate_node(new_model, type, model_name + key)

	return new_model!

Element function model_copy(src_model : Element):
	Element dst_model
	Element queue
	Element name
	String type

	dst_model = instantiate_model(src_model["metamodel"])
	dict_add(dst_model, "model", create_node())
	dict_add(dst_model, "type_mapping", create_node())

	queue = set_to_list(dict_keys(src_model["model"]))

	while (read_nr_out(queue) > 0):
		name = list_pop(queue, 0)

		if (is_edge(src_model["model"][name])):
			// Is an edge, so potentially queue it
			String src
			String dst

			src = reverseKeyLookup(src_model["model"], read_edge_src(src_model["model"][name]))
			dst = reverseKeyLookup(src_model["model"], read_edge_dst(src_model["model"][name]))
			type = reverseKeyLookup(src_model["metamodel"]["model"], dict_read_node(src_model["type_mapping"], src_model["model"][name]))

			if (bool_and(dict_in(dst_model["model"], src), dict_in(dst_model["model"], dst))):
				// All present, so create the link between them
				instantiate_link(dst_model, type, name, src, dst)
			else:
				list_append(queue, name)

		elif (has_value(src_model["model"][name])):
			// Has a value, so copy that as well
			type = reverseKeyLookup(src_model["metamodel"]["model"], dict_read_node(src_model["type_mapping"], src_model["model"][name]))
			instantiate_value(dst_model, type, name, src_model["model"][name])

		else:
			// Is a node
			type = reverseKeyLookup(src_model["metamodel"]["model"], dict_read_node(src_model["type_mapping"], src_model["model"][name]))
			instantiate_node(dst_model, type, name)

	return dst_model!

Element function model_retype_on_name(model : Element, new_MM : Element, operation : String, name : String):
	String key
	String type
	Element keys
	Integer length

	keys = dict_keys(model["model"])
	length = string_len(name)

	while (read_nr_out(keys) > 0):
		key = set_pop(keys)
		if (dict_in(model["model"], key)):
			// Check if the element is still there, as a delete of a node might remove all attached links automatically
			type = reverseKeyLookup(model["metamodel"]["model"], dict_read_node(model["type_mapping"], model["model"][key]))

			if (operation == "+"):
				// Keep all, but augment typename
				dict_delete_node(model["type_mapping"], model["model"][key])
				dict_add(model["type_mapping"], model["model"][key], new_MM["model"][name + type])
			elif (operation == "-"):
				// Keep only if typename beginning matches and remove from typename
				if (string_startswith(type, name)):
					dict_delete_node(model["type_mapping"], model["model"][key])
					dict_add(model["type_mapping"], model["model"][key], new_MM["model"][string_substr(type, length, string_len(type))])
				else:
					model_delete_element(model, key)

	dict_delete(model, "metamodel")
	dict_add(model, "metamodel", new_MM)

	return model!

Void function model_join(dst_model : Element, src_model : Element, retyping_key : String):
	Element queue
	Element mapping
	String name
	String type
	String src
	String dst

	queue = set_to_list(dict_keys(src_model["model"]))
	mapping = create_node()

	while (read_nr_out(queue) > 0):
		name = list_pop(queue, 0)

		type = read_type(src_model, name)

		if (is_edge(src_model["model"][name])):
			// Is an edge, so potentially queue it
			String src
			String dst

			src = reverseKeyLookup(src_model["model"], read_edge_src(src_model["model"][name]))
			dst = reverseKeyLookup(src_model["model"], read_edge_dst(src_model["model"][name]))

			if (bool_and(dict_in(mapping, src), dict_in(mapping, dst))):
				// All present, so create the link between them
				dict_add(mapping, name, instantiate_link(dst_model, retyping_key + type, "", mapping[src], mapping[dst]))
			else:
				list_append(queue, name)

		elif (has_value(src_model["model"][name])):
			// Has a value, so copy that as well
			dict_add(mapping, name, instantiate_value(dst_model, retyping_key + type, "", src_model["model"][name]))

		else:
			// Is a node
			dict_add(mapping, name, instantiate_node(dst_model, retyping_key + type, ""))

	return!
	
Element function model_split(src_model : Element, target_metamodel : Element, retyping_key : String):
	Element dst_model

	dst_model = instantiate_model(target_metamodel)

	Element queue
	Element mapping
	String name
	String type
	String src
	String dst
	Integer length
	String new_type

	queue = set_to_list(dict_keys(src_model["model"]))
	mapping = create_node()
	length = string_len(retyping_key)

	while (read_nr_out(queue) > 0):
		name = list_pop(queue, 0)

		type = read_type(src_model, name)
		if (string_startswith(type, retyping_key)):
			new_type = string_substr(type, length, string_len(type))
			if (is_edge(src_model["model"][name])):
				// Is an edge, so potentially queue it
				String src
				String dst

				src = reverseKeyLookup(src_model["model"], read_edge_src(src_model["model"][name]))
				dst = reverseKeyLookup(src_model["model"], read_edge_dst(src_model["model"][name]))

				if (bool_and(dict_in(mapping, src), dict_in(mapping, dst))):
					// All present, so create the link between them
					dict_add(mapping, name, instantiate_link(dst_model, new_type, "", mapping[src], mapping[dst]))
				else:
					list_append(queue, name)

			elif (has_value(src_model["model"][name])):
				// Has a value, so copy that as well
				dict_add(mapping, name, instantiate_value(dst_model, new_type, "", src_model["model"][name]))

			else:
				// Is a node
				dict_add(mapping, name, instantiate_node(dst_model, new_type, ""))

	return dst_model!
