include "primitives.alh"
include "conformance_scd.alh"
include "constructors.alh"
include "modelling.alh"
include "typing.alh"

Element function allInstances(model : Element, type_name : String):
	if (dict_in(model["metamodel"]["model"], type_name)):
		Element result
		Element accepted
		Element keys
		Element tm
		String key
		String class
		Element results

		result = set_create()
		accepted = get_subclasses(model["metamodel"], type_name)

		while (set_len(accepted) > 0):
			class = set_pop(accepted)
			set_merge(result, elements_typed_by(model, class))
		return result!
	else:
		log("No such type in the metamodel: " + type_name)
		return set_create()!

Element function selectPossibleIncoming(model : Element, target : String, limit_set : Element):
	// Find all possible incoming link types for the target model
	// Should also include those specified on the superclass(es)
	String type
	Element model_dict
	Element elem
	Element result
	Element target_element
	result = set_create()
	model_dict = model["model"]
	while (list_len(limit_set) > 0):
		type = set_pop(limit_set)
		elem = model_dict[type]
		if (is_nominal_subtype(model, target, reverseKeyLookup(model_dict, read_edge_dst(elem)))):
			set_add(result, type)
	
	return result!

Element function selectPossibleOutgoing(model : Element, source : String, limit_set : Element):
	// Find all possible outgoing link types for the source model
	// Should also include those specified on the superclass(es)
	String type
	Element model_dict
	Element elem
	Element result
	Element source_element

	result = set_create()
	model_dict = model["model"]
	
	while (list_len(limit_set) > 0):
		type = set_pop(limit_set)
		elem = model_dict[type]
		if (is_nominal_subtype(model, source, reverseKeyLookup(model_dict, read_edge_src(elem)))):
			set_add(result, type)
	
	return result!

Element function allOutgoingAssociationInstances(model : Element, source_name : String, assoc_name : String):
	// Read out all outgoing edges of the model and select those that are typed by the specified association
	Element result
	Element source
	String option
	Integer all_out
	Integer i

	result = set_create()
	source = model["model"][source_name]
	all_out = read_nr_out(source)
	i = 0
	while (i < all_out):
		option = reverseKeyLookup(model["model"], read_out(source, i))
		if (option != ""):
			if (assoc_name != ""):
				if (is_nominal_instance(model, option, assoc_name)):
					set_add(result, option)
			else:
				set_add(result, option)
		i = i + 1

	return result!

Element function allIncomingAssociationInstances(model : Element, target_name : String, assoc_name : String):
	// Read out all outgoing edges of the model and select those that are typed by the specified association
	Element result
	Element source
	String option
	Integer all_out
	Integer i

	result = set_create()
	source = model["model"][target_name]
	all_out = read_nr_in(source)
	i = 0
	while (i < all_out):
		option = reverseKeyLookup(model["model"], read_in(source, i))
		if (option != ""):
			if (assoc_name != ""):
				if (is_nominal_instance(model, option, assoc_name)):
					set_add(result, option)
			else:
				set_add(result, option)
		i = i + 1

	return result!

Element function getAttributeList(model : Element, element : String):
	Element result
	Element keys
	Element type
	Element attr_name
	Element types
	Element mm
	String attr_type

	result = dict_create()
	mm = model["metamodel"]["model"]
	types = get_superclasses(model["metamodel"], read_type(model, element))

	while (set_len(types) > 0):
		type = set_pop(types)

		// Add our own attributes
		keys = dict_keys(mm[type])
		while (set_len(keys) > 0):
			attr_name = set_pop(keys)
			if (is_physical_string(attr_name)):
				if (read_type(model["metamodel"], reverseKeyLookup(mm, dict_read_edge(mm[type], attr_name))) == "AttributeLink"):
					attr_type = reverseKeyLookup(mm, mm[type][attr_name])
					// WARNING: do not change this to dict_add_fast, as this crashes random code...
					dict_add(result, attr_name, attr_type)

	return result!

