from typing import List
from enum import IntEnum

from UniTAP.dev.modules.dut_tests.test_group_params_types import Param


class AudioTestPattern(IntEnum):
    """
    Describes available values for audio test pattern.
    """
    OperatorSpecificWaveform = 0
    SawtoothWaveform = 1


class AudioDpChannelsConfig:
    """
    Class `AudioDp14ChannelsConfig` allows working with audio channel configurations.
    - Get Channel count `channels_count`.
    - Select type of audio channel `select_channels`.

    List of audio channel types:
    - FL+FR
    - RL+RR
    - FLH+FRH
    - TC
    - LFE
    - RLC+RRC
    - FLW+FRW
    - FHC
    - FC
    - RC
    - FLC+FRC
    """
    __AUDIO_CHANNELS_INFO = {"FL+FR": 0x001, "RL+RR": 0x008, "FLH+FRH": 0x100, "TC": 0x200, 'LFE': 0x002,
                             "RLC+RRC": 0x040,  "FLW+FRW": 0x080, "FHC": 0x400, 'FC': 0x004, 'RC': 0x010,
                             "FLC+FRC": 0x020}

    __AUDIO_CHANNELS_COUNT = {0x001: 2, 0x008: 2, 0x100: 2, 0x200: 1, 0x002: 1, 0x040: 2, 0x080: 2, 0x400: 1, 0x004: 1,
                              0x010: 1, 0x020: 2}

    __AUDIO_CHANNELS_VARIANTS = '\n'.join(__AUDIO_CHANNELS_INFO.keys())

    def __init__(self, parameters):
        self.__channels_count = parameters[0]
        self.__channels_config = parameters[1]

    @property
    def channels_count(self) -> int:
        """
        Get channels count.

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

    def __check_audio(self, channel_type: str) -> bool:
        old_value = new_value = self.__channels_config.default_value
        new_value &= ~ self.__AUDIO_CHANNELS_INFO.get(channel_type)
        return old_value == new_value

    def __already_selected(self) -> List[str]:
        selected = []
        for item in self.__AUDIO_CHANNELS_INFO.keys():
            if self.__channels_config.default_value & self.__AUDIO_CHANNELS_INFO.get(item):
                selected.append(item)
        return selected

    def select_channels(self, channels: List[str], enable: bool):
        """
        Set channels types.

        Args:
            channels (list[str])
            enable (bool)
        """
        if all(item in list(self.__AUDIO_CHANNELS_INFO.keys()) for item in channels) is False:
            channels_type = '\n'.join(channels)
            raise ValueError(f"Incorrect channels type{'s' if len(channels) > 1 else ''} {channels_type}. "
                             f"Available audio channels variants:\n{self.__AUDIO_CHANNELS_VARIANTS}")
        if enable:
            count = int(self.channels_count)
            new_value = self.__channels_config.default_value
            for channel_type in channels:
                if self.__check_audio(channel_type):
                    count += self.__AUDIO_CHANNELS_COUNT.get(self.__AUDIO_CHANNELS_INFO.get(channel_type))
                    new_value |= self.__AUDIO_CHANNELS_INFO.get(channel_type)
            if count >= 9:
                already_selected_list = self.__already_selected()
                already_selected_list.extend(channels)
                already_selected = ', '.join(already_selected_list)
                raise ValueError(f"Max supported audio channels = 8. Current channels count = {count}, "
                                 f"selected - {already_selected}")
            else:
                self.__channels_config.default_value |= new_value
                self.__channels_count.default_value = count
        else:
            count = 0
            old_value = new_value = self.__channels_config.default_value
            for channel_type in channels:
                if not self.__check_audio(channel_type):
                    count += self.__AUDIO_CHANNELS_COUNT.get(self.__AUDIO_CHANNELS_INFO.get(channel_type))
                    new_value &= ~self.__AUDIO_CHANNELS_INFO.get(channel_type)
            if old_value != new_value:
                self.__channels_count.default_value -= count
                self.__channels_config.default_value = new_value

    def clear_all(self):
        """
        Clear channel count and configs.
        """
        self.__channels_count.default_value = 0
        self.__channels_config.default_value = 0


class AudioSourceDpSettingTabBase:
    __SAMPLE_RATE = [32000, 44100, 48000, 88200, 96000, 176400, 192000]
    __available_variants = '\n'.join(map(str, __SAMPLE_RATE))

    def __init__(self,
                 min_sample_rate,
                 max_sample_rate,
                 min_ch_config_min_rate,
                 max_ch_config_min_rate,
                 min_ch_config_max_rate,
                 max_ch_config_max_rate,
                 audio_pattern,
                 sample_size):
        self.__min_sample_rate = min_sample_rate
        self.__max_sample_rate = max_sample_rate
        self.__min_ch_config_min_rate = AudioDpChannelsConfig(min_ch_config_min_rate)
        self.__max_ch_config_min_rate = AudioDpChannelsConfig(max_ch_config_min_rate)
        self.__min_ch_config_max_rate = AudioDpChannelsConfig(min_ch_config_max_rate)
        self.__max_ch_config_max_rate = AudioDpChannelsConfig(max_ch_config_max_rate)
        self.__audio_pattern = audio_pattern  # AudioTestPattern
        self.__sample_size = sample_size

    @property
    def min_sample_rate(self) -> int:
        """
        Set and get minimum sample rate.

        Returns:
            object of int type
        """
        return int(self.__min_sample_rate.default_value)

    @min_sample_rate.setter
    def min_sample_rate(self, min_sample_rate: int):
        if min_sample_rate not in self.__SAMPLE_RATE:
            raise ValueError(
                f"Value {min_sample_rate} not in available variants:\n{self.__available_variants}")
        self.__min_sample_rate.default_value = min_sample_rate

    @property
    def max_sample_rate(self) -> int:
        """
        Set and get maximum sample rate.

        Returns:
            object of int type
        """
        return int(self.__max_sample_rate.default_value)

    @max_sample_rate.setter
    def max_sample_rate(self, max_sample_rate: int):
        if max_sample_rate not in self.__SAMPLE_RATE:
            raise ValueError(
                f"Value {max_sample_rate} not in available variants:\n{self.__available_variants}")
        self.__max_sample_rate.default_value = max_sample_rate

    @property
    def min_ch_config_min_rate(self) -> AudioDpChannelsConfig:
        """
        Get object of config minimum channels and minimum sample rate.

        Returns:
            object of `AudioDp14ChannelsConfig` type
        """
        return self.__min_ch_config_min_rate

    @property
    def max_ch_config_min_rate(self) -> AudioDpChannelsConfig:
        """
        Get object of config maximum channels and minimum sample rate.

        Returns:
            object of `AudioDp14ChannelsConfig` type
        """
        return self.__max_ch_config_min_rate

    @property
    def min_ch_config_max_rate(self) -> AudioDpChannelsConfig:
        """
        Get object of config minimum channels and maximum sample rate.

        Returns:
            object of `AudioDp14ChannelsConfig` type
        """
        return self.__min_ch_config_max_rate

    @property
    def max_ch_config_max_rate(self) -> AudioDpChannelsConfig:
        """
        Get object of config maximum channels and maximum sample rate.

        Returns:
            object of `AudioDp14ChannelsConfig` type
        """
        return self.__max_ch_config_max_rate

    @property
    def audio_pattern(self):
        """
        Set and get audio pattern.

        Returns:
            object of `AudioTestPattern` type
        """
        return self.__audio_pattern

    @audio_pattern.setter
    def audio_pattern(self, audio_pattern: AudioTestPattern):
        self.__audio_pattern.default_value = audio_pattern.value

    @property
    def sample_size(self) -> int:
        """
        Set and get sample size.

        Returns:
            object of int type
        """
        return int(self.__sample_size.default_value)

    @sample_size.setter
    def sample_size(self, sample_size: int):
        if sample_size not in [16, 20, 24]:
            raise ValueError(f"Value {sample_size} not in available variants:\n16\n20\n24\n")
        self.__sample_size.default_value = sample_size

    def clear_all(self):
        """
        Clear all settings.
        """
        self.__min_sample_rate.default_value = 32000
        self.__max_sample_rate.default_value = 32000
        self.min_ch_config_min_rate.clear_all()
        self.max_ch_config_min_rate.clear_all()
        self.min_ch_config_max_rate.clear_all()
        self.max_ch_config_max_rate.clear_all()
        self.__sample_size.default_value = 16


class AudioSourceDp14SettingTab(AudioSourceDpSettingTabBase):
    """
    Class `AudioSourceDp14SettingTab` allows working with audio parameters for DP 1.4 DP LLCTS tests.
    - Set and get minimum sample rate `min_sample_rate`.
    - Set and get maximum sample rate `max_sample_rate`.
    - Set and get minimum channels and minimum sample rate `min_ch_config_min_rate` type `AudioDp14ChannelsConfig`.
    - Set and get maximum channels and minimum sample rate `max_ch_config_min_rate` type `AudioDp14ChannelsConfig`.
    - Set and get minimum channels and maximum sample rate `min_ch_config_max_rate` type `AudioDp14ChannelsConfig`.
    - Set and get maximum channels and maximum sample rate `max_ch_config_max_rate` type `AudioDp14ChannelsConfig`.
    - Set and get audio pattern `audio_pattern` type `AudioTestPattern`.
    - Set and get sample `sample_size``.
    """

    def __init__(self, json_obj):
        super().__init__(
            Param(json_obj["TSI_DP14_SRCCTS_MIN_SAMPLE_RATE"]),
            Param(json_obj["TSI_DP14_SRCCTS_MAX_SAMPLE_RATE"]),
            (Param(json_obj["TSI_DP14_SRCCTS_CHANNELS1"]),
             Param(json_obj["TSI_DP14_SRCCTS_CH_ALLOC1"])),
            (Param(json_obj["TSI_DP14_SRCCTS_CHANNELS2"]),
             Param(json_obj["TSI_DP14_SRCCTS_CH_ALLOC2"])),
            (Param(json_obj["TSI_DP14_SRCCTS_CHANNELS3"]),
             Param(json_obj["TSI_DP14_SRCCTS_CH_ALLOC3"])),
            (Param(json_obj["TSI_DP14_SRCCTS_CHANNELS4"]),
             Param(json_obj["TSI_DP14_SRCCTS_CH_ALLOC4"])),
            Param(json_obj["TSI_DP14_SRCCTS_AUDIO_TEST_PATTERN"]),
            Param(json_obj["TSI_DP14_SRCCTS_EDID_SAMPLE_SIZE"])
        )


class AudioSourceDp21SettingTab(AudioSourceDpSettingTabBase):
    """
    Class `AudioSourceDp21SettingTab` inherited of class`AudioSourceDp14SettingTab` allows working with
    audio parameters for DP 1.4 DP LLCTS tests.
    Class `AudioSourceDp21SettingTab` has all the `AudioSourceDp14SettingTab` functionality.
    """
    def __init__(self, json_obj):
        super().__init__(
            Param(json_obj["TSI_DP20_SRCCTS_MIN_SAMPLE_RATE"]),
            Param(json_obj["TSI_DP20_SRCCTS_MAX_SAMPLE_RATE"]),
            (Param(json_obj["TSI_DP20_SRCCTS_CHANNELS1"]),
             Param(json_obj["TSI_DP20_SRCCTS_CH_ALLOC1"])),
            (Param(json_obj["TSI_DP20_SRCCTS_CHANNELS2"]),
             Param(json_obj["TSI_DP20_SRCCTS_CH_ALLOC2"])),
            (Param(json_obj["TSI_DP20_SRCCTS_CHANNELS3"]),
             Param(json_obj["TSI_DP20_SRCCTS_CH_ALLOC3"])),
            (Param(json_obj["TSI_DP20_SRCCTS_CHANNELS4"]),
             Param(json_obj["TSI_DP20_SRCCTS_CH_ALLOC4"])),
            Param(json_obj["TSI_DP20_SRCCTS_AUDIO_TEST_PATTERN"]),
            Param(json_obj["TSI_DP20_SRCCTS_EDID_SAMPLE_SIZE"])
        )
