include "primitives.alh"
include "io.alh"
include "object_operations.alh"
include "constructors.alh"
include "metamodels.alh"
include "library.alh"
include "typing.alh"
include "utils.alh"
include "core_algorithm.alh"

Element global_models = ?

String function instantiated_name(element : Element, original : String):
	if (original == ""):
		return "__" + cast_id(element)!
	else:
		return original!

Element function instantiate_bottom():
	// Just create a new node that serves as the basis for everything
	// We don't know anything about the model yet, so just create an empty one
	Element new_model

	// The actual root node of the model
	new_model = dict_create()

	// Add an empty model and empty type mapping
	dict_add_fast(new_model, "model", dict_create())

	// Return the created model
	return new_model!

String function model_add_node(model : Element, name : String):
	// Adds a new node to the specified model with the desired name
	// This is a bottom operation, as it doesn't take any type
	Element new_node
	String actual_name

	new_node = create_node()
	actual_name = instantiated_name(new_node, name)
	dict_add_fast(model["model"], actual_name, new_node)

	return actual_name!

String function model_add_value(model : Element, name : String, value : Element):
	// Similar to model_add_node, but add a value as well
	String actual_name

	actual_name = instantiated_name(value, name)
	dict_add_fast(model["model"], actual_name, value)

	return actual_name!

String function model_add_edge(model : Element, name : String, source : String, destination : String):
	// Add an edge between the source and destination nodes
	// Nodes are specified using their string representation previously defined
	Element new_edge
	String actual_name
	
	if (bool_not(dict_in(model["model"], source))):
		log("In link " + name)
		log("ERROR: source of link unknown: " + source)
		log("Destination: " + destination)
		return ""!
	if (bool_not(dict_in(model["model"], destination))):
		log("In link " + name)
		log("ERROR: destination of link unknown: " + destination)
		log("Source: " + source)
		return ""!

	new_edge = create_edge(model["model"][source], model["model"][destination])
	actual_name = instantiated_name(new_edge, name)
	dict_add_fast(model["model"], actual_name, new_edge)

	return actual_name!

Void function retype_model(model : Element, metamodel : Element):
	// Remove the type mapping and add a new one for the specified metamodel
	new_type_mapping(model)
	dict_add_fast(model, "metamodel", metamodel)
	return!

Element function instantiate_model(metamodel : Element):
	// Instantiate a model
	// Basically create an untyped model and retype it
	Element model
	model = instantiate_bottom()
	retype_model(model, metamodel)
	return model!

String function reuse_element(model : Element, type_name : String, instance_name : String, element : Element):
	String actual_name

	if (bool_not(dict_in(model["metamodel"]["model"], type_name))):
		log("ERROR: (instantiate_node) no such type in metamodel: " + type_name)
		log("    for " + instance_name)
		return ""!

	actual_name = instantiated_name(element, instance_name)
	dict_add(model["model"], actual_name, element)
	retype(model, actual_name, type_name)

	return actual_name!

String function instantiate_node(model : Element, type_name : String, instance_name : String):
	String actual_name

	if (bool_not(dict_in(model["metamodel"]["model"], type_name))):
		log("ERROR: (instantiate_node) no such type in metamodel: " + type_name)
		log("    for " + instance_name)
		return ""!

	Element value
	value = create_node()

	actual_name = instantiated_name(value, instance_name)
	dict_add_fast(model["model"], actual_name, value)
	retype(model, actual_name, type_name)
	return actual_name!

String function instantiate_value(model : Element, type_name : String, instance_name : String, value : Element):
	String actual_name

	if (bool_not(dict_in(model["metamodel"]["model"], type_name))):
		log("ERROR: (instantiate_value) no such type in metamodel: " + type_name)
		log("     for " + instance_name)
		return ""!
	
	actual_name = instantiated_name(value, instance_name)
	dict_add_fast(model["model"], actual_name, value)
	retype(model, actual_name, type_name)
	return actual_name!

String function find_attribute_type(model : Element, elem : String, name : String):
	String mm_elem
	String direct_type
	String result

	direct_type = read_type(model, elem)

	if (direct_type == ""):
		return ""!
	
	mm_elem = find_attribute_definer(model["metamodel"], direct_type, name)

	if (mm_elem == ""):
		// Couldn't find element, so is not allowed!
		return ""!
	else:
		result = reverseKeyLookup(model["metamodel"]["model"], dict_read_edge(model["metamodel"]["model"][mm_elem], name))
		return result!

