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

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

    bootstrap_files = glob.glob("bootstrap/*.alc")

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

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

    primitives = {  "integer_addition": ["Integer", "Integer", "Integer"],
                    "integer_subtraction": ["Integer", "Integer", "Integer"],
                    "integer_multiplication": ["Integer", "Integer", "Integer"],
                    "integer_division": ["Integer", "Integer", "Integer"],
                    "integer_gt": ["Boolean", "Integer", "Integer"],
                    "integer_lt": ["Boolean", "Integer", "Integer"],
                    "integer_neg": ["Integer", "Integer"],
                    "float_addition": ["Float", "Float", "Float"],
                    "float_subtraction": ["Float", "Float", "Float"],
                    "float_multiplication": ["Float", "Float", "Float"],
                    "float_division": ["Float", "Float", "Float"],
                    "float_gt": ["Boolean", "Float", "Float"],
                    "float_lt": ["Boolean", "Float", "Float"],
                    "float_neg": ["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"],
                    "value_neq": ["Boolean", "Element", "Element"],
                    "cast_i2f": ["Float", "Integer"],
                    "cast_i2s": ["String", "Integer"],
                    "cast_i2b": ["Boolean", "Integer"],
                    "cast_f2i": ["Integer", "Float"],
                    "cast_f2b": ["Boolean", "Float"],
                    "cast_f2s": ["String", "Float"],
                    "cast_s2i": ["Integer", "String"],
                    "cast_s2f": ["Float", "String"],
                    "cast_s2b": ["Boolean", "String"],
                    "cast_b2i": ["Integer", "Boolean"],
                    "cast_b2f": ["Float", "Boolean"],
                    "cast_b2s": ["String", "Boolean"],
                    "cast_e2s": ["String", "Element"],
                    "cast_a2s": ["String", "Action"],
                    "cast_v2s": ["String", "Element"],
                    "cast_id2s": ["String", "Element"],
                    "list_read": ["Element", "Element", "Integer"],
                    "list_append": ["Element", "Element", "Element"],
                    "list_insert": ["Element", "Element", "Integer", "Element"],
                    "list_delete": ["Element", "Element", "Integer"],
                    "list_len": ["Integer", "Element"],
                    "dict_add": ["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_len": ["Integer", "Element"],
                    "dict_in": ["Boolean", "Element", "Element"],
                    "dict_in_node": ["Boolean", "Element", "Element"],
                    "dict_keys": ["Element", "Element"],
                    "set_add": ["Element", "Element", "Element"],
                    "set_pop": ["Element", "Element"],
                    "set_remove": ["Element", "Element", "Element"],
                    "set_remove_node": ["Element", "Element", "Element"],
                    "set_in": ["Boolean", "Element", "Element"],
                    "set_in_node": ["Boolean", "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"],
                    "create_node": ["Element"],
                    "create_edge": ["Element", "Element", "Element"],
                    "create_value": ["Element", "Element"],
                    "is_edge": ["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"],
                    "element_neq": ["Boolean", "Element", "Element"],
                    "read_root": ["Element"],
                    "read_userroot": ["Element"],
                    "deserialize": ["Element", "String"],
                    "log": ["String", "String"],
                }

    initial_user = "user_manager"
    initial_user_code = \
    '''
    include "bootstrap/primitives.alc"
    include "user_manager.alh"

    Void function __main():
    \tElement root
    \troot = read_root()
    \troot = root["__hierarchy"]["objects"]
    \texec(root["bootstrap/user_manager.alc"]["initializers"])
    \tuser_management()
    '''

    code_new_users = \
    '''
    include "bootstrap/primitives.alc"
    include "user_interface.alh"

    Void function __main():
    \tElement root
    \troot = read_root()
    \troot = root["__hierarchy"]["objects"]
    \texec(root["bootstrap/compilation_manager.alc"]["initializers"])
    \texec(root["bootstrap/constructors.alc"]["initializers"])
    \texec(root["bootstrap/library.alc"]["initializers"])
    \texec(root["bootstrap/object_operations.alc"]["initializers"])
    \texec(root["bootstrap/conformance_scd.alc"]["initializers"])
    \texec(root["bootstrap/metamodels.alc"]["initializers"])
    \texec(root["bootstrap/modelling.alc"]["initializers"])
    \texec(root["bootstrap/user_interface.alc"]["initializers"])
    \tnew_user()
    '''

    ### 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):
            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("Edge _%s(root, %s)\n" % (node, node))
                    f.write('Node __%s("%s")\n' % (node, node))
                    f.write("Edge ___%s(_%s, __%s)\n" % (node, node, node))

                f.write("Node primitives()\n")
                f.write("Edge _primitives(__hierarchy, primitives)\n")
                f.write('Node __primitives("primitives")\n')
                f.write("Edge ___primitives(_primitives, __primitives)\n")

                # Define all primitive functions
                for function, parameters in primitives.iteritems():
                    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("Edge _primitives_%s(primitives, _func_signature_%s)\n" % (function, function))
                    f.write('Node _name_%s("%s")\n' % (function, function))
                    f.write("Edge _primitives_name_%s(_primitives_%s, _name_%s)\n" % (function, function, function))

                    f.write('Node _body_%s("body")\n' % function)
                    f.write("Edge _signature_body_%s(_func_signature_%s, _func_body_%s)\n" % (function, function, function))
                    f.write("Edge _signature_body_str_%s(_signature_body_%s, _body_%s)\n" % (function, function, function))

                    f.write('Node _params_%s("params")\n' % function)
                    f.write("Edge _signature_params_%s(_func_signature_%s, _func_params_%s)\n" % (function, function, function))
                    f.write("Edge _signature_params_str_%s(_signature_params_%s, _params_%s)\n" % (function, 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('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))

                # Create the initial user
                f.write("Node user_root()\n")

                for data in user_data:
                    f.write("Node user_%s()\n" % data)
                    f.write('Node ___user_%s("%s")\n' % (data, data))
                    f.write("Edge _user_%s(user_root, user_%s)\n" % (data, data))
                    f.write("Edge __user_%s(_user_%s, ___user_%s)\n" % (data, data, data))

                for data in user_frame:
                    f.write("Node user_%s()\n" % data)
                    f.write('Node ___user_%s("%s")\n' % (data, data))
                    f.write("Edge _user_%s(user_frame, user_%s)\n" % (data, data))
                    f.write("Edge __user_%s(_user_%s, ___user_%s)\n" % (data, data, data))

                # Add last_input and last_output links
                for data in ["input", "output"]:
                    f.write('Node ___user_last_%s("last_%s")\n' % (data, data))
                    f.write("Edge _user_last_%s(user_root, user_%s)\n" % (data, data))
                    f.write("Edge __user_last_%s(_user_last_%s, ___user_last_%s)\n" % (data, data, data))
                    
                # Bind user to the root
                f.write('Node ___new_user("%s")\n' % initial_user)
                f.write("Edge _new_user(root, user_root)\n")
                f.write("Edge __new_user(_new_user, ___new_user)\n")

                def compile_code_AL(code, target, prepend="", is_file=False, main=False, symbols=None):
                    import sys
                    sys.path.append("interface/HUTN/")
                    from hutn_compiler.compiler import main as compile_code

                    if not is_file:
                        f = tempfile.NamedTemporaryFile()
                        f.write(code)
                        f.flush()
                        filename = f.name
                    else:
                        filename = code
                    code = compile_code(filename, "interface/HUTN/grammars/actionlanguage.g", "BS", ["--debug", "--prepend:%s" % prepend, "--main" if main else "--not-main"], symbols=symbols)
                    if not is_file:
                        f.close()
                    return code.replace("auto_initial_IP", target)

                # Create all library code
                # But first create the structure to hold compiled data
                f.write("Node __objects()\n", both=False)
                f.write('Node __objects_name("objects")\n', both=False)
                f.write("Edge __obj_link(__hierarchy, __objects)\n", both=False)
                f.write("Edge _name_obj_link(__obj_link, __objects_name)\n", both=False)

                # Compile all files and add to structure manually
                for bootstrap_file in bootstrap_files:
                    # Compile the subfile
                    print("[COMP] %s" % bootstrap_file)
                    symbols = {}
                    result = compile_code_AL(bootstrap_file, "initial_IP", prepend=bootstrap_file, is_file=True, symbols=symbols)
                    f.write(result, both=False)

                    # Now link the code with the compilation manager structure
                    f.write("Node elem()\n", both=False)
                    f.write('Node initializers("initializers")\n', both=False)
                    f.write('Node hash("hash_md5")\n', both=False)
                    f.write("Edge _(__objects, elem)\n", both=False)
                    f.write('Node filename("%s")\n' % bootstrap_file, both=False)
                    f.write("Edge _(_, filename)\n", both=False)
                    f.write("Edge _(elem, %s_initial_IP)\n" % bootstrap_file, both=False)
                    f.write("Edge _(_, initializers)\n", both=False)
                    md5 = hashlib.md5()
                    md5.update(open(bootstrap_file, 'r').read())
                    f.write('Node hash_value("%s")\n' % md5.hexdigest(), both=False)
                    f.write("Edge _(elem, hash_value)\n", both=False)
                    f.write("Edge _(_, hash)\n", both=False)

                    f.write('Node symbols("symbols")\n', both=False)
                    f.write('Node __symbols()\n', both=False)
                    f.write('Edge _(elem, __symbols)\n', both=False)
                    f.write('Edge _(_, symbols)\n', both=False)

                    for k, v in symbols.items():
                        f.write('Node v(%s)\n' % v, both=False)
                        f.write('Node k("%s")\n' % k, both=False)
                        f.write('Edge _(__symbols, v)\n', both=False)
                        f.write('Edge _(_, k)\n', both=False)

                # Create code for initial user
                print("[BOOT] initial_user")
                f.write(compile_code_AL(initial_user_code, "initial_IP", prepend="user_manager", main=True), both=False)
                f.write('Node _IP_str("IP")\n', both=False)
                f.write("Edge _user_frame(user_frame, user_manager_initial_IP)\n", both=False)
                f.write("Edge __user_frame(_user_frame, _IP_str)\n", both=False)

                f.write('Node __phase("init")\n', both=False)
                f.write('Node __phase_str("phase")\n', both=False)
                f.write("Edge _user_phase(user_frame, __phase)\n", both=False)
                f.write("Edge __user_phase(_user_phase, __phase_str)\n", both=False)

                # Create code for new users to start at
                print("[BOOT] new_user")
                f.write(compile_code_AL(code_new_users, "initial_IP", prepend="new_user", main=True), both=False)
                f.write('Node __IP_str("__IP")\n', both=False)
                f.write("Edge _user_IP(__hierarchy, new_user_initial_IP)\n", both=False)
                f.write("Edge __user_IP(_user_IP, __IP_str)\n", both=False)
    except:
        os.remove("bootstrap/bootstrap.m.gz")
        os.remove("bootstrap/minimal.m.gz")
        raise
