include "primitives.alh"
include "conformance_scd.alh"
include "library.alh"
include "io.alh"
include "modelling.alh"
include "utils.alh"
include "object_operations.alh"

Element while_stack = ?
Element variable_map = ?

Element function construct_function_list(list : Element):
	String command
	Element result
	String main_function
	Boolean continue
	String prev_element
	String first_element

	Element model
	model = instantiate_model(import_node("models/ActionLanguage"))

	list = list_reverse(list)

	// Initialize variables
	prev_element = read_root()
	main_function = read_root()

	// Clear global variables
	while_stack = list_create()
	variable_map = dict_create()

	continue = True
	while (continue):
		command = list_pop_final(list)
		if (command == "global"):
			result = construct_global(model, list)
		elif (command == "funcdef"):
			result = construct_funcdef(model, list, False)
		elif (command == "mutable_funcdef"):
			result = construct_funcdef(model, list, True)
		else:
			log("ERROR (1): did not understand command " + cast_value(command))
			output("ERROR: compiled code not understood: " + cast_value(command))
			return read_root()!

		continue = list_pop_final(list)
		if (element_neq(prev_element, read_root())):
			create_al_link(model, "Statement_next", prev_element, result["start"], "next")
		else:
			first_element = result["start"]

		if (bool_and(element_eq(main_function, read_root()), command != "global")):
			// In case no main function is defined, it is the first defined function
			// This is mostly there to ensure backwards compatibility
			main_function = result["instruction"]

		prev_element = result["end"]

		if (value_eq(result["name"], "main")):
			// It was the function that we want to call
			main_function = result["instruction"]

	if (element_eq(main_function, read_root())):
		log("ERROR (2): no main function found")
		output("ERROR: no main function found")
		return read_root()!

	// Overwrite the main function with our declaration function
	prev_element = set_pop(allAssociationDestinations(model, main_function, "funcdef_body"))
	model_delete_element(model, set_pop(allOutgoingAssociationInstances(model, main_function, "funcdef_body")))
	create_al_link(model, "funcdef_body", main_function, first_element, "body")
	create_al_link(model, "Statement_next", result["end"], prev_element, "next")
	instantiate_link(model, "initial_funcdef", "", instantiate_node(model, "Initial", ""), main_function)
	return model!

Void function create_al_link(model : Element, linktype : String, source : String, target : String, dictname : String):
	instantiate_attribute(model, instantiate_link(model, linktype, "", source, target), "name", dictname)
	return!

String function construct_global(model : Element, list : Element):
	String this_element
	String declared_element
	String op

	this_element = instantiate_value(model, "global", "", create_value(!global))
	declared_element = instantiate_value(model, "String", "", create_value(list_pop_final(list)))
	create_al_link(model, "global_var", this_element, declared_element, "var")

	op = list_pop_final(list)
	if (op != "none"):
		// Defines
		String assign
		String resolve
		String value
		assign = instantiate_value(model, "assign", "", create_value(!assign))
		create_al_link(model, "Statement_next", this_element, assign, "next")
		resolve = instantiate_value(model, "resolve", "", create_value(!resolve))
		create_al_link(model, "assign_var", assign, resolve, "var")
		create_al_link(model, "resolve_var", resolve, declared_element, "var")
		if (op == "deref"):
			value = construct_deref(model, list)
		elif (op == "empty"):
			value = instantiate_value(model, "call", "", create_value(!call))
			String res
			String acc
			res = instantiate_value(model, "resolve", "", create_value(!resolve))
			acc = instantiate_value(model, "access", "", create_value(!access))
			create_al_link(model, "call_func", value, acc, "func")
			create_al_link(model, "access_var", acc, res, "var")
			create_al_link(model, "resolve_var", res, instantiate_value(model, "String", "", "create_node"), "var")
		elif (op == "const"):
			value = instantiate_value(model, "constant", "", create_value(!constant))
			create_al_link(model, "constant_node", value, instantiate_value(model, "Element", "", list_pop_final(list)), "node")
		create_al_link(model, "assign_value", assign, value, "value")

		Element result
		result = dict_create()
		dict_add_fast(result, "name", "")
		dict_add_fast(result, "instruction", this_element)
		dict_add_fast(result, "start", this_element)
		dict_add_fast(result, "end", assign)
		return result!
	else:
		Element result
		result = dict_create()
		dict_add_fast(result, "name", "")
		dict_add_fast(result, "instruction", this_element)
		dict_add_fast(result, "start", this_element)
		dict_add_fast(result, "end", this_element)
		return result!

