include "primitives.alh"
include "modelling.alh"
include "object_operations.alh"

//    def print_instruction(self, inst, indent=0):
//        intrinsics = {"integer_addition": (lambda x, y: "(%s + %s)" % (x, y)),
//                      #"string_join": (lambda x, y: "(str(%s) + str(%s))" % (x, y)),
//                      #"dict_read": (lambda x, y: "(%s[%s])" % (x, y)),
//                      #"dict_overwrite": (lambda x, y, z: "(%s[%s] = %s)" % (x, y, z)),
//                      #"dict_add_fast": (lambda x, y, z: "(%s[%s] = %s)" % (x, y, z)),
//                      #"dict_add_fast": (lambda x, y, z: "(%s[%s] = %s)" % (x, y, z)),
//                      }
//
//        inst_type, = yield [("RV", [inst])]
//        instruction = "(no_printer_for_%s)" % inst_type["value"]
//
//        if inst_type["value"] == "if":
//            cond, true, false = yield [("RD", [inst, "cond"]),
//                                       ("RD", [inst, "then"]),
//                                       ("RD", [inst, "else"])]
//            cond, = yield [("CALL_ARGS", [self.print_instruction, (cond, 0)])]
//            true, = yield [("CALL_ARGS", [self.print_instruction, (true, indent+1)])]
//            if false:
//                false, = yield [("CALL_ARGS", [self.print_instruction, (false, indent+1)])]
//                false = ("  " * indent + "else:\n%s\n") % false
//            else:
//                false = ""
//
//            instruction = "  " * indent + "if (" + cond + "):\n" + true + "\n" + false
//            #TODO fix for inline calls
//        elif inst_type["value"] == "constant":
//            node, = yield [("RD", [inst, "node"])]
//            node, = yield [("RV", [node])]
//            if isinstance(node, str):
//                instruction = "  " * indent + '"%s"' % node
//            else:
//                instruction = "  " * indent + str(node)
//        elif inst_type["value"] == "return":
//            value, = yield [("RD", [inst, "value"])]
//            if value:
//                value, = yield [("CALL_ARGS", [self.print_instruction, (value, 0)])]
//                instruction = "  " * indent + "return %s\n" % value
//            else:
//                instruction = "  " * indent + "return\n"
//            #TODO fix for inline calls
//        elif inst_type["value"] == "declare":
//            instruction = ""
//        elif inst_type["value"] == "resolve":
//            value, = yield [("RD", [inst, "var"])]
//            str_value, = yield [("RV", [value])]
//            if str_value:
//                instruction = str_value
//            else:
//                instruction = "var_%s" % value
//        elif inst_type["value"] == "assign":
//            var, val = yield [("RD", [inst, "var"]),
//                              ("RD", [inst, "value"])]
//            var, val = yield [("CALL_ARGS", [self.print_instruction, (var, 0)]),
//                              ("CALL_ARGS", [self.print_instruction, (val, 0)])]
//            instruction = "  " * indent + var + " = " + val + "\n"
//            #TODO fix for inline calls
//        elif inst_type["value"] == "call":
//            func_name, = yield [("RD", [inst, "func"])]
//            func_name, = yield [("CALL_ARGS", [self.print_instruction, (func_name, 0)])]
//            param_list = []
//
//            param, = yield [("RD", [inst, "params"])]
//            while param:
//                value, = yield [("RD", [param, "value"])]
//                res, param = yield [("CALL_ARGS", [self.print_instruction, (value, 0)]),
//                                    ("RD", [param, "next_param"])]
//                param_list.append(res)
//
//            if func_name in intrinsics:
//                instruction = "  " * indent + intrinsics[func_name](*param_list)
//            else:
//                instruction = "  " * indent + func_name + "(" + ", ".join(param_list) + ")"
//                """
//                if indent == 0:
//                    #TODO fix for inline calls
//                    instruction = 'result, = yield [("CALL_ARGS", (%s, (' % func_name + ",".join(param_list) + ',)))]'
//                else:
//                    instruction = "  " * indent + 'yield [("CALL_ARGS", (%s, (' % func_name + ",".join(param_list) + ',)))]'
//                """
//            if indent:
//                instruction += "\n"
//        elif inst_type["value"] == "access":
//            value, = yield [("RD", [inst, "var"])]
//            instruction, = yield [("CALL_ARGS", [self.print_instruction, (value, 0)])]
//        elif inst_type["value"] == "while":
//            cond, body = yield [("RD", [inst, "cond"]),
//                                ("RD", [inst, "body"])]
//            cond, body = yield [("CALL_ARGS", [self.print_instruction, (cond, 0)]),
//                                ("CALL_ARGS", [self.print_instruction, (body, indent+1)])]
//            instruction = "  " * indent + "while (%s):\n" % cond + body
//            #TODO fix for inline calls
//
//        next_inst, = yield [("RD", [inst, "next"])]
//        if next_inst:
//            next_inst, = yield [("CALL_ARGS", [self.print_instruction, (next_inst, indent)])]
//        else:
//            next_inst = ""
//
//        raise primitive_functions.PrimitiveFinished(instruction + next_inst)