Element function get_subclasses(model : Element, name : String):
	Element result
	Integer i
	Integer j
	Integer num_edges
	Element edge
	String elem
	Element nodes

	nodes = set_create()
	set_add(nodes, name)

	// Initialize empty set
	result = set_create()

	while (set_len(nodes) > 0):
		elem = set_pop(nodes)
		if (bool_not(set_in(result, elem))):
			set_add(result, elem)
			// Read out all incoming edges
			num_edges = read_nr_in(model["model"][elem])
			j = 0
			while (j < num_edges):
				edge = read_in(model["model"][elem], j)
				if (dict_in(model["model"], reverseKeyLookup(model["model"], edge))):
					if (read_type(model, reverseKeyLookup(model["model"], edge)) == "Inheritance"):
						set_add(nodes, reverseKeyLookup(model["model"], read_edge_src(edge)))
				j = j + 1
	return result!

Element function get_superclasses(model : Element, name : String):
	Element result
	Integer j
	Integer num_edges
	Element edge
	String elem
	Element nodes
	String edge_name

	nodes = set_create()
	set_add(nodes, name)

	// Initialize empty set
	result = set_create()

	while (set_len(nodes) > 0):
		elem = set_pop(nodes)
		if (bool_not(set_in(result, elem))):
			set_add(result, elem)
			// Read out all outgoing edges
			num_edges = read_nr_out(model["model"][elem])
			j = 0
			while (j < num_edges):
				edge = read_out(model["model"][elem], j)
				edge_name = reverseKeyLookup(model["model"], edge)
				if (edge_name != ""):
					if (read_type(model, edge_name) == "Inheritance"):
						set_add(nodes, reverseKeyLookup(model["model"], read_edge_dst(edge)))
				j = j + 1

	return result!

String function find_attribute_definer(model : Element, elem_name : String, name : String):
	Element superclasses
	String current
	Integer nr_out
	Element out
	String name_attr
	String elem

	superclasses = get_superclasses(model, elem_name)
	while (set_len(superclasses) > 0):
		current = set_pop(superclasses)
		
		// TODO is it possible to use allOutgoingAssociationInstances here?
		nr_out = read_nr_out(model["model"][current])
		while (nr_out > 0):
			nr_out = nr_out - 1
			out = read_out(model["model"][current], nr_out)
			elem = reverseKeyLookup(model["model"], out)
			if (elem != ""):
				name_attr = read_attribute(model, elem, "name")
				if (name_attr == name):
					return current!

	return ""!

Void function instantiate_attribute(model : Element, element : String, attribute_name : String, value : Element):
	// Instantiate an attribute of something that needs to be instantiated
	// Actually a bit more difficult than all the rest, as we need to find the attribute to instantiate
	String attr_type
	String attr_name

	if (element_neq(read_attribute(model, element, attribute_name), read_root())):
		unset_attribute(model, element, attribute_name)

	attr_type = find_attribute_type(model, element, attribute_name)

	if (attr_type == ""):
		return!

	if (has_value(value)):
		value = create_value(value)
		
	attr_name = model_add_value(model, (element + ".") + attribute_name, value)
	retype(model, attr_name, reverseKeyLookup(model["metamodel"]["model"], read_edge_dst(model["metamodel"]["model"][attr_type])))
	instantiate_link(model, attr_type, "", element, attr_name)

	return!

Void function instantiate_attribute_ref(model : Element, element : String, attribute_name : String, ref : String):
	// Instantiate an attribute of something that needs to be instantiated
	// Actually a bit more difficult than all the rest, as we need to find the attribute to instantiate
	String attr_type
	String attr_name

	attr_type = find_attribute_type(model, element, attribute_name)

	if (attr_type == ""):
		log("Could not find attribute " + cast_value(attribute_name))
		return!
		
	instantiate_link(model, attr_type, "", element, ref)

	return!

