# -*- coding: utf-8 -*-
# Copyright 2015-2016 Freescale Semiconductor, Inc.
# Copyright 2017-2020, 2022-2025 NXP
# SPDX-License-Identifier: BSD-3-Clause

# Flash Programmer initialization script

import gdb   # @UnresolvedImport  # pylint: disable=F0401
import os
import signal
import time
import os.path
import sys
import importlib
import inspect

from subprocess import Popen, PIPE

from flash.scripts.remote import execute_remote
from flash.scripts.utils import gdb_execute
from flash.scripts.hooks import install_hook_quit_cmd
from flash.scripts.hooks import uninstall_hook_quit_cmd
from errors import Result, Ok, Error, ErrorCode, safecall, exceptcall

GTA_INSTALL_DIR = ''.join([os.path.dirname(os.path.realpath(__file__)),
                          '/', '../../../gta/'])
GDB_EXT_DIR = ''.join([os.path.dirname(os.path.realpath(__file__)),
                          '/', '../../../gdb_extensions/'])


#module private instance
_gta_proc = None
_fp_prompt ='cwFP'

HSE_limits = None

def internal_start(args):
    """
        Flash initialization script
    """
    gdb.write('Starting flash programmer services...\n')
    if internal_initialize_target(args) is False:
        return False

    #set current device
    if args.dev is not None:
        try:
            gdb.write('monitor flash current %s\n' % args.dev.lower())
            execute_remote('monitor flash current %s' % args.dev.lower(), no_wait = True)
            #change prompt to flash programming prompt
            gdb.write('set prompt (%s)\n ' % args.dev)
            gdb_execute('set prompt (%s) ' % args.dev)
        except gdb.GdbError as e:
            gdb.write("%s\n" % str(e))
            gdb.write('F-ERROR: fail to select device %s\n'
                  % args.dev.lower())
            return False
    else:
        gdb_execute('set prompt (%s) ' % _fp_prompt)
        gdb.write('Info: no device is selected, please select one before any \
            operations with fl_current <{nor, nand, qspi}>\n')

    from flash.gdbcommands.commands import create_commands
    create_commands(HSE_limits, is_MRAM, is_crc_implemented)

    ###################################################################
    ######### Flash sequence
    ###################################################################
    gdb.write("S32DS Flash Initialized\n")

def internal_start_server(args):
    """
        Start gta (gdb server)
    """

    # Disable pagination. The text displayed as part of the connection process does not fit
    # inside one page.
    gdb.execute("set pagination off")

    gtaip = None
    if args.gtaip:
        gdb.write('Using gdb server session %s\n' % args.gtaip)
        gtaip = args.gtaip
    else:
        gdb.write('Starting local server...\n')
        try:
            global _gta_proc
            gta_app = ''.join([GTA_INSTALL_DIR, 'gta'])
            gta_args = [gta_app, '-i', 'SIGINT']
            _gta_proc = Popen(gta_args, stdout = PIPE, cwd = GTA_INSTALL_DIR)
            # Allow GTA a short time to initialize.
            time.sleep( 2 )
            # now poll process to see if it is still alive
            # maybe bad parameters was passed to it
            if _gta_proc.poll():
                gdb.write('F-ERROR: fail to start debugger server from %s\n'
                  % GTA_INSTALL_DIR)
                _gta_proc = None
                return False

            out = _gta_proc.stdout.readline()
            words = out.split()
            port = words.pop()
            gtaip = ''.join(['127.0.0.1:', port])
            args.gtaip = gtaip
            gdb.write('Successfully started gdb server %s\n' % gtaip)
            install_hook_quit_cmd()
        except OSError:
            gdb.write('F-ERROR: fail to start debugger server from %s\n'
                  % GTA_INSTALL_DIR)
            return False

    if args.timeout:
        # Number of seconds to wait for the remote target responses.
        # Flash operations like "chip erase" may take up to 10 minutes to complete.
        gdb.write('Set gdb remote timeout to %d\n' % args.timeout)
        gdb_execute('set remotetimeout %d' % args.timeout)

    # Connect GDB to GDB server (GTA)
    try:
        gdb_execute("target extended-remote localhost:%s" % gtaip)
    except:
        gdb.write('F-ERROR: fail to use debugger server %s\n' % gtaip)
        return False

    return True

