include "primitives.alh"
include "utils.alh"
include "random.alh"

// This function must be kept internally, only called through the "sleep" and "interruptable_sleep" functions
Float function __sleep(a : Float, b : Boolean) = ?primitives/__sleep

Element function list_create():
	return create_node()!

Element function dict_create():
	return create_node()!

Element function set_create():
	return create_node()!

Boolean function value_neq(a : Element, b : Element):
	return bool_not(value_eq(a, b))!

Boolean function integer_gt(a : Element, b : Element):
	return bool_not(bool_or(integer_lt(a, b), value_eq(a, b)))!

Boolean function float_gt(a : Element, b : Element):
	return bool_not(bool_or(float_lt(a, b), value_eq(a, b)))!

Element function dict_add(a : Element, b : Element, c : Element):
	create_edge(create_edge(a, c), b)
	return a!

Boolean function element_neq(a : Element, b : Element):
	return bool_not(element_eq(a, b))!

Integer function dict_len(a : Element):
	return read_nr_out(a)!

Integer function list_len(a : Element):
	return read_nr_out(a)!

Integer function set_len(a : Element):
	return read_nr_out(a)!

Float function float_neg(a : Float):
	return float_subtraction(0, a)!

Integer function integer_neg(a : Integer):
	return integer_subtraction(0, a)!

Element function list_read(a : Element, b : Integer):
	return dict_read(a, b)!

Element function list_append(a : Element, b : Element):
	dict_add(a, list_len(a), b)
	return a!

Element function set_add(a : Element, b : Element):
	if (bool_not(set_in(a, b))):
		// Treat set as a dictionary with a mock-value
		dict_add(a, b, a)
	return a!

Element function set_add_node(a : Element, b : Element):
	if (bool_not(set_in_node(a, b))):
		// Treat set as a dictionary with a mock-value
		dict_add(a, b, a)
	return a!

Element function set_pop(a : Element):
	if (integer_gt(set_len(a), 0)):
		Element edge
		Element result
		edge = read_out(a, 0)
		result = read_edge_dst(read_out(edge, 0))
		delete_element(edge)
		return result!
	else:
		return read_root()!

Element function set_read(a : Element):
	if (integer_gt(set_len(a), 0)):
		return read_edge_dst(read_out(read_out(a, 0), 0))!
	else:
		return read_root()!

Void function sleep(a : Float):
	__sleep(a, False)
	return!

Void function interruptable_sleep(a : Float):
	__sleep(a, True)
	return!

Element mutable function exec(first_instr : Element):
	// This does very ugly things, so beware!
	// Basically, we dynamically construct an if True condition with as body the provided instructions
	// after the if conditional, we append a return of an empty element, as we need a return at the end
	// returns in the code are therefore allowed (and will be the return value), but not necessarily
	Element n
	Element exec_if
	Element exec_const_true
	Element exec_return

	n = create_node()
	exec_if = create_value(!if)
	exec_const_true = create_value(!constant)
	exec_return = create_value(!return)

	dict_add(n, "params", create_node())
	dict_add(exec_const_true, "node", create_value(True))
	dict_add(exec_if, "cond", exec_const_true)
	dict_add(exec_if, "then", first_instr)
	dict_add(n, "body", exec_if)
	dict_add(exec_if, "next", exec_return)

	return n()!

Boolean function string_startswith(a: String, b: String):
	Integer i
	i = 0
	if (string_len(b) > string_len(a)):
		return False!

	while (i < string_len(b)):
		if (string_get(a, i) != string_get(b, i)):
			return False!
		i = i + 1
	
	return True!

Boolean function has_value(a: Element):
	return bool_or(bool_or(bool_or(is_physical_action, is_physical_int(a)), bool_or(is_physical_none(a), is_physical_float(a))), bool_or(is_physical_string(a), is_physical_boolean(a)))!

Boolean function float_gte(a: Float, b: Float):
	return bool_or(float_gt(a, b), value_eq(a, b))!

Boolean function float_lte(a: Float, b: Float):
	return bool_or(float_lt(a, b), value_eq(a, b))!

Boolean function integer_lte(a: Integer, b: Integer):
	return bool_or(integer_lt(a, b), value_eq(a, b))!
	
Boolean function integer_gte(a: Integer, b: Integer):
	return bool_or(integer_gt(a, b), value_eq(a, b))!

String function string_substr(a: String, b: Integer, c: Integer):
	String result
	Integer i

	// First handle corner cases!

	// If the string is too short for b to even start, return empty
	if (b > string_len(a)):
		return ""!

	// If the part we want to snip is negative, we return empty
	if (b > c):
		return ""!

	i = 0
	result = ""
	while (i < string_len(a)):
		if (bool_and(i >= b, i <= c)):
			result = result + string_get(a, i)

		if (i > c):
			return result!

		i = i + 1
	return result!

String function string_replace(a : String, b : String, c : String):
	Element lst
	String result

	lst = string_split(a, b)
	result = cast_string(list_pop_final(lst))
	while (set_len(lst) > 0):
		result = cast_string(list_pop_final(lst)) + c + result

	return result!
	
