"""
---------------------------------------------------------------------

SWprofiling_builder.py <board_name>

Description:
   builds starting point for the Software Profiling lab
   creates a series of projects per lab instructions for the specified board

Note:
   Vitis Unified IDE uses Python2 - make sure your coding style matches! (needs verification)
   Xilinx internal use only - if the validationMode variable is defined, then
   the modifications to the BSP are automatically configured as the student usually does in step 1
   
Usage:
   this script requires a single argument which is the name of the board. Currently supports:
   zc702, zed, zcu104, and vck190.
   
Debug:
   cd training/SWprofiling/support; source SWprofiling_builder.py;   

Instructions for developers:
   the block of code beginning with the comment "define the variables to make the ..." is the only
   portion of the code that should be modified for a general-purpose Vitis UIED workspace assembly.
   the code *can* be modified for including special actions such as setting symbols, etc.
   It is **STRONGLY*** recommended that no hard-paths are added.
   This file should become a template and, as new features are uncovered
   and added, the template should be updated.
   
Debugging:
   source /opt/amd/Vitis/2023.2/settings64.sh
   cd ~/training/<topic_cluster>/support
   vitis -s <topic_cluster>_builder.py <board> > log_file.txt; gedit log_file.txt

History:
    2024/08/23 - DY - 2024.1 - Removed tkinter, client.get_platform to client.get_component
    2024/04/30 - RS - initial - ported from Tcl
---------------------------------------------------------------------
"""

import vitis
import os
import sys
import fnmatch
#from tkinter import messagebox

# grab the one environment variable that points to the lab area
training_path = os.environ['TRAINING_PATH']
if len(training_path) == 0:
    print("Can't continue until the environment variable for TRAINING_PATH is set\nAborting!")
    exit(1)
print(f"Using {training_path} as the location of the lab files")

# *** MODIFY HERE ***
# define the variables to make the generic form of this script specific
tc_name: str = "SWprofiling"
lab_or_demo: str = "lab"
os_type: str = "standalone"
app_template: str = "empty_application"
src_list = ["profile_main.c", "funs_c.c", "funs_abde.c", "profile.h"]
src_list_z7k = src_list
src_list_mpsoc = src_list
src_list_versal = src_list

# *** DO NOT MODIFY BELOW THIS LINE!!! ***

# these global variables are set by functions based on the argument or are derived from the previous user arguments
lab_name: str = tc_name
app_name: str = f"{tc_name}_app"
dom_name: str = f"standalone_psu_cortexa53_0"
sys_name: str = f"{tc_name}_sys"
plat_name: str = f"{tc_name}_plat"
platform: str = ""  # do not assign - set by board_select()
hw_name: str = ""  # may not be needed
hw_spec: str = ""  # set by get_xsa_file()
cpu_type: str = ""  # do not assign - set by board_select()

def find_files(path: str, pattern: str = '*', recursive: bool = True) -> []:
    """
    @descr: finds all the file names matching the criteria specified by pattern.
    @param path: the location from which to begin the search
    @param pattern: the target file name with wildcards
    @param recursive: defaults to a recursive search
    @return: list of files as strings
    """
    result: list[str] = []
    if os.path.isdir(path):     # does the target exist?
        for (root, dirs, files) in os.walk(path):
            for name in files:
                if fnmatch.fnmatch(name, pattern):
                    result.append(os.path.join(root, name))
            if not recursive:
                break
    else:
        print(f"find_files: could not find the starting point ({path})")
    return result


def find_directories(path: str, pattern: str = '*') -> []:
    """
    # @descr - finds the specified directly name from within the starting point.
    # @param path: the location from which to begin the search
    # @param pattern: the target file name with wildcards
    # @return: list of directories
    """
    result = []
    if os.path.isdir(path):     # does the target exist?
        for (root, dirs, files) in os.walk(path):
            for name in dirs:
                if fnmatch.fnmatch(name, pattern):
                    result.append(os.path.join(root, name))
    else:
        print(f"find_directories: could not find the starting point ({path})")
    return result


