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

import argparse
from unittest.mock import patch, call

import pytest

from runners.nrfjprog import NrfJprogBinaryRunner
from conftest import RC_KERNEL_HEX


#
# Test values
#

TEST_DEF_SNR = 'test-default-serial-number'  # for mocking user input
TEST_OVR_SNR = 'test-override-serial-number'

#
# Expected results.
#
# This dictionary maps different configurations to the commands we expect to be
# executed for them. Verification is done by mocking the check_call() method,
# which is used to run the commands.
#
# The key naming scheme is <F><SR><SN><E>, where:
#
# - F: family, 1 for 'NRF51' or 2 for 'NRF52'
# - SR: soft reset, Y for yes, N for pin reset
# - SNR: serial number override, Y for yes, N for 'use default'
# - E: full chip erase, Y for yes, N for sector / sector and UICR only
#

EXPECTED_COMMANDS = {
    # NRF51:
    '1NNN':
    (['nrfjprog', '--program', RC_KERNEL_HEX, '-f', 'NRF51', '--snr', TEST_DEF_SNR, '--sectorerase'],  # noqa: E501
     ['nrfjprog', '--pinreset', '-f', 'NRF51', '--snr', TEST_DEF_SNR]),

    '1NNY':
    (['nrfjprog', '--eraseall', '-f', 'NRF51', '--snr', TEST_DEF_SNR],
     ['nrfjprog', '--program', RC_KERNEL_HEX, '-f', 'NRF51', '--snr', TEST_DEF_SNR],  # noqa: E501
     ['nrfjprog', '--pinreset', '-f', 'NRF51', '--snr', TEST_DEF_SNR]),

    '1NYN':
    (['nrfjprog', '--program', RC_KERNEL_HEX, '-f', 'NRF51', '--snr', TEST_OVR_SNR, '--sectorerase'],  # noqa: E501
     ['nrfjprog', '--pinreset', '-f', 'NRF51', '--snr', TEST_OVR_SNR]),

    '1NYY':
    (['nrfjprog', '--eraseall', '-f', 'NRF51', '--snr', TEST_OVR_SNR],
     ['nrfjprog', '--program', RC_KERNEL_HEX, '-f', 'NRF51', '--snr', TEST_OVR_SNR],  # noqa: E501
     ['nrfjprog', '--pinreset', '-f', 'NRF51', '--snr', TEST_OVR_SNR]),

    '1YNN':
    (['nrfjprog', '--program', RC_KERNEL_HEX, '-f', 'NRF51', '--snr', TEST_DEF_SNR, '--sectorerase'],  # noqa: E501
     ['nrfjprog', '--reset', '-f', 'NRF51', '--snr', TEST_DEF_SNR]),

    '1YNY':
    (['nrfjprog', '--eraseall', '-f', 'NRF51', '--snr', TEST_DEF_SNR],
     ['nrfjprog', '--program', RC_KERNEL_HEX, '-f', 'NRF51', '--snr', TEST_DEF_SNR],  # noqa: E501
     ['nrfjprog', '--reset', '-f', 'NRF51', '--snr', TEST_DEF_SNR]),

    '1YYN':
    (['nrfjprog', '--program', RC_KERNEL_HEX, '-f', 'NRF51', '--snr', TEST_OVR_SNR, '--sectorerase'],  # noqa: E501
     ['nrfjprog', '--reset', '-f', 'NRF51', '--snr', TEST_OVR_SNR]),

    '1YYY':
    (['nrfjprog', '--eraseall', '-f', 'NRF51', '--snr', TEST_OVR_SNR],
     ['nrfjprog', '--program', RC_KERNEL_HEX, '-f', 'NRF51', '--snr', TEST_OVR_SNR],  # noqa: E501
     ['nrfjprog', '--reset', '-f', 'NRF51', '--snr', TEST_OVR_SNR]),

    # NRF52:
    '2NNN':
    (['nrfjprog', '--program', RC_KERNEL_HEX, '-f', 'NRF52', '--snr', TEST_DEF_SNR, '--sectoranduicrerase'],  # noqa: E501
     ['nrfjprog', '--pinresetenable', '-f', 'NRF52', '--snr', TEST_DEF_SNR],
     ['nrfjprog', '--pinreset', '-f', 'NRF52', '--snr', TEST_DEF_SNR]),

    '2NNY':
    (['nrfjprog', '--eraseall', '-f', 'NRF52', '--snr', TEST_DEF_SNR],
     ['nrfjprog', '--program', RC_KERNEL_HEX, '-f', 'NRF52', '--snr', TEST_DEF_SNR],  # noqa: E501
     ['nrfjprog', '--pinresetenable', '-f', 'NRF52', '--snr', TEST_DEF_SNR],
     ['nrfjprog', '--pinreset', '-f', 'NRF52', '--snr', TEST_DEF_SNR]),

    '2NYN':
    (['nrfjprog', '--program', RC_KERNEL_HEX, '-f', 'NRF52', '--snr', TEST_OVR_SNR, '--sectoranduicrerase'],  # noqa: E501
     ['nrfjprog', '--pinresetenable', '-f', 'NRF52', '--snr', TEST_OVR_SNR],
     ['nrfjprog', '--pinreset', '-f', 'NRF52', '--snr', TEST_OVR_SNR]),

    '2NYY':
    (['nrfjprog', '--eraseall', '-f', 'NRF52', '--snr', TEST_OVR_SNR],
     ['nrfjprog', '--program', RC_KERNEL_HEX, '-f', 'NRF52', '--snr', TEST_OVR_SNR],  # noqa: E501
     ['nrfjprog', '--pinresetenable', '-f', 'NRF52', '--snr', TEST_OVR_SNR],
     ['nrfjprog', '--pinreset', '-f', 'NRF52', '--snr', TEST_OVR_SNR]),

    '2YNN':
    (['nrfjprog', '--program', RC_KERNEL_HEX, '-f', 'NRF52', '--snr', TEST_DEF_SNR, '--sectoranduicrerase'],  # noqa: E501
     ['nrfjprog', '--reset', '-f', 'NRF52', '--snr', TEST_DEF_SNR]),

    '2YNY':
    (['nrfjprog', '--eraseall', '-f', 'NRF52', '--snr', TEST_DEF_SNR],
     ['nrfjprog', '--program', RC_KERNEL_HEX, '-f', 'NRF52', '--snr', TEST_DEF_SNR],  # noqa: E501
     ['nrfjprog', '--reset', '-f', 'NRF52', '--snr', TEST_DEF_SNR]),

    '2YYN':
    (['nrfjprog', '--program', RC_KERNEL_HEX, '-f', 'NRF52', '--snr', TEST_OVR_SNR, '--sectoranduicrerase'],  # noqa: E501
     ['nrfjprog', '--reset', '-f', 'NRF52', '--snr', TEST_OVR_SNR]),

    '2YYY':
    (['nrfjprog', '--eraseall', '-f', 'NRF52', '--snr', TEST_OVR_SNR],
     ['nrfjprog', '--program', RC_KERNEL_HEX, '-f', 'NRF52', '--snr', TEST_OVR_SNR],  # noqa: E501
     ['nrfjprog', '--reset', '-f', 'NRF52', '--snr', TEST_OVR_SNR]),

    # NRF91:
    '9NNN':
    (['nrfjprog', '--program', RC_KERNEL_HEX, '-f', 'NRF91', '--snr', TEST_DEF_SNR, '--sectorerase'],  # noqa: E501
     ['nrfjprog', '--pinreset', '-f', 'NRF91', '--snr', TEST_DEF_SNR]),

    '9NNY':
    (['nrfjprog', '--eraseall', '-f', 'NRF91', '--snr', TEST_DEF_SNR],
     ['nrfjprog', '--program', RC_KERNEL_HEX, '-f', 'NRF91', '--snr', TEST_DEF_SNR],  # noqa: E501
     ['nrfjprog', '--pinreset', '-f', 'NRF91', '--snr', TEST_DEF_SNR]),

    '9NYN':
    (['nrfjprog', '--program', RC_KERNEL_HEX, '-f', 'NRF91', '--snr', TEST_OVR_SNR, '--sectorerase'],  # noqa: E501
     ['nrfjprog', '--pinreset', '-f', 'NRF91', '--snr', TEST_OVR_SNR]),

    '9NYY':
    (['nrfjprog', '--eraseall', '-f', 'NRF91', '--snr', TEST_OVR_SNR],
     ['nrfjprog', '--program', RC_KERNEL_HEX, '-f', 'NRF91', '--snr', TEST_OVR_SNR],  # noqa: E501
     ['nrfjprog', '--pinreset', '-f', 'NRF91', '--snr', TEST_OVR_SNR]),

    '9YNN':
    (['nrfjprog', '--program', RC_KERNEL_HEX, '-f', 'NRF91', '--snr', TEST_DEF_SNR, '--sectorerase'],  # noqa: E501
     ['nrfjprog', '--reset', '-f', 'NRF91', '--snr', TEST_DEF_SNR]),

    '9YNY':
    (['nrfjprog', '--eraseall', '-f', 'NRF91', '--snr', TEST_DEF_SNR],
     ['nrfjprog', '--program', RC_KERNEL_HEX, '-f', 'NRF91', '--snr', TEST_DEF_SNR],  # noqa: E501
     ['nrfjprog', '--reset', '-f', 'NRF91', '--snr', TEST_DEF_SNR]),

    '9YYN':
    (['nrfjprog', '--program', RC_KERNEL_HEX, '-f', 'NRF91', '--snr', TEST_OVR_SNR, '--sectorerase'],  # noqa: E501
     ['nrfjprog', '--reset', '-f', 'NRF91', '--snr', TEST_OVR_SNR]),

    '9YYY':
    (['nrfjprog', '--eraseall', '-f', 'NRF91', '--snr', TEST_OVR_SNR],
     ['nrfjprog', '--program', RC_KERNEL_HEX, '-f', 'NRF91', '--snr', TEST_OVR_SNR],  # noqa: E501
     ['nrfjprog', '--reset', '-f', 'NRF91', '--snr', TEST_OVR_SNR]),

}


