import copy
from UniTAP.common import Timing
from typing import Union, List, Optional


class TimingManager:
    """

    Class `TimingManager` allows working with all available predefined timings from device.
    You can get cvt timing by index `get_cvt`, dmt timing by index `get_dmt`, cta timing by index `get_cta`,
    get list of all timing `get_all` ot search timing by parameters `search`.

    """
    def __init__(self, available_list: List[Timing]):
        self.__timings = available_list

    def get_cvt(self, index: int) -> Optional[Timing]:
        """

        Returns cvt `Timing` by index.

        Args:
            index (int): CVT timing index

        Returns:
            timing (Timing | None) - type `Timing` if search was success, `None` if not.
        """
        return copy.deepcopy(self.__search_by_standard_and_index(Timing.Standard.SD_CVT, index))

    def get_dmt(self, index: int) -> Optional[Timing]:
        """

        Returns cvt `Timing` by index.

        Args:
            index (int): DMT timing index

        Returns:
            timing (Timing | None) - type `Timing` if search was success, `None` if not.
        """
        return copy.deepcopy(self.__search_by_standard_and_index(Timing.Standard.SD_DMT, index))

    def get_cta(self, index: int) -> Optional[Timing]:
        """

        Returns cvt `Timing` by index.

        Args:
            index (int): CTA timing index

        Returns:
            timing (Timing | None) - type `Timing` if search was success, `None` if not.
        """
        return copy.deepcopy(self.__search_by_standard_and_index(Timing.Standard.SD_CTA, index))

    def get_all(self) -> List[Timing]:
        """

        Returns list of `Timing` objects.

        Returns:
            timing (list[Timing])
        """
        return copy.deepcopy(self.__timings)

    def get_by_list_index(self, index: int) -> Optional[Timing]:
        """

        Returns `Timing` objects by index in timings list.

        Args:
            index (int) index of timing in list

        Returns:
            timing (Timing)
        """
        if index < 0 or index >= len(self.__timings):
            raise ValueError(f"Incorrect index {index} of timings. Must be between 0 and {len(self.__timings)}")
        return self.__timings[index]

    def print_all(self) -> str:
        """

        Print list of `Timing` objects.

        Returns:
            str
        """
        return " \n".join(f'# {index} - '
                          f'{item.standard.name.replace("SD_", "") if item.standard != Timing.Standard.SD_NONE else ""} '
                          f'{item.hactive}x{item.vactive} @ {item.frame_rate / 1000}Hz'
                          f'{self.__print_timing_id(item)}'
                          for index, item in enumerate(self.__timings))

    def search(self, h_active: Optional[int] = None, v_active: Optional[int] = None, f_rate: Optional[int] = None,
               standard: Optional[Timing.Standard] = None,
               rb: Optional[Timing.ReduceBlanking] = None) -> Optional[Timing]:
        """

        Search timing by transferred parameters.

        Args:
            h_active (int | None): h active resolution of timing
            v_active (int | None): v active resolution of timing
            f_rate (int | None): frame rate of timing
            standard (Standard | None): timing `Standard`
            rb (ReduceBlanking | None): timing `ReduceBlanking`

        Returns:
            timing (Timing | None) - type `Timing` if search was success, `None` if not.
        """
        for timing in self.__timings:
            if (timing.hactive == h_active or h_active is None) and (timing.vactive == v_active or v_active is None)\
                    and (f_rate is None or abs(timing.frame_rate - f_rate) <= 500) \
                    and (timing.standard == standard or standard is None) \
                    and (timing.reduce_blanking == rb or rb is None):
                return copy.deepcopy(timing)

    def __search_by_standard_and_index(self, sd: Timing.Standard, index: int) -> Union[Timing, None]:
        search_result = [
            t for t in self.__timings if t.id == index and t.standard == sd
        ]
        return None if len(search_result) == 0 else search_result[0]

    @staticmethod
    def __print_timing_id(timing: Timing) -> str:
        if timing.standard == Timing.Standard.SD_CTA:
            return f" (VIC {timing.id})"
        elif timing.standard == Timing.Standard.SD_DMT:
            return f" (ID {hex(timing.id).replace('0x', '').upper()}h)" \
                   f"{f' [{timing.reduce_blanking.name}]' if timing.reduce_blanking != Timing.ReduceBlanking.RB_NONE else ''}"
        elif timing.standard == Timing.Standard.SD_CVT:
            return f" {f'[{timing.reduce_blanking.name}]' if timing.reduce_blanking != Timing.ReduceBlanking.RB_NONE else ''}"
        else:
            return ""