String function construct_funcdef(model : Element, list : Element, mutable : Boolean):
	String assign
	String resolve
	String constant
	String formal
	String func
	String params
	String declare
	String name

	declare = instantiate_value(model, "global", "", create_value(!global))
	assign = instantiate_value(model, "assign", "", create_value(!assign))
	resolve = instantiate_value(model, "resolve", "", create_value(!resolve))
	constant = instantiate_value(model, "constant", "", create_value(!constant))
	name = list_pop_final(list)
	if (dict_in(variable_map, name)):
		formal = dict_read(variable_map, name)
	else:
		formal = instantiate_value(model, "String", "", name)
	func = instantiate_node(model, "funcdef", "")
	params = instantiate_node(model, "param_dict", "")
	create_al_link(model, "global_var", declare, formal, "var")
	create_al_link(model, "Statement_next", declare, assign, "next")
	create_al_link(model, "assign_var", assign, resolve, "var")
	create_al_link(model, "assign_value", assign, constant, "value")
	create_al_link(model, "resolve_var", resolve, formal, "var")
	create_al_link(model, "constant_node", constant, func, "node")
	create_al_link(model, "funcdef_params", func, params, "params")

	if (mutable):
		create_al_link(model, "funcdef_mutable", func, instantiate_node(model, "Element", ""), "mutable")

	Integer nrParams
	nrParams = list_pop_final(list)
	Integer counter
	counter = 0
	Element param

	String arg_names_decl
	arg_names_decl = "abcdefghijklmnopqrstuvwxyz"

	while (counter < nrParams):
		param = instantiate_node(model, "Element", "")
		create_al_link(model, "param_dict_link", params, param, string_get(arg_names_decl, counter))
		dict_add_fast(variable_map, list_pop_final(list), param)
		// Output each parameter in turn
		counter = counter + 1

	// Now add the body
	create_al_link(model, "funcdef_body", func, construct_unknown(model, list), "body")

	Element result
	result = dict_create()
	dict_add_fast(result, "name", name)
	dict_add_fast(result, "instruction", func)
	dict_add_fast(result, "start", declare)
	dict_add_fast(result, "end", assign)
	return result!

Element function construct_unknown(model : Element, list : Element):
	String elem
	Element new_model
	Element new_model_model
	elem = list_pop_final(list)

	if (elem == "if"):
		return construct_if(model, list)!
	elif (elem == "while"):
		return construct_while(model, list)!
	elif (elem == "access"):
		return construct_access(model, list)!
	elif (elem == "resolve"):
		return construct_resolve(model, list)!
	elif (elem == "assign"):
		return construct_assign(model, list)!
	elif (elem == "call"):
		return construct_call(model, list)!
	elif (elem == "return"):
		return construct_return(model, list)!
	elif (elem == "const"):
		return construct_const(model, list)!
	elif (elem == "declare"):
		return construct_declare(model, list)!
	elif (elem == "output"):
		return construct_output(model, list)!
	elif (elem == "input"):
		return construct_input(model, list)!
	elif (elem == "deref"):
		return construct_deref(model, list)!
	elif (elem == "break"):
		return construct_break(model, list)!
	elif (elem == "continue"):
		return construct_continue(model, list)!
	else:
		log("ERROR (2): did not understand command " + cast_value(elem))
		output("ERROR: compiled code not understood: " + cast_value(elem))
		return read_root()!

String function construct_if(model : Element, list : Element):
	String this_element
	this_element = instantiate_value(model, "if", "", create_value(!if))
	create_al_link(model, "if_cond", this_element, construct_unknown(model, list), "cond")
	create_al_link(model, "if_then", this_element, construct_unknown(model, list), "then")
	if (list_pop_final(list)):
		create_al_link(model, "if_else", this_element, construct_unknown(model, list), "else")
	if (list_pop_final(list)):
		create_al_link(model, "Statement_next", this_element, construct_unknown(model, list), "next")
	return this_element!

String function construct_while(model : Element, list : Element):
	String this_element
	this_element = instantiate_value(model, "while", "", create_value(!while))
	create_al_link(model, "while_cond", this_element, construct_unknown(model, list), "cond")

	list_append(while_stack, this_element)
	create_al_link(model, "while_body", this_element, construct_unknown(model, list), "body")
	list_delete(while_stack, list_len(while_stack) - 1)

	if (list_pop_final(list)):
		create_al_link(model, "Statement_next", this_element, construct_unknown(model, list), "next")
	return this_element!

String function construct_access(model : Element, list : Element):
	String this_element
	this_element = instantiate_value(model, "access", "", create_value(!access))
	create_al_link(model, "access_var", this_element, construct_unknown(model, list), "var")
	return this_element!

String function construct_resolve(model : Element, list : Element):
	String this_element
	Element linked_element
	String name

	this_element = instantiate_value(model, "resolve", "", create_value(!resolve))
	name = list_pop_final(list)
	// TODO might have to check if this is to be added
	if dict_in(variable_map, name):
		linked_element = variable_map[name]
	else:
		linked_element = instantiate_value(model, "String", "", name)
	create_al_link(model, "resolve_var", this_element, linked_element, "var")
	return this_element!

String function construct_assign(model : Element, list : Element):
	String this_element
	this_element = instantiate_value(model, "assign", "", create_value(!assign))
	create_al_link(model, "assign_var", this_element, construct_unknown(model, list), "var")
	create_al_link(model, "assign_value", this_element, construct_unknown(model, list), "value")
	if (list_pop_final(list)):
		create_al_link(model, "Statement_next", this_element, construct_unknown(model, list), "next")
	return this_element!

