# Copyright (c) 2018 Foundries.io
# Copyright (c) 2019 Nordic Semiconductor ASA.
#
# SPDX-License-Identifier: Apache-2.0

import argparse
from unittest.mock import patch, call
import os

import pytest

from runners import blackmagicprobe
from runners.blackmagicprobe import BlackMagicProbeRunner
from conftest import RC_KERNEL_ELF, RC_GDB
import serial.tools.list_ports

TEST_GDB_SERIAL = 'test-gdb-serial'

# Expected subprocesses to be run for each command. Using the
# runner_config fixture (and always specifying gdb-serial) means we
# don't get 100% coverage, but it's a starting out point.
EXPECTED_COMMANDS = {
    'attach':
    ([RC_GDB,
      '-ex', "set confirm off",
      '-ex', "target extended-remote {}".format(TEST_GDB_SERIAL),
      '-ex', "monitor swdp_scan",
      '-ex', "attach 1",
      '-ex', "file {}".format(RC_KERNEL_ELF)],),
    'debug':
    ([RC_GDB,
      '-ex', "set confirm off",
      '-ex', "target extended-remote {}".format(TEST_GDB_SERIAL),
      '-ex', "monitor swdp_scan",
      '-ex', "attach 1",
      '-ex', "file {}".format(RC_KERNEL_ELF),
      '-ex', "load {}".format(RC_KERNEL_ELF)],),
    'flash':
    ([RC_GDB,
      '-ex', "set confirm off",
      '-ex', "target extended-remote {}".format(TEST_GDB_SERIAL),
      '-ex', "monitor swdp_scan",
      '-ex', "attach 1",
      '-ex', "load {}".format(RC_KERNEL_ELF),
      '-ex', "kill",
      '-ex', "quit",
      '-silent'],),
}

EXPECTED_CONNECT_SRST_COMMAND = {
        'attach': 'monitor connect_rst disable',
        'debug': 'monitor connect_rst enable',
        'flash': 'monitor connect_rst enable',
}

def require_patch(program):
    assert program == RC_GDB

@pytest.mark.parametrize('command', EXPECTED_COMMANDS)
@patch('runners.core.ZephyrBinaryRunner.require', side_effect=require_patch)
@patch('runners.core.ZephyrBinaryRunner.check_call')
def test_blackmagicprobe_init(cc, req, command, runner_config):
    '''Test commands using a runner created by constructor.'''
    runner = BlackMagicProbeRunner(runner_config, TEST_GDB_SERIAL)
    runner.run(command)
    assert cc.call_args_list == [call(x) for x in EXPECTED_COMMANDS[command]]

@pytest.mark.parametrize('command', EXPECTED_COMMANDS)
@patch('runners.core.ZephyrBinaryRunner.require', side_effect=require_patch)
@patch('runners.core.ZephyrBinaryRunner.check_call')
def test_blackmagicprobe_create(cc, req, command, runner_config):
    '''Test commands using a runner created from command line parameters.'''
    args = ['--gdb-serial', TEST_GDB_SERIAL]
    parser = argparse.ArgumentParser(allow_abbrev=False)
    BlackMagicProbeRunner.add_parser(parser)
    arg_namespace = parser.parse_args(args)
    runner = BlackMagicProbeRunner.create(runner_config, arg_namespace)
    runner.run(command)
    assert cc.call_args_list == [call(x) for x in EXPECTED_COMMANDS[command]]

@pytest.mark.parametrize('command', EXPECTED_CONNECT_SRST_COMMAND)
@patch('runners.core.ZephyrBinaryRunner.require', side_effect=require_patch)
@patch('runners.core.ZephyrBinaryRunner.check_call')
def test_blackmagicprobe_connect_rst(cc, req, command, runner_config):
    '''Test that commands list the correct connect_rst value when enabled.'''
    args = ['--gdb-serial', TEST_GDB_SERIAL, '--connect-rst']
    parser = argparse.ArgumentParser(allow_abbrev=False)
    BlackMagicProbeRunner.add_parser(parser)
    arg_namespace = parser.parse_args(args)
    runner = BlackMagicProbeRunner.create(runner_config, arg_namespace)
    runner.run(command)
    expected = EXPECTED_CONNECT_SRST_COMMAND[command]
    assert expected in cc.call_args_list[0][0][0]

@pytest.mark.parametrize('arg, env, expected', [
    # Argument has priority
    ('/dev/XXX', None, '/dev/XXX'),
    ('/dev/XXX', '/dev/YYYY', '/dev/XXX'),

    # Then BMP_GDB_SERIAL env variable
    (None, '/dev/XXX', '/dev/XXX'),
    ])
