import warnings
from typing import Optional

from UniTAP.libs.lib_tsi import PortIO
from UniTAP.libs.lib_tsi.tsi_types import *
from .link_tx_types import *
from .private_link_tx_types import *
from .link_status_common import *
from .private_link_status_common import *
from typing import Union
from UniTAP.dev.ports.modules.dpcd import DPCDRegisters


class LinkDisplayPortStatusSource:
    """
    Class `LinkDisplayPortStatusSource` describes information about DP link status. Contains following info:
    - MST stream count `mst_stream_count`.
    - Link encoding `link_encoding`.
    - Link rate `link_rate`.
    - Lane count `lane_count`.
    - State of HPD `hpd_asserted`.
    - State of framing `enhanced_framing`.
    - State of scrambling `scrambling_enabled`.
    - State of DSC `dsc_enabled`
    - State of FEC `fec_enabled`.
    - State of MST `mst_enabled`.
    - State of SSC `ssc_enabled`.
    - State of ILA `ila`.
    - State of EQ ILA `eq_ila`.
    - State of CDS ILA `cds_ila`.
    - State of LT fail `lt_fail`.
    - State of selected lane `lane`.
    - State of VCP `vcp`.
    - State of selected stream `stream`.
    - Send ACT command `send_act`.
    """
    def __init__(self, port_io: PortIO, caps: DPTXHWCaps, dpcd: DPCDRegisters):
        self.__io = port_io
        self.__caps = caps
        self.__dpcd = dpcd

    @property
    def mst_stream_count(self) -> int:
        """
        Returns current mst stream count.

        Returns:
            object of int type
        """
        result, mst_status = self.__io.get(TSI_DPTX_MST_STATUS_R, c_uint)
        return (mst_status >> 8) & 0xFF

    @property
    def link_encoding(self) -> DpLinkEncoding:
        """
        Returns current link encoding `DpLinkEncoding`.

        Returns:
            object of DpLinkEncoding type
        """
        result, value = self.__io.get(TSI_DPTX_LINK_MODE_R, c_uint)

        if value == 0:
            return DpLinkEncoding.LE_8b10b
        else:
            return DpLinkEncoding.LE_128b132b

    @property
    def link_rate(self) -> float:
        """
        Returns current link rate.

        Returns:
            object of float type
        """
        if self.link_encoding == DpLinkEncoding.LE_128b132b:
            if self.__check_custom_bit_rate() != 0:
                return self.__check_custom_bit_rate()
            else:
                return DP21_LinkRate.get(self.__io.get(TSI_DP2TX_LT_RATE_R, c_uint)[1])
        else:
            return round(self.__io.get(TSI_DPTX_LINK_RATE_R, c_uint)[1] * 0.27, 2)

    @property
    def lane_count(self) -> int:
        """
        Returns current lane count.

        Returns:
            object of int type
        """
        return self.__io.get(TSI_DPTX_LINK_LANE_COUNT_R, c_int)[1]

    @property
    def hpd_asserted(self) -> bool:
        """
        Returns current state of HPD asserted.

        Returns:
            object of bool type
        """
        return (self.__io.get(TSI_DPTX_HPD_STATUS_R, c_int)[1] & 1) != 0

    @property
    def available_link_rate(self) -> float:
        """

        Returns available link rate.

        Returns:
            object of float type
        """

        if self.link_encoding == DpLinkEncoding.LE_128b132b:
            link_rate = self.link_rate * 1000000000 * self.lane_count * ((128 * 383) / (132 * 384))
        else:
            link_rate = self.link_rate * 1000000000 * self.lane_count
            if self.mst_enabled:
                if self.fec_enabled:
                    link_rate = link_rate * 7690 / 10000
                else:
                    link_rate = link_rate * 7875 / 10000
            else:
                if self.fec_enabled:
                    link_rate = link_rate * 7813 / 10000
                else:
                    link_rate = link_rate * 8000 / 10000

        return link_rate

    def lane(self, lane_number: int) -> LaneStatus:
        """
        Returns current status of selected lane `LaneStatus`.

        Args:
            lane_number (int) - number of selected lane

        Returns:
            object of LaneStatus type
        """
        if not (0 <= lane_number <= 3):
            raise ValueError(f"Incorrect lane number {lane_number}. Available range: 0-{self.lane_count}")

        status = self.__read_link_status()
        voltage_swing = self.__io.get(TSI_DPTX_LINK_VOLTAGE_SWING_R, LinkVoltageSwing)[1]
        pre_emphasis = self.__io.get(TSI_DPTX_LINK_PRE_EMPHASIS_R, LinkPreEmphasis)[1]
        error_counters = self.__dpcd.read(0x210, 8).data

        lane_status = LaneStatus()

        if lane_number == 0:
            lane_status.cr = status.l0_cr
            lane_status.sl = status.l0_sl
            lane_status.eq = status.l0_eq
            lane_status.voltage_swing = voltage_swing.vs_l0
            lane_status.pre_emphasis = pre_emphasis.pe_l0
            lane_status.error_count = error_counters[0] | ((error_counters[1] & 0x7F) << 8)
        elif lane_number == 1:
            lane_status.cr = status.l1_cr
            lane_status.sl = status.l1_sl
            lane_status.eq = status.l1_eq
            lane_status.voltage_swing = voltage_swing.vs_l1
            lane_status.pre_emphasis = pre_emphasis.pe_l1
            lane_status.error_count = error_counters[2] | ((error_counters[3] & 0x7F) << 8)
        elif lane_number == 2:
            lane_status.cr = status.l2_cr
            lane_status.sl = status.l2_sl
            lane_status.eq = status.l2_eq
            lane_status.voltage_swing = voltage_swing.vs_l2
            lane_status.pre_emphasis = pre_emphasis.pe_l2
            lane_status.error_count = error_counters[4] | ((error_counters[5] & 0x7F) << 8)
        elif lane_number == 3:
            lane_status.cr = status.l3_cr
            lane_status.sl = status.l3_sl
            lane_status.eq = status.l3_eq
            lane_status.voltage_swing = voltage_swing.vs_l3
            lane_status.pre_emphasis = pre_emphasis.pe_l3
            lane_status.error_count = error_counters[6] | ((error_counters[7] & 0x7F) << 8)

        if self.link_encoding == DpLinkEncoding.LE_128b132b:
            ffe_preset = self.__io.get(TSI_DP2TX_LT_FFE_PRESET_R, c_uint32)[1]
            lane_status.ffe_preset = (ffe_preset >> (8 * lane_number)) & 0xFF
        else:
            lane_status.ffe_preset = 0

        return lane_status

    @property
    def dsc_enabled(self) -> Union[bool, None]:
        """
        Returns current state of DSC (enabled or disabled).
        None if DSC does not support.

        Returns:
            object of bool or None type
        """
        if self.__caps.dsc or self.__caps.dsc2:
            return (self.__io.get(TSI_DPTX_DSC_STATUS_R)[1] & 1) != 0
        else:
            return None

    @property
    def mst_enabled(self) -> Union[bool, None]:
        """
        Returns current state of MST (enabled or disabled).
        None if MST does not support.

        Returns:
            object of bool or None type
        """
        if self.__caps.mst:
            return (self.__io.get(TSI_DPTX_MST_STATUS_R, c_uint32)[1] & 1) != 0
        else:
            return None

    @property
    def ssc_enabled(self) -> Union[bool, None]:
        """
        Returns current state of SSC (enabled or disabled).
        None if SSC does not support.

        Returns:
            object of bool or None type
        """
        res = self.__io.get(TSI_DPTX_DOWNSPREAD_STATUS_R)[1]
        if ((res >> 1) & 1) != 0:
            return (res & 1) != 0
        else:
            return None

    @property
    def fec_enabled(self) -> Union[bool, None]:
        """
        Returns current state of FEC (enabled or disabled).
        None if FEC does not support.

        Returns:
            object of bool or None type
        """
        if self.__caps.fec or self.__caps.fec2:
            return (self.__io.get(TSI_DPTX_FEC_STATUS_R)[1] & 1) != 0
        else:
            return None

    @property
    def enhanced_framing(self) -> bool:
        """
        Returns current state of enhanced framing (enabled or disabled).

        Returns:
            object of bool type
        """
        return self.__read_lt_features().framing

    @property
    def scrambling_enabled(self) -> bool:
        """
        Returns current state of scrambling (enabled or disabled).

        Returns:
            object of bool type
        """
        return bool(self.__caps.scrambler_seed)

    @property
    def lttpr_active(self) -> Union[bool, None]:
        """
        Returns current state of LTTPR (enabled or disabled).
        None if LTTPR does not support.

        Returns:
            object of bool or None type
        """
        if self.link_encoding == DpLinkEncoding.LE_128b132b:
            return (self.__io.get(TSI_DPTX_LTTPR_STATUS_R, c_uint32)[1] & 1) != 0
        else:
            return None

    @property
    def ila(self) -> bool:
        """
        Returns current ILA state.

        Returns:
            object of bool type
        """
        return bool(self.__read_link_status().ila)

    @property
    def eq_ila(self) -> bool:
        """
        Returns current EQ ILA state.

        Returns:
            object of bool type
        """
        return bool(self.__read_link_status().eq_ila)

    @property
    def cds_ila(self) -> bool:
        """
        Returns current CDS ILA state.

        Returns:
            object of bool type
        """
        return bool(self.__read_link_status().cds_ila)

    @property
    def lt_fail(self) -> bool:
        """
        Returns current LT fail state.

        Returns:
            object of bool type
        """
        return bool(self.__read_link_status().cds_ila)

    def stream(self, stream_index: int) -> StreamStatusDP:
        """
        Returns status of selected stream `StreamStatusDP`.

        Args:
            stream_index (int) - number of selected number

        Returns:
            object of `StreamStatusDP` type
        """
        stream_status = StreamStatusDP()
        result, msa_info, size = self.__io.get(TSI_DPTX_MSA_INFO_R, DpMsa, 4)

        if stream_index < size:
            msa_info_private = MSAInfo(msa_info[stream_index])
            stream_status.video_mode = msa_info_to_video_mode(msa_info_private, self.link_rate * 100000000,
                                                self.link_encoding)
            stream_status.mvid = msa_info_private.m_video
            stream_status.nvid = msa_info_private.n_video

            result = self.__io.get(TSI_DPTX_CRC_R, DpCrc, self.mst_stream_count)
            if result[0] >= TSI_SUCCESS and self.mst_stream_count > 1:
                stream_status.crc = [result[1][stream_index].r, result[1][stream_index].g, result[1][stream_index].b]
            elif result[0] >= TSI_SUCCESS and self.mst_stream_count == 1:
                stream_status.crc = [result[1].r, result[1].g, result[1].b]

            result = self.__io.get(TSI_DPTX_DSC_CRC_R, DpCrc, self.mst_stream_count)
            if result[0] >= TSI_SUCCESS and self.mst_stream_count > 1:
                stream_status.dsc_crc = [result[1][stream_index].r, result[1][stream_index].g, result[1][stream_index].b]
            elif result[0] >= TSI_SUCCESS and self.mst_stream_count == 1:
                stream_status.dsc_crc = [result[1].r, result[1].g, result[1].b]
        else:
            raise ValueError(f"Selected stream {stream_index} is not available. "
                             f"Available stream number: {size}")

        return stream_status

    def vcp(self, stream_index: int = 0) -> Optional[VCPStatus]:
        """
        Returns VCP status of selected stream `VCPStatus`.

        Args:
            stream_index (int) - number of selected number

        Returns:
            object of `VCPStatus` | None type
        """
        if self.__caps.mst:
            if not stream_index < self.mst_stream_count:
                raise ValueError(f"Selected stream {stream_index} is not available. "
                                 f"Available stream number: {self.mst_stream_count}")

            res = self.__io.get(TSI_DPTX_VCP_TABLE_R, VCPTable, 4)[1]

            if self.mst_enabled:
                status = VCPStatus()

                status.port_number = res[stream_index].port_number
                status.stream_id = res[stream_index].stream_id
                status.req_pbn = res[stream_index].req_pbn
                status.alloc_pbn = res[stream_index].alloc_pbn
                status.first_slot = res[stream_index].first_slot
                status.slot_num = res[stream_index].slot_num

                return status
            else:
                warnings.warn("MST not enabled. Cannot show VCP status.")
                return VCPStatus()

    def send_act(self):
        """
        Send ACT command.
        """
        self.__io.set(TSI_DPTX_MST_COMMAND_W, 1, c_uint32)

    def __read_lt_features(self) -> LTFeatures:
        return self.__io.get(TSI_DPTX_LT_FEATURES, LTFeatures)[1]

    def __read_link_status(self) -> LinkTxStatus:
        return self.__io.get(TSI_DPTX_LINK_STATUS_R, LinkTxStatus)[1]

    def __check_custom_bit_rate(self) -> float:
        res = self.__io.get(TSI_DP2TX_CUSTOM_RATE_MAP, c_uint32, 3)[1]
        for i in range(3):
            if res[i] != 0:
                return res[i] * 200000 / 1000000000
        return 0

    def __str__(self) -> str:
        lane_status_str = ""
        stream_info_str = ""
        vcp_table_str = ""
        for i in range(self.mst_stream_count):
            lane_status_str += f"Lane {i}\n{self.lane(i)}\n"
            stream_info_str += f"Stream {i}\n{self.stream(i)}\n"
            vcp_table_str += f"#{i}\n{self.vcp(i)}"

        return f"Lane count: {self.lane_count}" \
               f"Bit rate: {self.link_rate:.3} Gbps\n" \
               f"Enhanced framing mode: {self.enhanced_framing}\n" \
               f"MST: {self.mst_enabled}\n" \
               f"DSC: {self.dsc_enabled}\n" \
               f"LTTPR: {self.lttpr_active}\n" \
               f"Link Encoding: {self.link_encoding.name}\n" \
               f"Scrambling: {self.scrambling_enabled}\n" \
               f"SSC: {self.ssc_enabled}\n" \
               f"FEC: {self.fec_enabled}\n" \
               f"ILA: {self.ila}\n" \
               f"EQ_ILA: {self.eq_ila}\n" \
               f"CDS_ILA: {self.cds_ila}\n" \
               f"LT_FAIL: {self.lt_fail}\n" \
               f"Lane status\n{lane_status_str}\n" \
               f"Stream info:\n{stream_info_str}\n" \
               f"VCP status:\n{vcp_table_str}\n"
