#
# Copyright (C) 2015 Intel Corporation
#
# SPDX-License-Identifier: MIT
#

# Provides functions to help with exporting binaries obtained from built targets

import os, re, glob as g, shutil as sh,sys
from time import sleep
from .commands import runCmd
from difflib import SequenceMatcher as SM

try:
    import bb
except ImportError:
    class my_log():
        def __init__(self):
            pass
        def plain(self, msg):
            if msg:
                print(msg)
        def warn(self, msg):
            if msg:
                print("WARNING: " + msg)
        def fatal(self, msg):
            if msg:
                print("FATAL:" + msg)
                sys.exit(1)
    bb = my_log()    


def determine_if_poky_env():
    """
    used to determine if we are inside the poky env or not. Usefull for remote machine where poky is not present
    """
    check_env = True if ("/scripts" and "/bitbake/bin") in os.getenv("PATH") else False
    return check_env


def get_dest_folder(tune_features, folder_list):
    """
    Function to determine what rpm deploy dir to choose for a given architecture based on TUNE_FEATURES
    """
    features_list = tune_features.split(" ")
    features_list.reverse()
    features_list = "_".join(features_list)
    match_rate = 0
    best_match = None
    for folder in folder_list:
        curr_match_rate = SM(None, folder, features_list).ratio()
        if curr_match_rate > match_rate:
            match_rate = curr_match_rate
            best_match = folder
    return best_match


