# Copyright 2017-2025 NXP

# SPDX-License-Identifier: BSD-3-Clause

from __future__ import print_function
import enum
import gdb
import time
from collections import namedtuple

from errors import Result, Error, ErrorCode, Ok, exceptcall, safecallexc
from gta_error_dict import GTA_ERROR_DICT
from ta_error_dict import TA_ERROR_DICT
from fp_error_dict import FP_ERROR_DICT
import json
import traceback
import functools

_log_enable = False

global AXI_CFG_AP_LDE
#If this variable is Unimplemented, then 8-byte read/write operations are not implemented for the NPI.
class Axi_Cfg_Ap_Lde_State(enum.Enum):
    longdata_supported  = True
    longdata_unsupported = False

AXI_CFG_AP_LDE = Axi_Cfg_Ap_Lde_State.longdata_supported

error_dict = {}
error_dict.update(TA_ERROR_DICT)
error_dict.update(FP_ERROR_DICT)
error_dict.update(GTA_ERROR_DICT)

# Keep all memory regions that have to be initialized before use
POSTPONED_REGIONS = []

RegCheck = namedtuple("RegCheck", "reg mask value")


# Multiple register checks in row.
def multiple_check_reg(name, context, reg_checks):
    try:
        for check in reg_checks:
            reg_val = read_mem32_le(context, "physical", check.reg).unwrap_exc()
            if (reg_val & check.mask) != check.value:
                return False

        # All checks have passed
        return True
    except Exception as e:
        print("Exception: {} while checking: {}".format(str(e), name))

    return False


def log_mem_reg(msg):
    if _log_enable:
        print(msg)

