"""Test SimCore."""

import pytest

from pymodbus.constants import ExcCodes
from pymodbus.simulator import DataType, SimData, SimDevice
from pymodbus.simulator.simruntime import SimRuntime


class TestSimRuntime:
    """Test simulator runtime component."""

    async def my_action(
            self,
            function_code,
            _start_address,
            _address,
            _count,
            current_registers,
            set_values
         ):
        """Run action."""
        if function_code in {1, 3}:
            current_registers[1] = 17
        if function_code in {2, 4}:
            return ExcCodes.ILLEGAL_ADDRESS
        elif function_code == 15:
            set_values[0] = False
        elif function_code == 16:
            set_values[0] = 17
    sd_block = (
        [SimData(0, values=[14], datatype=DataType.BITS),
         SimData(16, values=[15], readonly=True, datatype=DataType.BITS),
         SimData(32, values=[16], datatype=DataType.BITS),
         ],
        [SimData(0, values=[14], datatype=DataType.BITS),
         SimData(16, values=[15], readonly=True, datatype=DataType.BITS),
         SimData(32, values=[16], datatype=DataType.BITS),
         ],
        [SimData(0, values=[14], datatype=DataType.REGISTERS),
         SimData(1, values=[15], readonly=True, datatype=DataType.REGISTERS),
         SimData(2, values=[16], datatype=DataType.REGISTERS),
         ],
        [SimData(0, values=[14], datatype=DataType.REGISTERS),
         SimData(1, values=[15], readonly=True, datatype=DataType.REGISTERS),
         SimData(2, values=[16], datatype=DataType.REGISTERS),
         ],
    )
    sd_shared = [SimData(0, datatype=DataType.REGISTERS, values=[14]),
                 SimData(1, readonly=True, datatype=DataType.REGISTERS, values=[15]),
                 SimData(2, datatype=DataType.REGISTERS, values=[16]),
                 ]
    block_ids = {
        1: "c",
        2: "d",
        3: "h",
        4: "i",
        15: "c",
        16: "h"
        }

    def test_simruntime_instanciate(self):
        """Test that simdata can be objects."""
        SimRuntime(SimDevice(0, self.sd_block))
        SimRuntime(SimDevice(0, self.sd_shared))

    @pytest.mark.parametrize("block", [False, True])
    @pytest.mark.parametrize("fc", range(1, 25))
    async def test_simruntime_fc(self, fc, block):
        """Test that simdata can be objects."""
        sd = SimDevice(1, simdata=(self.sd_block if block else self.sd_shared))
        rt = SimRuntime(sd)
        if fc in (1,2,3,4,5,6,15,16,22,23):
            ret = await rt.get_block(fc, 1, 1, None)
            assert not isinstance(ret, ExcCodes)
        else:
            with pytest.raises(AttributeError):
                await rt.get_block(fc, 1, 1, None)

    @pytest.mark.parametrize("block", [True, False])
    @pytest.mark.parametrize(("fc", "values", "expect"), [
        (1, None, [14, 17, 16, 0]),
        (3, None, [14, 17, 16, 0]),
        (4, None, -1),
        (2, None, -1),
        (15, [True, False], [12, 15, 16, 0]),
        (16, [12], [17, 15, 16, 0]),
        ])
    async def test_simruntime_action(self, block, fc, values, expect):
        """Test that simdata can be objects."""
        rt = SimRuntime(SimDevice(1,
            action=self.my_action,
            simdata=(self.sd_block if block else self.sd_shared))
        )
        block_id = self.block_ids[fc] if block else "x"
        count = len(values) if values else 2
        ret = await rt.get_block(fc, 0, count, values)
        if expect == -1:
            assert ret == ExcCodes.ILLEGAL_ADDRESS
        else:
            assert rt.block[block_id][2] == expect

    @pytest.mark.parametrize("block", [True, False])
    @pytest.mark.parametrize(("fc", "addr", "count", "values", "expect"), [
        (1, 0, 2, None, [False, True]),
        (1, 0, 17, None, [False] + [True]*3 + [False]*12 + [True]),
        (2, 0, 2, None, [False, True]),
        (3, 0, 2, None, [14, 15]),
        (4, 0, 2, None, [14, 15]),
        (15, 0, 2, [True, False], [True, False]),
        (16, 0, 1, [12], [12]),
        (99, 0, 2, None, AttributeError),
        (3, 15, 2, None, ExcCodes.ILLEGAL_ADDRESS),
        (3, 2, 2, None, ExcCodes.ILLEGAL_ADDRESS),
        (16, 2, 2, [12, 12], ExcCodes.ILLEGAL_ADDRESS),
        (16, 1, 1, [12], ExcCodes.ILLEGAL_ADDRESS),
        (1, 64, 2, None, ExcCodes.ILLEGAL_ADDRESS),
        (15, 48, 17, [True]*17, ExcCodes.ILLEGAL_ADDRESS),
        ])
    async def test_simruntime_block(self, block, fc, addr, count, values, expect):
        """Test that simdata can be objects."""
        rt = SimRuntime(SimDevice(1,
            simdata=(self.sd_block if block else self.sd_shared))
        )
        if not isinstance(expect, list) and expect in {AttributeError}:
            with pytest.raises(expect):
                await rt.get_block(fc, addr, count, values)
        else:
            ret = await rt.get_block(fc, addr, count, values)
            assert ret == expect

    async def test_simruntime_getValues(self):
        """Test that simdata can be objects."""
        sd = SimDevice(0, simdata=SimData(10, values=15, datatype=DataType.REGISTERS))
        rt = SimRuntime(sd)
        result = await rt.async_getValues(0x03, 10, 1)
        assert result == [15]
        result = await rt.async_getValues(0x03, 15, 1)
        assert isinstance(result, ExcCodes)

    async def test_simruntime_setValues(self):
        """Test that simdata can be objects."""
        sd = SimDevice(0, simdata=SimData(10, values=15, datatype=DataType.REGISTERS))
        rt = SimRuntime(sd)
        result = await rt.async_setValues(0x03, 10, [1])
        assert not result
        result2 = await rt.async_getValues(0x03, 10, 1)
        assert result2 == [1]
        result3 = await rt.async_setValues(0x03, 15, [1])
        assert isinstance(result3, ExcCodes)
