def main():

    print logo
    os.system("sleep 1")

    print "~~~ Compiler Initiated ~~~\n\n"
    # -----------------------------------------------------------------------------#
    # Initial checks:                                                             #
    #  * Arugment list                                                            #
    #  * Compilation Tools                                                        #
    # -----------------------------------------------------------------------------#
    # Check for Jam build systyem
    execute_cmd("jam -v")

    # Check command line argument
    num_args = len(sys.argv)

    # Check to make sure that there are enough args
    if num_args >= 2:
        # Save filenames
        SRC_FILE_PATH = sys.argv[1]
        # Check if valid C file supplied
        match = re.search("\.c$", os.path.basename(SRC_FILE_PATH))
        if match > -1:
            pass
        else:
            print "Invalid C source file supplied (Missing '.c' extension)."
            sys.exit(2)
    else:
        # Insufficient number of args, exit with error
        print "Incorrect argument usage!! Aborting..."
        print "Correct usage :\n    ./hcompile.py <src>\n"
        sys.exit(1)

    print "\t-----------------------------------"
    print "\t|+ Checking if source file exists |"
    print "\t-----------------------------------"
    assertCheck(checkInput(SRC_FILE_PATH))
    print "\t\t...Done"

    # -----------------------------------------------------------------------------#
    # Copy selected program to host build directory and slave build directory.    #
    # Also, extract program name and generate header file.                        #
    # -----------------------------------------------------------------------------#
    print "\t-----------------------------------------------------------"
    print "\t|+ Copying selected source file into host build directory |"
    print "\t-----------------------------------------------------------"
    cmd_list = []
    execute_cmd(copy + SRC_FILE_PATH + " " + host_build_dir)
    print "\t\t...Done"
    print "\t------------------------------------------------------------"
    print "\t|+ Copying selected source file into slave build directory |"
    print "\t------------------------------------------------------------"
    execute_cmd(copy + SRC_FILE_PATH + " " + hetero_build_dir)
    print "\t\t...Done"

    print "\t------------------------------------------------------"
    print "\t|+ Generating header file name for input source file |"
    print "\t------------------------------------------------------"
    # Extract just the source name
    SRC_FILE = os.path.basename(SRC_FILE_PATH)

    # Save the original source path without the name of source file
    # This will be used to copy the generated header file to this path
    # in case the user wants to view the generated header file.
    OLD_SRC_FILE_PATH = os.path.dirname(SRC_FILE_PATH)

    # Now modify the source path to reflect where we copied it above
    SRC_FILE_PATH = host_build_dir + SRC_FILE

    # Form what will be the the name of the associated header file
    # in order to clean up at the end of hcompile
    HEADER_FILE_PATH = re.sub("\.c$", EMBED_EXTENSION, SRC_FILE_PATH)

    # Remove and create empty header file
    execute_cmd("rm -f " + HEADER_FILE_PATH + " && touch " + HEADER_FILE_PATH)

    # At the end of hcompile, remove the copied sources and the associated header file
    print "\t\t ...Done\n"

    # -----------------------------------------------------------------------------#
    # Appending "int main() { return 0; }" into source file for the user.         #
    # -----------------------------------------------------------------------------#
    print "\t---------------------------------"
    print "\t|+ Appending to HAL source file |"
    print "\t---------------------------------"
    cmd = "echo -e 'int main() {\n\treturn 0;\n}' >> " + hetero_build_dir + "/" + SRC_FILE
    status = execute_cmd(cmd)
    if status != 0:
        print "\t\t -> Unable to append 'int main()...' to HAL source ..."
        sys.exit(1)
    print "\t\t -> Appended to HAL Source File Successfully."
    print "\t\t...Done"

    # -----------------------------------------------------------------------------#
    # Read in selected platform, update the HAL config/settings, and copy         #
    # platform into HAL platform folders.                                         #
    # -----------------------------------------------------------------------------#
    print "\t-----------------------------------------"
    print "\t|+ Reading in Platform Selected By User |"
    print "\t-----------------------------------------"
    platform_name = read_config_settings("PLATFORM_BOARD", "config/settings")
    print "\t\tPlatform = " + platform_name + "\n"

    print "\t-------------------------------------------------------------"
    print "\t|+ Updating HAL config/settings file with selected platform |"
    print "\t-------------------------------------------------------------"
    write_config_settings("PLATFORM_BOARD", platform_name, hetero_root_dir + "/config/settings")
    print "\t\t...Done"

    print "\t-----------------------------------------------------"
    print "\t|+ Copying platform to the slave's build directory. |"
    print "\t-----------------------------------------------------"
    execute_cmd(mkdir + hetero_platform_dir + platform_name + "/config")
    execute_cmd(mkdir + hetero_platform_dir + platform_name + "/design")
    execute_cmd(mkdir + hetero_platform_dir + platform_name + "/include")

    execute_cmd(
        copy + host_platform_dir + platform_name + "/include/* " + hetero_platform_dir + platform_name + "/include/."
    )
    execute_cmd(
        copy
        + host_platform_dir
        + platform_name
        + "/design/Jamfile "
        + hetero_platform_dir
        + platform_name
        + "/design/."
    )
    execute_cmd(
        copy + host_platform_dir + platform_name + "/config/* " + hetero_platform_dir + platform_name + "/config/."
    )
    print "\t\t...Done"

    # -----------------------------------------------------------------------------#
    # Read hardware description file from selected platform to determine          #
    # parameters such as:                                                         #
    #  * number of processors                                                     #
    #  * ISA for each processor                                                   #
    #  * compiler flags                                                           #
    # -----------------------------------------------------------------------------#
    print "\t----------------------------------------------"
    print "\t|+ Loading details for the selected platform |"
    print "\t----------------------------------------------"
    platform_path = host_platform_dir + platform_name
    # Extract the hardware description file
    check, hw_file_path = get_hardware_file(platform_path)
    assertCheck(check)

    # Get number of processors
    PROCESSORS = get_processors(hw_file_path)
    print "\t\tFound " + str(len(PROCESSORS)) + " processors"
    for index, processor in enumerate(PROCESSORS):
        print "\t\t\t " + str(index) + ": " + processor["NAME"]
    print "\n"

    if len(PROCESSORS) == 1:
        print "\n\n\tPlatform: " + platform_name + " only contains one processor!"
        print "\tYou should run 'jam' only TO COMPILE!"
        os.system("sleep 1")
        sys.exit(1)

    print "\t--------------------------------------------------"
    print "\t|+ Determining compiler flags for each processor |"
    print "\t--------------------------------------------------"
    # Determine Compiler flags for each processor
    COMPILER_FLAGS = get_compiler_flags(PROCESSORS)
    print "\t\t...Done"

    # -----------------------------------------------------------------------------#
    # Determine the targetted ISA for the host.                                   #
    # * If no processor's name matches 'host', default to first processor's ISA   #
    # * Switch to Hthreads naming convention using support_arch dictionary at top #
    #   of the file.                                                              #
    # -----------------------------------------------------------------------------#
    host_compilation_isa = None
    host_index = 0
    print "\t----------------------------------------"
    print "\t|+ Determining ISA for Host processor. |"
    print "\t----------------------------------------"
    for index, processor in enumerate(PROCESSORS):
        if processor["NAME"] == host_name:
            host_index = index
            host_compilation_isa = processor["HTHREADS_ISA"]
            break

    if host_compilation_isa == None:
        print "\t\t+-+-+-+-+-+-+-+"
        print "\t\t|W|A|R|N|I|N|G|"
        print "\t\t+-+-+-+-+-+-+-+"
        print "\t\tDid not find host processor under the name: " + host_name
        print "\t\tDefaulting to processor: " + PROCESSORS[0]["NAME"]
        host_compilation_isa = PROCESSORS[0]["HTHREADS_ISA"]

    # Remove host processor from list of all processors
    host_processor = PROCESSORS.pop(host_index)

    # Remove host processor's compiler flags from list of compiler flags
    host_flags = COMPILER_FLAGS.pop(host_index)

    # Add HEADERFILE_ISA parameter for the host
    modified_processor_parameters = add_processor_parameter(host_processor, "HEADERFILE_ISA", "TYPE_HOST")
    host_processor = modified_processor_parameters

    # -----------------------------------------------------------------------------#
    # Determine similar processors based on compiler flags & linkerscript.        #
    # * Since the processor list (compiler flags list, etc) is ordered the same,  #
    #   there is no need to check for similar processors once I have come to the  #
    #   point in the nested loops where inner_index == outer_index. Therefore,    #
    #   the first slave processor will break out immediately which makes sense as #
    #   there won't be any previously built slave processor images --yes, ELF     #
    #   images are built/examined in the same order.                              #
    # - - - - - - - - - - - - - - - -  - - - - - - - - - - - - - - - - - - - - - - #
    # Also, create a new ISA each time a processor is found to have no similar    #
    # processors. Even if the platform is all MicroBlaze, this indicates that     #
    # they are configured differently.                                            #
    # -----------------------------------------------------------------------------#
    print "\t----------------------------------------------"
    print "\t|+ Checking which processors are identical   |"
    print "\t|+ to each other and generating an ISA list. |"
    print "\t----------------------------------------------"
    SDK_WORKSPACE = host_platform_dir + platform_name + "/design/design.sdk/"
    SIMILAR_PROCESSORS = {}
    # Data structure to keep track of all Processor types that will be embedded
    # into the header file
    HEADERFILE_ISAs = []

    # Add the host HEADERFILE ISA before continuing
    HEADERFILE_ISAs.append(host_processor["HEADERFILE_ISA"])

    new_isa = None
    for outer_index, outer_processor in enumerate(PROCESSORS):
        # For this processor, create a list of similar processors
        tmp_list = []

        for inner_index, inner_processor in enumerate(PROCESSORS[:outer_index]):
            # if compiler flags are the same
            if COMPILER_FLAGS[outer_index] == COMPILER_FLAGS[inner_index]:
                # if processor linkerscripts are the same
                # NOTE: Only checks for files that are exactly the same.
                # In the future, you can parse the linkerscript to compare.
                inner_lscript = SDK_WORKSPACE + inner_processor["NAME"] + "/src/lscript.ld"
                outer_lscript = SDK_WORKSPACE + outer_processor["NAME"] + "/src/lscript.ld"
                if filecmp.cmp(inner_lscript, outer_lscript) == True:
                    tmp_list.append(inner_processor["NAME"])
                    new_isa = inner_processor["HEADERFILE_ISA"]
                    # NOTE: I can break here and stop at first match if need be.
                    # break

        # if no similar processors found, then we found a new ISA.
        if len(tmp_list) == 0:
            new_isa = "ISA" + str(len(HEADERFILE_ISAs))
            HEADERFILE_ISAs.append(new_isa)

        # Add new parameter for this processor
        processor_parameters = PROCESSORS[outer_index]
        modified_processor_parameters = add_processor_parameter(processor_parameters, "HEADERFILE_ISA", new_isa)
        PROCESSORS[outer_index] = modified_processor_parameters
        # Append temporary list from this round of comparisons
        SIMILAR_PROCESSORS[outer_processor["NAME"]] = tmp_list
        new_isa = None
    print "\t\t...Done"

    # -----------------------------------------------------------------------------#
    # For each slave processor, we should configure the HAL folder:               #
    #  * Configure the slave's config/settings file: PLATFORM_ARCH                #
    #  * Create a Jamrules file with the processor's specific compilation flags.  #
    #  * Extract symbols, handle names, and handle init functions                 #
    #  * Compile!                                                                 #
    # -----------------------------------------------------------------------------#
    # For Embedding purposes later
    SYMBOLS = []  # Everyone is assumed to have same _thread symbols
    HANDLE_LIST = {}
    INIT_FUNC_LIST = {}
    INTERMEDIATES = {}
    INTERMEDIATES_SIZE = {}

    for index, processor in enumerate(PROCESSORS):
        # Embedding list for this processor
        init_fcn_list = []
        func_list = []
        handle_list = []
        print "\t----------------------------------" + "-" * len(processor["NAME"]) + "-"
        print "\t|+ Building source for processor: " + processor["NAME"] + "|"
        print "\t----------------------------------" + "-" * len(processor["NAME"]) + "-"

        # -----------------------------------------------------------------------------#
        # Check whether we should build any sources for this slave processor or just  #
        # link it to a previously, similarly built ELF file.                          #
        # -----------------------------------------------------------------------------#
        other_similar_processors = SIMILAR_PROCESSORS[processor["NAME"]]
        # if this list is empty, build this ELF file
        if len(other_similar_processors) == 0:
            # -----------------------------------------------------------------------------#
            # Update slave config/setting file. PLATFORM_BOARD has already been set above #
            # -----------------------------------------------------------------------------#
            slave_isa = processor["HTHREADS_ISA"]
            # Write ISA to config/settings
            write_config_settings("PLATFORM_ARCH", slave_isa, hetero_root_dir + "/config/settings")
            # TODO: I can probably remove this PLATFORM_CREATE_TO_ARCH parameter
            write_config_settings("PLATFORM_CREATE_TO_ARCH", slave_isa, hetero_root_dir + "/config/settings")

            # -----------------------------------------------------------------------------#
            # Create a Jamfile specific for this processor. This will overwrite the       #
            # previously copied Jamefile from the host & previous slaves build directory. #
            # -----------------------------------------------------------------------------#
            # Copy the ISA-specific Jamrules template to the slave's platform folder
            Jamfile_path = hetero_platform_dir + platform_name + "/config/Jamrules"
            execute_cmd(copy + "compiler/common/slave_Jamrules " + Jamfile_path)

            # Create the Jamfile
            create_Jamfile(Jamfile_path, COMPILER_FLAGS[index], processor["NAME"])

            # -----------------------------------------------------------------------------#
            # Copy linkerscript specific for this processor from platform's folder.       #
            # -----------------------------------------------------------------------------#
            slave_lscript_path = hetero_platform_dir + platform_name + "/config/lscript.ld"
            slave_template_lscript_path = host_platform_dir + platform_name + "/config/linkscript_slave.ld"
            execute_cmd(copy + slave_template_lscript_path + " " + slave_lscript_path)

            # -----------------------------------------------------------------------------#
            # Build source in HAL folder for this particular ISA.                         #
            # -----------------------------------------------------------------------------#
            cmd_list = []
            # Save current directory
            old_path = os.getcwd()
            # Change directory to slave compilation folder
            os.chdir(hetero_root_dir)
            # Execute command in changed folder
            hal_cmd_status = execute_cmd(run_build, exit_if_error=False)
            # Change to old path
            os.chdir(old_path)
            if hal_cmd_status != SUCCESS:
                print "\t\t" + processor["NAME"]
                print "\t\t -> Build Unsuccessful. Rolling back..."
                # Undo changes you made such as copying the source files
                file_to_remove = hetero_build_dir + SRC_FILE
                execute_cmd("rm -f " + file_to_remove)
                execute_cmd("rm -f " + SRC_FILE_PATH)
                # Exit immediately
                sys.exit(1)

            # -----------------------------------------------------------------------------#
            # Append each ISA ELF into existing Header file                               #
            # -----------------------------------------------------------------------------#
            # Form path to where the ELF image resides for this ISA
            elf_image = hetero_exec_dir + SRC_FILE.rstrip(".c")

            # Embed this ISA
            init_fcn_list, func_list, handle_list = hetero_utils.embed(
                elf_image, HEADER_FILE_PATH, slave_isa, processor["HEADERFILE_ISA"]
            )

            # Grab the lists and append it to top level lists
            # TODO: Check to make sure func_list match
            SYMBOLS = func_list
            HANDLE_LIST[processor["HEADERFILE_ISA"]] = handle_list
            INIT_FUNC_LIST[processor["HEADERFILE_ISA"]] = init_fcn_list
            INTERMEDIATES[processor["HEADERFILE_ISA"]] = processor["HEADERFILE_ISA"] + "_intermediate"
            INTERMEDIATES_SIZE[processor["HEADERFILE_ISA"]] = INTERMEDIATES[processor["HEADERFILE_ISA"]] + "_len"
            print "\t\tDone"

        # Found other similar processors
        else:
            print "\t\tSkipping..."

    # Append Host information to handle, init_function, and intermediate lists
    # Add function handles for the host (which is just the Symbols minus the '_HANDLE'
    HANDLE_LIST[host_processor["HEADERFILE_ISA"]] = SYMBOLS
    INIT_FUNC_LIST[host_processor["HEADERFILE_ISA"]] = []
    INTERMEDIATES[host_processor["HEADERFILE_ISA"]] = "NULL"
    INTERMEDIATES_SIZE[host_processor["HEADERFILE_ISA"]] = "0"

    # -----------------------------------------------------------------------------#
    # Now use all of the symbols you collected to finish header file              #
    # NOTE: The following code assumes all slaves and host source files see the   #
    # same number of thread functions (Grabbing the symbols for the first slave). #
    # -----------------------------------------------------------------------------#
    # Now finish writing the _prog.h file with FUNC_ID's, the
    # template header file, and the load_my_table() function.
    f = open(HEADER_FILE_PATH, "a")
    f.write("\n// Thread Table Code:\n")
    # The number of architectures we are targetting
    f.write("#define MAX_HANDLES_PER_ENTRY\t" + str(len(HEADERFILE_ISAs)) + "\n")
    # TODO: Assuming that all of the ISA types had the same number of thread functions
    f.write("#define MAX_ENTRIES_PER_TABLE\t" + str(len(SYMBOLS)) + "\n")
    f.write("\n// Function IDs:\n")
    for index, func_id in enumerate(SYMBOLS):
        # Append _FUNC_ID, and write to file
        f.write("#define " + func_id + "_FUNC_ID\t" + str(index) + "\n")
    f.close()

    # -----------------------------------------------------------------------------#
    # Write out generated/header file ISA                                         #
    # -----------------------------------------------------------------------------#
    f = open(HEADER_FILE_PATH, "a")
    f.write("\n// Processor Types/ISAs:\n")
    for index, processor_type in enumerate(HEADERFILE_ISAs):
        f.write("#define " + processor_type + "\t(" + str(index) + ")\n")
    f.close()

    # --------------------------------#
    # Append includes to header file #
    # --------------------------------#
    subprocess.check_call("cat " + includes_file + " >> " + HEADER_FILE_PATH, shell=True)

    # -----------------------------------------#
    # Append Typdef structures to header file #
    # -----------------------------------------#
    subprocess.check_call("cat " + typedef_file + " >> " + HEADER_FILE_PATH, shell=True)

    # ---------------------------------------#
    # Create processor array in header file #
    # ---------------------------------------#
    hetero_utils.create_hwti_array(VHWTI_base, VHWTI_offset, len(PROCESSORS), HEADER_FILE_PATH)

    # ---------------------------------------------#
    # Create Slave/Resource Table with known data #
    # ---------------------------------------------#
    hetero_utils.create_slave_table(PROCESSORS, HEADER_FILE_PATH)

    # ------------------------------------#
    # Now write table_code_template_file #
    # ------------------------------------#
    subprocess.check_call("cat " + table_code_template_file + " >> " + HEADER_FILE_PATH, shell=True)

    # ---------------------------------
    # Insert "load_my_table()" function
    # ---------------------------------
    f = open(HEADER_FILE_PATH, "a")
    f.write("\n\nvoid load_my_table() {\n")

    # For all ISAs, #NOTE: Assuming each ISA/Type has same number of handles
    for i, processor_type in enumerate(HEADERFILE_ISAs):
        f.write("\t// ISA: " + processor_type + "\n")
        # For each processor type, write out each init_handle function and insert_table_entry
        init_functions = INIT_FUNC_LIST[processor_type]
        intermediate = INTERMEDIATES[processor_type]
        intermediate_size = INTERMEDIATES_SIZE[processor_type]
        for j, handle in enumerate(HANDLE_LIST[processor_type]):
            temp_intermediate = intermediate
            # if this is a slave, it should have a init handle function
            if len(init_functions) > 0:
                # Write the initialization function call for this HANDLE
                f.write("\t" + init_functions[j] + "();\n")
                temp_intermediate = "(void *) &" + intermediate
            else:
                pass  # No init handle func for Host
            # Write code for inserting this symbol into global_thread_table
            f.write(
                "\tinsert_table_entry(&global_thread_table, "
                + SYMBOLS[j]
                + "_FUNC_ID, "
                + processor_type
                + ", (void*)"
                + handle
                + ", "
                + temp_intermediate
                + ", "
                + intermediate_size
                + ");\n"
            )
    f.write("}\n\n")
    f.close()

    print "\t\t -> Routine returned successfully."

    # -----------------------------Building HOST-----------------------------------#

    # -----------------------------------------------------------------------------#
    # Update host config/setting file. PLATFORM_BOARD has already been set.       #
    # -----------------------------------------------------------------------------#
    host_isa = host_processor["HTHREADS_ISA"]
    # Write ISA to config/settings
    write_config_settings("PLATFORM_ARCH", host_isa, "config/settings")

    # -----------------------------------------------------------------------------#
    # Create a Jamfile specific for this processor. This will overwrite the       #
    # previously copied Jamefile from the host & previous slaves build directory. #
    # -----------------------------------------------------------------------------#
    # Copy the ISA-specific Jamrules template to the slave's platform folder
    Jamfile_path = host_platform_dir + platform_name + "/config/Jamrules"
    execute_cmd(copy + "compiler/common/host_Jamrules " + Jamfile_path)

    # Create the Jamfile
    create_Jamfile(Jamfile_path, host_flags, host_processor["NAME"])

    # -----------------------------------------------------------------------------#
    # Copy linkerscript specific for this processor from platform's folder.       #
    # TODO: Assumed to have an lscript in config folder already.                  #
    # -----------------------------------------------------------------------------#
    # lscript_path = hetero_platform_dir+platform_name+"/config/"
    # execute_cmd(copy + SDK_WORKSPACE + processor['NAME'] +"/src/lscript.ld " + lscript_path)

    # *****************************************************************************
    # Build Application for host, now that you have the header file.
    # *****************************************************************************
    print "\t-----------------------------------"
    print "\t|+ Compiling application for Host |"
    print "\t-----------------------------------"
    print "\t\t -> Header file created, compiling source file for Host processor(s)..."
    # Recompile host code that will not include the auto-generated header
    execute_cmd(run_build)
    print "\t\t -> The compilation was successful."

    # *****************************************************************************
    # Remove copied heterogeneous application from src/test/system/ and its
    # associated header file. File cleanup.
    # *****************************************************************************
    print "\t---------------------------------------------------------"
    print "\t|+ Cleaning up (Removing copied header and source file) |"
    print "\t---------------------------------------------------------"
    cmd_list = []
    # Remove source file that was copied originally
    cmd_list.append("rm -f " + SRC_FILE_PATH)
    # Copy the associated header file to the original source file path.
    # Remove the file name from this variable
    cmd_list.append(sys_mv + HEADER_FILE_PATH + " " + OLD_SRC_FILE_PATH)
    # Execute command
    execute_cmd(cmd_list)
    print "\t\t Done"

    print "\n~~~ Compiler Completed ~~~"
   f.close()
   
   #-----------------------------------------#
   # Append Typdef structures to header file #
   #-----------------------------------------#
   subprocess.check_call('cat '+typedef_file+' >> '+HEADER_FILE_PATH, shell=True)

   #--------------------------------#
   # Append includes to header file #
   #--------------------------------#
   subprocess.check_call('cat '+includes_file+' >> '+HEADER_FILE_PATH, shell=True)
   
   #---------------------------------------#
   # Create processor array in header file #
   #---------------------------------------#
   hetero_utils.create_hwti_array(VHWTI_base, VHWTI_offset,len(PROCESSORS) ,HEADER_FILE_PATH)

   #---------------------------------------------#
   # Create Slave/Resource Table with known data #
   #---------------------------------------------#
   PROCESSORS = get_accelerators(hw_file_path, PROCESSORS)
   
   COPROCESSORS = hetero_utils.create_slave_table(PROCESSORS, HEADER_FILE_PATH)
   
   #---------------------------------------------#
   # Embed polymorphic function info into thread #
   #---------------------------------------------#
   #TODO: Assumes that the call graph is same for all slaves!

   #-------------------------------------------------#
   # Create Thread profile table, and preferred list #