# /*******************************************************************************
# Copyright Intel Corporation.
# This software and the related documents are Intel copyrighted materials, and your use of them
# is governed by the express license under which they were provided to you (License).
# Unless the License provides otherwise, you may not use, modify, copy, publish, distribute, disclose
# or transmit this software or the related documents without Intel's prior written permission.
# This software and the related documents are provided as is, with no express or implied warranties,
# other than those that are expressly stated in the License.
#
# *******************************************************************************/

import json
import shutil
import os.path
import subprocess
import re
from typing import Dict, List
from modules.check import CheckSummary, CheckMetadataPy

# TODO: Move device id list to the database
supported_devices = ["0x4f80", "0x4f81", "0x4f82", "0x4f83", "0x4f84", "0x4f87", "0x4f88", "0x5690",
                     "0x5691", "0x5692", "0x5693", "0x5694", "0x5695", "0x56a0", "0x56a1", "0x56a2",
                     "0x56a5", "0x56a6", "0x56c0", "0x56c1", "0x0bd0", "0x0bd5", "0x0bd6", "0x0bd7",
                     "0x0bd8", "0x0bd9", "0x0bda", "0x0bdb"]


def get_OS(data: Dict) -> str:
    return data["System"]["CheckResult"]


def get_gdb_base_dir():
    gdb_bin_path = shutil.which("gdb-oneapi")
    if gdb_bin_path:
        gdb_bin_dir = os.path.dirname(gdb_bin_path)
        return os.path.dirname(gdb_bin_dir)
    return None


def check_file_in_gdb_dir(json_node: Dict, rel_path: str, component_name: str) -> None:
    check_result = {"CheckResult": "Undefined", "CheckStatus": "PASS"}
    try:
        gdb_base_dir = get_gdb_base_dir()
        if gdb_base_dir and os.path.exists(os.path.join(gdb_base_dir, rel_path)):
            check_result["CheckResult"] = "Found"
            check_result["Message"] = f"{component_name} found."
            check_result["CheckStatus"] = "PASS"
        else:
            check_result["CheckResult"] = "Not Found"
            check_result["Message"] = f"{component_name} not found."
            check_result["CheckStatus"] = "FAIL"
    except Exception as error:
        check_result["CheckResult"] = "Error"
        check_result["CheckStatus"] = "ERROR"
        check_result["Message"] = f"Error while searching {component_name}: {error}"
    json_node.update({f"{component_name} exist": check_result})


def check_gdb_exist(json_node: Dict) -> None:
    check_file_in_gdb_dir(json_node, os.path.join("bin", "gdb-oneapi"), "Debugger")


def check_libipt_exist(json_node: Dict) -> None:
    check_file_in_gdb_dir(json_node, os.path.join("lib", "libipt.so"), "libipt")


def check_libiga_exist(json_node: Dict) -> None:
    check_file_in_gdb_dir(json_node, os.path.join("lib", "libiga64.so"), "libiga")


def check_linux_kernel_version(data: Dict, json_node: Dict) -> None:
    check_result_pass = {"CheckResult": "Supported", "CheckStatus": "PASS", "Command": "uname -r"}
    check_result_fail = {"CheckResult": "Not supported", "CheckStatus": "FAIL", "Command": "uname -r",
                         "Message": "This Linux kernel version is not supported."}
    linux_kernel_version = data["Release"]["CheckResult"].split(".")

    if len(linux_kernel_version) < 2:
        json_node.update({"Linux kernel version": check_result_fail})
        return

    try:
        linux_kernel_version[0] = int(linux_kernel_version[0])
        linux_kernel_version[1] = int(linux_kernel_version[1])
    except ValueError:
        json_node.update({"Linux kernel version": check_result_fail})
        return

    if linux_kernel_version[0] < 4 or (linux_kernel_version[0] == 4 and linux_kernel_version[1] < 14):
        json_node.update({"Linux kernel version": check_result_fail})
        return

    json_node.update({"Linux kernel version": check_result_pass})


