from UniTAP.libs.lib_tsi.tsi_io import PortIO
from .types import FrlMode, LtpLanesPattern, FrlCaps, _update_frl_values, _update_ltp_pattern_values
from UniTAP.libs.lib_tsi.tsi_types import TSI_HDTX_FRL_CAPABILITY, TSI_HDTX_FRL_PATTERN_R, TSI_HDTX_FRL_TIMERS, \
    TSI_HDTX_SINK_FEATURE_W, TSI_HDTX_FRL_STATUS_R
from ctypes import c_uint32


class FfeMax:
    """
    Class `FfeMax` allows working with FFE on HDMI.
    Possible to configure:
    - Set and get Value for mode 3 lanes and 3 Gbps `mode_3lanes_3gbps`.
    - Set and get Value for mode 3 lanes and 6 Gbps `mode_3lanes_6gbps`.
    - Set and get Value for mode 4 lanes and 6 Gbps `mode_4lanes_6gbps`.
    - Set and get Value for mode 4 lanes and 8 Gbps `mode_4lanes_8gbps`.
    - Set and get Value for mode 4 lanes and 10 Gbps `mode_4lanes_10gbps`.
    - Set and get Value for mode 4 lanes and 12 Gbps `mode_4lanes_12gbps`.
    """
    def __init__(self, mode_3lanes_3gbps: int = 0, mode_3lanes_6gbps: int = 0, mode_4lanes_6gbps: int = 0,
                 mode_4lanes_8gbps: int = 0, mode_4lanes_10gbps: int = 0, mode_4lanes_12gbps: int = 0):
        self.__mode_3lanes_3gbps = mode_3lanes_3gbps
        self.__mode_3lanes_6gbps = mode_3lanes_6gbps
        self.__mode_4lanes_6gbps = mode_4lanes_6gbps
        self.__mode_4lanes_8gbps = mode_4lanes_8gbps
        self.__mode_4lanes_10gbps = mode_4lanes_10gbps
        self.__mode_4lanes_12gbps = mode_4lanes_12gbps

    @property
    def mode_3lanes_3gbps(self) -> int:
        """
        Returns current value for mode 3 lanes and 3 Gbps.

        Returns:
            object of int type
        """
        return self.__mode_3lanes_3gbps

    @mode_3lanes_3gbps.setter
    def mode_3lanes_3gbps(self, ffe_value: int):
        """
        Set new FFE value for mode 3 lanes and 3 Gbps.

        Args:
            ffe_value (int)
        """
        if not(0 <= ffe_value <= 3):
            raise ValueError(f"FFE value must be in range 0-3. Current value = {ffe_value}")
        self.__mode_3lanes_3gbps = ffe_value

    @property
    def mode_3lanes_6gbps(self) -> int:
        """
        Returns current value for mode 3 lanes and 6 Gbps.

        Returns:
            object of int type
        """
        return self.__mode_3lanes_6gbps

    @mode_3lanes_6gbps.setter
    def mode_3lanes_6gbps(self, ffe_value: int):
        """
        Set new FFE value for mode 3 lanes and 6 Gbps.

        Args:
            ffe_value (int)
        """
        if not(0 <= ffe_value <= 3):
            raise ValueError(f"FFE value must be in range 0-3. Current value = {ffe_value}")
        self.__mode_3lanes_6gbps = ffe_value

    @property
    def mode_4lanes_6gbps(self) -> int:
        """
        Returns current value for mode 6 lanes and 6 Gbps.

        Returns:
            object of int type
        """
        return self.__mode_4lanes_6gbps

    @mode_4lanes_6gbps.setter
    def mode_4lanes_6gbps(self, ffe_value: int):
        """
        Set new FFE value for mode 4 lanes and 6 Gbps.

        Args:
            ffe_value (int)
        """
        if not (0 <= ffe_value <= 3):
            raise ValueError(f"FFE value must be in range 0-3. Current value = {ffe_value}")
        self.__mode_4lanes_6gbps = ffe_value

    @property
    def mode_4lanes_8gbps(self) -> int:
        """
        Returns current value for mode 6 lanes and 8 Gbps.

        Returns:
            object of int type
        """
        return self.__mode_4lanes_8gbps

    @mode_4lanes_8gbps.setter
    def mode_4lanes_8gbps(self, ffe_value: int):
        """
        Set new FFE value for mode 4 lanes and 8 Gbps.

        Args:
            ffe_value (int)
        """
        if not (0 <= ffe_value <= 3):
            raise ValueError(f"FFE value must be in range 0-3. Current value = {ffe_value}")
        self.__mode_4lanes_8gbps = ffe_value

    @property
    def mode_4lanes_10gbps(self) -> int:
        """
        Returns current value for mode 6 lanes and 10 Gbps.

        Returns:
            object of int type
        """
        return self.__mode_4lanes_10gbps

    @mode_4lanes_10gbps.setter
    def mode_4lanes_10gbps(self, ffe_value: int):
        """
        Set new FFE value for mode 4 lanes and 10 Gbps.

        Args:
            ffe_value (int)
        """
        if not (0 <= ffe_value <= 3):
            raise ValueError(f"FFE value must be in range 0-3. Current value = {ffe_value}")
        self.__mode_4lanes_10gbps = ffe_value

    @property
    def mode_4lanes_12gbps(self) -> int:
        """
        Returns current value for mode 6 lanes and 12 Gbps.

        Returns:
            object of int type
        """
        return self.__mode_4lanes_12gbps

    @mode_4lanes_12gbps.setter
    def mode_4lanes_12gbps(self, ffe_value: int):
        """
        Set new FFE value for mode 4 lanes and 12 Gbps.

        Args:
            ffe_value (int)
        """
        if not (0 <= ffe_value <= 3):
            raise ValueError(f"FFE value must be in range 0-3. Current value = {ffe_value}")
        self.__mode_4lanes_12gbps = ffe_value

    def value(self) -> int:
        """
        Returns current combined value from all modes.

        Returns:
            object of int type
        """
        return ((self.mode_3lanes_3gbps & 0x3) << 5) | ((self.mode_3lanes_6gbps & 0x3) << 7) |\
               ((self.mode_4lanes_6gbps & 0x3) << 9) | ((self.mode_4lanes_8gbps & 0x3) << 11) | \
               ((self.mode_4lanes_10gbps & 0x3) << 13) | ((self.mode_4lanes_12gbps & 0x3) << 15)

    def __str__(self):
        return f"3 lanes 3 Gbps - {self.mode_3lanes_3gbps}\n" \
               f"3 lanes 6 Gbps - {self.mode_3lanes_6gbps}\n" \
               f"4 lanes 6 Gbps - {self.mode_4lanes_6gbps}\n" \
               f"4 lanes 8 Gbps - {self.mode_4lanes_8gbps}\n" \
               f"4 lanes 10 Gbps - {self.mode_4lanes_10gbps}\n" \
               f"4 lanes 12 Gbps - {self.mode_4lanes_12gbps}\n"