Void function instantiate_attribute_code(model : Element, element : String, attribute_name : String, code : Element):
	// First create a new model for the AL part
	String location
	location = "code/" + cast_id(code)

	export_node(location, code)

	// Now link it with a complex attribute
	instantiate_attribute(model, element, attribute_name, location)

	return!

Void function instantiate_existing_attribute(model : Element, element : String, attribute_name : String, attr_ref : String):
	// Instantiate an attribute of something that needs to be instantiated
	// Actually a bit more difficult than all the rest, as we need to find the attribute to instantiate
	String attr_type
	String attr_name

	attr_type = find_attribute_type(model, element, attribute_name)

	if (attr_type == ""):
		log("Could not find attribute " + cast_value(attribute_name))
		return!
		
	// Make a copy of the value, as it is likely that this value is reused later on
	retype(model, attr_ref, reverseKeyLookup(model["metamodel"]["model"], read_edge_dst(model["metamodel"]["model"][attr_type])))
	instantiate_link(model, attr_type, "", element, attr_ref)

	return!

String function instantiate_link(model : Element, type : String, name : String, source : String, destination : String):
	// Create a typed link between two nodes
	String actual_name

	if (type == ""):
		// Have to find the type ourselves, as it isn't defined
		Element out
		Element in
		Element options

		options = allowedAssociationsBetween(model, source, destination)

		if (set_len(options) == 1):
			type = set_pop(options)
		elif (set_len(options) == 0):
			log("ERROR: cannot find possible link between entries")
			log("    for " + source)
			log("    to " + destination)
			return ""!
		else:
			log("ERROR: too many possible links between entries")
			log("   options: " + set_to_string(options))
			return ""!

	if (bool_not(dict_in(model["model"], source))):
		log("ERROR: source of link undefined: " + source)
		set_pop(create_node())
		return ""!

	if (bool_not(dict_in(model["model"], destination))):
		log("ERROR: destination of link undefined: " + destination)
		set_pop(create_node())
		return ""!

	if (bool_not(dict_in(model["metamodel"]["model"], type))):
		log("ERROR: (instantiate_link) no such type in metamodel: " + type)
		log("    for " + name)
		return ""!
	
	Element v
	v = create_edge(model["model"][source], model["model"][destination])
	actual_name = instantiated_name(v, name)
	dict_add_fast(model["model"], actual_name, v)
	retype(model, actual_name, type)
	return actual_name!

Void function model_delete_element(model : Element, name : String):
	// Delete all attributes
	//Element attrs
	//String attr
	//attrs = dict_keys(getAttributeList(model, name))
	//while (set_len(attrs) > 0):
	//	attr = set_pop(attrs)
	//	log("Remove attr " + attr)
	//	attr = reverseKeyLookup(model["model"], read_attribute(model, name, attr))
	//	remove_type(model, attr)
	//	delete_element(model["model"][attr])

	// Delete element itself
	remove_type(model, name)
	delete_element(model["model"][name])
	return!

String function model_undefine_attribute(model : Element, elem : String, attr_name : String):
	String attr_edge
	attr_edge = reverseKeyLookup(model["model"], dict_read_edge(model["model"][elem], attr_name))
	unset_attribute(model, attr_edge, "name")
	unset_attribute(model, attr_edge, "optional")
	model_delete_element(model, attr_edge)
	return attr_edge!

String function model_define_attribute(model : Element, elem : String, name : String, optional : Boolean, type : String):
	// Create the necessary links to make it an attribute
	String edge_name

	edge_name = (elem + "_") + name
	while (dict_in(model["model"], edge_name)):
		// Already exists, so make random name
		edge_name = edge_name + cast_id(model["model"][elem])
		log("Name clash detected for attribute: try new name: " + edge_name)

	edge_name = instantiate_link(model, "AttributeLink", edge_name, elem, type)
	instantiate_attribute(model, edge_name, "name", name)
	instantiate_attribute(model, edge_name, "optional", optional)

	return edge_name!

String function model_define_attribute_ID(model : Element, elem : String, name : String, optional : Boolean, type : String, ID : String):
	// Create the necessary links to make it an attribute
	String edge_name

	edge_name = ID
	while (dict_in(model["model"], edge_name)):
		// Already exists, so make random name
		edge_name = edge_name + cast_id(model["model"][elem])
		log("Name clash detected for attribute: try new name: " + edge_name)

	edge_name = instantiate_link(model, "AttributeLink", edge_name, elem, type)
	instantiate_attribute(model, edge_name, "name", name)
	instantiate_attribute(model, edge_name, "optional", optional)

	return edge_name!

