include "primitives.alh"
include "modelling.alh"
include "object_operations.alh"
include "random.alh"
include "utils.alh"

Boolean function main(model : Element):
	String result
	Element nodes
	String node
	Element edges
	String edge
	String new_node
	String source
	String destination
	String name
	Element to_explore
	Element rules
	String rule
	String value
	Element explored
	Element remainder_to_explore
	String attr

	explored = set_create()
	result = ""

	// Add the preamble...
	result = result + "import modelverse_kernel.primitives as primitive_functions\n"
	result = result + "\n"
	result = result + "class ModelverseKernel(object):\n"
	result = result + "    def __init__(self, root):\n"
	result = result + "        self.root = root\n"
	result = result + "        self.primitives = {}\n"
	result = result + "        self.returnvalue = None\n"
	result = result + "        self.success = True\n"
	result = result + "        self.generators = {}\n"
	result = result + "\n"
    result = result + "    def set_overlap(self, lst):\n"
    result = result + "        s = set(lst.pop())\n"
    result = result + "        while (lst):\n"
    result = result + "            s &= set(lst.pop())\n"
    result = result + "        return list(s)\n"
	result = result + "\n"
	result = result + "    def execute_yields(self, taskname, operation, params, reply):\n"
	result = result + "        try:\n"
	result = result + "            self.success = True\n"
	result = result + "            self.taskname = taskname\n"
	result = result + "            if taskname not in self.generators:\n"
	result = result + "                self.generators[taskname] = {}\n"
	result = result + "            if operation not in self.generators[taskname]:\n"
	result = result + "                # Create the generator for the function to execute\n"
	result = result + "                self.generators[taskname][operation] = getattr(self, operation)(taskname, *params)\n"
	result = result + "\n"
	result = result + "            if reply is not None:\n"
	result = result + "                return self.generators[taskname][operation].send(reply)\n"
	result = result + "            else:\n"
	result = result + "                return self.generators[taskname][operation].next()\n"
	result = result + "        except StopIteration:\n"
	result = result + "            # Done, so remove the generator\n"
	result = result + "            del self.generators[taskname][operation]\n"
	result = result + "            return None\n"
	result = result + "        except:\n"
	result = result + "            raise\n"
	result = result + "\n"
	result = result + "    ##########################\n"
	result = result + "    ### Process primitives ###\n"
	result = result + "    ##########################\n"
	result = result + "    def load_primitives(self, taskname):\n"
	result = result + "        hierarchy, =     yield [('RD', [self.root, '__hierarchy'])]\n"
	result = result + "        primitives, =    yield [('RD', [hierarchy, 'primitives'])]\n"
	result = result + "        keys, =          yield [('RDK', [primitives])]\n"
	result = result + "        function_names = yield [('RV', [f]) for f in keys]\n"
	result = result + "        signatures  =    yield [('RDN', [primitives, f]) for f in keys]\n"
	result = result + "        bodies =         yield [('RD', [f, 'body']) for f in signatures]\n"
	result = result + "        for i in range(len(keys)):\n"
	result = result + "            self.primitives[bodies[i]] = getattr(primitive_functions, function_names[i])\n"
	result = result + "\n"
	result = result + "    ########################################\n"
	result = result + "    ### Execute input and output methods ###\n"
	result = result + "    ########################################\n"
	result = result + "    def get_output(self, taskname):\n"
	result = result + "        task_root, =        yield [('RD', [self.root, taskname])]\n"
	result = result + "        first_output, =     yield [('RD', [task_root, 'output'])]\n"
	result = result + "        next_output, rv =   yield [('RD', [first_output, 'next']),\n"
	result = result + "                                   ('RD', [first_output, 'value']),\n"
	result = result + "                                  ]\n"
	result = result + "        if next_output is None:\n"
	result = result + "            self.success = False\n"
	result = result + "            self.returnvalue = None\n"
	result = result + "        else:\n"
	result = result + "            rv_value, =     yield [('RV', [rv])]\n"
	result = result + "            _, _ =          yield [('CD', [task_root, 'output', next_output]),\n"
	result = result + "                                   ('DN', [first_output]),\n"
	result = result + "                                  ]\n"
	result = result + "            self.returnvalue = rv_value\n"
	result = result + "\n"
	result = result + "    def set_input(self, taskname, value):\n"
	result = result + "        task_root, =        yield [('RD', [self.root, taskname])]\n"
	result = result + "        old_input, link =   yield [('RD', [task_root, 'last_input']),\n"
	result = result + "                                   ('RDE', [task_root, 'last_input']),\n"
	result = result + "                                  ]\n"
	result = result + "        new_input, =        yield [('CN', [])]\n"
	result = result + "        _, _ =              yield [('CD', [task_root, 'last_input', new_input]),\n"
	result = result + "                                   ('CD', [old_input, 'next', new_input]),\n"
	result = result + "                                  ]\n"
	result = result + "\n"
	result = result + "        new_value, =        yield [('CNV', [value])]\n"
	result = result + "        _, _ =              yield [('CD', [old_input, 'value', new_value]),\n"
	result = result + "                                   ('DE', [link])\n"
	result = result + "                                  ]\n"
	result = result + "        self.returnvalue = {'id': 100, 'value': 'success'}\n"
	result = result + "\n"
	result = result + "    ### Generated rules\n"
	result = result + "    def execute_rule(self, taskname):\n"

	// Done, now generate the variable code
	result = result + "        root, = yield [('RR', [])]\n"
	nodes = allInstances(model, "Rules/Root")
	while (set_len(nodes) > 0):
		node = set_pop(nodes)
		source = string_replace(node, "/", "_")
		result = result + "        " + source + " = root\n"

		// Keep following outgoing edges to find matching nodes
		to_explore = set_create()
		remainder_to_explore = set_create()
		set_add(to_explore, node)

		while (set_len(to_explore) > 0):
			// Still explore more!
			node = set_pop(to_explore)
			source = string_replace(node, "/", "_")

			edges = allOutgoingAssociationInstances(model, node, "Rules/MatchEdge")
			while (set_len(edges) > 0):
				edge = set_pop(edges)
				new_node = readAssociationDestination(model, edge)
				if (value_eq(read_attribute(model, new_node, "match"), True)):
					// Is a match node, so fetch the value on the edge
					name = read_attribute(model, edge, "value")
					destination = string_replace(new_node, "/", "_")

					if (element_eq(name, read_root())):
						String node_key
						node_key = string_replace(set_pop(allAssociationDestinations(model, edge, "")), "/", "_")
						if (set_in(explored, node_key)):
							result = result + "        " + destination + ", = yield [('RDN', [" + source + ", " + node_key + "])]\n"
						else:
							set_add(remainder_to_explore, node)
					else:
						if (set_in(explored, destination)):
							// Already visited this one in another way, so try to merge!
							String rand
							rand = random_string(10)
							result = result + "        " + rand + ", = yield [('RD', [" + source + ", " + name + "])]\n"
							result = result + "        " + "if " + rand + " != " + destination + ":\n"
							// If the values don't agree, this is not a correct match, and we say that this element remains unmatched (i.e., assign None)
							result = result + "            " + destination + " = None\n"
						else:
							// First visit to this element, so just assign
							result = result + "        " + destination + ", = yield [('RD', [" + source + ", " + name + "])]\n"
							set_add(explored, destination)

							String value
							value = read_attribute(model, new_node, "value")
							if (element_neq(value, read_root())):
								// Match node has a value we should compare as well!
								// Read out the value from the Modelverse
								result = result + "        " + destination + "_V, = yield [('RV', [" + destination + "])]\n"

							set_add(to_explore, new_node)

			if (bool_and(set_len(to_explore) == 0, set_len(remainder_to_explore) > 0)):
				to_explore = remainder_to_explore
				remainder_to_explore = set_create()

	rules = allInstances(model, "Rules/Rule")
	result = result + "        " + "if (False):\n"
	result = result + "            # here to make code generation nicer...\n"
	result = result + "            pass\n"
	while (set_len(rules) > 0):
		// Check if this rule is applicable
		rule = set_pop(rules)

		// Fetch all elements with a "match" label and check that they are not None (and possibly, that they have a value)
		result = result + "        elif (True "
		nodes = allAssociationDestinations(model, rule, "Rules/contains")
		while (set_len(nodes) > 0):
			node = set_pop(nodes)
			if (value_eq(read_attribute(model, node, "match"), True)):
				// We should match on this node:
				value = read_attribute(model, node, "value")
				if (bool_and(read_type(model, node) == "Rules/NAC", element_eq(value, read_root()))):
					result = result + " and " + string_replace(node, "/", "_") + " is None "
				else:
					result = result + " and " + string_replace(node, "/", "_") + " is not None "

				if (element_neq(value, read_root())):
					// Got a value, so match that as well
					// But check if it is an action element (first character == !)
					String sign

					if (read_type(model, node) == "Rules/NAC"):
						sign = "!="
					else:
						sign = "=="

					if (string_get(value, 0) == "!"):
						result = result + " and isinstance(" + string_replace(node, "/", "_") + "_V , dict) and " + string_replace(node, "/", "_") + "_V['value'] " + sign + " '" + string_replace(value, "!", "") + "'"
					elif (bool_or(bool_or(string_get(value, 0) == "'", string_get(value, 0) == "!"), bool_or(value == "True", value == "False"))):
						result = result + " and " + string_replace(node, "/", "_") + "_V " + sign + " " + value

		result = result + "):\n"
		result = result + "            # Execute rule " + rule + "\n"

		// We know all the nodes that we already have (in variable "explored")
		// Still have to match all "match" and "delete" elements
		// For "match", it is exactly the same as before
		// For "delete" edges, we match using "RD" and "RDE"
		Element create_edges
		Element all_nodes
		Element assigned

		log("Matching rule " + rule)
		nodes = allAssociationDestinations(model, rule, "Rules/contains")
		all_nodes = set_overlap(nodes, allInstances(model, "Rules/Match"))

		create_edges = set_create()
		explored = set_create()
		assigned = set_create()
		// Username is always assigned in the beginning
		set_add(assigned, "username")
		set_add(assigned, "taskname")
		while (set_len(nodes) > 0):
			node = set_pop(nodes)
			if (read_type(model, node) == "Rules/Root"):
				// Found the root of this rule, so start exploring again
			
				// Keep following outgoing edges to find matching nodes
				to_explore = set_create()
				remainder_to_explore = set_create()
				set_add(to_explore, node)

				while (set_len(explored) < set_len(all_nodes)):
					while (set_len(to_explore) > 0):
						// Still explore more!
						node = set_pop(to_explore)
						log("Explore " + node)
						set_add(explored, node)
						source = string_replace(node, "/", "_")

						edges = allOutgoingAssociationInstances(model, node, "Rules/MatchEdge")
						while (set_len(edges) > 0):
							edge = set_pop(edges)
							new_node = readAssociationDestination(model, edge)

							name = read_attribute(model, edge, "value")
							destination = string_replace(new_node, "/", "_")

							if (element_eq(name, read_root())):
								String node_key
								node_key = set_pop(allAssociationDestinations(model, edge, ""))
								if (set_in(explored, node_key)):
									result = result + "            " + destination + ", = yield [('RDN', [" + source + ", " + string_replace(node_key, "/", "_") + "])]\n"
									set_add(to_explore, new_node)
								else:
									set_add(remainder_to_explore, node)
									continue!

								if (read_type(model, edge) == "Rules/DeleteEdge"):
									// Delete edge
									result = result + "            " + destination + "_DEL, = yield [('RDNE', [" + source + ", " + string_replace(node_key, "/", "_") + "])]\n"
									result = result + "            yield [('DE', [" + destination + "_DEL])]\n"
							else:
								if (bool_or(string_get(name, 0) == "'", set_in(assigned, name))):
									if (set_in(explored, new_node)):
										// Already visited this one in another way, so try to merge!
										String rand
										rand = random_string(10)
										result = result + "            " + rand + ", = yield [('RD', [" + source + ", " + name + "])]\n"
										result = result + "            " + "if " + rand + " != " + destination + ":\n"
										// If the values don't agree, this is not a correct match, and we say that this element remains unmatched (i.e., assign None)
										result = result + "            " + "    " + destination + " = None\n"
									else:
										// First visit to this element, so just assign
										result = result + "            " + destination + ", = yield [('RD', [" + source + ", " + name + "])]\n"

										String value
										value = read_attribute(model, new_node, "value")
										if (element_neq(value, read_root())):
											// Read out the value from the Modelverse, as this can be used later for reading edges
											if (bool_and(bool_and(string_get(value, 0) != "!", string_get(value, 0) != "'"), bool_and(value != "False", value != "True"))):
												result = result + "            " + value + ", = yield [('RV', [" + destination + "])]\n"
												set_add(assigned, value)

										set_add(to_explore, new_node)

									if (read_type(model, edge) == "Rules/DeleteEdge"):
										// Delete edge
										result = result + "            " + destination + "_DEL, = yield [('RDE', [" + source + ", " + name + "])]\n"
										result = result + "            yield [('DE', [" + destination + "_DEL])]\n"
								else:
									set_add(remainder_to_explore, node)

						if (bool_and(set_len(to_explore) == 0, set_len(remainder_to_explore) > 0)):
							to_explore = remainder_to_explore
							remainder_to_explore = set_create()

					// Check for nodes that are unexplored, but seemingly not reachable from the root directly
					Element remaining
					String check_elem
					Boolean found
					if (set_len(explored) < set_len(all_nodes)):
						// Find all unexplored nodes
						remaining = set_difference(all_nodes, explored)
						log("Remain unexplored: " + set_to_string(remaining))
						while (set_len(remaining) > 0):
							check_elem = set_pop(remaining)

							edges = allOutgoingAssociationInstances(model, check_elem, "Rules/MatchEdge")
							found = False
							while (set_len(edges) > 0):
								edge = set_pop(edges)
								new_node = readAssociationDestination(model, edge)
								name = read_attribute(model, edge, "value")
								if (set_in(explored, new_node)):
									// We already explored this node, so we can do a RRD from this place!
									if (bool_not(found)):
										result = result + "            matched = []\n"
									result = result + "            _tmp, = yield [('RRD', [" + string_replace(new_node, "/", "_") + ", " + name + "])]\n"
									result = result + "            matched.append(_tmp)\n"
									found = True

							// Iterated over all edges, so check how many we found!
							if (found):
								set_add(to_explore, check_elem)
								set_add(explored, check_elem)
								// Found at least one match, so we try to find an overlapping node
								result = result + "            " + string_replace(check_elem, "/", "_") + " = self.set_overlap(matched)[0]\n"

			elif (read_type(model, node) == "Rules/Create"):
				// A node that we should create: do that already, as otherwise we might have to sort
				String attr
				attr = read_attribute(model, node, "value")
				if (element_eq(attr, read_root())):
					// Create empty node
					result = result + "            " + string_replace(node, "/", "_") + ", = yield [('CN', [])]\n"
				else:
					// Create value node
					result = result + "            " + string_replace(node, "/", "_") + ", = yield [('CNV', [" + cast_string(attr) + "])]\n"
			elif (read_type(model, node) == "Rules/CreateEdge"):
				// Encounter a create edge, so alreade record this for later
				set_add(create_edges, node)

		// Now create all the new edges, given that we assume everything to be matched
		while (set_len(create_edges) > 0):
			edge = set_pop(create_edges)
			attr = read_attribute(model, edge, "value")
			if (element_neq(attr, read_root())):
				result = result + "            " + string_replace(edge, "/", "_") + ", = yield [('CD', [" + string_replace(readAssociationSource(model, edge), "/", "_") + ", " + attr + ", " + string_replace(readAssociationDestination(model, edge), "/", "_") + "])]\n"
			else:
				result = result + "            " + string_replace(edge, "/", "_") + ", = yield [('CE', [" + string_replace(readAssociationSource(model, edge), "/", "_") + ", " + string_replace(readAssociationDestination(model, edge), "/", "_") + "])]\n"
		
	result = result + "        else:\n"
	result = result + "            task_root, = yield [('RD', [root, taskname])]\n"
	result = result + "            task_frame, = yield [('RD', [task_root, 'frame'])]\n"
	result = result + "            task_IP, = yield [('RD', [task_frame, 'IP'])]\n"
	result = result + "            task_phase, = yield [('RD', [task_frame, 'phase'])]\n"
	result = result + "            task_phase, = yield [('RV', [task_phase])]\n"
	result = result + "            if (task_IP is not None and task_IP in self.primitives and task_phase == 'init'):\n"
	result = result + "                # Execute a primitive!\n"
	result = result + "                symbols, =       yield [('RD', [task_frame, 'symbols'])]\n"
	result = result + "                all_links, =     yield [('RO', [symbols])]\n"
	result = result + "                containers =    yield [('RE', [v]) for v in all_links]\n"
	result = result + "                outgoings =     yield [('RO', [v]) for v in all_links]\n"
	result = result + "                dict_values =   yield [('RD', [v[1], 'value']) for v in containers]\n"
	result = result + "                formals_1 =     yield [('RE', [v[0]]) for v in outgoings]\n"
	result = result + "                dict_keys_ref = yield [('RD', [v[1], 'name']) for v in formals_1]\n"
	result = result + "                dict_keys =     yield [('RV', [v]) for v in dict_keys_ref]\n"
	result = result + "                parameters = dict(zip(dict_keys, dict_values))\n"
	result = result + "                parameters['root'] = root\n"
	result = result + "                parameters['task_root'] = task_root\n"
	result = result + "                parameters['taskname'] = taskname\n"
	result = result + "                parameters['mvk'] = self\n"
	result = result + "                # prim is a generator itself!\n"
	result = result + "                try:\n"
	result = result + "                    # Forward the message we get to this generator\n"
	result = result + "                    # Sometimes it might not even be a generator, in which case this should already be in the except block (i.e., for the Read Root operation)\n"
	result = result + "                    prim = self.primitives[task_IP](**parameters)\n"
	result = result + "                    inp = None\n"
	result = result + "                    while 1:\n"
	result = result + "                        inp = yield prim.send(inp)\n"
	result = result + "                except StopIteration:\n"
	result = result + "                    # Execution has ended without return value, so we have no idea what to do\n"
	result = result + "                    raise Exception('Primitive finished without returning a value!')\n"
	result = result + "                except primitive_functions.PrimitiveFinished as e:\n"
	result = result + "                    # Execution has ended with a returnvalue, so read it out from the exception being thrown\n"
	result = result + "                    result = e.result\n"
	result = result + "                    #if result is None:\n"
	result = result + "                    #    raise Exception('Primitive raised exception: value of None for operation %s with parameters %s' % (self.compiled[task_IP], str(parameters)))\n"
	result = result + "                # Clean up the current stack, as if a return happened\n"
	result = result + "                old_frame, =    yield [('RD', [task_frame, 'prev'])]\n"
	result = result + "                lnk, =          yield [('RDE', [old_frame, 'returnvalue'])]\n"
	result = result + "                _, _, _, _ =    yield [('CD', [old_frame, 'returnvalue', result]),\n"
	result = result + "                                       ('CD', [task_root, 'frame', old_frame]),\n"
	result = result + "                                       ('DE', [lnk]),\n"
	result = result + "                                       ('DN', [task_frame]),\n"
	result = result + "                                      ]\n"
	result = result + "\n"
	result = result + "            else:\n"
	result = result + "                # no rules were applicable, so idle for some time\n"
	result = result + "                pass #TODO\n"
	result = result + "                raise primitive_functions.SleepKernel(1.0, True)\n"

	log("Got result:")
	log(result)

	String file
	file = instantiate_node(model, "Files/File", "")
	instantiate_attribute(model, file, "name", "generated_kernel.py")
	instantiate_attribute(model, file, "content", result)

	return True!
