# /*******************************************************************************
# 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"]["Value"]


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:
    value = {"Value": "Undefined", "RetVal": "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)):
            value["Value"] = "Found"
            value["Message"] = f"{component_name} found."
            value["RetVal"] = "PASS"
        else:
            value["Value"] = "Not Found"
            value["Message"] = f"{component_name} not found."
            value["RetVal"] = "FAIL"
    except Exception as error:
        value["Value"] = "Error"
        value["RetVal"] = "ERROR"
        value["Message"] = f"Error while searching {component_name}: {error}"
    json_node.update({f"{component_name} exist": value})


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:
    value_pass = {"Value": "Supported", "RetVal": "PASS", "Command": "uname -r"}
    value_fail = {"Value": "Not supported", "RetVal": "FAIL", "Command": "uname -r",
                  "Message": "This Linux kernel version is not supported."}
    linux_kernel_version = data["Release"]["Value"].split(".")

    if len(linux_kernel_version) < 2:
        json_node.update({"Linux kernel version": value_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": value_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": value_fail})
        return

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

def check_i915_debug(json_node: Dict) -> None:
    value = {"Value": "i915 debug", "RetVal": "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:
        value["RetVal"] = "ERROR"
        value["Message"] = "Failed to run 'sycl-ls' command. Make sure Intel(R) oneAPI Compiler is installed."
        json_node.update({"i915 debug": value})
        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:
        value["Message"] = "No devices found that support debugging of GPU offload code."
        value["RetVal"] = "ERROR"
        json_node.update({"i915 debug": value})
        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:
        value["Message"] = "Could not find any i915 devices."
        value["RetVal"] = "ERROR"
        json_node.update({"i915 debug": value})
        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:
            value["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'."
            value["RetVal"] = "ERROR"

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

def check_gdb_env_vars(json_node: Dict) -> None:
    value = {"Value": "Env variables", "RetVal": "PASS", "Message": "Environmental variables correct."}

    l0_dbg_enabled = os.getenv('ZET_ENABLE_PROGRAM_DEBUGGING')

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

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


def check_gdb_processes(json_node: Dict) -> None:
    value = {"Value": "Gdb processes", "RetVal": "PASS"}

    gdb_ps = subprocess.check_output("ps aux | grep gdbserver-ze", shell=True).decode()
    lines = gdb_ps.split('\n')
    del lines[-1]

    for line in lines:
        if "grep gdbserver-ze" in line:
            continue
        else:
            value["Message"] = "Some gdbserver-ze processes are left running. Kill them with command 'killall gdbserver-ze' and try again."
            value["RetVal"] = "WARNING"
            break
    
    json_node.update({"Gdb processes": value})
    
def run_debugger_check(data: dict) -> CheckSummary:
    result_json = {"Value": {}}
    os_info = data["base_system_check"]["Value"]["Operating system information"]["Value"]
    if get_OS(os_info) == "Linux":
        check_linux_kernel_version(os_info, result_json["Value"])
        check_gdb_exist(result_json["Value"])
        check_libipt_exist(result_json["Value"])
        check_libiga_exist(result_json["Value"])
        check_i915_debug(result_json["Value"])
        check_gdb_env_vars(result_json["Value"])
        check_gdb_processes(result_json["Value"])
    else:
        result_json["Value"] = {
            "Error message": {
                "Value": "",
                "RetVal": "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.1"


def get_check_list() -> List[CheckMetadataPy]:
    someCheck = CheckMetadataPy(
        name="debugger_sys_check",
        type="Data",
        tags="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\": 1}",
        merit=60,
        timeout=10,
        version=1,
        run="run_debugger_check"
    )
    return [someCheck]


## 68ecdebfa
