include "primitives.alh"
include "object_operations.alh"
include "typing.alh"

Boolean function find_type_mapping(model : Element):
	// Finds a correct type mapping for the provided model (with partial type mapping)
	// We go through several phases:
	//   1) remove elements from type mapping that are not in the model or metamodel
	//   2) find a mapping based on the current partial mapping
	//   3) (optional) verify that the mapping is correct with conformance checking
	// Returns True if the type mapping was altered

	// Start of with some initialization
	Element tm
	Element elems
	String elem

	tm = get_type_mapping_as_dict(model)

	// 1) remove elements from type mapping that are not in the model or metamodel
	elems = dict_keys(tm)
	while (set_len(elems) > 0):
		elem = set_pop(elems)
		if (bool_not(dict_in(model["model"], elem))):
			// Remove the key, as the model does not contain the element anymore
			dict_delete(tm, elem)
		else:
			if (bool_not(dict_in(model["metamodel"]["model"], tm[elem]))):
				// Remove the key, as the metamodel does not contain the type anymore
				dict_delete(tm, elem)
		
	// 2) find a mapping based on the current partial mapping, but only if it is not yet complete
	// TODO this must be expanded for other things than trivial metamodels!
	if (dict_len(model["model"]) > dict_len(tm)):
		// TODO for now, this only returns something for a simple case, where the MM has one edge, and one node
		//      and it makes the assumption that SCD is the M3 level...

		// First find the name of the edge and node elements
		Element elems
		String elem

		Element nodes
		Element edges
		nodes = allInstances(model["metamodel"], "Class")
		edges = allInstances(model["metamodel"], "Association")

		// Filter out classes from the associations
		Element new_nodes
		String node
		new_nodes = set_create()
		while (set_len(nodes) > 0):
			node = set_pop(nodes)
			if (bool_not(is_edge(model["metamodel"]["model"][node]))):
				set_add(new_nodes, node)
		nodes = new_nodes

		log("Nodes: " + set_to_string(nodes))
		log("Edges: " + set_to_string(edges))

		// Simple allocation: this seems like conformance bottom
		if (bool_and(set_len(edges) == 1, set_len(nodes) == 1)):
			String node_source_element
			String edge_element

			node_source_element = set_pop(nodes)
			edge_element = set_pop(edges)

			elems = dict_keys(model["model"])
			while (set_len(elems) > 0):
				elem = set_pop(elems)
				if (is_edge(model["model"][elem])):
					retype(model, elem, edge_element)
				else:
					retype(model, elem, node_source_element)
			return True!

		// Simple allocation: this seems like a traceability model or so
		// Make sure to check that the edge goes from one node to the other!
		if (bool_and(set_len(edges) == 1, set_len(nodes) == 2)):
			String node_target_element
			String node_source_element
			String edge_element

			edge_element = set_pop(edges)
			node_source_element = readAssociationSource(model["metamodel"], edge_element)
			node_target_element = readAssociationDestination(model["metamodel"], edge_element)

			if (value_eq(node_source_element, node_target_element)):
				log("Could not automatically deduce mapping in a trivial way!")
				return False!

			elems = dict_keys(model["model"])
			while (set_len(elems) > 0):
				elem = set_pop(elems)

				if (bool_and(is_edge(model["model"][elem]), read_nr_out(model["model"][elem]) == 0)):
					// An edge, and there is always exactly one, so type
					retype(model, elem, edge_element)

					// The source and target are ALWAYS typed as well!
					retype(model, readAssociationSource(model, elem), node_source_element)
					retype(model, readAssociationDestination(model, elem), node_target_element)

			return True!

		// Simple allocation: this seems like a dictionary model or so
		if (bool_and(set_len(edges) == 2, set_len(nodes) == 3)):
			String main_edge
			String small_edge
			main_edge = set_pop(edges)
			small_edge = set_pop(edges)

			if (readAssociationSource(model["metamodel"], main_edge) == small_edge):
				// Switch both
				String tmp
				tmp = main_edge
				main_edge = small_edge
				small_edge = tmp

			if (readAssociationSource(model["metamodel"], small_edge) == main_edge):
				String origin
				String value
				String key
				String middle_edge
				origin = readAssociationSource(model["metamodel"], main_edge)
				value = readAssociationDestination(model["metamodel"], main_edge)
				key = readAssociationDestination(model["metamodel"], small_edge)

				if (bool_and(bool_and(origin != value, origin != key), value != key)):
					// All three nodes are different, meaning that we are complete and have identified a simple mapping!

					elems = dict_keys(model["model"])
					while (set_len(elems) > 0):
						elem = set_pop(elems)
						if (bool_and(is_edge(model["model"][elem]), read_nr_out(model["model"][elem]) == 0)):
							// An edge with no outgoing links, meaning that it is the small_edge
							retype(model, elem, small_edge)

							// Source is main_edge and target is key
							middle_edge = readAssociationSource(model, elem)
							retype(model, middle_edge, main_edge)
							retype(model, readAssociationDestination(model, elem), key)

							// The main_edge goes from root to the value
							retype(model, readAssociationSource(model, middle_edge), origin)
							retype(model, readAssociationDestination(model, middle_edge), value)

				return True!

		log("Could not automatically deduce mapping in a trivial way!")
		log("Model: " + set_to_string(dict_keys(model["model"])))
		log("TM: " + set_to_string(dict_keys(tm)))
		log("DIFF: " + set_to_string(set_difference(dict_keys(model["model"]), dict_keys(tm))))
		return False!

	return True!
