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

Copyright 2018-2022, 2024-2025 NXP.

SPDX-License-Identifier: BSD-3-Clause

"""

import gdb  # @UnresolvedImport  # pylint: disable=F0401
import argparse
import os
import re
import string
import binascii
import filecmp

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

from flash.scripts.utils import string_to_offset
from flash.scripts.utils import string_to_size
from flash.scripts.utils import string_to_abs_path
from flash.scripts.utils import is_hex_sequence
from flash.scripts.utils import size_to_human_readable
from flash.scripts.utils import get_soc_ctx_from_current
from flash.scripts.remote import execute_remote
from flash.scripts.elfparser import ElfFile
from flash.scripts.elfparser import ProgType
from flash.scripts.elfparser import SectionFlag
from flash.scripts.flashdesc import FlashDesc, FlashWriter
from flash.scripts.flashcommandsimpl import FlashCommandsImplBinary

from errors import Result, Error, ErrorCode

global BASE_ALIAS
BASE_ALIAS = None

#for internal usage
class ElfSector_GCC:
    sector = None
    section = None

    def __init__(self, sector, section):
        self.sector = sector
        self.section = section

    def get_offset(self, base):
        return self.get_addr() - base

    def get_size(self):
        return self.section.size

    def get_addr(self):
        return self.section.addr - self.sector.vaddr + self.sector.paddr

    def __str__(self):
        return "" \
            + "sector     %s\n" % self.sector \
            + "section     %s\n" % self.section
            
class ElfSector_GHS(ElfSector_GCC):
    def get_addr(self):
        return self.section.addr


W_DIR = os.path.dirname(os.path.realpath(__file__))
W_FILE = W_DIR + '/written.dat'
D_FILE = W_DIR + '/dump.dat'

###################################################################
######### flash programmer write command
###################################################################
class CwFlashWriteElf(CwFlashBase):
    """
    usage: [-h] [-v] [-e] [-b address] filename

    S32DS Flash Programming write elf command.

    positional arguments:
      filename              elf file

    optional arguments:
      -h, --help            show this help message and exit
      -v, --verify          perform verify of the content written in flash
      -e, --erase           erase section before flash
      -b, --base            re-arrange flash base.
    """

    # 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 parameters
                    An optional --erase parameter is added to specify if an erase of the
                    affected area should be performed first
        """
        super(CwFlashWriteElf, self).__init__(CMD_CW_FLASH_WRITE_ELF,
                                              gdb.COMMAND_USER, gdb.COMPLETE_FILENAME)
        parser = argparse.ArgumentParser(usage='[-v][-e][-b] file', \
                                     description='S32DS Flash Programming write elf command.')
        parser.add_argument('file', type=string_to_abs_path, help='elf file path')
        parser.add_argument('-v', '--verify', action="store_true", default=False, help='verify written data')
        parser.add_argument('-e', '--erase', action="store_true", default=False, help='erase before write')
        parser.add_argument('--verbose',  action="store_true", default=False, help='Enable logs')
        parser.add_argument('-b', '--base',  type=self.auto_int, default=0, help='Allows flash file in the alias area')
        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

        CwFlashWriteElf.print_result = None
        
    def can_execute(self, args):
        """
            Internal method
        """
        pass
        
    def auto_int(self, v):
        return int(v, 0)

    def execute(self, args, from_tty):

        self._output = ""
        abs_path = self._get_abs_path(args.file)
        if abs_path:
            abs_path = abs_path.replace("\\", "/")
        else:
            Result(Error(ErrorCode.FlashParameterError, "F-ERROR: File Not Found"))

        f_info = self._info_for_current_device(from_tty)
        if f_info is None:
            Result(Error(ErrorCode.FlashInitError, "F-ERROR: Flash is not configured"))

        # base can be set as external variable by flash initialization script so we must check it too
        f_base = int(f_info['address'], 0) if args.base == 0 else args.base
        if 'BASE_ALIAS' in globals() and BASE_ALIAS != None:
         #   global BASE_ALIAS
            f_base = BASE_ALIAS
        f_size = int(f_info['size'], 0)
        
        
        f_data_base = f_info['data_flash_address']
        # take file and extract loadable sections.
        file = ElfFile(abs_path)
        if file is None:
            Result(Error(ErrorCode.FlashParameterError, "F-ERROR: This file format is not supported"))
            
        # extract data
        if (f_data_base == "0xdeadbeef"):
            load_sect = self._check_and_scan(file, f_base, f_size)
        else:
            #program flash info
            f_program_base = int(f_info['program_flash_address'], 0)
            f_size = int(f_info['size'], 0)
            #data flash info
            f_data_base = int(f_info['data_flash_address'], 0)
            f_data_sectors = int(f_info['data_sectors'],0)
            f_data_sect_size = int(f_info['data_sect_size'], 0)
            f_size_buff = hex(f_data_sect_size * f_data_sectors)
            f_data_size = int(f_size_buff,0)
            load_sect = self._check_and_scan(file, f_program_base, f_size)
            load_sect += self._check_and_scan(file, f_data_base, f_data_size)
        if len(load_sect) == 0 or load_sect is None:
            Result(Error(ErrorCode.FlashParameterError, "F-ERROR: No Loadable data available"))
            
        reply = execute_remote('mon flash async false', from_tty, no_wait=True)

        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 f_base != desc.base_address:
            gdb.write("internal remapping to 0x%X\n" % desc.base_address)

        [self.check_restricted(s) for sublist in load_sect for s in sublist]
        
        fw = FlashWriter(desc, True)

        gdb.write("Writing....\n")
        if not self.is_MRAM:
            for sublist in load_sect:
                for s in sublist:
                    fw.load_data(None, None, None, s, f_base)
            try:
                fw.execute(args.erase, args.verify, self.is_MRAM)
            except:
                Result(Error(ErrorCode.FlashWriteError, "F-ERROR: Can not flash file")).unwrap()
        else:
            for sublist in load_sect:
                for s in sublist:
                    fw.load_data(None, None, None, s, f_base)
                    try:
                        fw.execute(args.erase, args.verify, self.is_MRAM)
                    except:
                        Result(Error(ErrorCode.FlashWriteError, "F-ERROR: Can not flash file")).unwrap()
        gdb.write("SUCCESS\n")
                
    def check_restricted(self, section):
        size = section.get_size()
        addr = section.get_addr()
        if(self.HSE_limits != None):
            try:
                self.HSE_limits.check_address(addr, size)
            except:
                Result(Error(ErrorCode.FlashWriteError, "F-ERROR: Restricted area detected. Aborting Command"))
                return

    def _get_abs_path(self, path):
        abs_path = os.path.abspath(path)
        if os.path.isfile(abs_path):
            return abs_path
        return None
        
    def _check_and_scan(self, file, f_base, f_size):
        isghs = True
        for x in file.iter_prog_headers():
            if x.paddr != 0:
                isghs = False
        
        if isghs:
            return self._extract_prog_headers_ghs(file, f_base, f_size)
        
        return self._extract_prog_headers_gcc(file, f_base, f_size)
      

    def _extract_prog_headers_gcc(self, f, f_base, f_size):
        #scan all prog headers. If it PT_LOAD and fit in flash try to find sections
        return [self._find_sect_gcc(x, f) for x in f.iter_prog_headers()
                if (x.prog_type is ProgType.PT_LOAD) and
                (f_base <= x.paddr) and (x.paddr + x.filesz <= f_base + f_size)]
                
    def _extract_prog_headers_ghs(self, f, f_base, f_size):
        #scan all prog headers. If it PT_LOAD and fit in flash try to find sections
        return [self._find_sect_ghs(x, f) for x in f.iter_prog_headers()
                if (x.prog_type is ProgType.PT_LOAD) and
                (f_base <= x.vaddr) and (x.vaddr + x.filesz <= f_base + f_size)]

    def _find_sect_gcc(self, p_head, f):
        return [ElfSector_GCC(p_head, x) for x in f.iter_section_headers()
                if (p_head.vaddr <= x.addr) and (p_head.vaddr + p_head.filesz >= x.addr + x.size)
                and x.size != 0 and (x.flags&SectionFlag.SHF_ALLOC is SectionFlag.SHF_ALLOC)]
                
    def _find_sect_ghs(self, p_head, f):
        return [ElfSector_GHS(p_head, x) for x in f.iter_section_headers()
                if (p_head.vaddr <= x.addr) and (p_head.vaddr + p_head.filesz >= x.addr + x.size)
                and x.size != 0 and (x.flags&SectionFlag.SHF_ALLOC is SectionFlag.SHF_ALLOC)]

    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=True)
        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.FlashInitError, '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 show_progress_bar(self):
        pass

    def end_progress_bar(self):
        pass

    def show_result(self):
        summary = ""

        if self._output:
            summary = '%s\n' % self._output
            if 'succeeded' in summary:
                summary += 'Write %s ELF in %.2fs\n' % (self._args.file, self._time)
            else:
                summary += 'F-ERROR: Write failed due to verify fail\n'
                Result(Error(ErrorCode.FlashCompareError, summary)).unwrap()
        else:
            summary = 'Write %s ELF in %.2fs\n' % (self._args.file, self._time)
        gdb.write(summary)


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


