#!/usr/bin/env python3
# Copyright (c) 2023 Intel Corporation
#
# SPDX-License-Identifier: Apache-2.0
"""
Blackbox tests for twister's command line functions
"""
import importlib
import mock
import os
import pytest
import sys

from conftest import ZEPHYR_BASE, testsuite_filename_mock, clear_log_in_test
from twisterlib.testplan import TestPlan

sys.path.insert(0, os.path.join(ZEPHYR_BASE, "scripts/pylib/twister/twisterlib"))

@mock.patch.object(TestPlan, 'TESTSUITE_FILENAME', testsuite_filename_mock)
class TestHardwaremap:
    TESTDATA_1 = [
        (
            [
                'ARM',
                'SEGGER',
                'MBED'
            ],
            [
                'DAPLink CMSIS-DAP',
                'MBED CMSIS-DAP'
            ],
            [1234, 'abcd'],
            'pyocd'
        ),
        (
            [
                'STMicroelectronics',
                'Atmel Corp.'
            ],
            [
                'J-Link',
                'J-Link OB'
            ],
            [1234, 'abcd'],
            'jlink'
        ),
        (
            [
                'Silicon Labs',
                'NXP Semiconductors',
                'Microchip Technology Inc.'
            ],
            [
                'STM32 STLink',
                '^XDS110.*',
                'STLINK-V3'
            ],
            [1234, 'abcd'],
            'openocd'
        ),
        (
            [
                'FTDI',
                'Digilent',
                'Microsoft'
            ],
            [
                'TTL232R-3V3',
                'MCP2200 USB Serial Port Emulator'
            ],
            [1234, 'abcd'],
            'dediprog'
        )
    ]
    TESTDATA_2 = [
        (
            'FTDI',
            'DAPLink CMSIS-DAP',
            1234,
            'pyocd'
        )
    ]
    TESTDATA_3 = [
        (
            'Texas Instruments',
            'DAPLink CMSIS-DAP',
            'abcd', 'las'
        ),
        (
            'Texas Instruments',
            'DAPLink CMSIS-DAP',
            'abcd', 'dse0'
        )
    ]

    @classmethod
    def setup_class(cls):
        apath = os.path.join(ZEPHYR_BASE, 'scripts', 'twister')
        cls.loader = importlib.machinery.SourceFileLoader('__main__', apath)
        cls.spec = importlib.util.spec_from_loader(cls.loader.name, cls.loader)
        cls.twister_module = importlib.util.module_from_spec(cls.spec)

    @classmethod
    def teardown_class(cls):
        pass

    @pytest.mark.parametrize(
        ('manufacturer', 'product', 'serial', 'runner'),
        TESTDATA_1,
    )
    def test_generate(self, capfd, out_path, manufacturer, product, serial, runner):
        file_name = "test-map.yaml"
        path = os.path.join(ZEPHYR_BASE, file_name)
        args = ['--outdir', out_path, '--generate-hardware-map', file_name]

        if os.path.exists(path):
            os.remove(path)

        def mocked_comports():
            return [
                mock.Mock(device='/dev/ttyUSB23',
                          manufacturer=id_man,
                          product=id_pro,
                          serial_number=id_serial
                          )
            ]

        for id_man in manufacturer:
            for id_pro in product:
                for id_serial in serial:
                    with mock.patch.object(sys, 'argv', [sys.argv[0]] + args), \
                            mock.patch('serial.tools.list_ports.comports',
                                       side_effect=mocked_comports), \
                            pytest.raises(SystemExit) as sys_exit:
                        self.loader.exec_module(self.twister_module)

                    out, err = capfd.readouterr()
                    sys.stdout.write(out)
                    sys.stderr.write(err)

                    assert os.path.exists(path)

                    expected_data = '- connected: true\n' \
                                    f'  id: {id_serial}\n' \
                                    '  platform: unknown\n' \
                                    f'  product: {id_pro}\n' \
                                    f'  runner: {runner}\n' \
                                    '  serial: /dev/ttyUSB23\n'

                    load_data = open(path).read()
                    assert load_data == expected_data

                    if os.path.exists(path):
                        os.remove(path)

                    assert str(sys_exit.value) == '0'
                    clear_log_in_test()

    @pytest.mark.parametrize(
        ('manufacturer', 'product', 'serial', 'runner'),
        TESTDATA_2,
    )
    def test_few_generate(self, capfd, out_path, manufacturer, product, serial, runner):
        file_name = "test-map.yaml"
        path = os.path.join(ZEPHYR_BASE, file_name)
        args = ['--outdir', out_path, '--generate-hardware-map', file_name]

        if os.path.exists(path):
            os.remove(path)

        def mocked_comports():
            return [
                mock.Mock(device='/dev/ttyUSB23',
                          manufacturer=manufacturer,
                          product=product,
                          serial_number=serial
                          ),
                mock.Mock(device='/dev/ttyUSB24',
                          manufacturer=manufacturer,
                          product=product,
                          serial_number=serial + 1
                          ),
                mock.Mock(device='/dev/ttyUSB24',
                          manufacturer=manufacturer,
                          product=product,
                          serial_number=serial + 2
                          ),
                mock.Mock(device='/dev/ttyUSB25',
                          manufacturer=manufacturer,
                          product=product,
                          serial_number=serial + 3
                          )
            ]

        with mock.patch.object(sys, 'argv', [sys.argv[0]] + args), \
                mock.patch('serial.tools.list_ports.comports',
                           side_effect=mocked_comports), \
                pytest.raises(SystemExit) as sys_exit:
            self.loader.exec_module(self.twister_module)

        out, err = capfd.readouterr()
        sys.stdout.write(out)
        sys.stderr.write(err)

        assert os.path.exists(path)

        expected_data = '- connected: true\n' \
                        f'  id: {serial}\n' \
                        '  platform: unknown\n' \
                        f'  product: {product}\n' \
                        f'  runner: {runner}\n' \
                        '  serial: /dev/ttyUSB23\n' \
                        '- connected: true\n' \
                        f'  id: {serial + 1}\n' \
                        '  platform: unknown\n' \
                        f'  product: {product}\n' \
                        f'  runner: {runner}\n' \
                        '  serial: /dev/ttyUSB24\n' \
                        '- connected: true\n' \
                        f'  id: {serial + 2}\n' \
                        '  platform: unknown\n' \
                        f'  product: {product}\n' \
                        f'  runner: {runner}\n' \
                        '  serial: /dev/ttyUSB24\n' \
                        '- connected: true\n' \
                        f'  id: {serial + 3}\n' \
                        '  platform: unknown\n' \
                        f'  product: {product}\n' \
                        f'  runner: {runner}\n' \
                        '  serial: /dev/ttyUSB25\n'

        load_data = open(path).read()
        assert load_data == expected_data

        if os.path.exists(path):
            os.remove(path)

        assert str(sys_exit.value) == '0'

    @pytest.mark.parametrize(
        ('manufacturer', 'product', 'serial', 'location'),
        TESTDATA_3,
    )
    def test_texas_exeption(self, capfd, out_path, manufacturer, product, serial, location):
        file_name = "test-map.yaml"
        path = os.path.join(ZEPHYR_BASE, file_name)
        args = ['--outdir', out_path, '--generate-hardware-map', file_name]

        if os.path.exists(path):
            os.remove(path)

        def mocked_comports():
            return [
                mock.Mock(device='/dev/ttyUSB23',
                          manufacturer=manufacturer,
                          product=product,
                          serial_number=serial,
                          location=location
                          )
            ]

        with mock.patch.object(sys, 'argv', [sys.argv[0]] + args), \
                mock.patch('serial.tools.list_ports.comports',
                           side_effect=mocked_comports), \
                pytest.raises(SystemExit) as sys_exit:
            self.loader.exec_module(self.twister_module)

        out, err = capfd.readouterr()
        sys.stdout.write(out)
        sys.stderr.write(err)

        assert os.path.exists(path)

        expected_data = '- connected: true\n' \
                        f'  id: {serial}\n' \
                        '  platform: unknown\n' \
                        f'  product: {product}\n' \
                        '  runner: pyocd\n' \
                        '  serial: /dev/ttyUSB23\n'
        expected_data2 = '[]\n'

        load_data = open(path).read()
        if location.endswith('0'):
            assert load_data == expected_data
        else:
            assert load_data == expected_data2
        if os.path.exists(path):
            os.remove(path)

        assert str(sys_exit.value) == '0'