Element function getAttributes(model : Element, element : String):
	Element result
	Element keys
	Element type
	Element attr_name
	Element types
	Element mm

	result = dict_create()
	mm = model["metamodel"]["model"]
	types = get_superclasses(model["metamodel"], read_type(model, element))

	while (set_len(types) > 0):
		type = set_pop(types)

		// Add our own attributes
		keys = dict_keys(mm[type])
		while (set_len(keys) > 0):
			attr_name = set_pop(keys)
			if (is_physical_string(attr_name)):
				// WARNING: do not change this to dict_add_fast, as this crashes random code...
				dict_add(result, attr_name, read_attribute(model, element, attr_name))

	return result!

Element function getInstantiatableAttributes(model : Element, element : String, type : String):
	Element all_links
	Element result
	String link

	result = dict_create()

	all_links = allOutgoingAssociationInstances(model, element, type)
	while (set_len(all_links) > 0):
		link = set_pop(all_links)
		// WARNING: do not change this to dict_add_fast, as this crashes random code...
		dict_add(result, read_attribute(model, link, "name"), readAssociationDestination(model, link))

	return result!

String function print_dict(dict : Element):
	Element keys
	Element key
	String result

	keys = dict_keys(dict)
	result = ""
	while (0 < list_len(keys)):
		key = set_pop(keys)
		result = result + cast_value(key)
		result = result + ": "
		result = result + cast_value(dict[key])
		result = result + "\n"
	return result!

String function readAssociationSource(model : Element, name : String):
	return reverseKeyLookup(model["model"], read_edge_src(model["model"][name]))!

String function readAssociationDestination(model : Element, name : String):
	return reverseKeyLookup(model["model"], read_edge_dst(model["model"][name]))!

String function anAssociationDestination(model : Element, name : String, association_type : String):
	Element tmp
	Element result
	String val

	result = set_create()
	tmp = allOutgoingAssociationInstances(model, name, association_type)

	while (set_len(tmp) > 0):
		val = readAssociationDestination(model, set_pop(tmp))
		if (val != ""):
			return val!

	return ""!

Element function allAssociationDestinations(model : Element, name : String, association_type : String):
	Element tmp
	Element result
	String val

	result = set_create()
	tmp = allOutgoingAssociationInstances(model, name, association_type)

	while (set_len(tmp) > 0):
		val = readAssociationDestination(model, set_pop(tmp))
		if (val != ""):
			set_add(result, val)

	return result!

String function anAssociationOrigin(model : Element, name : String, association_type : String):
	Element tmp
	Element result
	String val

	result = set_create()
	tmp = allIncomingAssociationInstances(model, name, association_type)

	while (set_len(tmp) > 0):
		val = readAssociationSource(model, set_pop(tmp))
		if (val != ""):
			return val!

	return ""!

Element function allAssociationOrigins(model : Element, name : String, association_type : String):
	Element tmp
	Element result
	String val

	result = set_create()
	tmp = allIncomingAssociationInstances(model, name, association_type)

	while (set_len(tmp) > 0):
		val = readAssociationSource(model, set_pop(tmp))
		if (val != ""):
			set_add(result, val)

	return result!

Element function allowedAssociationsBetween(model : Element, src : String, dst : String):
	// Go to the type and find all possibilities
	String type
	Element all_types
	Integer nr_edges
	Integer i
	Element result
	Element edge
	String edge_name
	String dst_name

	result = set_create()
	type = read_type(model, src)
	all_types = get_superclasses(model["metamodel"], type)

	while (set_len(all_types) > 0):
		type = set_pop(all_types)
		nr_edges = read_nr_out(model["metamodel"]["model"][type])
		i = 0
		while (i < nr_edges):
			edge = read_out(model["metamodel"]["model"][type], i)
			edge_name = reverseKeyLookup(model["metamodel"]["model"], edge)

			if (dict_in(model["metamodel"]["model"], edge_name)):
				// Find destination
				dst_name = reverseKeyLookup(model["metamodel"]["model"], read_edge_dst(edge))

				if (is_nominal_instance(model, dst, dst_name)):
					// Find out whether our dst is an instance of the found destination type

					if (is_nominal_instance(model["metamodel"], edge_name, "Association")):
						set_add(result, edge_name)

			i = i + 1

	return result!
