import math
import warnings
from .pdc_io import PDCPortIO, PDCControlCommand
from .pdc_utils import support_or_not
from .pdc_types import FrSwapCurrent, ContractTypePriority, InternalResistance, ExternalResistance
from typing import Union


class PowerContractControlBase:

    """
    Class `PowerContractControlBase` allows setting and getting field related with power contract.
    - Set and get Give Back flag `give_back_flag`, type `bool`.
    - Set and get No USB suspend `no_usb_suspend`, type `bool`.
    - Set and get PDO type priority `pdo_type_priority`, type `ContractTypePriority`.
    - Set and get Maximum operation current `max_operation_current`, type `int`.
    - Set FW Swap required current `fw_swap_required_current`, type `FrSwapCurrent`.
    """

    __fr_swap_current = {FrSwapCurrent.Disable: PDCControlCommand.SetFRSwapNoSupported,
                         FrSwapCurrent.EnableDefaultUSBPower: PDCControlCommand.SetFRSwapUSBPower,
                         FrSwapCurrent.Enable_1_5A_5V: PDCControlCommand.SetFRSwap1_5A_5V,
                         FrSwapCurrent.Enable_3A_5V: PDCControlCommand.SetFRSwap3A_5V}

    def __init__(self, pdc_io: PDCPortIO):
        self._io = pdc_io
        self.__caps = self._io.read_pdc_caps()
        self.__pdo_count = self._io.read_hw_caps().pdo_count

    @property
    def give_back_flag(self) -> bool:
        """
        Returns state of Give back flag.

        Returns:
            object of `bool` type
        """
        return self._io.read_pdc_contract_control().give_back_flag != 0

    @give_back_flag.setter
    def give_back_flag(self, enable: bool):
        data = self._io.read_pdc_contract_control()
        data.give_back_flag = int(enable)
        self._io.write_pdc_contract_control(data)

    @property
    def no_usb_suspend(self) -> bool:
        """
        Returns state of No USB suspend flag.

        Returns:
            object of `bool` type
        """
        return self._io.read_pdc_contract_control().no_usb_suspend != 0

    @no_usb_suspend.setter
    def no_usb_suspend(self, enable: bool):
        data = self._io.read_pdc_contract_control()
        data.no_usb_suspend = int(enable)
        self._io.write_pdc_contract_control(data)

    def fw_swap_required_current(self, fr_swap_current: FrSwapCurrent):
        """
        Set FW Swap required current.

        Args:
            fr_swap_current (`FrSwapCurrent`)
        """
        if self.__caps.fr_swap:
            self._io.write_pdc_command(self.__fr_swap_current.get(fr_swap_current))
        else:
            warnings.warn("Do not support Fast Role Swap.")

    @property
    def pdo_type_priority(self) -> ContractTypePriority:
        """
        Returns current PDO Type priority.

        Returns:
            object of `ContractTypePriority` type
        """
        return ContractTypePriority(self._io.read_pdc_contract_control().type_priority)

    @pdo_type_priority.setter
    def pdo_type_priority(self, type_priority: ContractTypePriority):
        data = self._io.read_pdc_contract_control()
        data.type_priority = type_priority.value
        self._io.write_pdc_contract_control(data)

    @property
    def max_operation_current(self) -> int:
        """
        Returns current value of maximum operation current.

        Returns:
            object of `int` type
        """
        data = self._io.read_contract_data()
        return data[3] & 0x03FF

    @max_operation_current.setter
    def max_operation_current(self, current: int):
        if current < 0:
            raise ValueError(f"Incorrect current {current}. Must be more than 0.")
        data = self._io.read_contract_data()
        data[3] = (data[3] & 0xFFFFFC00) | (current & 0x03FF)
        self._io.write_contract_data(data)

    def established(self) -> bool:
        """
        Returns state of power contract (established or not).

        Returns:
            object of `bool` type
        """
        return self._io.read_pdc_status().pwr_contract == 1

    def __str__(self) -> str:
        return f"Power Contract Control\n" \
               f"Give Back flag active: {support_or_not(self.give_back_flag)}\n" \
               f"No USB suspend flag active: {support_or_not(self.no_usb_suspend)}\n" \
               f"Contract type priority: {self.pdo_type_priority.name}\n" \
               f"Max operation current: {self.max_operation_current} mA"