Element function resolve(name : String):
	// Could directly access it through introspection
	// But seems safer to create some code and execute it...
	Element task_root
	task_root = read_taskroot()
	task_root = task_root["globals"][name]["value"]
	return task_root!

Integer function integer_modulo(a : Integer, b : Integer):
	return a - b * (a / b)!

Boolean function has_input():
	return dict_in(dict_read(read_taskroot(), "input"), "value")!

Element function list_pop(lst : Element, index : Integer):
	Element v
	v = list_read(lst, index)
	list_delete(lst, index)
	return v!

Element function list_pop_final(lst : Element):
	return list_pop(lst, list_len(lst) - 1)!

Element function set_copy(a : Element):
	return dict_keys(a)!

String function set_to_string(s : Element):
	String result

	result = "{"
	s = set_copy(s)
	while (set_len(s) > 0):
		result = (result + cast_value(set_pop(s))) + ", "
	
	result = result + "}"

	return result!

String function list_to_string(s : Element):
	String result
	Integer i

	result = "["
	i = 0
	while (i < list_len(s)):
		result = result + cast_value(list_read(s, i))
		result = result + ", "
		i = i + 1
	
	result = result + "]"

	return result!

Element function create_tuple(a : Element, b : Element):
	Element tuple

	tuple = list_create()
	list_append(tuple, a)
	list_append(tuple, b)

	return tuple!

String function dict_to_string(d : Element):
	String result
	Element keys
	Element key

	result = "{"

	keys = dict_keys(d)
	while (set_len(keys) > 0):
		key = set_pop(keys)

		result = result + cast_value(key)
		result = result + ": "
		result = result + cast_value(dict_read_node(d, key))
		if (set_len(keys) > 0):
			result = result + ", "
	
	result = result + "}"

	return result!

Element function set_overlap(sa : Element, sb : Element):
	Element result
	Element elem

	if (set_len(sa) > set_len(sb)):
		// Pick the smallest set to iterate over, so switch if sa is not the smallest
		result = sa
		sa = sb
		sb = result

	result = set_create()
	sa = set_copy(sa)

	// Iterate over each element of sa and only add it to the result if it is also in sb
	while (set_len(sa) > 0):
		elem = set_pop(sa)
		if (set_in(sb, elem)):
			// Shared between both
			set_add(result, elem)

	return result!

Boolean function set_equality(sa : Element, sb : Element):
	if (set_len(sa) != set_len(sb)):
		return False!

	sa = set_copy(sa)
	while (0 < set_len(sa)):
		if (bool_not(set_in(sb, set_pop(sa)))):
			return False!
	return True!

Boolean function dict_eq(da : Element, db : Element):
	if (bool_not(set_equality(dict_keys(da), dict_keys(db)))):
		// They don't even share the same keys
		return False!

	Element keys
	Element key
	keys = dict_keys(da)
	while (set_len(keys) > 0):
		key = set_pop(keys)
		if (value_neq(da[key], db[key])):
			// Value that is not equal
			return False!

	return True!

Element function dict_copy(d : Element):
	String result
	Element keys
	Element key

	result = dict_create()

	keys = dict_keys(d)
	while (set_len(keys) > 0):
		key = set_pop(keys)
		dict_add_fast(result, key, dict_read_node(d, key))

	return result!

Element function list_copy(lst : Element):
	Integer counter
	Element result

	result = list_create()
	counter = 0

	while (counter < list_len(lst)):
		list_append(result, lst[counter])
		counter = counter + 1

	return result!

Element function set_to_list(s : Element):
	Element result
	Element tmp

	result = list_create()
	tmp = set_copy(s)
	while (set_len(tmp) > 0):
		list_append(result, set_pop(tmp))

	return result!

Element function list_to_set(s : Element):
	Element result
	Integer i

	i = 0
	result = set_create()
	while (i < list_len(s)):
		set_add(result, s[i])
		i = i + 1

	return result!

Void function dict_overwrite(d : Element, key : Element, value : Element):
	if (dict_in(d, key)):
		dict_delete(d, key)
	if (dict_in_node(d, key)):
		dict_delete_node(d, key)
	dict_add(d, key, value)

	return !

Void function set_merge(a : Element, b : Element):
	b = set_copy(b)
	while (set_len(b) > 0):
		set_add(a, set_pop(b))
	return!

Element function make_reverse_dictionary(dict : Element):
	Element keys
	Element reverse
	String key
	String value

	reverse = dict_create()
	keys = dict_keys(dict)
	while (set_len(keys) > 0):
		key = set_pop(keys)
		value = cast_id(dict[key])
		if (dict_in(reverse, value)):
			dict_delete(reverse, value)
		dict_add(reverse, value, key)

	return reverse!

Element function set_remove(a: Element, b: Element):
	return dict_delete(a, b)!

Element function set_remove_node(a: Element, b: Element):
	return dict_delete_node(a, b)!

