# -*- coding: utf-8 -*-
"""
    Custom GDB command for reading and writing registers.


 Copyright 2020-2025 NXP

 NXP Confidential and Proprietary. This software is owned or controlled by NXP
 and may only be used strictly in accordance with the applicable license terms.
 By expressly accepting such terms or by downloading, installing, activating
 and/or otherwise using the software, you are agreeing that you have read, and
 that you agree to comply with and are bound by, such license terms. If you do
 not agree to be bound by the applicable license terms, then you may not retain,
 install, activate or otherwise use the software.


"""

import gdb
import os
import sys
import string

CURRENT_DIR = "".join([os.path.dirname(os.path.realpath(__file__)), "/"])
sys.path.insert(0, CURRENT_DIR)
sys.path.insert(0, CURRENT_DIR + "/../../utils")

import mem_reg_utils as utils
from errors import GtaLibException, ErrorCode
from register_name_lookup import register_lookup

class ReadRegisterCommand(gdb.Command):
    """Read register value.
    usage: reg_read [context] REG_INDEX/REG_NAME ACCESS_SIZE [format]
        [context]   -   GTA (GDB server) debug context :ccs:<soc>[:core]
                       If present, the context can be a core context;
                       if not, the current context is used.
                       e.g. ":ccs:S32G2XX:CortexA53#0"
        REG_INDEX   - Register index OR Register Name
        ACCESS_SIZE - Access size.
        FORMAT      - format the output value of the command, possible values are "hex", "bin" and "dec".
                    In case of a wrong input argument the default value will be used, which is "hex".
    """

    def __init__(self):
        """
        Constructor
        """
        super(ReadRegisterCommand, self).__init__("reg_read", gdb.COMMAND_USER)

    def invoke(self, args, from_tty):
        """Called when the command is invoked"""
        try:
            self.dont_repeat()
            argv = gdb.string_to_argv(args)

            fmt = "hex"
            access_size = 4
            
            if len(argv) == 4:
                dbg_ctx = argv[0]
                reg = argv[1].upper()
                access_size = conv_to_int(argv[2])
                fmt = argv[3].lower()
            elif len(argv) == 3:
                if ":ccs:" in argv[0]:
                    dbg_ctx = argv[0]
                    reg = argv[1].upper()
                    access_size = conv_to_int(argv[2])
                else:
                    dbg_ctx = utils.gdb_exec("monitor ctx get current", False, True).unwrap().rstrip()
                    reg = argv[0].upper()
                    access_size = conv_to_int(argv[1])
                    fmt = argv[2].lower()
            elif len(argv) == 2:
                if ":ccs:" in argv[0]:
                    dbg_ctx = argv[0]
                    reg = argv[1].upper()
                else:
                    dbg_ctx = utils.gdb_exec("monitor ctx get current", False, True).unwrap().rstrip()
                    reg = argv[0].upper()
                    access_size = conv_to_int(argv[1])
            elif len(argv) == 1:
                dbg_ctx = utils.gdb_exec("monitor ctx get current", False, True).unwrap().rstrip()
                reg = argv[0].upper()
            else:
                errtxt = "Error: invalid number of parameters"
                gdb.write(errtxt + os.linesep)
                raise gdb.GdbError(errtxt)
            
            address = register_lookup.get_address_by_name(reg.lower(), dbg_ctx)
            if address:
                # If the register name is successfully looked up, use the address
                reg = address
                
            if access_size == 4:
                raw_value = checkError(utils.read_reg32(dbg_ctx, reg))
            elif access_size == 8:
                raw_value = checkError(utils.read_reg64(dbg_ctx, reg))
            else:
                errtxt = "[S32Debugger]Error: {}".format(ErrorCode.ReadRegisterError.value)
                gdb.write(errtxt + os.linesep)
                raise gdb.GdbError(errtxt)

            formatted_value = format_value(int(raw_value), fmt)
            gdb.write("[S32Debugger] " + formatted_value + os.linesep)
            gdb.flush()

        except gdb.GdbError as e:
            # TODO: to be checked why DS is quiting when converting GdbError
            gdb.write("[S32Debugger] Error detected in reg_read" + os.linesep)


