import time
import warnings

from typing import Optional

from UniTAP.libs.lib_tsi import PortIO
from UniTAP.libs.lib_tsi.tsi_private_types import *
from UniTAP.libs.lib_tsi.tsi_types import *
from UniTAP.utils.function_wrapper import function_scheduler
from .private_link_rx_types import DPRXHWCaps
from .link_status_common import *
from .private_link_status_common import VCPTable, DpMsa, MSAInfo, DpCrc, msa_info_to_video_mode
from typing import Union


class LinkDisplayPortStatusSink:
    """
    Class `LinkDisplayPortStatusSink` describes information about DP link status. Contains following info:
    - MST stream count `mst_stream_count`.
    - Lane count `lane_count`.
    - Link encoding `link_encoding`.
    - Link rate `link_rate`.
    - State of HPD `hpd_asserted`.
    - Cable state `cable_state`.
    - 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`.
    """
    def __init__(self, port_io: PortIO, caps: DPRXHWCaps):
        self.__io = port_io
        self.__caps = caps

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

        Returns:
            object of int type
        """
        res, value = self.__io.get(TSI_DPRX_MST_STATUS_R, c_uint)
        return value >> 8

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

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

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

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

        if value >> 20 & 1:
            return DpLinkEncoding.LE_128b132b
        else:
            return DpLinkEncoding.LE_8b10b

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

        Returns:
            object of float type
        """
        current_link_rate = self.__io.get(TSI_DPRX_LINK_BR_R, c_int)[1]
        if self.link_encoding == DpLinkEncoding.LE_8b10b:
            return round(current_link_rate * 0.27, 2)
        else:
            if current_link_rate == 1:
                return 10.0
            elif current_link_rate == 2:
                return 20.0
            elif current_link_rate == 4:
                return 13.5
            else:
                return 0.0

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

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

    @property
    def cable_state(self) -> bool:
        """
        Returns current cable state.

        Returns:
            object of bool type
        """
        return bool(self.__io.get(TSI_DPRX_HPD_STATUS_R, c_int)[1] & 4)

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

        Returns:
            object of bool type
        """
        return bool((self.__io.get(TSI_DPRX_LINK_STATUS_FLAGS_R, c_uint32)[1] >> 17) & 1)

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

        Returns:
            object of bool type
        """
        return bool((self.__io.get(TSI_DPRX_LINK_STATUS_FLAGS_R, c_uint32)[1] >> 18) & 1)

    @property
    def dsc_enabled(self) -> Union[bool, None]:
        """
        Returns current DSC state.

        Returns:
            object of bool|None type
        """
        if self.__caps.dsc or self.__caps.dsc2:
            return bool((self.__io.get(TSI_DPRX_DSC_STATUS_R)[1] >> 1) & 1)
        else:
            return None

    @property
    def fec_enabled(self) -> Union[bool, None]:
        """
        Returns current FEC state.

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

    @property
    def mst_enabled(self) -> Union[bool, None]:
        """
        Returns current MST state.

        Returns:
            object of bool|None type
        """
        if self.__caps.mst:
            return bool((self.__io.get(TSI_DPRX_LINK_STATUS_FLAGS_R, c_uint32)[1] >> 19) & 1)
        else:
            return None

    @property
    def ssc_enabled(self) -> Union[bool, None]:
        """
        Returns current SSC state.

        Returns:
            object of bool|None type
        """
        if (self.__io.get(TSI_DPRX_SSC_STATUS_R, c_int)[1] >> 1) & 1:
            return bool(self.__io.get(TSI_DPRX_SSC_STATUS_R, c_int)[1] & 1)
        else:
            return None

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

        Returns:
            object of bool type
        """
        return bool((self.__io.get(TSI_DPRX_LINK_STATUS_FLAGS_R, c_uint32)[1] >> 16) & 1)

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

        Returns:
            object of bool type
        """
        return bool((self.__io.get(TSI_DPRX_LINK_STATUS_FLAGS_R, c_uint32)[1] >> 30) & 1)

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

        Returns:
            object of bool type
        """
        return bool((self.__io.get(TSI_DPRX_LINK_STATUS_FLAGS_R, c_uint32)[1] >> 29) & 1)

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

        Returns:
            object of bool type
        """
        return bool((self.__io.get(TSI_DPRX_LINK_STATUS_FLAGS_R, c_uint32)[1] >> 28) & 1)

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

        Args:
            lane_number (int) - number of selected number

        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.__io.get(TSI_DPRX_LINK_STATUS_FLAGS_R, c_uint32)[1]
        voltage_swing = self.__io.get(TSI_DPRX_LINK_VS_R, c_uint32)[1]
        pre_emphasis = self.__io.get(TSI_DPRX_LINK_PE_R, c_uint32)[1]
        _, error_counters = self.__io.get(TSI_DPRX_GET_ERROR_COUNTS_R, c_uint32 * 4)

        lane_status = LaneStatus()

        if lane_number == 0:
            lane_status.cr = bool(status & 0x1)
            lane_status.sl = bool((status >> 1) & 0x1)
            lane_status.eq = bool((status >> 2) & 0x1)
            lane_status.error_count = error_counters[0]
        elif lane_number == 1:
            lane_status.cr = bool((status >> 4) & 0x1)
            lane_status.sl = bool((status >> 5) & 0x1)
            lane_status.eq = bool((status >> 6) & 0x1)
            lane_status.error_count = error_counters[1]
        elif lane_number == 2:
            lane_status.cr = bool((status >> 8) & 0x1)
            lane_status.sl = bool((status >> 9) & 0x1)
            lane_status.eq = bool((status >> 10) & 0x1)
            lane_status.error_count = error_counters[2]
        elif lane_number == 3:
            lane_status.cr = bool((status >> 12) & 0x1)
            lane_status.sl = bool((status >> 13) & 0x1)
            lane_status.eq = bool((status >> 14) & 0x1)
            lane_status.error_count = error_counters[3]

        if self.__caps.dp2_support_rates and self.link_encoding == DpLinkEncoding.LE_128b132b:
            lane_status.voltage_swing = 0
            lane_status.pre_emphasis = 0
            _, ffe_preset = self.__io.get(TSI_DP2RX_LINK_FFE_PRESET_R, c_uint32)
            lane_status.ffe_preset = (ffe_preset >> (8 * lane_number)) & 0xF
        else:
            lane_status.voltage_swing = (voltage_swing >> (8 * lane_number)) & 0x3
            lane_status.pre_emphasis = (pre_emphasis >> (8 * lane_number)) & 0x3
            lane_status.ffe_preset = 0

        return lane_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_DPRX_VC_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 does not work. Cannot show VCP status.")
                return VCPStatus()

    def __check_video(self) -> bool:
        def is_msa_available(io):
            result, msa_info, size = io.get(TSI_DPRX_MSA_INFO_R, DpMsa, 4)
            return size > 0

        return function_scheduler(is_msa_available, self.__io, interval=5, timeout=10)

    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()

        if not self.__check_video():
            warnings.warn("Video is not available.")
            return stream_status

        time.sleep(1)

        result, msa_info, size = self.__io.get(TSI_DPRX_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_DPRX_CRC_LOG_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 size == 1:
                stream_status.crc = [result[1].r, result[1].g, result[1].b]

            result = self.__io.get(TSI_DPRX_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 size == 1:
                stream_status.dsc_crc = [result[1].r, result[1].g, result[1].b]

            if self.__caps.dp2_support_rates:
                res = self.__io.get(TSI_DPRX_SDP_CRC16_CTRL, c_uint32)
                stream_status.sdp_crc16.state = False if res[0] < TSI_SUCCESS else (res[1] & 0x1 == 1)
                stream_status.sdp_crc16.errors = self.__io.get(TSI_DPRX_SDP_CRC16_COUNTERS, c_uint8,
                                                               self.__caps.mst_stream_count)[1][stream_index]
        else:
            raise ValueError(f"Selected stream {stream_index} is not available. "
                             f"Available stream number: {size}")

        return stream_status

    def reset_sdp_crc16_errors(self):
        """
        Reset SDP CRC16 errors.
        """
        if not self.__caps.dp2_support_rates:
            warnings.warn("SDP CRC16 is not supported. Cannot reset errors.")
            return
        self.__io.set(TSI_DPRX_SDP_CRC16_COUNTERS, 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}\n" \
               f"Bit rate: {self.link_rate} Gbps\n" \
               f"Enhanced framing mode: {self.enhanced_framing}\n" \
               f"MST: {self.mst_enabled}\n" \
               f"DSC: {self.dsc_enabled}\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\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"
