"""
This module is intended to distributed as part of automatically generated code by the PeakRDL
Python tool. It provides a set of classes used by the autogenerated code to represent register
fields
"""
from typing import List, cast, Optional

from .base import Base
from .base import swap_msb_lsb_ordering
from .register import Reg
from .register import RegReadOnly
from .register import RegReadWrite
from .register import RegWriteOnly
from .register import ReadableRegister
from .register import WriteableRegister


class FieldSizeProps:
    """
    class to hold the key attributes of a field
    """
    __slots__ = ['__msb', '__lsb', '__width', '__high', '__low']

    def __init__(self, width: int, msb: int, lsb: int, high: int, low: int): #pylint: disable=too-many-arguments
        self.__width = width
        self.__msb = msb
        self.__lsb = lsb
        self.__high = high
        self.__low = low

        if self.width < 1:
            raise ValueError('width must be greater than 0')

        if self.high < self.low:
            raise ValueError('field high bit position can not be less than the '
                             'low bit position')

        if self.lsb < 0:
            raise ValueError('field low bit position cannot be less than zero')

    @property
    def lsb(self) -> int:
        """
        bit position of the least significant bit (lsb) of the field in the
        parent register

        Note:
            fields can be defined as msb in bit 0 or as lsb in bit 0
        """
        return self.__lsb

    @property
    def msb(self) -> int:
        """
        bit position of the most significant bit (msb) of the field in the
        parent register

        Note:
            fields can be defined as msb in bit 0 or as lsb in bit 0
        """
        return self.__msb

    @property
    def width(self) -> int:
        """
        The width of the field in bits
        """
        return self.__width

    @property
    def max_value(self) -> int:
        """maximum unsigned integer value that can be stored in the field

        For example:

        * 8-bit field returns 0xFF (255)
        * 16-bit field returns 0xFFFF (65535)
        * 32-bit field returns 0xFFFF_FFFF (4294967295)

        """
        return (2 ** self.width) - 1

    @property
    def high(self) -> int:
        """
        low index of the bit range of the field in the
        parent register

        Note:
            The first bit in the register is bit 0
        """
        return self.__high

    @property
    def low(self) -> int:
        """
        low index of the bit range of the field in the
        parent register

        Note:
            The first bit in the register is bit 0
        """
        return self.__low