def expected_commands(family, softreset, snr, erase):
    '''Expected NrfJprogBinaryRunner results given parameters.

    Returns a factory function which expects the following arguments:

    - family: string, 'NRF51', 'NRF52' or 'NRF91'
    - softreset: boolean, controls whether soft reset is performed
    - snr: string serial number of board, or None
    - erase: boolean, whether to do a full chip erase or not
    '''
    expected_key = '{}{}{}{}'.format(
        '1' if family == 'NRF51' else '2' if family == 'NRF52' else '9',
        'Y' if softreset else 'N',
        'Y' if snr else 'N',
        'Y' if erase else 'N')

    return EXPECTED_COMMANDS[expected_key]


#
# Test cases
#

TEST_CASES = [(f, sr, snr, e)
              for f in ('NRF51', 'NRF52', 'NRF91')
              for sr in (False, True)
              for snr in (TEST_OVR_SNR, None)
              for e in (False, True)]


def get_board_snr_patch():
    return TEST_DEF_SNR


def require_patch(program):
    assert program == 'nrfjprog'


def id_fn(test_case):
    ret = ''
    for x in test_case:
        if x in ('NRF51', 'NRF52'):
            ret += x[-1:]
        else:
            ret += 'Y' if x else 'N'
    return ret


@pytest.mark.parametrize('test_case', TEST_CASES, ids=id_fn)
@patch('runners.core.ZephyrBinaryRunner.require', side_effect=require_patch)
@patch('runners.nrfjprog.NrfJprogBinaryRunner.get_board_snr_from_user',
       side_effect=get_board_snr_patch)
