from typing import List, Union, Optional
from enum import IntEnum
from UniTAP.dev.modules.dut_tests.test_group_params_types import Param


class VideoPixelTestColorDepth(IntEnum):
    """
    Class `VideoPixelTestColorDepth` describes available supported color depth.
    """
    ColorDepth6 = 6
    ColorDepth8 = 8
    ColorDepth10 = 10
    ColorDepth12 = 12
    ColorDepth16 = 16


class VideoPixelTestElementFormat(IntEnum):
    """
    Class `VideoPixelTestElementFormat` describes available supported color formats.
    """
    RGB_6BPC = 16
    RGB_8BPC = 17
    RGB_10BPC = 18
    RGB_12BPC = 19
    RGB_16BPC = 20
    YCbCr444_080808 = 256
    YCbCr444_161616 = 257
    YCbCr420_8BPC_080808 = 800
    YCbCr420_10BPC_080808 = 801
    YCbCr420_12BPC_161616 = 802
    YCbCr420_16BPC_161616 = 803


class VideoPixelTestAlignment(IntEnum):
    """
    Class `VideoPixelTestColorDepth` describes available supported alignment.
    """
    MSB = 0
    LSB = 1


class VideoPixelTestExportFormat(IntEnum):
    """
    Class `VideoPixelTestColorDepth` describes available supported files format.
    """
    BIN = 0
    PPM = 1
    BMP = 2


