"""Simulator runtime implementation.

**REMARK** This class is internal to the server/simulator,.
"""
from __future__ import annotations

from typing import cast

from ..constants import ExcCodes
from .simdevice import SimDevice
from .simutils import DataType, SimUtils


class SimRuntime:
    """Memory setup for device."""

    _fx_mapper = {2: "d", 4: "i"} # Discrete input and Input registers
    _fx_mapper.update([(i, "h") # Holding registers
                    for i in (3, 6, 16, 22, 23)])
    _fx_mapper.update([(i, "c") # Coils
                    for i in (1, 5, 15)])


    def __init__(self, device: SimDevice):
        """Build device memory."""
        self.action = device.action
        self.use_bit_addressing = device.use_bit_addressing
        build = device.build_device()
        self.block: dict[str, tuple[int, int, list[int], list[int]]] = {}
        if not isinstance(build, dict):
            self.block["x"] = (
                build[0],
                len(build[2]),
                build[1],
                build[2],
            )
            return
        for i in ("c", "d", "h", "i"):
            self.block[i] = (build[i][0], len(build[i][1]), build[i][1],  build[i][2])

    async def __check_block(self, func_code: int, block_id: str, address: int, count: int,  offset: int, values: list[int] | list[bool] | None) -> ExcCodes | None:
        """Check block request."""
        start_address, _, registers, flags = self.block[block_id]
        if self.action and (result := await self.action(func_code, start_address, address, count, registers, values)):
            return result
        for i in range(count):
            addr = offset + i
            if flags[addr] & SimUtils.RunTimeFlag_TYPE == DataType.INVALID:
                return ExcCodes.ILLEGAL_ADDRESS
            if values:
                if flags[addr] & SimUtils.RunTimeFlag_READONLY:
                    return ExcCodes.ILLEGAL_ADDRESS
        return None

    async def get_bit_block(self, block_id: str, func_code: int, address: int, count: int, values: list[bool] | None) -> list[bool] | ExcCodes:
        """Handle coils and discrete input."""
        start_address, register_count, registers, _ = self.block[block_id]
        offset = (int(address / 16) if self.use_bit_addressing else address) - start_address
        bit_offset = address % 16
        reg_count = (bit_offset + count + 15) // 16
        if offset < 0 or offset + reg_count > register_count:
            return ExcCodes.ILLEGAL_ADDRESS
        if (result := await self.__check_block(func_code, block_id, address, reg_count, offset, values)):
            return result
        list_bools = SimUtils.registersToBits(registers[offset:offset+reg_count])
        if values:
            list_bools[bit_offset:bit_offset+count] = values
            registers[offset:offset+reg_count] = SimUtils.bitsToRegisters(list_bools)
        return list_bools[bit_offset:bit_offset+count]

    async def get_reg_block(self, block_id: str, func_code: int, address: int, count: int, values: list[int] | None) -> list[int] | ExcCodes:
        """Handle holding registers and input registers."""
        start_address, register_count, registers, _ = self.block[block_id]
        offset = address - start_address
        if offset < 0 or offset + count > register_count:
            return ExcCodes.ILLEGAL_ADDRESS
        if (result := await self.__check_block(func_code, block_id, address, count, offset, values)):
            return result
        if values:
            registers[offset:offset+count] = values
        return registers[offset:offset+count]

    async def get_block(self, func_code: int, address: int, count: int, values: list[int] | list[bool] | None) -> list[int] | list[bool] | ExcCodes:
        """Calculate offset."""
        if values:
            values = cast(list[int], values if isinstance(values, list) else [values])
        if (fc_block := self._fx_mapper.get(func_code, "z")) == "z":
            raise AttributeError("Datastore, not supported function code")
        block_id = "x" if "x" in self.block else fc_block
        if fc_block in {"c", "d"}:
            return await self.get_bit_block(block_id, func_code, address, count, cast(list[bool], values))
        return await self.get_reg_block(block_id, func_code, address, count, cast(list[int], values))

    async def async_getValues(self, func_code: int, address: int, count: int) -> list[int] | list[bool] | ExcCodes:
        """Get `count` values from datastore."""
        return await self.get_block(func_code, address, count, None)

    async def async_setValues(self, func_code: int, address: int, values: list[int] | list[bool] ) -> None | ExcCodes:
        """Set the datastore with the supplied values."""
        block = await self.get_block(func_code, address, len(values), values)
        return block if isinstance(block, ExcCodes) else None

