import warnings

from UniTAP.dev.ports.modules.pdc.pdc_dpam_types import DPAMVersion
from .pdc_io import PDCPortIO, DPAMCommand
from .pdc_private_dpam_types import PinAssignmentModes, DISCDpSignaling, DISCCapability
from .pdc_utils import support_or_not


class PinAssignment:

    """
    Class `PinAssignment` allows enabling and disabling assignment.
    - Enable/Disable mode C 4 lanes `c_4_lanes`, type `bool`.
    - Enable/Disable mode D 2 lanes `d_2_lanes`, type `bool`.
    - Enable/Disable mode E 4 lanes `e_4_lanes`, type `bool`.
    """

    def __init__(self):
        self.__c_4_lanes = False
        self.__d_2_lanes = False
        self.__e_4_lanes = False

    @property
    def c_4_lanes(self) -> bool:
        """
        Returns state of C 4 lanes mode.

        Returns:
            object of `bool` type.
        """
        return self.__c_4_lanes

    @c_4_lanes.setter
    def c_4_lanes(self, enable: bool):
        self.__c_4_lanes = enable

    @property
    def d_2_lanes(self) -> bool:
        """
        Returns state of D 2 lanes mode.

        Returns:
            object of `bool` type.
        """
        return self.__d_2_lanes

    @d_2_lanes.setter
    def d_2_lanes(self, enable: bool):
        self.__d_2_lanes = enable

    @property
    def e_4_lanes(self) -> bool:
        """
        Returns state of E 4 lanes mode.

        Returns:
            object of `bool` type.
        """
        return self.__e_4_lanes

    @e_4_lanes.setter
    def e_4_lanes(self, enable: bool):
        self.__e_4_lanes = enable

    def __str__(self) -> str:
        return f"C 4 lanes: {self.c_4_lanes}\n" \
               f"D 2 lanes: {self.d_2_lanes}\n" \
               f"E 4 lanes: {self.e_4_lanes}\n"


class DutDpAltModeStatus:

    """
    Class `DutDpAltModeStatus` describes DUT DP Alt mode status.
    - Get DUT connection state `dut_connection`, type `str`.
    - Get DUT multi-function state `dut_multi_function`, type `bool`.
    - Get DUT power low state `dut_power_low`, type `bool`.
    """

    __dut_connection_status = {DISCCapability.Reserved: "No connection",
                               DISCCapability.DPF_D_capable: "DFP_D is connected",
                               DISCCapability.UFP_D_capable: "UFP_D is connected",
                               DISCCapability.UFP_D_and_DPF_D: "Both DFP_D and UFP_D are connected"}

    def __init__(self, pdc_io: PDCPortIO):
        self.__io = pdc_io

    @property
    def dut_connection(self) -> str:
        """
        Returns DUT connection state.

        Returns:
            object of `str` type.
        """
        return self.__dut_connection_status.get(DISCCapability(self.__io.read_dpam_recv().connection))

    @property
    def dut_multi_function(self) -> bool:
        """
        Returns DUT multi-function state.

        Returns:
            object of `bool` type.
        """
        return bool(self.__io.read_dpam_recv().multi_func)

    @property
    def dut_power_low(self) -> bool:
        """
        Returns DUT power low state.

        Returns:
            object of `bool` type.
        """
        return bool(self.__io.read_dpam_recv().power_low)

    def __str__(self):
        return f"DUT Alt mode: {self.dut_connection}\n" \
               f"DUT Multi-function preferred: {support_or_not(self.dut_multi_function)}\n" \
               f"DUT Power low: {'Low power or DP support disabled' if self.dut_power_low else 'Normal operation'}\n"