Element function read_attribute(model : Element, element : String, attribute : String):
	if (dict_in(model["model"], element)):
		Integer i
		Integer count
		Element edge
		Element edge_type
		Element elem
		String name

		Element model_dict
		Element mm_dict
		model_dict = model["model"]
		mm_dict = model["metamodel"]["model"]

		elem = model_dict[element]
		count = read_nr_out(elem)

		i = 0
		while (i < count):
			edge = read_out(elem, i)
			name = reverseKeyLookup(model_dict, edge)
			if (name != ""):
				edge_type = mm_dict[read_type(model, name)]
				if (element_eq(edge_type, dict_read_edge(read_edge_src(edge_type), attribute))):
					return read_edge_dst(edge)!
			i = i + 1

	else:
		log("Element does not exist: " + element)

	// Not found: either element doesn't exist, or we couldn't find it
	return read_root()!

Void function unset_attribute(model : Element, element : String, attribute : String):
	// Removes an attribute if it exists
	String attr_type
	Element attr_links
	String attr_link
	String attr_value
	
	attr_type = find_attribute_type(model, element, attribute)
	attr_links = allOutgoingAssociationInstances(model, element, attr_type)

	while (set_len(attr_links) > 0):
		attr_link = set_pop(attr_links)
		attr_value = readAssociationDestination(model, attr_link)
		remove_type(model, attr_link)
		
		if (dict_in(model["model"], attr_value)):
			remove_type(model, attr_value)
			dict_delete(model["model"], attr_value)

		delete_element(model["model"][attr_link])

	return!

Element function get_func_AL_model(al_model : Element):
	Element initial_function

	// Find the initial function
	initial_function = allInstances(al_model, "Initial")

	if (set_len(initial_function) == 0):
		log("Could not find function to execute in this model!")
		return read_root()!
	elif (set_len(initial_function) > 1):
		log("Too many functions to execute in this model!")
		return read_root()!
	else:
		return al_model["model"][set_pop(allAssociationDestinations(al_model, set_pop(initial_function), "initial_funcdef"))]!

Element function trim_AL_constructors(list : Element):
	Integer length
	Element lst
	Integer i
	length = list_pop_final(list)
	i = 0
	lst = list_create()

	while (i < length):
		list_append(lst, list_pop_final(list))
		i = i + 1

	return lst!

Element function add_text(elem : Element, code : String):
	dict_add(elem, "__text", code)
	return elem!

Element function construct_model_list(model : Element, list : Element):
	String command

	list = list_reverse(list)

	while (list_len(list) > 0):
		command = list_pop_final(list)
		if (command == "add_node"):
			model_add_node(model, list_pop_final(list))
		elif (command == "add_value"):
			model_add_value(model, list_pop_final(list), list_pop_final(list))
		elif (command == "add_edge"):
			model_add_edge(model, list_pop_final(list), list_pop_final(list), list_pop_final(list))
		elif (command == "instantiate_node"):
			instantiate_node(model, list_pop_final(list), list_pop_final(list))
		elif (command == "model_define_attribute"):
			model_define_attribute(model, list_pop_final(list), list_pop_final(list), list_pop_final(list), list_pop_final(list))
		elif (command == "instantiate_attribute"):
			instantiate_attribute(model, list_pop_final(list), list_pop_final(list), list_pop_final(list))
		elif (command == "instantiate_attribute_ref"):
			instantiate_attribute_ref(model, list_pop_final(list), list_pop_final(list), list_pop_final(list))
		elif (command == "instantiate_attribute_code"):
			instantiate_attribute_code(model, list_pop_final(list), list_pop_final(list), add_text(construct_function_list(trim_AL_constructors(list)), list_pop_final(list)))
		elif (command == "instantiate_link"):
			instantiate_link(model, list_pop_final(list), list_pop_final(list), list_pop_final(list), list_pop_final(list))
		else:
			log("Modelling error: did not understand command " + command)

	return model!