class Field(Base):
    """
    base class of register field wrappers

    Note:
        It is not expected that this class will be instantiated under normal
        circumstances however, it is useful for type checking
    """

    __slots__ = ['__parent_register', '__size_props',
                 '__bitmask', '__msb0', '__lsb0', '__reset_value']

    def __init__(self, parent_register: Reg, size_props: FieldSizeProps,
                 logger_handle: str, inst_name: str, reset_value: Optional[int]=None):

        super().__init__(logger_handle=logger_handle,
                         inst_name=inst_name)

        if not isinstance(size_props, FieldSizeProps):
            raise TypeError(f'size_props must be of {type(FieldSizeProps)} '
                            f'but got {type(size_props)}')
        self.__size_props = size_props

        if not isinstance(parent_register, Reg):
            raise TypeError(f'size_props must be of {type(Reg)} '
                            f'but got {type(parent_register)}')
        self.__parent_register = parent_register

        if self.width > self.register_data_width:
            raise ValueError('width can not be greater than parent width')

        if self.high > self.register_data_width:
            raise ValueError(f'field high bit position {self.high:d} must be less than the '
                             f'parent register width ({self.register_data_width:d})')

        if self.low > self.register_data_width:
            raise ValueError('field lsb must be less than the parent '
                             'register width')

        if self.high - self.low + 1 != self.width:
            raise ValueError('field width defined by lsb and msb does not match'
                             ' specified width')

        if (self.msb == self.high) and (self.lsb == self.low):
            self.__lsb0 = True
            self.__msb0 = False
        elif (self.msb == self.low) and (self.lsb == self.high):
            self.__lsb0 = False
            self.__msb0 = True
        else:
            raise ValueError('msb/lsb are inconsistent with low/high')

        self.__bitmask = 0
        for bit_position in range(self.low, self.high+1):
            self.__bitmask |= (1 << bit_position)

        self.__reset_value = reset_value

    @property
    def lsb(self) -> int:
        """
        bit position of the least significant bit (lsb) of the field in the
        parent register

        Note:
            fields can be defined as msb in bit 0 or as lsb in bit 0
        """
        return self.__size_props.lsb

    @property
    def msb(self) -> int:
        """
        bit position of the most significant bit (msb) of the field in the
        parent register

        Note:
            fields can be defined as msb in bit 0 or as lsb in bit 0
        """
        return self.__size_props.msb

    @property
    def width(self) -> int:
        """
        The width of the field in bits
        """
        return self.__size_props.width

    @property
    def max_value(self) -> int:
        """maximum unsigned integer value that can be stored in the field

        For example:

        * 8-bit field returns 0xFF (255)
        * 16-bit field returns 0xFFFF (65535)
        * 32-bit field returns 0xFFFF_FFFF (4294967295)

        """
        return (2 ** self.width) - 1

    @property
    def high(self) -> int:
        """
        low index of the bit range of the field in the
        parent register

        Note:
            The first bit in the register is bit 0
        """
        return self.__size_props.high

    @property
    def low(self) -> int:
        """
        low index of the bit range of the field in the
        parent register

        Note:
            The first bit in the register is bit 0
        """
        return self.__size_props.low

    @property
    def bitmask(self) -> int:
        """
        The bit mask needed to extract the field from its register

        For example a register field occupying bits 7 to 4 in a 16-bit register
        will have a bit mask of 0x00F0
        """
        return self.__bitmask

    @property
    def register_data_width(self) -> int:
        """
        The width of the register within which the field resides in bits
        """
        return self.__parent_register.width

    @property
    def inverse_bitmask(self) -> int:
        """
        The bitwise inverse of the bitmask needed to extract the field from its
        register

        For example a register field occupying bits 7 to 4 in a 16-bit register
        will have a inverse bit mask of 0xFF0F
        """
        return self.__parent_register.max_value ^ self.bitmask

    @property
    def msb0(self) -> bool:
        """
        The field can either be lsb0 or msb0

        Returns: true if msb0

        """
        return self.__msb0

    @property
    def lsb0(self) -> bool:
        """
        The field can either be lsb0 or msb0

        Returns: true if lsb0

        """
        return self.__lsb0

    @property
    def _parent_register(self) -> Reg:
        """
        parent register the field is placed in
        """
        return self.__parent_register

    @property
    def reset_value(self) -> Optional[int]:
        """
        reset value for the field, return None if it is unspecified
        """
        return self.__reset_value


class FieldReadOnly(Field):
    """
    class for a read only register field

    Args:
        parent_register: register within which the field resides
        size_props: object defining the msb, lsb, high bit, low bit and width
        logger_handle: name to be used logging messages associate with this
            object

    """
    __slots__ : List[str] = []

    def __init__(self,
                 parent_register: ReadableRegister,
                 size_props: FieldSizeProps,
                 logger_handle: str,
                 inst_name: str,
                 reset_value: Optional[int]=None):

        if not isinstance(parent_register, (RegReadWrite, RegReadOnly)):
            raise TypeError(f'size_props must be of {type(RegReadWrite)} or {type(RegReadOnly)} '
                            f'but got {type(parent_register)}')

        super().__init__(logger_handle=logger_handle,
                         size_props=size_props,
                         parent_register=parent_register,
                         inst_name=inst_name,
                         reset_value=reset_value)

    def decode_read_value(self, value) -> int:
        """
        extracts the field value from a register value, by applying the bit
        mask and shift needed

        Args:
            value: value to decode, normally read from a register

        Returns:
            field value
        """
        if not isinstance(value, int):
            raise TypeError(f'value must be an int but got {type(value)}')

        if value < 0:
            raise ValueError('value to be decoded must be greater '
                             'than or equal to 0')

        if value > self.parent_register.max_value:
            raise ValueError(f'value to bede coded must be less than or equal '
                             f'to {self.parent_register.max_value:d}')

        if self.msb0 is False:
            return_value = (value & self.bitmask) >> self.low
        else:
            return_value = swap_msb_lsb_ordering(value=(value & self.bitmask) >> self.low,
                                                 width=self.width)

        return return_value

    def read(self) -> int:
        """
        Reads the register that this field is located in and retries the field
        value applying the required masking and shifting

        Returns:
            field value

        """
        return self.decode_read_value(self.parent_register.read())

    @property
    def parent_register(self) -> ReadableRegister:
        """
        parent register the field is placed in
        """

        # this cast is OK because an explict typing check was done in the __init__
        return cast(ReadableRegister, self._parent_register)