Boolean function set_in(a: Element, b: Element):
	return dict_in(a, b)!

Element function set_in_node(a: Element, b: Element):
	return dict_in_node(a, b)!

String function reverseKeyLookup(dict : Element, element : Element):
	Integer nr_in
	Integer nr_out
	Integer counter
	Element link

	nr_in = read_nr_in(element)
	counter = 0
	while (counter < nr_in):
		if (element_eq(read_edge_src(read_in(element, counter)), dict)):
			// Got a match
			return (read_edge_dst(read_out(read_in(element, counter), 0)))!

		counter = counter + 1
	
	return ""!

Element function reverseKeyLookupMulti(dict : Element, element : Element):
	Integer nr_out
	Integer counter
	Element link
	Element result

	result = set_create()
	nr_out = read_nr_out(dict)
	counter = 0
	while (counter < nr_out):
		if (value_eq(read_edge_dst(read_out(dict, counter)), element)):
			// Got a match
			set_add(result, read_edge_dst(read_out(read_out(dict, counter), 0)))
		counter = counter + 1
	
	return result!

Boolean function list_in(list : Element, elem : Element):
	Integer i
	i = 0
	while (i < list_len(list)):
		if (value_eq(list_read(list, i), elem)):
			return True!
		i = i + 1
	return False!

Element function dict_values(dict : Element):
	Element result
	result = set_create()

	Element keys
	keys = dict_keys(dict)
	while (set_len(keys) > 0):
		set_add(result, dict[set_pop(keys)])

	return result!

Element function set_difference(sa : Element, sb : Element):
	Element result
	Element elem

	result = set_copy(sa)
	sb = set_copy(sb)

	while (set_len(sb) > 0):
		elem = set_pop(sb)
		if (set_in(sa, elem)):
			// Shared between both
			set_remove(result, elem)
	return result!

Void function set_subtract(set1 : Element, set2 : Element):
	Element elem
	set2 = set_copy(set2)
	while (set_len(set2) > 0):
		elem = set_pop(set2)
		if (set_in(set1, elem)):
			set_remove(set1, elem)
	return!

Element function range(max : Integer):
	Element result
	Integer counter
	result = list_create()
	counter = 0

	while (counter < max):
		list_append(result, counter)
		counter = counter + 1

	return result!

String function spawn(function : Element, arguments : Element):
	// Define a new task, with all the required stack information
	// This is taken from the "task_management" code
	Element task_root
	Element task_frame
	Element output_value
	Element input_value 
	Element root

	root = read_root()
	task_root = create_node()
	task_frame = create_node()
	output_value = create_node()
	input_value = create_node()

	dict_add_fast(task_root, "frame", task_frame)
	dict_add_fast(task_root, "output", output_value)
	dict_add_fast(task_root, "last_output", output_value)
	dict_add_fast(task_root, "input", input_value)
	dict_add_fast(task_root, "last_input", input_value)
	dict_add_fast(task_frame, "evalstack", create_node())
	dict_add_fast(task_frame, "returnvalue", create_node())
	dict_add_fast(task_frame, "phase", "init")
	dict_add_fast(task_frame, "symbols", create_node())

	// Instead of just spawning, we set a different IP
	dict_add_fast(task_frame, "IP", function["body"])

	// Additionally, we copy over all globals that we previously had
	dict_add_fast(task_root, "globals", dict_copy(root[get_taskname()]["globals"]))

	if (dict_in(function, "params")):
		// And add the arguments to the symbol table
		Element symbols
		String arg_names_call
		Element param_dict
		Integer arg_i
		symbols = task_frame["symbols"]
		arg_names_call = "abcdefghijklmnopqrstuvwxyz"
		arg_i = 0
		param_dict = function["params"]
		
		arguments = list_copy(arguments)
		Element t
		Element entry
		while (list_len(arguments) > 0):
			entry = create_node()
			dict_add(entry, "value", list_pop(arguments, 0))
			t = create_edge(symbols, entry)
			create_edge(t, param_dict[string_get(arg_names_call, arg_i)])
			arg_i = arg_i + 1

	// Add this only at the end, as otherwise the task will already be detected and executed
	String taskname
	taskname = random_string(30)

	while (dict_in(read_root(), taskname)):
		taskname = random_string(30)
	dict_add_fast(read_root(), taskname, task_root)

	return taskname!

Element function list_insert(a : Element, b : Element, c : Integer):
	Integer current
	Element prev
	Element new_prev

	current = c
	prev = b

	while (current < list_len(a)):
		new_prev = list_read(a, current)
		dict_delete(a, current)
		dict_add_fast(a, current, prev)
		prev = new_prev
		current = current + 1

	dict_add_fast(a, current, prev)
	
	return a!

Element function list_delete(a : Element, b : Integer):
	Integer current

	current = b
	
	while (current < (list_len(a) - 1)):
		dict_delete(a, current)
		dict_add_fast(a, current, a[current + 1])
		current = current + 1

	dict_delete(a, current)
	return a!
