# -*- coding: utf-8 -*-
"""
    Implementation of Flash Programmer write command

Copyright 2015-2016 Freescale Semiconductor, Inc.
Copyright 2017-2020, 2024-2025 NXP.

SPDX-License-Identifier: BSD-3-Clause

"""

import gdb   # @UnresolvedImport  # pylint: disable=F0401
import argparse
import os
import string
from time import time

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

from flash.scripts.utils import string_to_offset
from flash.scripts.utils import string_to_size
from flash.scripts.utils import is_hex_sequence
from flash.scripts.utils import int_to_bytes
from flash.scripts.utils import string_to_value
from flash.scripts.utils import size_to_human_readable
from flash.scripts.remote import execute_remote

from flash.scripts.flashcommandsimpl import FlashCommandsImplBinary
from flash.scripts.flashdesc import FlashDesc, FlashWriter

from errors import Result, Error, ErrorCode

###################################################################
######### flash programmer write command
###################################################################
class CwFlashWrite(CwFlashBase):
    """
usage: [-h] [-s [SIZE]] [--erase] offset data [data ...] [--verify [-n NUMBER]/[-a]]

S32DS Flash Programming write command.

positional arguments:
  offset                offset in device address range
  data                  data to be programmed in flash; can be a hex sequence
                        or a binary file

optional arguments:
  -h, --help            show this help message and exit
  -s [SIZE], --size [SIZE]
                        how much to write
  -e, --erase               perform erase first
  -v, --verify              perform verify of the content written in flash
    """

    """"
        Since user can check all flash device or a large range,
        we should read 512K chunks to improve usability
    """
    data_chunk_size = 512 * 1024

    def __init__(self, hse_limits, is_crc_implemented, is_MRAM):
        """
            Register new command with GDB
            Add a parameter parser to easily specify positional offset, data
            and size parameters
            An optional --erase parameter is added to specify if an erase of the
            affected area should be performed first
        """
        super(CwFlashWrite, self).__init__(CMD_CW_FLASH_WRITE,
        gdb.COMMAND_USER, gdb.COMPLETE_FILENAME)
        parser = argparse.ArgumentParser(description='S32DS \
                                         Flash Programming write command.')
        parser.add_argument('offset',
                            type = string_to_offset,
                            help = 'offset in device address range')
        parser.add_argument('data',
                            nargs='+',
                            help = 'data to be programmed in flash; \
                                    can be a hex sequence or a binary file')
        parser.add_argument('-s','--size',
                            type = string_to_size,
                            nargs ='?',
                            default = None,
                            help = 'how much to write')
        parser.add_argument('-e','--erase',
                            action="store_true",
                            help = 'perform erase first')
        parser.add_argument('-v','--verify',
                            action="store_true",
                            help = 'perform verify of the content written in flash')
        parser.add_argument('--verbose',
                            action="store_true",
                            help = 'Enable logs')
        parser.add_argument('--nt_limit',
                            default=0x1000,
                            help = 'Step of the size for write operation')

        self._parser = parser

        self.HSE_limits = hse_limits
        self.is_crc_implemented = is_crc_implemented
        self.is_MRAM = is_MRAM

        CwFlashWrite.print_result = None

    def can_execute(self, args):
        """
            Internal method
        """
        pass

    def execute(self, args, from_tty):
        """
            Execute command called with args parameters
            Build command string in the form of a monitor command
            and call internal gdb execution.
        """
        verbose = False
        self._output = ""
        
        data = None
        size = None


        if args.offset is not None and args.data is not None:
            args.data = ' '.join(args.data)
            if args.data.startswith('0x'):
                if is_hex_sequence(args.data):
                    intval = string_to_value(args.data)
                    data_bytes = int_to_bytes(intval, len(args.data[2:0]))
                    
                    data = data_bytes
                    size = len(data)
                    if args.size is not None and args.size <= size:
                        size = args.size
                else:
                    Result(Error(ErrorCode.FlashParameterError,'F-ERROR: invalid hex sequence'))
            # handle like a file
            else:
                abs_path = self._get_abs_path(args.data)
                if abs_path:
                    f = open(abs_path, 'rb')
                    data = f.read()
                    size = len(data)
                    f.close()
                    if args.size is not None:
                        size = args.size
                else:
                    Result(Error(ErrorCode.FlashParametersError, 'F-ERROR: file %s not found.' % args.data.replace("\\", "/")))
        
        f_info = self._info_for_current_device(from_tty)
        if f_info is None:
            Result(Error(ErrorCode.FlashInitError, "F-ERROR: Flash is not configured"))
        f_base = int(f_info['address'], 0) 
        f_io = FlashCommandsImplBinary(from_tty)

        restricted_addressess = None
        if(self.HSE_limits != None):
            restricted_addressess = self.HSE_limits.get_info()
            restricted_addressess.pop(0)

        desc = FlashDesc(f_info, f_io, args.verbose, args.nt_limit, restricted_addressess, self.is_crc_implemented)

        if(self.HSE_limits != None):
            try:
                self.HSE_limits.check_address(args.offset + f_base, size)
            except:
                Result(Error(ErrorCode.FlashWriteError, "F-ERROR: Restricted area detected. Aborting Command"))
                return

        fw = FlashWriter(desc, False)

        try:
            fw.load_data(args.offset + f_base, data, size)
            gdb.write("Writing....\n")
            fw.execute(args.erase, args.verify, self.is_MRAM)
            gdb.write("SUCCESS\n")
        except:
            Result(Error(ErrorCode.FlashWriteError, "F-ERROR: Can not flash data"))


    def _info_for_current_device(self, from_tty):
        cmd_string = 'mon flash current'
        current_flash = execute_remote(cmd_string, from_tty, to_string=True, \
                                       no_wait=False)
        current_flash = current_flash.rstrip('\n')

        if 'no device selected' in current_flash:
            gdb.write('F-ERROR: %s\n' % current_flash)
            return
        cmd_string = 'mon ctx get current'
        current_ctx = execute_remote(cmd_string, from_tty, to_string=True)
        if not current_ctx:
            Result(Error(ErrorCode.FlashParameterError, 'F-ERROR: no context selected\n'))

        current_ctx = current_ctx.rstrip('\n')
        cmd_string = 'mon ctx id %s:%s info' % (current_ctx, current_flash)
        ctxs = current_ctx.split(':')
        if len(ctxs) >= 3:
            device = ':%s:%s:%s' % (ctxs[1], ctxs[2], current_flash)
            cmd_string = 'mon ctx id %s info' % device
        result = execute_remote(cmd_string, from_tty, to_string=True)
        prop_bag = self._strToPropertyBag(result)
        return prop_bag

    def _strToPropertyBag(self, strProp):
        prop_bag = {}
        prop_list = strProp.split('\n')
        for prop in prop_list:
            prop = prop.replace(' ', '')
            name_value = prop.split(':')
            if len(name_value) == 2:
                # guard against inconsistency from gta side
                if name_value[1].startswith('0x') or \
                        not all(c in string.hexdigits for c in name_value[1]):
                    prop_bag[name_value[0]] = name_value[1]
                else:
                    prop_bag[name_value[0]] = ''.join(['0x', name_value[1]])
        return prop_bag

    def _get_abs_path(self, path):
        abs_path = os.path.abspath(path)
        if os.path.isfile(abs_path):
            return abs_path
        else:
            return None

        return None

    def show_result(self):
        summary = ""

        if self._output:
            summary = '%s\n' % self._output

            if 'succeeded' in summary:
                if self._args.size is None:
                    summary += 'Write %s in %.2fs\n' % \
                        (self._args.data, self._time)
                else:
                    summary += 'Write %s from %s in %.2fs\n' % \
                        (size_to_human_readable(self._args.size),
                        self._args.data, self._time)
            else:
                summary += 'F-ERROR: Write failed from 0x%0x due to verify fail\n' % \
                        (self._args.offset)
                Result(Error(ErrorCode.FlashCompareError, summary)).unwrap()
        else:
            if self._args.size is None:
                summary = 'Write %s in %.2fs\n' % \
                    (self._args.data, self._time)
            else:
                summary = 'Write %s from %s in %.2fs\n' % \
                    (size_to_human_readable(self._args.size),
                    self._args.data, self._time)

        gdb.write(summary)

    def show_progress_bar(self):
        pass

    def end_progress_bar(self):
        pass

def create_command(hse_limits, is_crc_implemented, is_MRAM):
    """
        Instantiate flash programmer write command
    """
    CwFlashWrite(hse_limits, is_crc_implemented, is_MRAM)