class FieldWriteOnly(Field):
    """
    class for a write only register field

    Args:
        parent_register: register within which the field resides
        size_props: object defining the msb, lsb, high bit, low bit and width
        logger_handle: name to be used logging messages associate with this
            object

    """
    __slots__ : List[str] = []

    def __init__(self,
                 parent_register: WriteableRegister,
                 size_props: FieldSizeProps,
                 logger_handle: str,
                 inst_name: str,
                 reset_value: Optional[int]=None):

        if not isinstance(parent_register, (RegReadWrite, RegWriteOnly)):
            raise TypeError(f'size_props must be of {type(RegReadWrite)} or {type(RegWriteOnly)} '
                            f'but got {type(parent_register)}')

        super().__init__(logger_handle=logger_handle,
                         size_props=size_props,
                         parent_register=parent_register,
                         inst_name=inst_name,
                         reset_value=reset_value)

    def encode_write_value(self, value: int) -> int:
        """
        Check that a value is legal for the field and then encode it in preparation to be written
        to the register

        Args:
            value: field value

        Returns:
            value which can be applied to the register to update the field

        """

        if not isinstance(value, int):
            raise TypeError(f'value must be an int but got {type(value)}')

        if value < 0:
            raise ValueError('value to be written to register must be greater '
                             'than or equal to 0')

        if value > self.max_value:
            raise ValueError(f'value to be written to register must be less '
                             f'than or equal to {self.max_value:d}')

        if self.msb0 is False:
            return_value = value << self.low
        else:
            return_value = swap_msb_lsb_ordering(value=value,  width=self.width) << self.low

        return return_value

    def write(self, value: int) -> None:
        """
        The behaviour of this method depends on whether the field is located in
        a readable register or not:

        If the register is readable, the method will perform a read-modify-write
        on the register updating the field with the value provided

        If the register is not writable all other field values will be written
        with zero.

        Args:
            value: field value to update to

        """

        if not isinstance(value, int):
            raise TypeError(f'value must be an int but got {type(value)}')

        if value < 0:
            raise ValueError('value to be written to register must be greater '
                             'than or equal to 0')

        if value > self.max_value:
            raise ValueError(f'value to be written to register must be less '
                             f'than or equal to {self.max_value:d}')

        if self.msb0:
            value = swap_msb_lsb_ordering(value=value, width=self.width)

        if (self.high == (self.register_data_width - 1)) and (self.low == 0):
            # special case where the field occupies the whole register,
            # there a straight write can be performed
            new_reg_value = value
        else:
            # do a read, modify write
            if isinstance(self.parent_register, RegReadWrite):
                reg_value = self.parent_register.read()
                masked_reg_value = reg_value & self.inverse_bitmask
                new_reg_value = masked_reg_value | (value << self.low)
            elif isinstance(self.parent_register, RegWriteOnly):
                new_reg_value = (value << self.low)
            else:
                raise TypeError('Unhandled parent type')

        self.parent_register.write(new_reg_value)

    @property
    def parent_register(self) -> WriteableRegister:
        """
        parent register the field is placed in
        """

        # this cast is OK because an explict typing check was done in the __init__
        return cast(WriteableRegister, self._parent_register)


class FieldReadWrite(FieldReadOnly, FieldWriteOnly):
    """
    class for a read/write only register field

    Args:
        parent_register: register within which the field resides
        size_props: object defining the msb, lsb, high bit, low bit and width
        logger_handle: name to be used logging messages associate with this
            object

    """
    __slots__ : List[str]  = []

    def __init__(self, parent_register: RegReadWrite,
                 size_props: FieldSizeProps,
                 logger_handle: str,
                 inst_name: str,
                 reset_value: Optional[int]=None):

        if not isinstance(parent_register, RegReadWrite):
            raise TypeError(f'size_props must be of {type(RegReadWrite)} '
                            f'but got {type(parent_register)}')

        super().__init__(logger_handle=logger_handle,
                         size_props=size_props,
                         parent_register=parent_register,
                         inst_name=inst_name,
                         reset_value=reset_value)

    @property
    def parent_register(self) -> RegReadWrite:
        """
        parent register the field is placed in
        """

        # this cast is OK because an explict typing check was done in the __init__
        return cast(RegReadWrite, self._parent_register)