class TeDpAltModeStatus:

    """
    Class `DutDpAltModeStatus` describes TE DP Alt mode status.
    - Get active state `te_active`, type `bool`.
    - Get DISC signaling DP v1.3 state, type `bool`.
    - Get DISC signaling USB Gen 2 state, type `bool`.
    - Get PIN assignment, type `PinAssignmentModes`.
    """

    def __init__(self, pdc_io: PDCPortIO):
        self.__io = pdc_io

    @property
    def te_active(self) -> bool:
        """
        Returns active state.

        Returns:
            object of `bool` type.
        """
        return bool(self.__io.read_pdc_status().cable_plug)

    @property
    def te_select_dp_1_3(self) -> bool:
        """
        Returns DISC signaling DP v1.3 state.

        Returns:
            object of `bool` type.
        """
        return self.__io.read_dpam_config().signaling == DISCDpSignaling.Dp_v13

    @property
    def te_select_usb_gen2(self) -> bool:
        """
        Returns DISC signaling USB Gen 2 state.

        Returns:
            object of `bool` type.
        """
        return self.__io.read_dpam_config().signaling == DISCDpSignaling.Gen2

    @property
    def te_pin_assignment(self) -> PinAssignmentModes:
        """
        Returns PIN assignment.

        Returns:
            object of `PinAssignmentModes` type.
        """

        return PinAssignmentModes(self.__io.read_dpam_config().pin_assign)

    def __str__(self):
        return f"TE Status Active: {support_or_not(self.te_active)}\n" \
               f"TE Select DP v1.3: {self.te_select_dp_1_3}\n" \
               f"TE Select USB gen2: {self.te_select_usb_gen2}\n" \
               f"TE Pin Assignment: {self.te_pin_assignment.name}\n"


class DpAltModeStatus:
    """
    Class `DpAltModeStatus` describes DP Alt mode status.
    - Get DUT DP Alt mode status `dut_dp_alt_mode`, type `DutDpAltModeStatus`.
    - Get TE DP Alt mode status, type `TeDpAltModeStatus`.
    - Get support state of DISC Signaling DP v13 `support_dp_1_3`, type `bool`.
    - Get support state of DISC Signaling USB Gen2 `support_usb_gen2`, type `bool`.
    - Get support state of DFP D `support_dfp_d`, type `bool`.
    - Get support state of UFP D `support_ufp_d`, type `bool`.
    """

    def __init__(self, pdc_io: PDCPortIO):
        self.__io = pdc_io
        self.__dut_dp_alt_mode = DutDpAltModeStatus(self.__io)
        self.__te_dp_alt_mode = TeDpAltModeStatus(self.__io)

    @property
    def support_dp_1_3(self) -> bool:
        """
        Returns support state of DISC Signaling DP v13.

        Returns:
            object of `bool` type.
        """
        return self.__io.read_dpam_disc_modes().signaling == DISCDpSignaling.Dp_v13

    @property
    def support_usb_gen2(self) -> bool:
        """
        Returns support state of DISC Signaling USB Gen2.

        Returns:
            object of `bool` type.
        """
        return self.__io.read_dpam_disc_modes().signaling == DISCDpSignaling.Gen2

    @property
    def support_dfp_d(self) -> bool:
        """
        Returns support state of DFP D.

        Returns:
            object of `bool` type.
        """
        return DISCCapability(self.__io.read_dpam_disc_modes().capability) in [DISCCapability.DPF_D_capable,
                                                                               DISCCapability.UFP_D_and_DPF_D]

    @property
    def support_ufp_d(self) -> bool:
        """
        Returns support state of UFP D.

        Returns:
            object of `bool` type.
        """
        return DISCCapability(self.__io.read_dpam_disc_modes().capability) in [DISCCapability.UFP_D_capable,
                                                                               DISCCapability.UFP_D_and_DPF_D]

    def multifunction_preferred(self) -> bool:
        """
        Returns state of multifunction preferred.

        Returns:
            object of `bool` type.
        """
        return self.__io.read_dpam_caps2()

    def auto_enter(self) -> bool:
        """
        Returns state of auto enter.

        Returns:
            object of `bool` type.
        """
        return self.__io.read_dpam_state().auto_dpam != 0

    @property
    def dut_dp_alt_mode(self) -> DutDpAltModeStatus:
        """
        Returns DUT DP Alt mode status.

        Returns:
            object of `DutDpAltModeStatus` type.
        """
        return self.__dut_dp_alt_mode

    @property
    def te_dp_alt_mode(self) -> TeDpAltModeStatus:
        """
        Returns TE DP Alt mode status.

        Returns:
            object of `TeDpAltModeStatus` type.
        """
        return self.__te_dp_alt_mode

    def __str__(self):
        return f"Supports DP v1.3: {support_or_not(self.support_dp_1_3)}\n" \
               f"Supports USB gen2: {support_or_not(self.support_usb_gen2)}\n" \
               f"Multifunction preferred: {support_or_not(self.multifunction_preferred())}\n" \
               f"Auto enter: {support_or_not(self.auto_enter())}\n" \
               f"Pin Assignment supported:\n" \
               f"  - Support DFP_D: {support_or_not(self.support_dfp_d)}\n" \
               f"  - Support UFP_D: {support_or_not(self.support_ufp_d)}\n" \
               f"{self.__dut_dp_alt_mode.__str__()}" \
               f"{self.__te_dp_alt_mode.__str__()}"