def rm_r(path: str) -> bool:
    """
    removes all files and directories below the specified path including the given path
    :param path: location of starting point to begin the removal
    :return: True if successful, False otherwise, any errors are dumped to the stdout
    """
    found_files = find_files('\\temp\\rmdir_lite_test_area\\test_area')
    found_directories = find_directories('\\temp\\rmdir_lite_test_area\\test_area')

    # set up a try/except structure to catch any elements that catastropically fail
    try:

        # iterate over the files and delete each
        for this_file in found_files:
            os.remove(this_file)

        # iterate over the directories and delete each
        found_directories = sorted(found_directories, reverse=True)
        for this_directory in found_directories:
            os.rmdir(this_directory)

        # finally, remove the original directory
        os.rmdir(path)

    except PermissionError as e1:
        print(f"Failed to remove a file or directory due to {e1}: {this_directory}")
        return False
    except OSError as e2:
        print(f"OS exception was thrown, reported as {e2}")
        return False
    except:
        print("Unknown exception thrown - not a PermissionError and not an OSError")
        return False

    return True


def clean_exit(msg: str) -> None:
    """
    :param msg: message to display in the pop-up box
    :return: None
    """
    print(msg)
#    messagebox.showerror(title=script_name,
#                         message=msg,
#                         type=messagebox.OK)  # pop-up display box for user
    vitis.dispose()  # shut down the vitis tool
    exit(1)


def board_select(thing: str) -> None:
    """
    sets some global variables based on what the user is telling us to use
    :param thing: board name
    :return: None
    """
    # since we are modifying some global variables, declare them here
    global cpu_type
    global platform
    global src_list

    # determine board and set the platform, processor names, file set
    if thing.lower() == "zcu104":
        platform = "zcu104"
        cpu_type = "psu_cortexa53_0"
        src_list = src_list_mpsoc
    elif thing.lower() == "zc702":
        platform = "zc702"
        cpu_type = "ps7_cortexa9_0"
        src_list = src_list_z7k
    elif thing.lower() == "zed":
        platform = "zed"
        cpu_type = "ps7_cortexa9_0"
        src_list = src_list_z7k
    elif thing.lower() == 'vck190':
        platform = "vck190"
        cpu_type = "psv_cortexa72_0"
        src_list = src_list_versal
    else:
        clean_exit(f"{script_name}:board_select: unknown platform: "
                   f"{thing}\nExiting {script_name} with errors")


def get_xsa_file():
    """
    generates the name of the UED's XSA file based on the platform
    Note: vck190 and zcu104 use the Unified Embedded Design (UED) whereas
    the Z7K uses the sharingResources design
    :return: none - sets global variable hw_spec
    """
    global hw_spec

    # create platform project from  XSA - if the project to build can't be found in the existing projects list, then ...
    if (platform.lower() == "vck190") or (platform.lower() == "zcu104"):
        hw_spec = f"{training_path}/CustEdIP/UED_{platform}.xsa"
    elif (platform.lower() == "zc702") or (platform.lower() == "zed"):
        hw_spec = f"{training_path}/CustEdIP/sharingResourcesZSA_{platform.lower()}.xsa"
    else:
        clean_exit(
            f"{script_name}:get_xsa_file: unknown platform: {platform}\nExiting {script_name} with errors")

    # test if the XSA exists
    if not os.path.isfile(hw_spec):
        clean_exit(
            f"{script_name}:get_xsa_file: can't find the XSA ({hw_spec})\nExiting {script_name} with errors")


# ----------------------- MAIN --------------------------------

