from enum import IntEnum
from UniTAP.dev.modules.dut_tests.test_group_params_types import Param
from UniTAP.dev.modules.dut_tests.test_utils import update_default_value


class BitStream(IntEnum):
    """
    Describes available values for bit stream.
    Sink DUT support 444 CRC for Simple 422 bitstream
    """
    YCbCr422 = 0
    YCbCr444 = 1


class PackedSource(IntEnum):
    """
    Describes available values for packer source.
    Source of the most packet video modes table.
    """
    UseTestConfig = 0
    UseSinkDutEdid = 1


class VisualCheck(IntEnum):
    """
    Describes available values for visual check during DisplayID CTS tests.
    """
    NeverSkip = 0
    SkipIfCrcMatches = 1


class StandardBase:

    __rate_shift_rb1 = {30: 1, 60: 4, 120: 7, 144: 9, 240: 12}
    __rate_shift_rb2 = {30: 2, 60: 5, 120: 8, 144: 10, 240: 13}

    def __init__(self, value: Param, rate: int):
        self._value = value
        self._rate = rate

    @property
    def rb1(self) -> bool:
        """
        Set and get RB1 flag support.

        Returns:
            object of bool type
        """
        return self._value.default_value & self._value.bit_field_list[self.__rate_shift_rb1.get(self._rate)].mask != 0

    @rb1.setter
    def rb1(self, value: bool):
        self._value._set_by_bit_number(value, 1, self.__rate_shift_rb1.get(self._rate))

    @property
    def rb2(self) -> bool:
        """
        Set and get RB2 flag support.

        Returns:
            object of bool type
        """
        return self._value.default_value & self._value.bit_field_list[self.__rate_shift_rb2.get(self._rate)].mask != 0

    @rb2.setter
    def rb2(self, value: bool):
        self._value._set_by_bit_number(value, 1, self.__rate_shift_rb2.get(self._rate))


class Rate30Hz(StandardBase):

    __rate_shift_cta = {30: 0, 60: 3, 120: 6}

    def __init__(self, value: Param, rate: int):
        super().__init__(value, rate)

    @property
    def cta(self) -> bool:
        """
        Set and get CTA flag support.

        Returns:
            object of bool type
        """
        return self._value.default_value & self._value.bit_field_list[self.__rate_shift_cta.get(self._rate)].mask != 0

    @cta.setter
    def cta(self, value: bool):
        self._value._set_by_bit_number(value, 1, self.__rate_shift_cta.get(self._rate))

    def set_all(self):
        self.cta = True
        self.rb1 = True
        self.rb2 = True

    def clear_all(self):
        self.cta = False
        self.rb1 = False
        self.rb2 = False


class Rate144Hz(StandardBase):

    __rate_shift_ovt = {144: 11, 240: 14}

    def __init__(self, value: Param, rate: int):
        super().__init__(value, rate)

    @property
    def ovt(self) -> bool:
        """
        Set and get OVT flag support.

        Returns:
            object of bool type
        """
        return self._value.default_value & self._value.bit_field_list[self.__rate_shift_ovt.get(self._rate)].mask != 0

    @ovt.setter
    def ovt(self, value: bool):
        self._value._set_by_bit_number(value, 1, self.__rate_shift_ovt.get(self._rate))

    def set_all(self):
        self.ovt = True
        self.rb1 = True
        self.rb2 = True

    def clear_all(self):
        self.ovt = False
        self.rb1 = False
        self.rb2 = False


class VideoModeInfo:

    def __init__(self, json_obj):
        self.__value = Param(json_obj)
        self.__30hz = Rate30Hz(self.__value, 30)
        self.__60hz = Rate30Hz(self.__value, 60)
        self.__120hz = Rate30Hz(self.__value, 120)
        self.__144hz = Rate144Hz(self.__value, 144)
        self.__240hz = Rate144Hz(self.__value, 240)

    @property
    def rate_30hz(self) -> Rate30Hz:
        return self.__30hz

    @property
    def rate_60hz(self) -> Rate30Hz:
        return self.__60hz

    @property
    def rate_120hz(self) -> Rate30Hz:
        return self.__120hz

    @property
    def rate_144hz(self) -> Rate144Hz:
        return self.__144hz

    @property
    def rate_240hz(self) -> Rate144Hz:
        return self.__240hz

    def set_all(self):
        self.rate_30hz.set_all()
        self.rate_60hz.set_all()
        self.rate_120hz.set_all()
        self.rate_144hz.set_all()
        self.rate_240hz.set_all()

    def clear_all(self):
        self.rate_30hz.clear_all()
        self.rate_60hz.clear_all()
        self.rate_120hz.clear_all()
        self.rate_144hz.clear_all()
        self.rate_240hz.clear_all()