class FrlControlTx:
    """
    Class `FrlControlTx` contains information about FRL on Source (TX - transmitter) side.
    allows working with:
    - Set and get FRL mode `frl_mode`.
    - Set and get FRL capabilities `frl_caps`.
    - Set and get FFE max `ffe_max`.
    - Set and get LTP requested `ltp_pattern` and additional `ltp_additional_pattern`.
    - Set link training timeout `lt_timeout` and link training poll timeout `lt_poll_timeout`.
    """
    def __init__(self, port_io: PortIO):
        self.__io = port_io
        self.__ffe_max = FfeMax(0)
        self.__frl_caps = FrlCaps()
        self.__lt_timeout = 0
        self.__lt_poll_timeout = 0
        self.__ltp_request = LtpLanesPattern()
        self.__ltp_additional = LtpLanesPattern()

    def __read_caps(self) -> int:
        return self.__io.get(TSI_HDTX_FRL_CAPABILITY, c_uint32)[1]

    def __write_caps(self, value: int):
        self.__io.set(TSI_HDTX_FRL_CAPABILITY, value, c_uint32)

    def __read_frl_timers(self) -> int:
        return self.__io.get(TSI_HDTX_FRL_TIMERS, c_uint32)[1]

    def __write_frl_timers(self, value: int):
        self.__io.set(TSI_HDTX_FRL_TIMERS, value, c_uint32)

    def __read_frl_patterns(self) -> int:
        return self.__io.get(TSI_HDTX_FRL_PATTERN_R, c_uint32)[1]

    @property
    def ltp_pattern(self) -> LtpLanesPattern:
        """
        Returns current LTP lanes pattern. Current Pattern that is being checking during Link Training.
        Each lane can request different pattern.

        Returns:
            object of `LtpLanesPattern` type
        """
        _update_ltp_pattern_values(self.__ltp_request, self.__read_frl_patterns())
        return self.__ltp_request

    @property
    def ltp_additional_pattern(self) -> LtpLanesPattern:
        """
        Returns current additional LTP lanes pattern. Current Pattern that is being checking during Link Training.
        Each lane can request different pattern.

        Returns:
            object of `LtpLanesPattern` type
        """
        _update_ltp_pattern_values(self.__ltp_additional, self.__read_frl_patterns() >> 16)
        return self.__ltp_additional

    @property
    def frl_caps(self) -> FrlCaps:
        """
        Returns current FRL capabilities.

        Returns:
            object of `FrlCaps` type
        """
        _update_frl_values(self.__frl_caps, self.__io.get(TSI_HDTX_FRL_STATUS_R, c_uint32)[1])
        return self.__frl_caps

    @property
    def frl_mode(self) -> FrlMode:
        """
        Returns current FRL mode.

        Returns:
            object of `FrlMode` type
        """
        return FrlMode(self.__read_caps() & 0x7)

    @frl_mode.setter
    def frl_mode(self, frl_mode: FrlMode):
        """
        Set new FRL mode.

        Args:
            frl_mode (FrlMode)
        """
        new_value = frl_mode.value
        new_value |= self.__read_caps() & ~0x7
        self.__write_caps(new_value)

    @property
    def ffe_max(self) -> FfeMax:
        """
        Returns current FFE MAX values.

        Returns:
            object of `FfeMax` type
        """
        ffe_values = self.__read_caps()
        self.__update_values(self.__ffe_max, ffe_values)
        return self.__ffe_max

    @ffe_max.setter
    def ffe_max(self, ffe_max: FfeMax):
        """
        Set new FFE max values.

        Args:
            ffe_max (FfeMax)
        """
        new_value = ffe_max.value()
        new_value |= (self.__read_caps() >> 5) & ~0xFFF
        self.__write_caps(new_value)
        self.__ffe_max = ffe_max

    @property
    def lt_timeout(self) -> int:
        """
        Returns current value of link training timeout.

        Returns:
            object of int type
        """
        self.__lt_timeout = self.__read_frl_timers() & 0xFFFF
        return self.__lt_timeout

    @lt_timeout.setter
    def lt_timeout(self, lt_timeout: int):
        """
        Set new link training timeout.

        Args:
            lt_timeout (int)
        """
        new_value = lt_timeout
        new_value |= self.__read_frl_timers() & ~0xFFFF
        self.__write_frl_timers(new_value)
        self.__lt_timeout = lt_timeout

    @property
    def lt_poll_timeout(self) -> int:
        """
        Returns current value of link training poll timeout.

        Returns:
            object of int type
        """
        self.__lt_timeout = self.__read_frl_timers() & 0xFFFF
        return self.__lt_timeout

    @lt_poll_timeout.setter
    def lt_poll_timeout(self, lt_poll_timeout: int):
        """
        Set new link training poll timeout.

        Args:
            lt_poll_timeout (int)
        """
        new_value = lt_poll_timeout
        new_value |= self.__read_frl_timers() & ~(0xFFFF << 16)
        self.__write_frl_timers(new_value)
        self.__lt_poll_timeout = lt_poll_timeout

    def link_training(self):
        """
        Do link training.
        """
        self.__io.set(TSI_HDTX_SINK_FEATURE_W, 5, c_uint32)

    @staticmethod
    def __update_values(ffe_max: FfeMax, value: int):
        ffe_max.mode_3lanes_3gbps = (value >> 5) & 0x3
        ffe_max.mode_3lanes_6gbps = (value >> 7) & 0x3
        ffe_max.mode_4lanes_6gbps = (value >> 9) & 0x3
        ffe_max.mode_4lanes_8gbps = (value >> 11) & 0x3
        ffe_max.mode_4lanes_10gbps = (value >> 13) & 0x3
        ffe_max.mode_4lanes_12gbps = (value >> 15) & 0x3

    def __str__(self):
        return f"FRL Mode: {self.frl_mode.name}\n" \
               f"FRL Caps:\n{self.frl_caps.__str__()}" \
               f"LTP Lanes Pattern:\n{self.ltp_pattern.__str__()}" \
               f"LTP Lanes Pattern Additional:\n{self.ltp_additional_pattern.__str__()}" \
               f"LT timeout: {self.lt_timeout}\n" \
               f"LT poll timeout: {self.lt_poll_timeout}\n" \
               f"FFE Max:\n{self.ffe_max.__str__()}\n"