# Custom GDB command for writing registers.
class WriteRegisterCommand(gdb.Command):
    """Write register value.
    Usage: reg_write [context] REG_INDEX VALUE
        [context]  -   GTA (GDB server) debug context :ccs:<soc>[:core]
                       e.g. ":ccs:S32G2XX:CortexA53#0"
                       If present, the context can be a core context;
                       if not, the current context is used.
        REG_INDEX  -   register indexthe name of the register from REG_GROUP. Can be hex value
        VALUE      -   Is the value to be written. Can be hex value
    """

    def __init__(self):
        """
        Constructor
        """
        super(WriteRegisterCommand, self).__init__("reg_write", gdb.COMMAND_USER)

    def invoke(self, args, from_tty):
        """Called when the command is invoked"""
        try:
            self.dont_repeat()
            argv = gdb.string_to_argv(args)
            
            if len(argv) == 3:
                dbg_ctx = argv[0]
                reg = argv[1].upper()
                value = conv_to_int(argv[2])
            elif len(argv) == 2:
                dbg_ctx = utils.gdb_exec("monitor ctx get current", False, True).unwrap().rstrip()
                reg = argv[0].upper()
                value = conv_to_int(argv[1])
            else:
                errtxt = "[S32Debugger]Error: invalid number of parameters"
                gdb.write(errtxt + os.linesep)
                raise gdb.GdbError(errtxt)

            address = register_lookup.get_address_by_name(reg.lower(), dbg_ctx)
            if address:
                # If the register name is successfully looked up, use the address
                reg = address

            if value > 0xFFFFFFFF:
                result = checkError(utils.write_reg64(dbg_ctx, reg, value))
            else:
                result = checkError(utils.write_reg32(dbg_ctx, reg, value))

            gdb.write("[S32Debugger]OK" + result + os.linesep)
            gdb.flush()

        except gdb.GdbError as e:
            # TODO: to be checked why DS is quiting when converting GdbError
            gdb.write("[S32Debugger] Error detected in reg_write" + os.linesep)


def create_commands():
    """Create GDB commands."""
    ReadRegisterCommand()
    WriteRegisterCommand()


def conv_to_int(num):
    # pylint: disable=all
    if sys.version_info.major == 2:
        if isinstance(num, str) or isinstance(num, unicode):
            if num.startswith("0x"):
                num = int(num, 16)
            else:
                num = int(num, 10)
        return num
    else:
        if isinstance(num, str):
            if num.startswith("0x"):
                num = int(num, 16)
            else:
                num = int(num, 10)
        return num


def checkError(result):
    if result.iserror():
        if not ping():
            err_txt = "[S32Debugger]Errors: {}".format(ErrorCode.BusError.value)  # pylint: disable=no-member
            gdb.write(err_txt + os.linesep)
            raise gdb.GdbError(err_txt)
        raise gdb.GdbError(
            "[S32Debugger]Error: {}".format(result.error.error_type.value)
        )
    else:
        return result.unwrap()


def ping():
    context = utils.gdb_exec("monitor ctx get current", False, True).unwrap().rstrip()
    arm_list = ["M7", "M4", "M33", "M0", "A53", "R52"]
    if any(arm in context for arm in arm_list):  # available only for ARM context
        cmd = 'printf "0x%lx\n", $pc'
        result = utils.gdb_exec(cmd)
        if result.iserror():
            return False
        # read from $PC location
        result = utils.read_mem_str(context, result.unwrap(), 4, "virtual", 1)
        if result.iserror():
            return False
    return True

def format_value(value, fmt):
    """Format the integer value based on the specified format."""
    if fmt == "dec":
        return str(value)
    elif fmt == "bin":
        return bin(value)
    else:  # default to hex
        return hex(value)