def process_binaries(d, params):
    param_list = params
    export_env = d.getVar("TEST_EXPORT_ONLY")

    def extract_binary(pth_to_pkg, dest_pth=None):
        cpio_command = runCmd("which cpio")
        rpm2cpio_command = runCmd("ls /usr/bin/rpm2cpio")
        if (cpio_command.status != 0) and (rpm2cpio_command.status != 0):
            bb.fatal("Either \"rpm2cpio\" or \"cpio\" tools are not available on your system."
                    "All binaries extraction processes will not be available, crashing all related tests."
                    "Please install them according to your OS recommendations") # will exit here
        if dest_pth:
            os.chdir(dest_pth)
        else:
            os.chdir("%s" % os.sep)# this is for native package
        extract_bin_command = runCmd("%s %s | %s -idm" % (rpm2cpio_command.output, pth_to_pkg, cpio_command.output)) # semi-hardcoded because of a bug on poky's rpm2cpio
        return extract_bin_command

    if determine_if_poky_env(): # machine with poky environment
        exportpath = d.getVar("TEST_EXPORT_DIR") if export_env else d.getVar("DEPLOY_DIR")
        rpm_deploy_dir = d.getVar("DEPLOY_DIR_RPM")
        arch = get_dest_folder(d.getVar("TUNE_FEATURES"), os.listdir(rpm_deploy_dir))
        arch_rpm_dir = os.path.join(rpm_deploy_dir, arch)
        extracted_bin_dir = os.path.join(exportpath,"binaries", arch, "extracted_binaries")
        packaged_bin_dir = os.path.join(exportpath,"binaries", arch, "packaged_binaries")
        # creating necessary directory structure in case testing is done in poky env.
        if export_env == "0":
            if not os.path.exists(extracted_bin_dir): bb.utils.mkdirhier(extracted_bin_dir)
            if not os.path.exists(packaged_bin_dir): bb.utils.mkdirhier(packaged_bin_dir)

        if param_list[3] == "native":
            if export_env == "1": #this is a native package and we only need to copy it. no need for extraction
                native_rpm_dir = os.path.join(rpm_deploy_dir, get_dest_folder("{} nativesdk".format(d.getVar("BUILD_SYS")), os.listdir(rpm_deploy_dir)))
                native_rpm_file_list = [item for item in os.listdir(native_rpm_dir) if re.search("nativesdk-" + param_list[0] + "-([0-9]+\.*)", item)]
                if not native_rpm_file_list:
                    bb.warn("Couldn't find any version of {} native package. Related tests will most probably fail.".format(param_list[0]))
                    return ""
                for item in native_rpm_file_list:# will copy all versions of package. Used version will be selected on remote machine
                    bb.plain("Copying native package file: %s" % item)
                    sh.copy(os.path.join(rpm_deploy_dir, native_rpm_dir, item), os.path.join(d.getVar("TEST_EXPORT_DIR"), "binaries", "native"))
            else: # nothing to do here; running tests under bitbake, so we asume native binaries are in sysroots dir.
                if param_list[1] or param_list[4]:
                    bb.warn("Native binary %s %s%s. Running tests under bitbake environment. Version can't be checked except when the test itself does it"
                            " and binary can't be removed."%(param_list[0],"has assigned ver. " + param_list[1] if param_list[1] else "",
                            ", is marked for removal" if param_list[4] else ""))
        else:# the package is target aka DUT intended and it is either required to be delivered in an extracted form or in a packaged version
            target_rpm_file_list = [item for item in os.listdir(arch_rpm_dir) if re.search(param_list[0] + "-([0-9]+\.*)", item)]
            if not target_rpm_file_list:
                bb.warn("Couldn't find any version of target package %s. Please ensure it was built. "
                        "Related tests will probably fail." % param_list[0])
                return ""
            if param_list[2] == "rpm": # binary should be deployed as rpm; (other, .deb, .ipk? ;  in the near future)
                for item in target_rpm_file_list: # copying all related rpm packages. "Intuition" reasons, someone may need other versions too. Deciding later on version
                    bb.plain("Copying target specific packaged file: %s" % item)
                    sh.copy(os.path.join(arch_rpm_dir, item), packaged_bin_dir)
                    return "copied"
            else: # it is required to extract the binary
                if param_list[1]: # the package is versioned
                    for item in target_rpm_file_list:
                        if re.match(".*-{}-.*\.rpm".format(param_list[1]), item):
                            destination = os.path.join(extracted_bin_dir,param_list[0], param_list[1])
                            bb.utils.mkdirhier(destination)
                            extract_binary(os.path.join(arch_rpm_dir, item), destination)
                            break
                    else:
                        bb.warn("Couldn't find the desired version %s for target binary %s. Related test cases will probably fail." % (param_list[1], param_list[0]))
                        return ""
                    return "extracted"
                else: # no version provided, just extract one binary
                    destination = os.path.join(extracted_bin_dir,param_list[0],
                                               re.search(".*-([0-9]+\.[0-9]+)-.*rpm", target_rpm_file_list[0]).group(1))
                    bb.utils.mkdirhier(destination)
                    extract_binary(os.path.join(arch_rpm_dir, target_rpm_file_list[0]), destination)
                    return "extracted"
    else: # remote machine
        binaries_path = os.getenv("bin_dir")# in order to know where the binaries are, bin_dir is set as env. variable
        if param_list[3] == "native": #need to extract the native pkg here
            native_rpm_dir = os.path.join(binaries_path, "native")
            native_rpm_file_list = os.listdir(native_rpm_dir)
            for item in native_rpm_file_list:
                if param_list[1] and re.match("nativesdk-{}-{}-.*\.rpm".format(param_list[0], param_list[1]), item): # native package has version
                    extract_binary(os.path.join(native_rpm_dir, item))
                    break
                else:# just copy any related native binary
                    found_version = re.match("nativesdk-{}-([0-9]+\.[0-9]+)-".format(param_list[0]), item).group(1)
                    if found_version:
                        extract_binary(os.path.join(native_rpm_dir, item))
            else:
                bb.warn("Couldn't find native package %s%s. Related test cases will be influenced." %
                        (param_list[0], " with version " + param_list[1] if param_list[1] else ""))
                return

        else: # this is for target device
            if param_list[2] == "rpm":
                return "No need to extract, this is an .rpm file"
            arch = get_dest_folder(d.getVar("TUNE_FEATURES"), os.listdir(binaries_path))
            extracted_bin_path = os.path.join(binaries_path, arch, "extracted_binaries")
            extracted_bin_list = [item for item in os.listdir(extracted_bin_path)]
            packaged_bin_path = os.path.join(binaries_path, arch, "packaged_binaries")
            packaged_bin_file_list = os.listdir(packaged_bin_path)
            # see if the package is already in the extracted ones; maybe it was deployed when exported the env.
            if os.path.exists(os.path.join(extracted_bin_path, param_list[0], param_list[1] if param_list[1] else "")):
                return "binary %s is already extracted" % param_list[0]
            else: # we need to search for it in the packaged binaries directory. It may have been shipped after export
                for item in packaged_bin_file_list:
                    if param_list[1]:
                        if re.match("%s-%s.*rpm" % (param_list[0], param_list[1]), item): # package with version
                            if not os.path.exists(os.path.join(extracted_bin_path, param_list[0],param_list[1])):
                                os.makedirs(os.path.join(extracted_bin_path, param_list[0], param_list[1]))
                                extract_binary(os.path.join(packaged_bin_path, item), os.path.join(extracted_bin_path, param_list[0],param_list[1]))
                                bb.plain("Using {} for {}".format(os.path.join(packaged_bin_path, item), param_list[0]))
                            break
                    else:
                        if re.match("%s-.*rpm" % param_list[0], item):
                            found_version = re.match(".*-([0-9]+\.[0-9]+)-", item).group(1)
                            if not os.path.exists(os.path.join(extracted_bin_path, param_list[0], found_version)):
                                os.makedirs(os.path.join(extracted_bin_path, param_list[0], found_version))
                                bb.plain("Used ver. %s for %s" % (found_version, param_list[0]))
                                extract_binary(os.path.join(packaged_bin_path, item), os.path.join(extracted_bin_path, param_list[0], found_version))
                            break
                else:
                    bb.warn("Couldn't find target package %s%s. Please ensure it is available "
                            "in either of these directories: extracted_binaries or packaged_binaries. "
                            "Related tests will probably fail." % (param_list[0], " with version " + param_list[1] if param_list[1] else ""))
                    return
                return "Binary %s extracted successfully." % param_list[0]