def check_i915_debug(json_node: Dict) -> None:
    check_result = {"CheckResult": "i915 debug", "CheckStatus": "PASS", "Message": "i915 debug is enabled."}

    # Find out which GPUs are installed
    try:
        ls_ret = subprocess.run(["sycl-ls"], capture_output=True, text=True, check=True)
    except:
        check_result["CheckStatus"] = "ERROR"
        check_result["Message"] = "Failed to run 'sycl-ls' command. Make sure Intel(R) oneAPI Compiler is installed."
        json_node.update({"i915 debug": check_result})
        return

    dev_ids = re.findall(r"Intel\(R\).*Graphics.*\[(0x.{4})\]", ls_ret.stdout)
    # Remove duplicates
    dev_ids = list(set(dev_ids))

    supported_gpu_found = False
    for dev_id in dev_ids:
        if dev_id in supported_devices:
            supported_gpu_found = True
            break

    if not supported_gpu_found:
        check_result["Message"] = "No devices found that support debugging of GPU offload code."
        check_result["CheckStatus"] = "ERROR"
        json_node.update({"i915 debug": check_result})
        return

    # Check i915 prelim_enable_eu_debug settings
    find_out = subprocess.check_output("find /sys/devices | grep prelim_enable_eu_debug", shell=True).decode()
    lines = find_out.split('\n')
    if len(lines) == 0:
        check_result["Message"] = "Could not find any i915 devices."
        check_result["CheckStatus"] = "ERROR"
        json_node.update({"i915 debug": check_result})
        return

    # All GPUs should have prelim_enable_eu_debug set to 1
    prelim_debug_enabled = True
    for line in lines:
        enabled = subprocess.run(["cat", line], capture_output=True, text=True).stdout
        if "0" in enabled:
            prelim_debug_enabled = False

    # If not set, then check kernel parameters from GRUB2
    # TODO: i915.debug_eu will be deprecated and removed in the future
    if not prelim_debug_enabled:
        # Get kernel parameters in use
        cmdline_out = subprocess.run(["cat", "/proc/cmdline"], capture_output=True, text=True).stdout
        if "i915.debug_eu=1" not in cmdline_out:
            check_result["Message"] = "i915.debug_eu=1 not found in kernel parameters.\n" \
                "  HINT: Add i915.debug_eu=1 parameter to GRUB_CMDLINE_LINUX_DEFAULT" \
                " in /etc/default/grub, rebuild GRUB and reboot.\n" \
                "Alternatively set prelim_enable_eu_debug to 1 for each GPU using sysfs.\n" \
                "  HINT: You can find the GPU's with command: 'find /sys/devices | grep prelim_enable_eu_debug'.\n" \
                "  HINT: Set the value with command: 'echo 1 | sudo tee <sysfs_path>/prelim_enable_eu_debug'.\n" \
                "  HINT: List all i915 kernel parameters: 'sudo grep . /sys/module/i915/parameters/*'"
            check_result["CheckStatus"] = "ERROR"

    json_node.update({"i915 debug": check_result})


def check_gdb_env_vars(json_node: Dict) -> None:
    check_result = {"CheckResult": "Env variables", "CheckStatus": "PASS",
                    "Message": "Environmental variables correct."}

    l0_dbg_enabled = os.getenv('ZET_ENABLE_PROGRAM_DEBUGGING')

    if l0_dbg_enabled != '1':
        check_result["Message"] = "Environmental variable ZET_ENABLE_PROGRAM_DEBUGGING=1 is not set."
        check_result["CheckStatus"] = "ERROR"

    json_node.update({"Env variables": check_result})


def check_gdb_processes(json_node: Dict) -> None:
    check_result = {"CheckResult": "Gdb processes", "CheckStatus": "PASS"}

    gdb_oneapi = subprocess.check_output("ps aux | grep gdb-oneapi", shell=True).decode()
    gdbserver_ze = subprocess.check_output("ps aux | grep gdbserver-ze", shell=True).decode()

    # If gdb-oneapi process is found, then it's safe to assume, user is
    # running debug session and there can also be gdbserver-ze processes.
    # If not found, then check if there are hanging gdbserver-ze processes
    # and let the user know those should be killed.
    gdboneapi_lines = gdb_oneapi.split('\n')
    del gdboneapi_lines[-1]
    gdb_count = 0
    # Only count gdb-oneapi lines without "grep"
    for line in gdboneapi_lines:
        if line.find("grep") == -1:
            gdb_count += 1

    # If there are no gdb-oneapi processes, check gdbserver-ze processes
    if gdb_count == 0:
        gdbserver_lines = gdbserver_ze.split('\n')
        del gdbserver_lines[-1]
        for line in gdbserver_lines:
            if line.find("grep") == -1:
                check_result["Message"] = "Some gdbserver-ze processes are left running. Kill them with command 'killall gdbserver-ze' and try again."
                check_result["CheckStatus"] = "WARNING"
                break

    json_node.update({"Gdb processes": check_result})


def check_compiler(json_node: Dict) -> None:
    check_result = {"CheckResult": "Compiler", "CheckStatus": "PASS"}
    icx_path = shutil.which("icx")
    if icx_path is None:
        check_result["Message"] = "Intel(R) oneAPI Compiler is not installed."
        check_result["CheckStatus"] = "ERROR"

    json_node.update({"Compiler": check_result})


def run_debugger_check(data: dict) -> CheckSummary:
    result_json = {"CheckResult": {}}
    os_info = data["base_system_check"]["CheckResult"]["Operating system information"]["CheckResult"]
    if get_OS(os_info) == "Linux":
        check_linux_kernel_version(os_info, result_json["CheckResult"])
        check_gdb_exist(result_json["CheckResult"])
        check_libipt_exist(result_json["CheckResult"])
        check_libiga_exist(result_json["CheckResult"])
        check_compiler(result_json["CheckResult"])
        check_i915_debug(result_json["CheckResult"])
        check_gdb_env_vars(result_json["CheckResult"])
        check_gdb_processes(result_json["CheckResult"])
    else:
        result_json["CheckResult"] = {
            "Error message": {
                "CheckResult": "",
                "CheckStatus": "ERROR",
                "Message": "This check works on linux only."
            }
        }

    check_summary = CheckSummary(
        result=json.dumps(result_json, indent=4)
    )
    return check_summary


def get_api_version() -> str:
    return "0.2"


def get_check_list() -> List[CheckMetadataPy]:
    someCheck = CheckMetadataPy(
        name="debugger_sys_check",
        type="Data",
        groups="debugger,gdb,sys_check",
        descr="This check verifies if the environment is ready to use gdb (Intel(R) Distribution for GDB*).",
        dataReq="{\"base_system_check\": 2}",
        merit=60,
        timeout=10,
        version=2,
        run="run_debugger_check"
    )
    return [someCheck]


## 7404d8ee2