if __name__ == '__main__':   # C equivalent of main() {}
    # extract the name of this script
    script_name = sys.argv[0]

    # check the command line arguments to see if a board was specified
    if len(sys.argv) > 1:  # at least one argument present, assume that it's the board name
        board_select(sys.argv[1])
        get_xsa_file()
    else:
        clean_exit(
            f"Usage: {script_name} <board name>\n\twhere <board name> is vck190 | zcu104 | zed | zc702\n"
            f"Exiting {script_name} with errors")

    # derived names/paths
    workspace_path = f"{training_path}/{tc_name}/{lab_or_demo}"  # point to where the lab is to run
    print(f"setting the workspace to: {workspace_path}")  # tell the user (debug)
    xpfm_platform_name = f"{workspace_path}/{plat_name}/export/{plat_name}/{plat_name}.xpfm"
    src_loc = f"{training_path}/{tc_name}/support"

    # todo: use home-grown directory deleter instead of shutil as shutil doesn't appear to be available in Vitis
    # clear the lab directory so we guarantee a clean start
    # warning! this may not be good for projects that have a hardware design
    # shutil.rmtree(workspace_path)  # recursive deletion
    # os.mkdir(workspace_path)  # recreate the lab directory

    # create a client that will interface with the Vitis Unified IDE
    client = vitis.create_client()  # create the client
    client.set_workspace(path=workspace_path)  # set the workspace (i.e. tell Vitis)

    current_action: str = "not started"
    if os.path.exists(hw_spec) and os.path.isfile(hw_spec):  # does the XSA file exist?
        try:
            # create the platform
            current_action = "platform"
            print(f"creating a platform component ({plat_name}) based on {hw_spec}")

            platform = client.create_platform_component(name=plat_name, hw_design=hw_spec, os=os_type, cpu=cpu_type)
            # print(f">>>>>1just created the platform which is of type: {type(platform)} with an id of {id(platform)}")
            # <class 'vitis._platform.Platform'>
            print(f"{platform=}")
            print("getting platform component")
            platform = client.get_component(name=plat_name)
            # print(f">>>>>2just retrieved the platform which is of type: {type(platform)} with an id of {id(platform)}")

            # and a domain for it
            # current_action = "domain"
            # print(f"creating a domain within the platform")
            # domain = platform.add_domain(cpu=cpu_type, os=os_type, name=dom_name, display_name=dom_name,
            #                             support_app=app_template)
                                         
            # todo: verify domain build
            # domain is of type <class 'vitis._domain.Domain'>
            
            # application creation
            current_action = "application"
            print(f"creating the application using the {xpfm_platform_name} platform")
            app_component = client.create_app_component(name=app_name, platform=xpfm_platform_name, template=app_template)
            # <class 'vitis._component.HostComponent'>
            # todo: add test to check if this was successfully created 
            app_component = client.get_component(name=app_name)  # note: it is not clear to my why we must retrieve this value
            # todo: add test to check if this was successfully retrieved
            # <class 'vitis._component.HostComponent'>

            # check that the files exist before importing
            current_action = "import"
            missing_src = False  # assume everything is present for now...
            for src_file in src_list:  # look for all sources in the list
                full_path_to_file = f"{src_loc}/{src_file}"  # generate the full path to the source
                if not os.path.isfile(full_path_to_file):  # is the source missing?
                    missing_src = True
                    print(f"Could not locate source file: {full_path_to_file}")
                if missing_src:
                    clean_exit(f"Could not complete the import process due to missing files! Aborting!")

            # import files
            if len(src_list) > 0:   # if there is anything to import...
                status = app_component.import_files(from_loc=src_loc, files=src_list, dest_dir_in_cmp="src")
                if not status:
                    clean_exit("Failed to import files - aborting!")

            # build the platform
            if platform.build() != 0:
                clean_exit("Platform failed to build!\nAborting!")

            # compile the project(s)
            current_action = "build"
            if app_component.build() != 0:
                clean_exit("Application failed to build!\nAborting!")

        except ValueError as ex1:  # vitis.StatusCode.INVALID_ARGUMENT:  # todo: get real status code - not catching!
            clean_exit(
                f"trapped an invalid argument exception. Was unable to complete {current_action} creation\n"
                f"Exception is {ex1}")
        except:
            clean_exit(
                f"trapped an unknown exception during {current_action} creation!")
        
    else:
        clean_exit(f"Could not locate the xsa file: {hw_spec}\nExiting {script_name} with errors")

    # nice exit message
    clean_exit(f"Successfully completed running {script_name}.\nExiting...")

# <copyright-disclaimer-start>
#<copyright-disclaimer-start>
#  **************************************************************************************************************
#  * © 2024 Advanced Micro Devices, Inc. All rights reserved.                                                   *
#  * DISCLAIMER                                                                                                 *
#  * The information contained herein is for informational purposes only, and is subject to change              *
#  * without notice. While every precaution has been taken in the preparation of this document, it              *
#  * may contain technical inaccuracies, omissions and typographical errors, and AMD is under no                *
#  * obligation to update or otherwise correct this information.  Advanced Micro Devices, Inc. makes            *
#  * no representations or warranties with respect to the accuracy or completeness of the contents of           *
#  * this document, and assumes no liability of any kind, including the implied warranties of noninfringement,  *
#  * merchantability or fitness for particular purposes, with respect to the operation or use of AMD            *
#  * hardware, software or other products described herein.  No license, including implied or                   *
#  * arising by estoppel, to any intellectual property rights is granted by this document.  Terms and           *
#  * limitations applicable to the purchase or use of AMD’s products are as set forth in a signed agreement     *
#  * between the parties or in AMD's Standard Terms and Conditions of Sale. GD-18                               *
#  *                                                                                                            *
#  **************************************************************************************************************
#<copyright-disclaimer-end>
# <copyright-disclaimer-end>