def files_to_copy(base_dir):
    """
    Produces a list of files relative to the base dir path sent as param
    :return: the list of relative path files
    """
    files_list = []
    dir_list = [base_dir]
    count = 1
    dir_count = 1
    while (dir_count == 1 or dir_count != count):
        count = dir_count
        for dir in dir_list:
            for item in os.listdir(dir):
                if os.path.isdir(os.path.join(dir, item)) and os.path.join(dir, item) not in dir_list:
                   dir_list.append(os.path.join(dir, item))
                   dir_count = len(dir_list)
                elif os.path.join(dir, item) not in files_list and os.path.isfile(os.path.join(dir, item)):
                   files_list.append(os.path.join(dir, item))
    return files_list


def send_bin_to_DUT(d,params):
    from oeqa.oetest import oeRuntimeTest
    param_list = params
    cleanup_list = list()
    bins_dir = os.path.join(d.getVar("TEST_EXPORT_DIR"), "binaries") if determine_if_poky_env() \
                    else os.getenv("bin_dir")
    arch = get_dest_folder(d.getVar("TUNE_FEATURES"), os.listdir(bins_dir))
    arch_rpms_dir = os.path.join(bins_dir, arch, "packaged_binaries")
    extracted_bin_dir = os.path.join(bins_dir, arch, "extracted_binaries", param_list[0])

    def send_extracted_binary():
        bin_local_dir = os.path.join(extracted_bin_dir, param_list[1] if param_list[1] else os.listdir(extracted_bin_dir)[0])
        for item in files_to_copy(bin_local_dir):
            split_path = item.split(bin_local_dir)[1]
            path_on_DUT = split_path if split_path[0] is "/" else "/" + split_path # create the path as on DUT; eg. /usr/bin/bin_file
            (status, output) = oeRuntimeTest.tc.target.copy_to(item, path_on_DUT)
            if status != 0:
                bb.warn("Failed to copy %s binary file %s on the remote target: %s" %
                        (param_list[0], "ver. " + param_list[1] if param_list[1] else "", d.getVar("MACHINE")))
                return
            if param_list[4] == "rm":
                cleanup_list.append(path_on_DUT)
        return cleanup_list

    def send_rpm(remote_path): # if it is not required to have an extracted binary, but to send an .rpm file
        rpm_to_send = ""
        for item in os.listdir(arch_rpms_dir):
            if param_list[1] and re.match("%s-%s-.*rpm"%(param_list[0], param_list[1]), item):
                rpm_to_send = item
                break
            elif re.match("%s-[0-9]+\.[0-9]+-.*rpm" % param_list[0], item):
                rpm_to_send = item
                break
        else:
            bb.warn("No rpm package found for %s %s in .rpm files dir %s. Skipping deployment." %
                    (param_list[0], "ver. " + param_list[1] if param_list[1] else "", rpms_file_dir) )
            return
        (status, output) = oeRuntimeTest.tc.target.copy_to(os.path.join(arch_rpms_dir, rpm_to_send), remote_path)
        if status != 0:
                bb.warn("Failed to copy %s on the remote target: %s" %(param_list[0], d.getVar("MACHINE")))
                return
        if param_list[4] == "rm":
            cleanup_list.append(os.path.join(remote_path, rpm_to_send))
            return cleanup_list

    if param_list[2] == "rpm": # send an .rpm file
        return send_rpm("/home/root") # rpms will be sent on home dir of remote machine
    else:
        return send_extracted_binary()


def rm_bin(removal_list): # need to know both if the binary is sent archived and the path where it is sent if archived
    from oeqa.oetest import oeRuntimeTest
    for item in removal_list:
        (status,output) = oeRuntimeTest.tc.target.run("rm " + item)
        if status != 0:
            bb.warn("Failed to remove: %s. Please ensure connection with the target device is up and running and "
                     "you have the needed rights." % item)

