# Copyright (c) 2020 Synopsys.
#
# SPDX-License-Identifier: Apache-2.0

'''Runners for Synopsys Metaware Debugger(mdb).'''


import shutil
import os
from os import path

from runners.core import ZephyrBinaryRunner, RunnerCaps


# normally we should create class with common functionality inherited from
# ZephyrBinaryRunner and inherit MdbNsimBinaryRunner and MdbHwBinaryRunner
# from it. However as we do lookup for runners with
# ZephyrBinaryRunner.__subclasses__() such sub-sub-classes won't be found.
# So, we move all common functionality to helper functions instead.
def is_simulation_run(mdb_runner):
    return mdb_runner.nsim_args != ''

def is_hostlink_used(mdb_runner):
    return mdb_runner.build_conf.getboolean('CONFIG_UART_HOSTLINK')

def is_flash_cmd_need_exit_immediately(mdb_runner):
    if is_simulation_run(mdb_runner):
        # for nsim, we can't run and quit immediately
        return False
    elif is_hostlink_used(mdb_runner):
        # if hostlink is used we can't run and quit immediately, as we still need MDB process
        # attached to process hostlink IO
        return False
    else:
        return True

def mdb_do_run(mdb_runner, command):
    commander = "mdb64"

    mdb_runner.require(commander)

    mdb_basic_options = ['-nooptions', '-nogoifmain', '-toggle=include_local_symbols=1']

    # remove previous .sc.project folder which has temporary settings
    # for MDB. This is useful for troubleshooting situations with
    # unexpected behavior of the debugger
    mdb_cfg_dir = path.join(mdb_runner.build_dir, '.sc.project')
    if path.exists(mdb_cfg_dir):
        shutil.rmtree(mdb_cfg_dir)

    # nsim
    if is_simulation_run(mdb_runner):
        mdb_target = ['-nsim', '@' + mdb_runner.nsim_args]
    # hardware target
    else:
        if mdb_runner.jtag == 'digilent':
            mdb_target = ['-digilent']
            if mdb_runner.dig_device: mdb_target += [mdb_runner.dig_device]
        else:
            # \todo: add support of other debuggers
            raise ValueError('unsupported jtag adapter {}'.format(mdb_runner.jtag))

    if command == 'flash':
        if is_flash_cmd_need_exit_immediately(mdb_runner):
            mdb_run = ['-run', '-cmd=-nowaitq run', '-cmd=quit', '-cl']
        else:
            mdb_run = ['-run', '-cl']
    elif command == 'debug':
        # use mdb gui to debug
        mdb_run = ['-OKN']

    if mdb_runner.cores == 1:
        # single core's mdb command is different with multicores
        mdb_cmd = [commander] + mdb_basic_options + mdb_target + mdb_run + [mdb_runner.elf_name]
    elif 1 < mdb_runner.cores <= 12:
        mdb_multifiles = '-multifiles='
        for i in range(mdb_runner.cores):
            mdb_sub_cmd = [commander] + ['-pset={}'.format(i + 1), '-psetname=core{}'.format(i)]
            # -prop=download=2 is used for SMP application debug, only the 1st core
            # will download the shared image.
            if i > 0: mdb_sub_cmd += ['-prop=download=2']
            mdb_sub_cmd += mdb_basic_options + mdb_target + [mdb_runner.elf_name]
            mdb_runner.check_call(mdb_sub_cmd, cwd=mdb_runner.build_dir)
            mdb_multifiles += ('core{}'.format(mdb_runner.cores-1-i) if i == 0 else ',core{}'.format(mdb_runner.cores-1-i))

        # to enable multi-core aware mode for use with the MetaWare debugger,
        # need to set the NSIM_MULTICORE environment variable to a non-zero value
        if is_simulation_run(mdb_runner):
            os.environ["NSIM_MULTICORE"] = '1'

        mdb_cmd = [commander] + [mdb_multifiles] + mdb_run
    else:
        raise ValueError('unsupported cores {}'.format(mdb_runner.cores))

    mdb_runner.call(mdb_cmd, cwd=mdb_runner.build_dir)


class MdbNsimBinaryRunner(ZephyrBinaryRunner):
    '''Runner front-end for nSIM via mdb.'''

    def __init__(self, cfg, cores=1, nsim_args=''):
        super().__init__(cfg)
        self.jtag = ''
        self.cores = int(cores)
        if nsim_args != '':
            self.nsim_args = path.join(cfg.board_dir, 'support', nsim_args)
        else:
            self.nsim_args = ''
        self.elf_name = cfg.elf_file
        self.build_dir = cfg.build_dir
        self.dig_device = ''

    @classmethod
    def name(cls):
        return 'mdb-nsim'

    @classmethod
    def capabilities(cls):
        return RunnerCaps(commands={'flash', 'debug'})

    @classmethod
    def do_add_parser(cls, parser):
        parser.add_argument('--cores', default=1,
                            help='''choose the cores that target has, e.g.
                                    --cores=1''')
        parser.add_argument('--nsim_args', default='',
                            help='''if given, arguments for nsim simulator
                                 through mdb which should be in
                                 <board_dir>/support, e.g. --nsim-args=
                                 mdb_em.args''')

    @classmethod
    def do_create(cls, cfg, args):
        return MdbNsimBinaryRunner(
            cfg,
            cores=args.cores,
            nsim_args=args.nsim_args)

    def do_run(self, command, **kwargs):
        mdb_do_run(self, command)


class MdbHwBinaryRunner(ZephyrBinaryRunner):
    '''Runner front-end for mdb.'''

    def __init__(self, cfg, cores=1, jtag='digilent', dig_device=''):
        super().__init__(cfg)
        self.jtag = jtag
        self.cores = int(cores)
        self.nsim_args = ''
        self.elf_name = cfg.elf_file
        if dig_device != '':
            self.dig_device = '-prop=dig_device=' + dig_device
        else:
            self.dig_device = ''
        self.build_dir = cfg.build_dir

    @classmethod
    def name(cls):
        return 'mdb-hw'

    @classmethod
    def capabilities(cls):
        return RunnerCaps(commands={'flash', 'debug'})

    @classmethod
    def do_add_parser(cls, parser):
        parser.add_argument('--jtag', default='digilent',
                            help='''choose the jtag interface for hardware
                                    targets, e.g. --jtag=digilent for digilent
                                    jtag adapter''')
        parser.add_argument('--cores', default=1,
                            help='''choose the number of cores that target has,
                                    e.g. --cores=1''')
        parser.add_argument('--dig-device', default='',
                            help='''choose the the specific digilent device to
                             connect, this is useful when multiple
                             targets are connected''')

    @classmethod
    def do_create(cls, cfg, args):
        return MdbHwBinaryRunner(
            cfg,
            cores=args.cores,
            jtag=args.jtag,
            dig_device=args.dig_device)

    def do_run(self, command, **kwargs):
        mdb_do_run(self, command)