String function get_indent(indentation : Integer):
	String code
	code = ""
	while (indentation > 0):
		code = code + "    "
		indentation = indentation - 1
	return code!

String function code_print(node : Element, indentation : Integer):
	String code
	String inst_type

	inst_type = cast_value(node)
	code = "(no_printer_for_" + inst_type + ")"

	if (inst_type == "if"):
		String cond_code
		String true_code
		String false_code

		cond_code = code_print(node["cond"], 0)
		true_code = code_print(node["then"], indentation + 1)
		if (dict_in(node, "else")):
			false_code = get_indent(indentation) + "else:\n"
			false_code = code_print(node["else"], indentation + 1)
		else:
			false_code = ""

		code = get_indent(indentation) + "if(" + cond_code + "):\n" + true_code + false_code
	elif (inst_type == "constant"):
		String value
		value = cast_value(node["node"])
		if (value == "true"):
			value = "True"
		elif (value == "false"):
			value = "False"
		code = get_indent(indentation) + value
	elif (inst_type == "return"):
		if (dict_in(node, "value")):
			code = get_indent(indentation) + "return " + code_print(node["value"], 0) + "\n"
		else:
			code = get_indent(indentation) + "return\n"
	elif (inst_type == "declare"):
		code = ""
	elif (inst_type == "resolve"):
		if (is_physical_string(node["var"])):
			code = cast_string(node["var"])
		else:
			code = "var_" + cast_id(node["var"])
	elif (inst_type == "assign"):
		code = get_indent(indentation) + code_print(node["var"], 0) + " = " + code_print(node["value"], 0) + "\n"
	elif (inst_type == "access"):
		code = code_print(node["var"], indentation)
	elif (inst_type == "while"):
		code = get_indent(indentation) + "while (" + code_print(node["cond"], 0) + "):\n" + code_print(node["body"], indentation + 1)
	elif (inst_type == "call"):
		String func_name
		String params

		params = ""
		func_name = code_print(node["func"], 0)

		Element param
		param = node["params"]
		Boolean continue
		continue = True
		while (continue):
			if (params == ""):
				params = code_print(param["value"], 0)
			else:
				params = params + ", " + code_print(param["value"], 0)
			if (bool_not(dict_in(param, "next_param"))):
				continue = False
			param = param["next_param"]

		code = func_name + "(" + params + ")"
		if (indentation > 0):
			code = get_indent(indentation) + code + "\n"

	if (dict_in(node, "next")):
		code = code + code_print(node["next"], indentation)

	return code!

Boolean function main(model : Element):
	// Read out the main function
	log("Start up MAIN function")
	String al_node
	Element function_element
	String code

	Element funcdefs
	String function
	funcdefs = allInstances(model, "AL/funcdef")
	code = ""
	while (set_len(funcdefs) > 0):
		// Iterate over all functions
		function = set_pop(funcdefs)

		log("Starting at function " + function)
		log("Got keys: " + set_to_string(dict_keys(model["model"][function])))
		log("Parameters:")
		Element params
		String param
		params = dict_keys(model["model"][function]["params"])
		code = code + "def func_" + cast_id(model["model"][function]) + "("
		while (set_len(params) > 0):
			param = set_pop(params)
			log("  " + param + " --> " + cast_id(model["model"][function]["params"][param]))
			code = code + "var_" + cast_id(model["model"][function]["params"][param])
			if (set_len(params) > 0):
				code = code + ", "
		code = code + "):\n"
			
		function_element = model["model"][function]["body"]
		al_node = cast_value(function_element)
		while (cast_value(function_element) == "global"):
			log("Assigning to " + cast_value(function_element["next"]["var"]["var"]))
			log("  the element: " + cast_id(function_element["next"]["value"]["node"]))
			log("  keys: " + set_to_string(dict_keys(function_element["next"]["value"]["node"])))
			log("Function is element: " + cast_id(function_element["next"]["value"]["node"]))
			function_element = function_element["next"]["next"]
		log("Started execution at " + cast_value(function_element))
		log("Started REAL execution at " + cast_value(function_element))
		
		code = code + code_print(function_element, 1)

	log("Finding initial function...")
	function = set_pop(allInstances(model, "AL/Initial"))
	function = set_pop(allAssociationDestinations(model, function, "AL/initial_funcdef"))
	log("Initial function: " + cast_id(model["model"][function]))
	code = code + "main = func_" + cast_id(model["model"][function]) + "\n"

	// Found the main function, though we only have the name of the function to assign...
	// What we can do is reassign main to that function!

	log("Generated code:")
	log(code)

	log("Function block can now be invoked by calling the 'main' function!")
	return True!