class Dp21SinkVideoModes:

    def __init__(self, json_obj):
        self.__1920x1080 = VideoModeInfo(json_obj["TSI_DP20_SINKCTS_VMT_1920_1080"])
        self.__3840x2160 = VideoModeInfo(json_obj["TSI_DP20_SINKCTS_VMT_3840_2160"])
        self.__5120x2160 = VideoModeInfo(json_obj["TSI_DP20_SINKCTS_VMT_5120_2160"])
        self.__7680x4320 = VideoModeInfo(json_obj["TSI_DP20_SINKCTS_VMT_7680_4320"])
        self.__10240x4320 = VideoModeInfo(json_obj["TSI_DP20_SINKCTS_VMT_10240_4320"])
        self.__15360x8640 = VideoModeInfo(json_obj["TSI_DP20_SINKCTS_VMT_15360_8640"])

    @property
    def vm_1920x1080(self) -> VideoModeInfo:
        return self.__1920x1080

    @property
    def vm_3840x2160(self) -> VideoModeInfo:
        return self.__3840x2160

    @property
    def vm_5120x2160(self) -> VideoModeInfo:
        return self.__5120x2160

    @property
    def vm_7680x4320(self) -> VideoModeInfo:
        return self.__7680x4320

    @property
    def vm_10240x4320(self) -> VideoModeInfo:
        return self.__10240x4320

    @property
    def vm_15360x8640(self) -> VideoModeInfo:
        return self.__15360x8640

    def set_all(self):
        self.vm_1920x1080.set_all()
        self.vm_3840x2160.set_all()
        self.vm_5120x2160.set_all()
        self.vm_7680x4320.set_all()
        self.vm_10240x4320.set_all()
        self.vm_15360x8640.set_all()

    def clear_all(self):
        self.vm_1920x1080.clear_all()
        self.vm_3840x2160.clear_all()
        self.vm_5120x2160.clear_all()
        self.vm_7680x4320.clear_all()
        self.vm_10240x4320.clear_all()
        self.vm_15360x8640.clear_all()


class DebugOptions:

    def __init__(self, json_obj):
        self.__param = Param(json_obj["TSI_DP20_SINKCTS_DEBUG_CONF"])

    @property
    def force_visual_check(self) -> bool:
        """
        Set and get Force manual visual check flag.

        Returns:
            object of bool type
        """
        return self.__param._get_by_bitmask(0, bool)

    @force_visual_check.setter
    def force_visual_check(self, force_visual_check: bool):
        self.__param.default_value = update_default_value(value=force_visual_check,
                                                          default_value=self.__param.default_value,
                                                          mask=self.__param.bit_field_list[0].mask)


class Dp21SinkTestParam:
    """
    Class `Dp21SinkTestParam` allows working with parameters for Sink DP 2.1 LLCTS tests.
    - Set and get test timeout, in milliseconds `timeout`.
    - Set and get DSC VideoModeInfo `video_modes ` type `Dp21SinkVideoModes`.
    - Set and get capabilities of DSC video mode `dsc_video_mode`.
    - Set and get bitstream type `bitstream` - type `BitStream`.
    - Set and get flag of display id visual `visual_check` type `VisualCheck`.
    - Set and get debug options `debug_options` type `DebugOptions`
    """
    def __init__(self, json_obj):
        self.__timeout = Param(json_obj["TSI_DP20_SINKCTS_TIMEOUT"])
        self.__dsc_video_mode = Param(json_obj["TSI_DP20_SINKCTS_DSC_SOURCE"])  # PackedSource
        self.__bitstream = Param(json_obj["TSI_DP20_SINKCTS_SUPPORT_444CRC"])  # BitStream
        self.__display_id_visual = Param(json_obj["TSI_DP20_SINKCTS_VISUAL_TEST_CHECK"])
        self.__video_modes = Dp21SinkVideoModes(json_obj)
        self.__debug_options = DebugOptions(json_obj)

    @property
    def timeout(self) -> int:
        """
        Set and get test timeout, in milliseconds.

        Returns:
            object of int type
        """
        return self.__timeout.default_value

    @timeout.setter
    def timeout(self, timeout: int):
        if not(self.__timeout.min_value < timeout < self.__timeout.max_value):
            raise ValueError(f"Timeout cannot be less than {self.__timeout.min_value} and more than "
                             f"{self.__timeout.max_value}.")
        self.__timeout.default_value = timeout

    @property
    def dsc_video_mode(self) -> PackedSource:
        """
        Set and get dsc video packed source.

        Returns:
            object `PackedSource`
        """
        return PackedSource(self.__dsc_video_mode.default_value)

    @dsc_video_mode.setter
    def dsc_video_mode(self, dsc_video_mode: PackedSource):
        self.__dsc_video_mode.default_value = dsc_video_mode.value

    @property
    def bitstream(self) -> BitStream:
        """
        Set and get bitstream.

        Returns:
            object `BitStream`
        """
        return BitStream(self.__bitstream.default_value)

    @bitstream.setter
    def bitstream(self, bitstream: BitStream):
        self.__bitstream.default_value = bitstream.value

    @property
    def visual_check(self) -> VisualCheck:
        """
        Set and get flag of visual check.

        Returns:
            object `DisplayIdVisualCheck`
        """
        return VisualCheck(self.__display_id_visual.default_value)

    @visual_check.setter
    def visual_check(self, display_id_visual: VisualCheck):
        self.__display_id_visual.default_value = display_id_visual

    @property
    def video_modes(self) -> Dp21SinkVideoModes:
        """
        Set and get DP 2.1 Sink video modes.

        Returns:
            object `Dp21SinkVideoModes`
        """
        return self.__video_modes

    @video_modes.setter
    def video_modes(self, video_modes: Dp21SinkVideoModes):
        self.__video_modes = video_modes

    @property
    def debug_options(self) -> DebugOptions:
        """
        Set and get debug options.

        Returns:
            object of 'DebugOptions' type
        """
        return self.__debug_options

    @debug_options.setter
    def debug_options(self, debug_config: DebugOptions):
        self.__debug_options = debug_config