@patch('runners.nrfjprog.NrfJprogBinaryRunner.check_call')
def test_nrfjprog_init(cc, get_snr, req, test_case, runner_config):
    family, softreset, snr, erase = test_case

    runner = NrfJprogBinaryRunner(runner_config, family, softreset, snr,
                                  erase=erase)
    runner.run('flash')

    assert req.called
    assert cc.call_args_list == [call(x) for x in
                                 expected_commands(*test_case)]
    if snr is None:
        get_snr.assert_called_once_with()
    else:
        get_snr.assert_not_called()


@pytest.mark.parametrize('test_case', TEST_CASES, ids=id_fn)
@patch('runners.core.ZephyrBinaryRunner.require', side_effect=require_patch)
@patch('runners.nrfjprog.NrfJprogBinaryRunner.get_board_snr_from_user',
       side_effect=get_board_snr_patch)
@patch('runners.nrfjprog.NrfJprogBinaryRunner.check_call')
def test_nrfjprog_create(cc, get_snr, req, test_case, runner_config):
    family, softreset, snr, erase = test_case

    args = ['--nrf-family', family]
    if softreset:
        args.append('--softreset')
    if snr is not None:
        args.extend(['--snr', snr])
    if erase:
        args.append('--erase')

    parser = argparse.ArgumentParser()
    NrfJprogBinaryRunner.add_parser(parser)
    arg_namespace = parser.parse_args(args)
    runner = NrfJprogBinaryRunner.create(runner_config, arg_namespace)
    runner.run('flash')

    assert req.called
    assert cc.call_args_list == [call(x) for x in
                                 expected_commands(*test_case)]
    if snr is None:
        get_snr.assert_called_once_with()
    else:
        get_snr.assert_not_called()