class VideoPixelTestParam:
    """
    Class `VideoPixelTestParam` describes parameters for video pixel test.
    - Defines frame width as number of elements `image_width`.
    - Defines frame height as number of elements `image_height`.
    - Defines the color depth of the image as number of bits per color channel regardless of the color format
    `color_depth` type `VideoPixelTestColorDepth`.
    - Defines the element format used to encode the pixel data of the bitmap `frame_data`.
    - Defines type of alignment (LSB-MSB data alignment) `alignment` type `VideoPixelTestAlignment`.
    - Defines the length of the video test as number of frames `frames_number`.
    - Defines number of frame that are allowed to be considered as 'failed' before the entire test is considered as
    'failed' `frames_number_mismatch`.
    - Defines the number of pixels that allowed to be considered as 'failed' before the frame is considered as 'failed'
     `pixel_tolerance`.
    - Maximum number of frames failed frames saved per test run `max_auto_save_failed`.
    - Contains the full path to the folder where failed frames are to be saved without trailing backslash
    `failed_frame_folder`.
    - Defines the number of failed frames to be exported from the video test `max_export_failed_frames`.
    - Defines export file format `export_data_format` type `VideoPixelTestExportFormat`.
    """
    def __init__(self, json_obj):
        self.__image_width = Param(json_obj["TSI_REF1_WIDTH"])
        self.__image_height = Param(json_obj["TSI_REF1_HEIGHT"])
        self.__element_size = Param(json_obj["TSI_REF1_ELEMENT_SIZE"])  # Do not provide
        self.__element_width = Param(json_obj["TSI_REF1_ELEMENT_WIDTH"])  # Do not provide
        self.__element_height = Param(json_obj["TSI_REF1_ELEMENT_HEIGHT"])  # Do not provide
        self.__color_depth = Param(json_obj["TSI_REF1_COLOR_DEPTH"])  # VideoPixelTestColorDepth
        self.__element_format = Param(json_obj["TSI_REF1_ELEMENT_FORMAT"])  # VideoPixelTestElementFormat
        self.__frame_data = Param(json_obj["TSI_REF1_FRAME_DATA"])
        self.__alignment = Param(json_obj["TSI_REF1_LSB_MSB"])  # VideoPixelTestAlignment
        self.__frames_number = Param(json_obj["TSI_TEST_LENGTH"])
        self.__frames_number_mismatch = Param(json_obj["TSI_LIM_FRAME_MISMATCHES"])
        self.__pixel_tolerance = Param(json_obj["TSI_PIXEL_TOLERANCE"])
        self.__max_auto_save_failed = Param(json_obj["TSI_MAX_AUTO_SAVE_FAILED"])
        self.__failed_frame_folder = Param(json_obj["TSI_FAILED_FRAME_TARGET_FOLDER"])
        self.__max_export_failed_frames = Param(json_obj["TSI_MAX_EXPORT_FAILED"])
        self.__export_data_format = Param(json_obj["TSI_EXPORT_FORMAT"])  # Export format VideoPixelTestExportFormat

    @property
    def image_width(self) -> int:
        """
        Set and get frame width as number of elements.

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

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

    @property
    def image_height(self) -> int:
        """
        Set and get frame height as number of elements.

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

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

    @property
    def color_depth(self):
        """
        Set and get color depth of the image.

        Returns:
            object `VideoPixelTestColorDepth`
        """
        return self.__color_depth.default_value

    @color_depth.setter
    def color_depth(self, color_depth: VideoPixelTestColorDepth):
        self.__color_depth.default_value = color_depth.value

    @property
    def element_format(self):
        """
        Set and get element format used to encode the pixel data.

        Returns:
            object `VideoPixelTestElementFormat`
        """
        return self.__element_format.default_value

    @element_format.setter
    def element_format(self, element_format: VideoPixelTestElementFormat):
        self.__element_format.direct_set_def_value(element_format.value)

    @property
    def frame_data(self) -> Optional[Union[bytearray, List[int], str]]:
        """
        Set and get pixel data of image.

        Returns:
            object bytearray | list[int] | None | str
        """
        return self.__image_height.default_value

    @frame_data.setter
    def frame_data(self, frame_data: Optional[Union[bytearray, List[int], str]]):
        self.__frame_data.default_value = frame_data

    @property
    def alignment(self):
        """
        Set and get type of alignment.

        Returns:
            object `VideoPixelTestAlignment`
        """
        return self.__alignment.default_value

    @alignment.setter
    def alignment(self, alignment: VideoPixelTestAlignment):
        self.__alignment.default_value = alignment.value

    @property
    def frames_number(self) -> int:
        """
        Set and get length of the video test as number of frames.

        Returns:
            object `VideoPixelTestAlignment`
        """
        return self.__frames_number.default_value

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

    @property
    def frames_number_mismatch(self) -> int:
        """
        Set and get number of frame that are allowed to be considered as 'failed' before the entire test is considered
        as 'failed'.

        Returns:
            object `VideoPixelTestAlignment`
        """
        return self.__frames_number_mismatch.default_value

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

    @property
    def pixel_tolerance(self) -> int:
        """
        Set and get number of pixels that allowed to be considered as 'failed' before the frame is considered as
        'failed'.

        Returns:
            object `VideoPixelTestAlignment`
        """
        return self.__pixel_tolerance.default_value

    @pixel_tolerance.setter
    def pixel_tolerance(self, pixel_tolerance: int):
        if not(self.__pixel_tolerance.min_value < pixel_tolerance < self.__pixel_tolerance.max_value):
            raise ValueError(f"Pixel tolerance cannot be less than {self.__pixel_tolerance.min_value} and more than "
                             f"{self.__image_height.max_value}.")
        self.__pixel_tolerance.default_value = pixel_tolerance

    @property
    def max_auto_save_failed(self) -> int:
        """
        Set and get maximum number of frames failed frames saved per test run.

        Returns:
            object `VideoPixelTestAlignment`
        """
        return self.__max_auto_save_failed.default_value

    @max_auto_save_failed.setter
    def max_auto_save_failed(self, max_auto_save_failed: int):
        if not(self.__max_auto_save_failed.min_value < max_auto_save_failed < self.__max_auto_save_failed.max_value):
            raise ValueError(f"Max auto save failed frames cannot be less than {self.__max_auto_save_failed.min_value}"
                             f" and more than {self.__max_auto_save_failed.max_value}.")
        self.__max_auto_save_failed.default_value = max_auto_save_failed

    @property
    def failed_frame_folder(self):
        """
        Set and get full path to the folder where failed frames are to be saved without trailing backslash.

        Returns:
            object str
        """
        return self.failed_frame_folder.default_value

    @failed_frame_folder.setter
    def failed_frame_folder(self, failed_frame_folder: str):
        self.failed_frame_folder.default_value = failed_frame_folder

    @property
    def max_export_failed_frames(self) -> int:
        """
        Set and get number of failed frames to be exported from the video test.

        Returns:
            object int
        """
        return self.__max_export_failed_frames.default_value

    @max_export_failed_frames.setter
    def max_export_failed_frames(self, max_export_failed_frames: int):
        if not(self.__max_export_failed_frames.min_value < max_export_failed_frames <
               self.__max_export_failed_frames.max_value):
            raise ValueError(f"Max export failed frames number cannot be less than "
                             f"{self.__max_export_failed_frames.min_value} and more than "
                             f"{self.__image_height.max_value}.")
        self.__max_export_failed_frames.default_value = max_export_failed_frames

    @property
    def export_data_format(self):
        """
        Set and get file format.

        Returns:
            object `VideoPixelTestExportFormat`
        """
        return self.__export_data_format.default_value

    @export_data_format.setter
    def export_data_format(self, export_data_format: VideoPixelTestExportFormat):
        self.__export_data_format.default_value = export_data_format.value