class DpAltModeBase:

    """
    Class `DpAltModeBase` describes basic DP Alt mode functionality.
    - Get DP Alt mode status `status`, type `DpAltModeStatus`.
    - Enter to 2 lane mode `enter_2_lane`.
    - Enter to 4 lane mode `enter_4_lane`.
    - Exit from DP Alt mode `exit`.
    - Disable DP Alt mode `disable`.
    - Enable/Disable auto enter to DP Alt mode `auto_enter`.
    - Enable/Disable multifunction preferred `multifunction_preferred`.
    - Enable/Disable align Dp and USB Data role `align_dp_and_usb_data_role`.
    - Set and get UFP caps `ufp_caps`, type `PinAssignment`.
    - Set and get DFP caps `dfp_caps`, type `PinAssignment`.
    """

    def __init__(self, pdc_io: PDCPortIO):
        self._io = pdc_io
        self.__caps = self._io.read_pdc_caps()
        self.__status = DpAltModeStatus(pdc_io)

    @property
    def status(self) -> DpAltModeStatus:
        """
        Returns DP Alt mode status.

        Returns:
            object of `DpAltModeStatus` type.
        """
        return self.__status

    def enter_2_lane(self):
        """
        Enter to 2 lane mode.
        """
        self._io.write_dpam_control(DPAMCommand.EnterDPAM2lanes)

    def enter_4_lane(self):
        """
        Enter to 4 lane mode.
        """
        self._io.write_dpam_control(DPAMCommand.EnterDPAM4lanes)

    def exit(self):
        """
        Exit from DP Alt mode.
        """
        self._io.write_dpam_control(DPAMCommand.ExitDPAM)

    def disable(self):
        """
        Disable DP Alt mode.
        """
        self._io.write_dpam_control(DPAMCommand.DisableDPAM)

    def auto_enter(self, enable: bool):
        """
        Enable/Disable auto enter to DP Alt mode.

        Args:
            enable (`bool`)
        """
        self._io.write_dpam_control(DPAMCommand.AutoEnterDPAM if enable else DPAMCommand.ManualDPAM)

    def multifunction_preferred(self, enable: bool):
        """
        Enable/Disable multifunction preferred.

        Args:
            enable (`bool`)
        """
        self._io.write_dpam_control(DPAMCommand.SetMFPreferredFlag if enable else DPAMCommand.ClearMFPreferredFlag)

    def align_dp_and_usb_data_role(self, enable: bool):
        """
        Enable/Disable align Dp and USB Data role.

        Args:
            enable (`bool`)
        """
        if self.__caps.align_dp_usb:
            self._io.write_dpam_control(DPAMCommand.SetAlignDpCUsb if enable else DPAMCommand.ClearAlignDpCUsb)
            self._io.write_dpam_control(DPAMCommand.SetAlignDpDUsb if enable else DPAMCommand.ClearAlignDpDUsb)
            self._io.write_dpam_control(DPAMCommand.SetAlignDpEUsb if enable else DPAMCommand.ClearAlignDpEUsb)
        else:
            warnings.warn("Align DP and USB data role is not supported.")

    @property
    def ufp_caps(self) -> PinAssignment:
        """
        Returns current UFP caps.

        Returns:
            object of `PinAssignment`
        """
        caps = self._io.read_dpam_caps()
        pin_assignment = PinAssignment()
        pin_assignment.c_4_lanes = ((caps.ufp_assign >> 2 & 1) != 0)
        pin_assignment.d_2_lanes = ((caps.ufp_assign >> 3 & 1) != 0)
        pin_assignment.e_4_lanes = ((caps.ufp_assign >> 4 & 1) != 0)
        return pin_assignment

    @ufp_caps.setter
    def ufp_caps(self, ufp_caps: PinAssignment):
        caps = self._io.read_dpam_caps()
        if ufp_caps.c_4_lanes:
            caps.ufp_assign |= PinAssignmentModes.C_4lanes.value
        else:
            caps.ufp_assign &= ~PinAssignmentModes.C_4lanes.value
        if ufp_caps.d_2_lanes:
            caps.ufp_assign |= PinAssignmentModes.D_2lanes.value
        else:
            caps.ufp_assign &= ~PinAssignmentModes.D_2lanes.value
        if ufp_caps.e_4_lanes:
            caps.ufp_assign |= PinAssignmentModes.E_4lanes.value
        else:
            caps.ufp_assign &= ~PinAssignmentModes.E_4lanes.value
        self._io.write_dpam_caps(caps)

    @property
    def dfp_caps(self) -> PinAssignment:
        """
        Returns current DFP caps.

        Returns:
            object of `PinAssignment`
        """
        caps = self._io.read_dpam_caps()
        pin_assignment = PinAssignment()
        pin_assignment.c_4_lanes = ((caps.ufp_assign >> 2 & 1) != 0)
        pin_assignment.d_2_lanes = ((caps.ufp_assign >> 3 & 1) != 0)
        pin_assignment.e_4_lanes = ((caps.ufp_assign >> 4 & 1) != 0)
        return pin_assignment

    @dfp_caps.setter
    def dfp_caps(self, dfp_caps: PinAssignment):
        caps = self._io.read_dpam_caps()
        if dfp_caps.c_4_lanes:
            caps.dfp_assign |= PinAssignmentModes.C_4lanes.value
        else:
            caps.dfp_assign &= ~PinAssignmentModes.C_4lanes.value
        if dfp_caps.d_2_lanes:
            caps.dfp_assign |= PinAssignmentModes.D_2lanes.value
        else:
            caps.dfp_assign &= ~PinAssignmentModes.D_2lanes.value
        if dfp_caps.e_4_lanes:
            caps.dfp_assign |= PinAssignmentModes.E_4lanes.value
        else:
            caps.dfp_assign &= ~PinAssignmentModes.E_4lanes.value
        self._io.write_dpam_caps(caps)

    @property
    def version(self) -> DPAMVersion:
        return DPAMVersion(self._io.read_dpam_caps().dpam_version)