def internal_connect_to_target(args):
    """
        Connect to target
    """
    gdb.write('Connecting to target...\n')

    if args.soc is None and args.interactive is True:
        gdb.write('No SoC selected - please choose one from the list:\n')
        gdb.write('S32V234\n')
        # pylint: disable=all
        if sys.version_info.major == 2:
            args.soc = raw_input('SoC: ')
        else:
            args.soc = input('SoC: ')

    #Current context (SoC:core) to be used by flash programmer.
    if args.soc is not None:
        gdb.write('Using %s SoC\n' % args.soc)
        try:
            cmd_str = 'monitor ctx find :ccs:%s | has prop type = core' % args.soc
            gdb.write('%s\n' % cmd_str)
            cores = execute_remote(cmd_str, to_string = True)
            cores = cores.split('\n')

            #Execute the rest of the commands using context for the first core.
            cmd_str = 'monitor ctx set current :ccs:%s:%s' % (args.soc, cores[0])
            gdb.write('%s\n' % cmd_str)
            ret = execute_remote(cmd_str, to_string = True)
            if ret:
                gdb.write('%s\n' % ret)
                return False

        except gdb.GdbError as e:
            gdb.write('%s\n' % str(e))
            gdb.write('F-ERROR: fail to use %s SoC\n' % args.soc)
            return False
    else:
        gdb.write('F-ERROR: no SoC selected. \
        \n Use interactive mode or select one in configuration file.\n')
        return False

    #Address and port that will be used by CCS.
    if args.ccsip:
        try:
            gdb.write('Using ccs server session %s\n' % args.ccsip)
            execute_remote("monitor ctx id :ccs set prop ccs_host:port %s:"
                        % args.ccsip)
        except gdb.GdbError as e:
            gdb.write('%s\n' % str(e))
            gdb.write('F-ERROR: fail to use ccs server session %s\n' % args.ccsip)
            return False

    if args.probe:
        #Configure connection connection.
        if args.probe.startswith("cmsisdap"):
            gdb.write('Using CMSIS-DAP\n')
            execute_remote('monitor ctx id :ccs set prop probe %s' % args.probe)

            # Increase CCS timeout for CMSIS-DAP.
            execute_remote("mon ctx id :ccs set prop ccs_timeout 300")
        else:
            gdb.write('Using CWTAP %s\n' % args.probe)
            execute_remote('monitor ctx id :ccs set prop probe cwtap:%s' % args.probe)
    else:
        gdb.write('Using CWTAP connected to local usb port\n')
        execute_remote('monitor ctx id :ccs set prop probe cwtap')

    #Set the JTAG speed.
    if args.jtag_speed:
        gdb.write('Using jtag speed %d\n' % args.jtag_speed)
        execute_remote('monitor ctx id :ccs set prop jtag_speed %d'
                    % args.jtag_speed)

    #Connect to the target
    gdb.write('Connecting to probe...\n')
    try:
        execute_remote('monitor ctx connect')
    except gdb.GdbError as e:
        gdb.write('%s\n' % str(e))
        return False
    gdb.write('Successfully connected to probe\n')
    return True

def internal_source_gdb_ext_script(relative_path):
    script = ''.join([GDB_EXT_DIR, relative_path])
    try:
        gdb_execute('source %s' % (script))
    except gdb.GdbError as e:
        gdb.write('%s\n' % str(e))
        Result(Error(ErrorCode.FlashUndefinedError, 'F-ERROR: fail to source %s\n' % relative_path)).unwrap()