String function construct_call(model : Element, list : Element):
	String this_element
	this_element = instantiate_value(model, "call", "", create_value(!call))
	create_al_link(model, "call_func", this_element, construct_unknown(model, list), "func")

	Integer nrParams
	nrParams = list_pop_final(list)
	Integer counter
	counter = 0

	Element param
	Element prev_param
	String arg_names_call
	arg_names_call = "abcdefghijklmnopqrstuvwxyz"

	while (counter < nrParams):
		param = instantiate_node(model, "param", "")

		create_al_link(model, "param_name", param, instantiate_value(model, "String", "", string_get(arg_names_call, counter)), "name")
		create_al_link(model, "param_value", param, construct_unknown(model, list), "value")

		if (counter == 0):
			create_al_link(model, "call_params", this_element, param, "params")
		else:
			create_al_link(model, "param_next_param", prev_param, param, "next_param")
		prev_param = param

		counter = counter + 1

	if (nrParams > 0):
		create_al_link(model, "call_last_param", this_element, prev_param, "last_param")

	if (list_pop_final(list)):
		create_al_link(model, "Statement_next", this_element, construct_unknown(model, list), "next")
	return this_element!

String function construct_return(model : Element, list : Element):
	if (list_pop_final(list)):
		String this_element
		this_element = instantiate_value(model, "return", "", create_value(!return))
		create_al_link(model, "return_value", this_element, construct_unknown(model, list), "value")
		return this_element!
	else:
		return instantiate_value(model, "return", "", create_value(!return))!

String function construct_const(model : Element, list : Element):
	String this_element
	this_element = instantiate_value(model, "constant", "", create_value(!constant))
	create_al_link(model, "constant_node", this_element, instantiate_value(model, "Element", "", list_pop_final(list)), "node")
	return this_element!

String function construct_declare(model : Element, list : Element):
	String this_element
	Element declared_element
	String name

	this_element = instantiate_value(model, "declare", "", create_value(!declare))
	declared_element = instantiate_node(model, "Element", "")
	create_al_link(model, "declare_var", this_element, declared_element, "var")
	name = list_pop_final(list)
	dict_add_fast(variable_map, name, declared_element)

	// Assign value
	String op
	op = list_pop_final(list)
	if (op != "none"):
		String assign
		String resolve
		String value
		assign = instantiate_value(model, "assign", "", create_value(!assign))
		create_al_link(model, "Statement_next", this_element, assign, "next")
		resolve = instantiate_value(model, "resolve", "", create_value(!resolve))
		create_al_link(model, "assign_var", assign, resolve, "var")
		create_al_link(model, "resolve_var", resolve, declared_element, "var")
		if (op == "deref"):
			value = construct_deref(model, list)
		elif (op == "empty"):
			value = instantiate_value(model, "call", "", create_value(!call))
			Element res
			Element acc
			res = instantiate_value(model, "resolve", "", create_value(!resolve))
			acc = instantiate_value(model, "access", "", create_value(!access))
			create_al_link(model, "call_func", value, acc, "func")
			create_al_link(model, "access_var", acc, res, "var")
			create_al_link(model, "resolve_var", res, instantiate_value(model, "String", "", "create_node"), "var")
		elif (op == "const"):
			value = instantiate_value(model, "constant", "", create_value(!constant))
			create_al_link(model, "constant_node", value, instantiate_value(model, "Element", "", list_pop_final(list)), "node")
		create_al_link(model, "assign_value", assign, value, "value")

		if (list_pop_final(list)):
			create_al_link(model, "Statement_next", assign, construct_unknown(model, list), "next")
	else:
		if (list_pop_final(list)):
			create_al_link(model, "Statement_next", this_element, construct_unknown(model, list), "next")
	return this_element!

String function construct_input(model : Element, list : Element):
	return instantiate_value(model, "input", "", create_value(!input))!

String function construct_output(model : Element, list : Element):
	String this_element
	this_element = instantiate_value(model, "output", "", create_value(!output))
	create_al_link(model, "output_value", this_element, construct_unknown(model, list), "value")

	if (list_pop_final(list)):
		create_al_link(model, "Statement_next", this_element, construct_unknown(model, list), "next")
	return this_element!

String function construct_deref(model : Element, list : Element):
	String this_element
	this_element = instantiate_value(model, "constant", "", create_value(!constant))
	String val
	val = list_pop_final(list)
	create_al_link(model, "constant_node", this_element, reuse_element(model, "Element", "", import_node(val)), "node")
	return this_element!

String function construct_break(model : Element, list : Element):
	String this_element
	this_element = instantiate_value(model, "break", "", create_value(!break))
	create_al_link(model, "break_while", this_element, while_stack[list_len(while_stack) - 1], "while")
	return this_element!

String function construct_continue(model : Element, list : Element):
	String this_element
	this_element = instantiate_value(model, "continue", "", create_value(!continue))
	create_al_link(model, "continue_while", this_element, while_stack[list_len(while_stack) - 1], "while")
	return this_element!