class DpAltMode500(DpAltModeBase):

    """
    Class `DpAltMode500` inherited from class `DpAltModeBase`.
    Class `DpAltMode500` allows controlling additional commands.
    Also has all the `DpAltModeBase` functionality.
    - Enable/Disable DP 2.1 Alt mode `enable_dp21`.
    """

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

    def enable_dp21(self, enable: bool):
        """
        Enable/Disable DP 2.1 Alt mode.

        Args:
            enable (`bool`)
        """
        self._io.write_dpam_control(DPAMCommand.EnableDPAM2_1 if enable else DPAMCommand.DisableDPAM2_1)


class DpAltMode340(DpAltModeBase):

    """
    Class `DpAltMode340` inherited from class `DpAltModeBase`.
    Class `DpAltMode340` allows controlling additional commands.
    Also has all the `DpAltModeBase` functionality.
    - Enable/Disable DP to Type-C cable adapter mode `dp_to_type_c_cable_adapter_mode`.
    """

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

    def dp_to_type_c_cable_adapter_mode(self, enable: bool):
        """
        Enable/Disable DP to Type-C cable adapter mode.

        Args:
            enable (`bool`)
        """
        self._io.write_dpam_control(DPAMCommand.EnableDPtoTypeCAdapter if enable else
                                    DPAMCommand.DisableDPtoTypeCAdapter)