def internal_initialize_target(args):
    """
        Initialize target
    """
    gdb.write('Initializing target...\n')

    #adding set of commands
    if args.no_init is False:
        board_init_path = args.init
        board_init_script = os.path.basename(board_init_path)
       
        #check if file exists
        if os.path.isfile(board_init_path) is True:
            gdb.write('Running init script %s\n' % board_init_path)
            try:
                fname = os.path.basename(board_init_path)
                path = os.path.dirname(board_init_path)
                sys.path.append(path)
            
                DbgInit = importlib.import_module(fname[:-3])
                core_name = args.core_name if args.core_name else ''
                common_setup_argv = [args.jtag_speed, int(args.gtaip,0), args.reset_type, args.probe, core_name, args.reset_delay, args.timeout, args.flash_name, args.log]
                common_setup_argc = len(common_setup_argv)
                
                secure_type = args.secure_type if args.secure_type else None
                secure_key = args.secure_key if args.secure_key else None
                target_lifecycle = args.target_lifecycle if args.target_lifecycle else None

                # ccsip has the folloing pattern: ip:port
                ccs_vars = args.ccsip.split(':')
                ccs_ip = ccs_vars[0]
                ccs_port = int(ccs_vars[1])
                
                # besides the common setup args, we may also support:
                additional_args = [args.soc_name, secure_type, secure_key, target_lifecycle, ccs_port, ccs_ip] # elements have to be in this exact order
                
                # use inspect module to get setup args supported by imported module
                supported_setup_argv = inspect.getargspec(DbgInit.setup)[0]
                supported_setup_argc = len(supported_setup_argv)
                
                # prepare final list of args to be passed
                setup_args = []
                setup_args.extend(common_setup_argv)
                # figure out how many additional parameters does the target setup function support
                # and append them to the parameter list that will be passed
                setup_args.extend(additional_args[:supported_setup_argc - common_setup_argc])
                DbgInit.setup(*setup_args)
                DbgInit.board_flash_init()
                try:
                    global HSE_limits
                    HSE_limits = DbgInit.get_HSE_limits()
                    HSE_limits.get_part()
                    HSE_limits.get_mem_layout()
                    HSE_limits.initialized = True
                except BaseException as e:
                    HSE_limits = None
                try:
                    global is_MRAM
                    is_MRAM = DbgInit.is_MRAM()
                except BaseException as e:
                    is_MRAM = False
                try:
                    global is_crc_implemented
                    is_crc_implemented = DbgInit.is_crc_implemented()
                except BaseException as e:
                    is_crc_implemented = False
            except gdb.GdbError as e:
                gdb.write('%s\n' % str(e))
                gdb.write('F-ERROR: fail to initialize target\n')
                return False
            gdb.write('Initialized board with script %s.\n' % board_init_script)
        else:
            gdb.write('F-ERROR: can\'t find init script %s\n' % board_init_path)
            return False
        return True
    else:
        execute_remote('monitor halt')
    return True

def internal_close_server():
    if _gta_proc is not None:
        uninstall_hook_quit_cmd()
        gdb.write('Closing debugger server...\n')
        #os.kill(_gta_proc.pid, signal.SIGINT)
        Result(Error(ErrorCode.FlashInitError, 'F-ERROR: Could not initialize flash service')).unwrap()

def start(args):
    """
        Start Flash Programming service
    """
    if internal_start(args) is False:
        internal_close_server()
        return False
    return True

def reset_board(init_script):
    """
        Reset board.
        If reset function exists in init_script perform function,
        else perform generic "monitor reset debug"
    """
    gdb.write('Reseting board...\n')
    board_init_path = init_script
    board_init_script = os.path.basename(board_init_path)
    
    #check if file exists
    if os.path.isfile(board_init_path) is True:
        try:
            fname = os.path.basename(board_init_path)
            path = os.path.dirname(board_init_path)
            sys.path.append(path)
            DbgInit = importlib.import_module(fname[:-3])
            DbgInit.reset_board()
            gdb.write("Reset function called\n")
        except AttributeError:
            gdb.write("monitor reset called\n")
            gdb.execute("monitor reset debug")
            return True
    else:
        gdb.write('F-ERROR: can\'t find init script %s\n' % board_init_path)
        return False
    return True
