### Configuration for creating the bootstrap model using conformance_bottom.
import glob
import hashlib
import tempfile
import gzip

def bootstrap():
    root = ["__hierarchy"]

    task_manager = "task_manager"
    main_file = "bootstrap/main.alc"
    primitive_file = "bootstrap/primitives.alc"

    task_data = [   "input",
                    "output",
                    "globals",
                    "frame",
                    ]

    task_frame = [  "evalstack",
                    "returnvalue",
                    "symbols",
                    ]

    primitives = {  "integer_addition": ["Integer", "Integer", "Integer"],
                    "integer_subtraction": ["Integer", "Integer", "Integer"],
                    "integer_multiplication": ["Integer", "Integer", "Integer"],
                    "integer_division": ["Integer", "Integer", "Integer"],
                    "integer_lt": ["Boolean", "Integer", "Integer"],
                    "float_addition": ["Float", "Float", "Float"],
                    "float_subtraction": ["Float", "Float", "Float"],
                    "float_multiplication": ["Float", "Float", "Float"],
                    "float_division": ["Float", "Float", "Float"],
                    "float_lt": ["Boolean", "Float", "Float"],
                    "bool_and": ["Boolean", "Boolean", "Boolean"],
                    "bool_or": ["Boolean", "Boolean", "Boolean"],
                    "bool_not": ["Boolean", "Boolean"],
                    "string_join": ["String", "String", "String"],
                    "string_get": ["String", "String", "Integer"],
                    "string_len": ["Integer", "String"],
                    "string_split": ["Element", "String", "String"],
                    "value_eq":  ["Boolean", "Element", "Element"],
                    "cast_float": ["Float", "Element"],
                    "cast_string": ["String", "Element"],
                    "cast_boolean": ["Boolean", "Element"],
                    "cast_integer": ["Integer", "Element"],
                    "cast_value": ["String", "Element"],
                    "cast_id": ["String", "Element"],
                    "dict_add_fast": ["Element", "Element", "Element", "Element"],
                    "dict_delete": ["Element", "Element", "Element"],
                    "dict_delete_node": ["Element", "Element", "Element"],
                    "dict_read": ["Element", "Element", "Element"],
                    "dict_read_edge": ["Element", "Element", "Element"],
                    "dict_read_node": ["Element", "Element", "Element"],
                    "dict_in": ["Boolean", "Element", "Element"],
                    "dict_in_node": ["Boolean", "Element", "Element"],
                    "dict_keys": ["Element", "Element"],
                    "is_physical_int": ["Boolean", "Element"],
                    "is_physical_boolean": ["Boolean", "Element"],
                    "is_physical_string": ["Boolean", "Element"],
                    "is_physical_action": ["Boolean", "Element"],
                    "is_physical_float": ["Boolean", "Element"],
                    "is_physical_none": ["Boolean", "Element"],
                    "create_node": ["Element"],
                    "create_edge": ["Element", "Element", "Element"],
                    "create_value": ["Element", "Element"],
                    "is_edge": ["Boolean", "Element"],
                    "is_error": ["Boolean", "Element"],
                    "read_nr_out": ["Integer", "Element"],
                    "read_out": ["Element", "Element", "Integer"],
                    "read_nr_in": ["Integer", "Element"],
                    "read_in": ["Element", "Element", "Integer"],
                    "read_edge_src": ["Element", "Element"],
                    "read_edge_dst": ["Element", "Element"],
                    "delete_element": ["Element", "Element"],
                    "element_eq": ["Boolean", "Element", "Element"],
                    "read_root": ["Element"],
                    "read_taskroot": ["Element"],
                    "list_sort": ["Element", "Element"],
                    "log": ["String", "String"],
                    "time": ["Float"],
                    "hash": ["String", "String"],
                    "__sleep": ["Float", "Float", "Boolean"],
                }

    jit_primitives = {
        "get_jit_enabled": ["Boolean"],
        "set_jit_enabled": ["Void", "Boolean"]
    }

    ### Actual script to generate the file
    import os
    import sys

    class Writer(object):
        def __init__(self, file_a, file_b):
            self.file_a = file_a
            self.file_b = file_b

        def write(self, text, both=True):

            if sys.version_info[0] > 2:
                text = text.encode()

            self.file_a.write(text)
            if both:
                self.file_b.write(text)

    try:
        with gzip.GzipFile("bootstrap/bootstrap.m.gz", "wb", mtime=0) as fa:
            with gzip.GzipFile("bootstrap/minimal.m.gz", "wb", mtime=0) as fb:
                f = Writer(fa, fb)
                # Create the root first
                f.write("Node root()\n")

                # Create all children of the root
                for node in root:
                    f.write("Node %s()\n" % node)
                    f.write("Dict (root, \"%s\", %s)\n" % (node, node))

                def declare_primitive_class(primitive_class_name, primitive_decls):
                    f.write("Node %s()\n" % primitive_class_name)
                    f.write("Dict (__hierarchy, \"%s\", %s)\n" % (primitive_class_name, primitive_class_name))

                    # Define all primitive functions
                    for function, parameters in list(primitive_decls.items()):
                        f.write("Node _func_signature_%s()\n" % function)
                        f.write("Node _func_params_%s()\n" % function)
                        f.write("Node _func_body_%s()\n" % function)

                        f.write('Dict (%s, "%s", _func_signature_%s)\n' % (primitive_class_name, function, function))
                        f.write('Dict (_func_signature_%s, "body", _func_body_%s)\n' % (function, function))
                        f.write('Dict (_func_signature_%s, "params", _func_params_%s)\n' % (function, function))

                        parameter_names = "abcdefghijklmnopqrstuvwxyz"
                        for number, param in enumerate(parameters[1:]):
                            param_encoding = "%s_%s" % (function, parameter_names[number])
                            f.write("Node _func_params_%s()\n" % (param_encoding))

                            f.write('Node _name_%s("%s")\n' % (param_encoding, parameter_names[number]))
                            #f.write("Edge _param_link_%s(_func_params_%s, _func_params_%s)\n" % (param_encoding, function, param_encoding))
                            #f.write("Edge _param_link_str_%s(_param_link_%s, _name_%s)\n" % (param_encoding, param_encoding, param_encoding))
                            f.write('Dict (_func_params_%s, "%s", _func_params_%s)\n' % (function, parameter_names[number], param_encoding))

                            #f.write('Node _name_str_%s("name")\n' % param_encoding)
                            #f.write("Edge _param_name_%s(_func_params_%s, _name_%s)\n" % (param_encoding, param_encoding, param_encoding))
                            #f.write("Edge _param_name_str_%s(_param_name_%s, _name_str_%s)\n" % (param_encoding, param_encoding, param_encoding))
                            f.write('Dict (_func_params_%s, "name", _name_%s)\n' % (param_encoding, param_encoding))

                declare_primitive_class('primitives', primitives)
                declare_primitive_class('jit', jit_primitives)

                # Create the initial task
                f.write("Node task_root()\n")

                for data in task_data:
                    f.write("Node task_%s()\n" % data)
                    f.write('Dict (task_root, "%s", task_%s)\n' % (data, data))

                for data in task_frame:
                    f.write("Node task_%s()\n" % data)
                    f.write('Dict (task_frame, "%s", task_%s)\n' % (data, data))

                # Add last_input and last_output links
                for data in ["input", "output"]:
                    f.write('Dict (task_root, "last_%s", task_%s)\n' % (data, data))
                    
                # Bind task to the root
                f.write('Dict (root, "%s", task_root)\n' % (task_manager))

                def compile_code_AL(filename, target, prepend="", main=False):
                    import sys
                    sys.path.append("interface/HUTN/")
                    from hutn_compiler.compiler import main as compile_code
                    code = compile_code(filename, "interface/HUTN/grammars/actionlanguage.g", "BS", ["--debug", "--prepend:%s" % prepend, "--main" if main else "--not-main"])
                    return code.replace("auto_initial_IP", target)

                # Create all library code
                def compile_code_MO(filename, model_name):
                    import sys
                    sys.path.append("interface/HUTN/")
                    from hutn_compiler.compiler import main as compile_code
                    model_code = compile_code(filename, "interface/HUTN/grammars/modelling_bootstrap.g", "MB", ["--modelname:%s" % model_name])
                    return model_code + "\n"

                # Compile all model definitions to ALC directly
                total_alc = []
                binding_alc = 'Void function initialize_MMs():\n\tinitialize_SCD("models/SimpleClassDiagrams")\n'

                bootstrap_models = sorted(glob.glob("bootstrap/*.mvc"))
                for bootstrap_model in bootstrap_models:
                    # Compile the subfile
                    bootstrap_model = bootstrap_model.replace("\\", "/")
                    model_name = bootstrap_model.rsplit(".mvc", 1)[0].rsplit("/", 1)[1]
                    print("[MVC] %s" % model_name)
                    alc = compile_code_MO(bootstrap_model, model_name)
                    total_alc.append(alc)
                    binding_alc += "\tinitialize_%s()\n" % model_name
                total_alc = "".join(total_alc)

                # Write out the ALC to a new .metamodels.alc
                binding_alc += "\treturn!\n"
                new_metamodels_alc = open("bootstrap/metamodels.alt", 'r').read() + total_alc + binding_alc

                # Now overwrite the .metamodels.alc file
                with open("bootstrap/.metamodels.alc", "w") as mm:
                    mm.write(new_metamodels_alc)

                # Compile all files and add to structure manually
                bootstrap_files = sorted(glob.glob("bootstrap/*.alc") + glob.glob("bootstrap/.*.alc"))
                for bootstrap_file in bootstrap_files:
                    # Compile the subfile
                    bootstrap_file = bootstrap_file.replace("\\", "/")
                    print("[ALC] %s" % bootstrap_file)
                    f.write(compile_code_AL(bootstrap_file, "initial_IP", prepend=bootstrap_file, main=bootstrap_file==main_file), both=False)

                    # TODO all assigns stored in:
                    #       bootstrap_file + "_initial_IP"

                    # Now link the code with the compilation manager structure

                # Stitch all IPs together
                print("[LINK]")
                counter = 0
                f.write('Node true(constant)\n', both=False)
                f.write('Node t(True)\n', both=False)
                f.write('Dict (true, "node", t)\n', both=False)

                first = True
                for bootstrap_file in [primitive_file] + [i for i in bootstrap_files if i not in [main_file, primitive_file]]:
                    f.write('Node _if_%s(if)\n' % counter, both=False)
                    f.write('Dict (_if_%s, "cond", true)\n' % counter, both=False)
                    f.write('Dict (_if_%s, "then", %s_initial_IP)\n' % (counter, bootstrap_file), both=False)

                    if first:
                        first = False
                    else:
                        f.write('Dict (%s, "next", _if_%s)\n' % (prev, counter), both=False)

                    prev = "_if_%s" % counter
                    counter += 1

                f.write('Dict (%s, "next", %s_initial_IP)\n' % (prev, main_file), both=False)

                # Create code for initial task
                print("[BOOT] task_manager")
                f.write('Dict (task_frame, "IP", _if_0)\n', both=False)

                f.write('Node __phase("init")\n', both=False)
                f.write('Dict (task_frame, "phase", __phase)\n', both=False)

                # Create code for new tasks to start at
                print("[BOOT] new_task")
                f.write('Dict (__hierarchy, "__IP", _if_0)\n', both=False)
    except:
        os.remove("bootstrap/bootstrap.m.gz")
        os.remove("bootstrap/minimal.m.gz")
        raise
