# -*- coding: utf-8 -*-
"""
    Implementation of Flash Programmer dump command
    
Copyright 2015 Freescale Semiconductor, Inc.
Copyright 2017-2018, 2024-2025 NXP.

SPDX-License-Identifier: BSD-3-Clause

"""

import gdb   # @UnresolvedImport  # pylint: disable=F0401
import argparse
import binascii

from flash.gdbcommands.base_command import CwFlashBase
from flash.gdbcommands.cmd_name import CMD_CW_FLASH_DUMP

from flash.scripts.utils import string_to_offset
from flash.scripts.utils import string_to_size
from flash.scripts.utils import size_to_human_readable
from flash.scripts.utils import string_to_abs_path
from flash.scripts.remote import execute_remote

from errors import Result, Error, ErrorCode

###################################################################
######### flash programmer dump command
###################################################################
class CwFlashDump(CwFlashBase):
    """
usage: [-h] offset size [-c {1,2,4,8,16} | -f [FILE]]

S32DS Flash Programming dump command.

positional arguments:
  offset                offset in device address range
  size                  size of memory section to be dumped

optional arguments:
  -h, --help            show this help message and exit
  -c {1,2,4,8,16}, --cell {1,2,4,8,16}
                        bytes per cell
  -f [FILE], --file [FILE]
                        output data file path
    """

    def __init__(self, hse_limits):
        """
            Register new command with GDB
            Add a parameter parser to easily specify positional offset
            and size parameters
            An optional -f parameter is added to specify if dump should be
            collected in a file
        """
        super(CwFlashDump, self).__init__(CMD_CW_FLASH_DUMP,
        gdb.COMMAND_USER, gdb.COMPLETE_FILENAME)
        parser = argparse.ArgumentParser(usage='[-h] offset size [-c {1,2,4,8,16} | -f [FILE]]',
                                         description='S32DS \
                                         Flash Programming dump command.')
        parser.add_argument('offset',
                            type = string_to_offset,
                            help = 'offset in device address range')
        parser.add_argument('size',
                            type = string_to_size,
                            help = 'size of memory section to be dumped')

        group = parser.add_mutually_exclusive_group()
        group.add_argument('-c', '--cell',
                           default = 4,
                           type = int,
                           choices = [1, 2, 4, 8, 16],
                           help = 'bytes pe cell')
        group.add_argument('-f', '--file',
                           nargs='+',
                           type=string_to_abs_path,
                           help = 'output data file path')
        self._parser = parser

        self.HSE_limits = hse_limits

    def execute(self, args, from_tty):
        """
            Execute command called with params parameters
            Build command string in the form of a monitor command
            and call internal gdb execution.
        """
        cmd_string = ''
        pretty_print = False
        self._output = ""
        if args.offset is not None and args.size is not None:
            if args.file is not None:
                cmd_string = 'mon flash dump binary %s offset 0x%x size 0x%x' \
                    % (''.join(args.file), args.offset, args.size)
            else:
                cmd_string = 'mon flash dump offset 0x%x size 0x%x' \
                    % (args.offset, args.size)
                pretty_print = True
        else:
            cmd_string = 'mon flash dump'

        if(self.HSE_limits != None):
            try:
                self.HSE_limits.check_address(args.offset, args.size)
            except:
                Result(Error(ErrorCode.FlashWriteError, "F-ERROR: Restricted area detected. Aborting Command"))
                return
        #execute command
        ctx_str = execute_remote(cmd_string, from_tty, to_string = True, \
                                 no_wait = True)


        if pretty_print:
            crt_offset = args.offset
            crt_size = args.size
            grouping = args.cell
            # Display 16 bytes for each console line
            BYTES_PER_LINE = 16
            buff = bytearray.fromhex(ctx_str)
            # Compute the number of cells per line
            no_cells = BYTES_PER_LINE // grouping
            # Compute the number of bytes that were not read
            # from the first aligned address.
            first_bytes = crt_offset % BYTES_PER_LINE
            read_bytes = len(ctx_str)
            crt_index = 0
            crt_offset = crt_offset - (crt_offset % BYTES_PER_LINE)
            while crt_index < read_bytes:
                if self._output:
                    self._output += "\n"
                # Print offset
                output_addr = hex(crt_offset)[2:].zfill(8) + ': '
                output_hex = ''
                output_ascii = ''
                i = 0
                while i < no_cells:
                    if first_bytes >= grouping:
                        first_bytes -= grouping
                        output_hex += (' ' * grouping * 2 )
                    elif first_bytes > 0:
                        output_hex += (' ' * first_bytes * 2 )
                        output_hex += ctx_str[crt_index : crt_index + grouping * 2 - first_bytes * 2]
                        crt_index += (grouping * 2) - first_bytes * 2
                        first_bytes = 0
                    else:
                        output_hex += ctx_str[crt_index : crt_index + grouping *2 - first_bytes]
                        output_ascii += ''.join(chr(byte) if byte >= 32 and byte <= 126 else '.' \
                                                for byte in buff[(crt_index // 2) : (crt_index // 2 + grouping) - first_bytes])
                        crt_index += grouping *2
                    output_hex += ' '
                    i += 1 
                crt_offset += BYTES_PER_LINE
                self._output += output_addr + output_hex.ljust(BYTES_PER_LINE *2 + no_cells) + '| ' + output_ascii
                
                
    def begin_command(self):
            gdb.write('Dumping....\n')

    def show_result(self):
        if self._output:
            gdb.write('%s\n' % self._output)
        summary = 'Dump %s in %.2fs\n' % \
            (size_to_human_readable(self._args.size), self._time)
        gdb.write(summary)

def create_command(hse_limits):
    """
        Instantiate flash programmer dump command
    """
    CwFlashDump(hse_limits)