def checkError(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        result = func(*args, **kwargs)
        if isinstance(result, Result):
            if result.iserror():
                error_info = gdb_exec("monitor errorinfo").unwrap()
                error_code = error_info.partition('\n')[0].strip() # pylint: disable=maybe-no-member
                error_msg = '\n'.join(error_info.split('\n')[1:]) # pylint: disable=maybe-no-member
                if error_code:
                    if error_code != result.error.error_type.value:
                        value = error_dict.get(int(error_code), ErrorCode.UndefinedError)
                        if value == ErrorCode.UndefinedError:
                            result = Result(Error(value, ":Undefined error"))
                        else:
                            result = Result(Error(value, str(error_msg)))
        return result
    return wrapper
    
    
@checkError
def gdb_exec(command, from_tty=False, to_string=True, errcode=ErrorCode.UndefinedError):
    """Execute a gdb command, remove the echo if it appears
    remove RSP protocol log if 'set debug remote 1' applied in client
    it should be used instead of gdb.execute in all cases when the value returned is used in the script
    """
    try:
        string = gdb.execute(command, from_tty, to_string)
        # Starting with GDB 12.1 the tag [remote] has been added
        if string and "[remote] " in string:
            string = string.replace("[remote] ", "")

        echo_command = "+++" + command + "\n"
        with_debug = "Sending packet:"

        if string is not None and string.startswith(with_debug):
            c = string.rstrip().split("\n")
            # verify for trailing string
            if c[len(c) - 1].startswith("Packet received"):
                return Result(Ok("\n".join(c[2 : len(c) - 1])))
            return Result(Ok("\n".join(c[2:])))

        if string is not None and string.startswith(echo_command):
            return Result(Ok(string[len(echo_command) :]))
        else:
            return Result(Ok(string))

    except BaseException as e:
        return Result(Error(errcode, str(e)))


def write_mem(context, addr, accSize, space, value):
    cmd = "monitor mem write %s %#x %d %s %#x" % (context, addr, accSize, space, value)
    log_mem_reg(cmd)

    return gdb_exec(cmd, errcode=ErrorCode.WriteMemoryError)


def fill_mem(context, addr, accSize, space, value, count):
    cmd = "monitor mem fill %s %#x %d %s %#x %d" % (
        context,
        addr,
        accSize,
        space,
        value,
        count,
    )
    log_mem_reg(cmd)

    return gdb_exec(cmd, errcode=ErrorCode.WriteMemoryError)


@safecallexc
def write_mem_exc(context, addr, accSize, space, value):
    return write_mem(context, addr, accSize, space, value)


def write_mem32(context, space, addr, value):
    return write_mem(context, addr, 4, space, value)


@safecallexc
def write_mem32_exc(context, space, addr, value):
    return write_mem32(context, space, addr, value)


def write_mem64(context, space, addr, value):
    return write_mem(context, addr, 8, space, value)


@safecallexc
def write_mem64_exc(context, space, addr, value):
    return write_mem64(context, space, addr, value)


def write_reg(context, regId, accsize, value):
    cmd = "monitor reg write %s %s %d %#x" % (context, regId, accsize, value)
    log_mem_reg(cmd)

    return gdb_exec(cmd, errcode=ErrorCode.WriteRegisterError)


@safecallexc
def write_reg_exc(context, regId, accsize, value):
    return write_reg(context, regId, accsize, value)


def write_reg32(context, regId, value):
    return write_reg(context, regId, 4, value)


@safecallexc
def write_reg32_exc(context, regId, value):
    return write_reg32(context, regId, value)


def write_reg64(context, regId, value):
    return write_reg(context, regId, 8, value)


@safecallexc
def write_reg64_exc(context, regId, value):
    return write_reg64(context, regId, value)


def _read_memory(context, addr, accSize, space, count):
    cmd = "monitor mem read %s %#x %d %s %d" % (context, addr, accSize, space, count)
    log_mem_reg(cmd)

    return gdb_exec(cmd, errcode=ErrorCode.ReadMemoryError)


def read_mem(context, addr, accSize, space):
    v = _read_memory(context, addr, accSize, space, 1)
    if v.isok():
        return Result(Ok(int(v.unwrap(), 16)))
    return v


def read_mem_str(context, addr, accSize, space, count):
    return _read_memory(context, addr, accSize, space, count)


@safecallexc
def read_mem_exc(context, addr, accSize, space):
    return read_mem(context, addr, accSize, space)


def read_mem32(context, space, addr):
    return read_mem(context, addr, 4, space)


@safecallexc
def read_mem32_exc(context, space, addr):
    return read_mem32(context, space, addr)


def read_mem64(context, space, addr):
    return read_mem(context, addr, 8, space)


@safecallexc
def read_mem64_exc(context, space, addr):
    return read_mem64(context, space, addr)


def read_reg(context, registerId, size):
    cmd = "monitor reg read %s %s %d" % (context, registerId, size)
    log_mem_reg(cmd)

    v = gdb_exec(cmd, errcode=ErrorCode.ReadRegisterError)
    if v.isok():
        return Result(Ok(int(v.unwrap(), 16)))
    return v


@safecallexc
def read_reg_exc(context, registerId, size):
    return read_reg(context, registerId, size)


def read_reg32(context, regId):
    return read_reg(context, regId, 4)


@safecallexc
def read_reg32_exc(context, regId):
    return read_reg32(context, regId)


def read_reg64(context, regId):
    return read_reg(context, regId, 8)


@safecallexc
def read_reg64_exc(context, regId):
    return read_reg(context, regId, 8)


# Swap utility function
def Swap16(x):
    return ((x << 8) & 0xFF00) | ((x >> 8) & 0x00FF)


def Swap32(x):
    return (
        ((x << 24) & 0xFF000000)
        | ((x << 8) & 0x00FF0000)
        | ((x >> 8) & 0x0000FF00)
        | ((x >> 24) & 0x000000FF)
    )


def Swap64(x):
    return (
        ((x << 56) & 0xFF00000000000000)
        | ((x << 40) & 0x00FF000000000000)
        | ((x << 16) & 0x0000FF0000000000)
        | ((x << 8) & 0x000000FF00000000)
        | ((x >> 8) & 0x00000000FF000000)
        | ((x >> 16) & 0x0000000000FF0000)
        | ((x >> 40) & 0x000000000000FF00)
        | ((x >> 56) & 0x00000000000000FF)
    )


####### WRITE MEM LE #########
def write_mem8_le(context, space, addr, value):
    return write_mem(context, addr, 1, space, value)


@safecallexc
def write_mem8_le_exc(context, space, addr, value):
    return write_mem8_le(context, space, addr, value)


def write_mem16_le(context, space, addr, value):
    return write_mem(context, addr, 2, space, Swap16(value))


@safecallexc
def write_mem16_le_exc(context, space, addr, value):
    return write_mem16_le(context, space, addr, value)


def write_mem32_le(context, space, addr, value):
    return write_mem(context, addr, 4, space, Swap32(value))


@safecallexc
def write_mem32_le_exc(context, space, addr, value):
    return write_mem32_le(context, space, addr, value)


def write_mem64_le(context, space, addr, value):
    return write_mem(context, addr, 8, space, Swap64(value))


@safecallexc
def write_mem64_le_exc(context, space, addr, value):
    return write_mem64_le(context, space, addr, value)


####### READ MEM LE #########
def read_mem16_le(context, space, addr):
    v = read_mem(context, addr, 2, space)
    if v.isok():
        return Result(Ok(Swap16(v.unwrap())))
    return v


@safecallexc
def read_mem16_le_exc(context, space, addr):
    return read_mem16_le(context, space, addr)


def read_mem32_le(context, space, addr):
    v = read_mem(context, addr, 4, space)
    if v.isok():
        return Result(Ok(Swap32(v.unwrap())))
    return v


@safecallexc
def read_mem32_le_exc(context, space, addr):
    return read_mem32_le(context, space, addr)


def read_mem64_le(context, space, addr):
    v = read_mem(context, addr, 8, space)
    if v.isok():
        return Result(Ok(Swap64(v.unwrap())))
    return v


@safecallexc
def read_mem64_le_exc(context, space, addr):
    return read_mem64_le(context, space, addr)


def _readcompare_mem(context, address, space, expected, mask, attempts, fnc):
    count = 0
    while True:
        time.sleep(0.3)

        val = fnc(context, space, address)
        if val.iserror():
            return val

        val = val.unwrap()

        log_mem_reg(
            "read={:x} mask={:x} val={:x} exp={:x}".format(
                val, mask, val & mask, expected
            )
        )

        count = count + 1
        if val & mask == expected:
            return Result(Ok(True))
        if count > attempts:
            return Result(Ok(False))


def _readcompare_reg(context, address, expected, mask, attempts, fnc):
    count = 0
    while True:
        time.sleep(0.5)
        val = fnc(context, address).unwrap_exc()

        if val.is_error():
            return val

        val.unwrap()

        log_mem_reg("read={:x} exp={:x}".format(val & mask, expected))

        count = count + 1
        if val & mask == expected:
            return Result(Ok(True))
        if count > attempts:
            return Result(Ok(False))


def read_mem_compare32(context, space, addr, mask, expected, attempts):
    return _readcompare_mem(context, addr, space, expected, mask, attempts, read_mem32)


@safecallexc
def read_mem_compare32_exc(context, space, addr, mask, expected, attempts):
    return read_mem_compare32(context, space, addr, mask, expected, attempts)


def read_mem_compare64(context, space, addr, mask, expected, attempts):
    return _readcompare_mem(context, addr, space, expected, mask, attempts, read_mem64)


@safecallexc
def read_mem_compare64_exc(context, space, addr, mask, expected, attempts):
    return read_mem_compare64(context, space, addr, mask, expected, attempts)


def read_mem_compare32_le(context, space, addr, mask, expected, attempts):
    return _readcompare_mem(
        context, addr, space, expected, mask, attempts, read_mem32_le
    )


@safecallexc
def read_mem_compare32_le_exc(context, space, addr, mask, expected, attempts):
    return read_mem_compare32_le(context, space, addr, mask, expected, attempts)


def read_mem_compare64_le(context, space, addr, mask, expected, attempts):
    return _readcompare_mem(
        context, addr, space, expected, mask, attempts, read_mem64_le
    )


@safecallexc
def read_mem_compare64_le_exc(context, space, addr, mask, expected, attempts):
    return read_mem_compare64_le(context, space, addr, mask, expected, attempts)


def read_reg_compare32(context, addr, mask, expected, attempts):
    return _readcompare_reg(context, addr, expected, mask, attempts, read_reg32)


@safecallexc
def read_reg_compare32_exc(context, addr, mask, expected, attempts):
    return read_reg_compare32(context, addr, mask, expected, attempts)


def read_reg_compare64(context, addr, mask, expected, attempts):
    return _readcompare_reg(context, addr, expected, mask, attempts, read_reg64)


@safecallexc
def read_reg_compare64_exc(context, addr, mask, expected, attempts):
    return read_reg_compare64(context, addr, mask, expected, attempts)


@exceptcall(ErrorCode.UndefinedError)
def _set_gdb_memory_regions(offset, regions_ar):
    gdb_exec("set mem inaccessible-by-default off")
    for region in regions_ar:
        # lower boundary is inclusive
        start = region.start - offset
        # upper boundary is exclusive!
        end = region.start + region.length - offset
        if region.attr == "ro":
            attr = "ro"
        else:
            attr = ""
        cmd = "mem %#x %#x %s" % (start, end, attr)
        gdb_exec(cmd)
        log_mem_reg("CMD: {}".format(cmd))
    gdb_exec("set mem inaccessible-by-default on")

def build_attributes_string(current_attribute_string, attribute_to_add):
    if current_attribute_string == "":
        new_attribute_string = attribute_to_add
    else:
        new_attribute_string = current_attribute_string + " " + attribute_to_add

    return new_attribute_string

def build_prop(offset, regions_ar, counter=0, data=None):
    if data is None:
        data = {}
    for region in regions_ar:
        attr = ""
        # lower boundary is inclusive
        start = region.start - offset
        # upper boundary is exclusive!
        end = region.start + region.length - offset
        if hasattr(region, 'attr'):
            if region.attr == "ro":
                attr = build_attributes_string(attr, "ro")
            
        if hasattr(region, 'override_to_core_access'):
            if region.override_to_core_access is not None:
                attr = build_attributes_string(attr, region.override_to_core_access)

        if hasattr(region, 'override_to_mem_space'):
            if region.override_to_mem_space is not None:
                attr = build_attributes_string(attr, region.override_to_mem_space)

        data[counter] = {}
        data[counter]["start"] = str(start)
        data[counter]["end"] = str(end)
        data[counter]["attr"] = attr
        counter += 1
    return data


@exceptcall(ErrorCode.UndefinedError)
def set_memory_space_regions(offset, regions_ar, space):
    data = build_prop(offset, regions_ar)
    ctx = gdb_exec("monitor ctx get current").unwrap().rstrip()
    cmd = "monitor ctx id {} add prop {} {}".format(ctx, space, json.dumps(data))
    log_mem_reg(cmd)
    gdb_exec(cmd)


@exceptcall(ErrorCode.UndefinedError)
def add_memory_space_region(offset, regions_ar, space):
    ctx = gdb_exec("monitor ctx get current").unwrap().rstrip()
    gdb_exec("set print elements 0")
    cmd = "monitor ctx id %s get prop %s" % (ctx, space)
    mem_list = gdb_exec(cmd, False, True)
    if mem_list.iserror():
        return
    mem_list_extended = json.loads(mem_list.unwrap().replace("\\", ""))
    counter = (
        int(sorted(list(mem_list_extended.keys()), key=lambda x: (len(x), x))[-1]) + 1
    )
    mem_list_extended = build_prop(offset, regions_ar, counter, mem_list_extended)
    ctx = gdb_exec("monitor ctx get current").unwrap().rstrip()
    cmd = "monitor ctx id {} set prop {} {}".format(
        ctx, space, json.dumps(mem_list_extended)
    )
    log_mem_reg(cmd)
    gdb_exec(cmd)


def is_range_init(range):
    result = False
    start_time = time.time()

    try:
        result = range.is_init()
    except Exception as e:
        pass

    end_time = time.time()

    log_mem_reg(
        "Execution time for {} is {:.3f} s".format(
            range.is_init.__name__, end_time - start_time
        )
    )

    return result


@exceptcall(ErrorCode.UndefinedError)
def set_gdb_memory_regions(offset, regions_ar):
    global POSTPONED_REGIONS
    postponed = []

    try:
        for r in regions_ar:
            result = "No"
            if hasattr(r, "is_init") and r.is_init:
                result = "Yes"
                postponed.append(r)

            log_mem_reg(
                "Filter region: <{} {} {}> {}".format(
                    hex(r.start), hex(r.length), r.attr, result
                )
            )

        # Remove the filtered ranges from original list
        for f in postponed:
            regions_ar.remove(f)

        # apply the non-filtered memory ranges
        if len(regions_ar):
            _set_gdb_memory_regions(offset, regions_ar)

        if len(postponed):
            dict = {"offset": offset, "regions_ar": postponed}

            POSTPONED_REGIONS.append(dict)

            register_gdb_stop_hook()

    except Exception as e:
        log_mem_reg("Warning: {}".format(str(e)))
        traceback.print_exc()


# Stop execution handler
def handle_stopped_event(event):
    global POSTPONED_REGIONS
    init_cache = {}
    pdone = []

    log_mem_reg("Stop event callback")

    try:
        for p in POSTPONED_REGIONS:
            ranges = p["regions_ar"]
            rdone = []
            for r in ranges:
                key = r.is_init.__name__
                initialized = False

                if key in init_cache:
                    initialized = init_cache[key]
                else:
                    # Re-check the memory init state
                    initialized = is_range_init(r)
                    init_cache[key] = initialized

                status = "not"
                if initialized:
                    status = ""
                    _set_gdb_memory_regions(p["offset"], [r])
                    add_memory_space_region(p["offset"], [r], "virtual")
                    add_memory_space_region(p["offset"], [r], "physical")
                    add_memory_space_region(p["offset"], [r], "physical_noncoherent")
                    rdone.append(r)

                log_mem_reg(
                    "Region: <{} {} {}> {} init".format(
                        hex(r.start), hex(r.length), r.attr, status
                    )
                )

            for r in rdone:
                p["regions_ar"].remove(r)

            if not len(p["regions_ar"]):
                pdone.append(p)

        for p in pdone:
            POSTPONED_REGIONS.remove(p)

        if not len(POSTPONED_REGIONS):
            gdb.events.stop.disconnect(handle_stopped_event)
    except Exception as e:
        log_mem_reg("Warning: {}".format(str(e)))
        traceback.print_exc()


# Register the STOP handler.
def register_gdb_stop_hook():
    log_mem_reg("Register stop hook")
    gdb.events.stop.connect(handle_stopped_event)


def validate_numeric_input(value_str, param_name, min_value=None, max_value=None):
    """
    Validates and converts a numeric string to integer.
  
    Args:
        value_str: String to validate and convert
        param_name: Parameter name for error messages
        min_value: Minimum allowed value (inclusive)
        max_value: Maximum allowed value (inclusive)
  
    Returns:
        int: The converted integer value
  
    Raises:
        gdb.GdbError: If validation fails
    """
    if not value_str:
        raise gdb.GdbError("[S32Debugger]Error: {} cannot be empty".format(param_name))
  
    # Handle negative numbers
    is_negative = value_str.startswith('-')
    if is_negative:
        value_str = value_str[1:]
  
    # Validate and convert
    try:
        if value_str.lower().startswith('0x'):
            hex_digits = value_str[2:]
            if not hex_digits:
                raise gdb.GdbError(
                    "[S32Debugger]Error: Invalid hex format for {}".format(param_name)
                )
            if not all(c in '0123456789abcdefABCDEF' for c in hex_digits):
                raise gdb.GdbError(
                    "[S32Debugger]Error: Invalid hex digits in {}: '{}'".format(
                        param_name, value_str
                    )
                )
            value = int(value_str, 16)
        elif value_str.isdigit():
            value = int(value_str, 10)
        else:
            raise gdb.GdbError(
                "[S32Debugger]Error: {} must be decimal or hex (0x...), got '{}'".format(
                    param_name, value_str
                )
            )
        
        if is_negative:
            value = -value
          
    except ValueError as e:
        raise gdb.GdbError(
            "[S32Debugger]Error: Failed to parse {}: {}".format(param_name, str(e))
        )
    
    # Validate against constraints
    if min_value is not None and value < min_value:
        raise gdb.GdbError(
            "[S32Debugger]Error: {} must be >= {}, got {}".format(
                param_name, min_value, value
            )
        )
        
    if max_value is not None and value > max_value:
        raise gdb.GdbError(
            "[S32Debugger]Error: {} must be <= {}, got {}".format(
                param_name, max_value, value
            )
        )
    return value

def set_axi_cfg_ap_lde(value):
    global AXI_CFG_AP_LDE
    if value == True:
        AXI_CFG_AP_LDE = Axi_Cfg_Ap_Lde_State.longdata_supported
    else:
        AXI_CFG_AP_LDE = Axi_Cfg_Ap_Lde_State.longdata_unsupported

def get_axi_cfg_ap_lde():
    global AXI_CFG_AP_LDE

    return AXI_CFG_AP_LDE