class PowerContractControl340(PowerContractControlBase):

    """
    Class `PowerContractControl340` inherited from class `PowerContractControlBase`.
    Class `PowerContractControl340` allows setting and getting internal and external resistance,
    index of power contract.
    Also has all the `PowerContractControlBase` functionality.
    - Set and get flag Selecting power contract by index `selecting_by_index`, type `bool`.
    - Set and get index of power contract `index_of_power_contract`, type `int`.
    - Set and get internal resistance `internal_resistance`, type `int` or `InternalResistance`.
    - Set and get external resistance `external_resistance`, type `int` or `ExternalResistance`.
    """

    __internal_resistance = {10000: InternalResistance.Resistance_10_Ohm,
                             5500: InternalResistance.Resistance_5_5_Ohm,
                             3550: InternalResistance.Resistance_3_55_Ohm,
                             3500: InternalResistance.Resistance_3_5_Ohm,
                             2600: InternalResistance.Resistance_2_6_Ohm,
                             2140: InternalResistance.Resistance_2_14_Ohm,
                             1760: InternalResistance.Resistance_1_76_Ohm}

    __external_resistance = {13900: ExternalResistance.Resistance_13_9_Ohm,
                             10600: ExternalResistance.Resistance_10_6_Ohm,
                             9100: ExternalResistance.Resistance_9_1_Ohm,
                             7600: ExternalResistance.Resistance_7_6_Ohm,
                             6600: ExternalResistance.Resistance_6_6_Ohm,
                             5600: ExternalResistance.Resistance_5_6_Ohm,
                             4600: ExternalResistance.Resistance_4_6_Ohm,
                             3600: ExternalResistance.Resistance_3_6_Ohm,
                             1800: ExternalResistance.Resistance_1_8_Ohm}

    def __init__(self, pdc_io: PDCPortIO):
        super().__init__(pdc_io)

    @property
    def selecting_by_index(self) -> bool:
        """
        Returns state of Selecting by index flag.

        Returns:
            object of `bool` type
        """
        return self._io.read_pdc_contract_control().manual != 0

    @selecting_by_index.setter
    def selecting_by_index(self, enable: bool):
        data = self._io.read_pdc_contract_control()
        data.manual = enable
        self._io.write_pdc_contract_control(data)

    @property
    def index_of_power_contract(self) -> int:
        """
        Returns index of current power contract.

        Returns:
            object of `int` type
        """
        if not self._io.read_pdc_contract_control().manual:
            return self._io.read_power_contract_select()
        else:
            warnings.warn("Negotiate power contract automatically disabled. "
                          "Please, enable (use function 'enable_selecting_by_index'.)")
            return -1

    @index_of_power_contract.setter
    def index_of_power_contract(self, index: int):
        if not self._io.read_pdc_contract_control().manual:
            if index < 0:
                raise ValueError(f"Incorrect index {index} of power contract. "
                                 f"Must be between 0 and {self.__pdo_count}.")
            self._io.write_power_contract_select(index)
        else:
            warnings.warn("Negotiate power contract automatically disabled. "
                          "Please, enable (use function 'enable_selecting_by_index'.)")

    @property
    def internal_resistance(self) -> Union[InternalResistance, int]:
        """
        Returns internal resistance.

        Returns:
            object of `int` or `InternalResistance` type
        """
        res = self._io.read_internal_resistance()
        for key in self.__internal_resistance.keys():
            if math.fabs(res - key) < 50:
                return self.__internal_resistance.get(key)
        return res

    @internal_resistance.setter
    def internal_resistance(self, resistance: InternalResistance):
        self._io.write_resistance(resistance.value)

    @property
    def external_resistance(self) -> Union[ExternalResistance, int]:
        """
        Returns external resistance.

        Returns:
            object of `int` or `ExternalResistance` type
        """
        if self._io.read_hw_status().ext_power:
            res = self._io.read_external_resistance()
            for key in self.__external_resistance.keys():
                if math.fabs(res - key) < 50:
                    return self.__external_resistance.get(key)
            return res
        else:
            return ExternalResistance.Disable

    @external_resistance.setter
    def external_resistance(self, resistance: ExternalResistance):
        if self._io.read_hw_status().ext_power:
            self._io.write_resistance(resistance.value)
        else:
            warnings.warn("Does not enabled external power. Please, attach external power device.")

    def __str__(self) -> str:
        return f"Power Contract Control\n" \
               f"Give Back flag active: {support_or_not(self.give_back_flag)}\n" \
               f"No USB suspend flag active: {support_or_not(self.no_usb_suspend)}\n" \
               f"Contract type priority: {self.pdo_type_priority.name}\n" \
               f"Max operation current: {self.max_operation_current} mA\n" \
               f"Internal resistance: {self.internal_resistance} Om\n" \
               f"External resistance: {self.external_resistance} Om\n" \
               f"Selecting power contract by index flag active: {support_or_not(self.selecting_by_index)}\n" \
               f"Power contract index: {self.index_of_power_contract if self.selecting_by_index else '0'}"