def test_blackmagicprobe_gdb_serial_generic(arg, env, expected):
    if env:
        os.environ['BMP_GDB_SERIAL'] = env
    else:
        if 'BMP_GDB_SERIAL' in os.environ:
            os.environ.pop('BMP_GDB_SERIAL')

    ret = blackmagicprobe.blackmagicprobe_gdb_serial(arg)
    assert expected == ret

@pytest.mark.parametrize('known_path, comports, globs, expected', [
    (True, False, ['/dev/ttyACM0', '/dev/ttyACM1'],
     blackmagicprobe.DEFAULT_LINUX_BMP_PATH),
    (False, True, [], '/dev/ttyACM3'),
    (False, False,  ['/dev/ttyACM0', '/dev/ttyACM1'], '/dev/ttyACM0'),
    (False, False, ['/dev/ttyACM1', '/dev/ttyACM0'], '/dev/ttyACM0'),
    ])
@patch('serial.tools.list_ports.comports')
@patch('os.path.exists')
@patch('glob.glob')
def test_blackmagicprobe_gdb_serial_linux(gg, ope, stlpc, known_path, comports,
                                          globs, expected):
    gg.return_value = globs
    ope.return_value = known_path
    if comports:
        fake_comport1 = serial.tools.list_ports_common.ListPortInfo(
                '/dev/ttyACM1')
        fake_comport1.interface = 'something'
        fake_comport2 = serial.tools.list_ports_common.ListPortInfo(
                '/dev/ttyACM2')
        fake_comport2.interface = None
        fake_comport3 = serial.tools.list_ports_common.ListPortInfo(
                '/dev/ttyACM3')
        fake_comport3.interface = blackmagicprobe.BMP_GDB_INTERFACE
        stlpc.return_value = [fake_comport1, fake_comport2, fake_comport3]
    else:
        stlpc.return_value = []

    ret = blackmagicprobe.blackmagicprobe_gdb_serial_linux()
    assert expected == ret

@pytest.mark.parametrize('comports, globs, expected', [
    (True, [], '/dev/cu.usbmodem3'),
    (False, ['/dev/cu.usbmodemAABBCC0', '/dev/cu.usbmodemAABBCC1'],
     '/dev/cu.usbmodemAABBCC0'),
    (False, ['/dev/cu.usbmodemAABBCC1', '/dev/cu.usbmodemAABBCC0'],
     '/dev/cu.usbmodemAABBCC0'),
    ])
@patch('serial.tools.list_ports.comports')
@patch('glob.glob')
def test_blackmagicprobe_gdb_serial_darwin(gg, stlpc, comports, globs, expected):
    gg.return_value = globs
    if comports:
        fake_comport1 = serial.tools.list_ports_common.ListPortInfo(
                '/dev/cu.usbmodem1')
        fake_comport1.description = 'unrelated'
        fake_comport2 = serial.tools.list_ports_common.ListPortInfo(
                '/dev/cu.usbmodem2')
        fake_comport2.description = None
        fake_comport3 = serial.tools.list_ports_common.ListPortInfo(
                '/dev/cu.usbmodem3')
        fake_comport3.description = f'{blackmagicprobe.BMP_GDB_PRODUCT} v1234'
        fake_comport4 = serial.tools.list_ports_common.ListPortInfo(
                '/dev/cu.usbmodem4')
        fake_comport4.description = f'{blackmagicprobe.BMP_GDB_PRODUCT} v1234'
        stlpc.return_value = [fake_comport1, fake_comport2,
                              fake_comport4, fake_comport3]
    else:
        stlpc.return_value = []

    ret = blackmagicprobe.blackmagicprobe_gdb_serial_darwin()
    assert expected == ret

@pytest.mark.parametrize('comports, expected', [
    (True, 'COM4'),
    (False, 'COM1'),
    ])
@patch('serial.tools.list_ports.comports')
def test_blackmagicprobe_gdb_serial_win32(stlpc, comports, expected):
    if comports:
        fake_comport1 = serial.tools.list_ports_common.ListPortInfo('COM2')
        fake_comport1.vid = 123
        fake_comport1.pid = 456
        fake_comport2 = serial.tools.list_ports_common.ListPortInfo('COM3')
        fake_comport2.vid = None
        fake_comport2.pid = None
        fake_comport3 = serial.tools.list_ports_common.ListPortInfo('COM4')
        fake_comport3.vid = blackmagicprobe.BMP_GDB_VID
        fake_comport3.pid = blackmagicprobe.BMP_GDB_PID
        fake_comport4 = serial.tools.list_ports_common.ListPortInfo('COM5')
        fake_comport4.vid = blackmagicprobe.BMP_GDB_VID
        fake_comport4.pid = blackmagicprobe.BMP_GDB_PID
        stlpc.return_value = [fake_comport1, fake_comport2,
                              fake_comport4, fake_comport3]
    else:
        stlpc.return_value = []

    ret = blackmagicprobe.blackmagicprobe_gdb_serial_win32()
    assert expected == ret
