import warnings
from typing import Optional, Union

from UniTAP.libs.lib_tsi.tsi_io import PortIO, PortProtocol
from .types import *
from UniTAP.libs.lib_tsi.tsi_private_types import *
from ctypes import c_int


class HdcpSourceStatus:

    """

    Class `HdcpSourceStatus` contains information about HDCP 1.4 and 2.3 statuses.
    If you want to get object of one the status, use function `get`.

    """

    def __init__(self, port_io: PortIO, ci_status_control: int, caps_1x: HdcpHwSourceCaps, caps_2x: HdcpHwSourceCaps):
        super().__init__()
        self.__io = port_io
        self.__ci_status_control = ci_status_control
        self.__caps_1x = caps_1x
        self.__caps_2x = caps_2x

    def get(self, hdcp_mode: Type[HdcpStatusType]) -> HdcpStatusType:
        """

        Returns one of possible HDCP Status Type:
        - Status1x (HDCP 1.4).
        - StatusTx2x (HDCP 2.3).

        Object contains info about:
        - HDCP keys (HdcpSource1XKeys if HDCP type 1.4; HdcpSource2XKeys if HDCP type 2.3).
        - Active state (True or False).
        - Authenticated state (True or False).
        - Capable state (True or False).

        If HDCP type is 2.3, then it contains more information:
        - KM stored state (True or False).
        - Try to authenticate state (True or False).
        - Try to encrypt state (True or False).
        - Content level.

        Returns:
            object of `HdcpStatusType` (`Status1x` or `StatusTx2x`)
        """
        if hdcp_mode not in [HdcpStatus.Status1x, HdcpStatus.StatusTx2x]:
            raise TypeError(f"Wrong type HDCP status, provided type {hdcp_mode}")

        if hdcp_mode == HdcpStatus.Status1x:
            if self.__caps_1x.hw_supported:
                status_value = self.__read_status(HdcpMode.Mode1_4)
                status = HdcpStatus.Status1x()
                status.active = (status_value >> 8) & 1
                status.keys = HdcpSource1XKeys(((status_value >> 9) & 3) + 0x100)
                status.capable = ((status_value >> 11) & 1)
                status.authenticated = ((status_value >> 12) & 1)
                return status
            else:
                warnings.warn(f"Not supported 'HDCP 1.4'")
        elif hdcp_mode == HdcpStatus.StatusTx2x:
            if self.__caps_2x.hw_supported:
                status_value = self.__read_status(HdcpMode.Mode2_3)
                status = HdcpStatus.StatusTx2x()
                if self.__io.protocol() == PortProtocol.HDMI:
                    status.keys = HdcpSource2XKeys.Production if (status_value >> 9) & 1 else HdcpSource2XKeys.Unload
                else:
                    if (status_value >> 14) & 1:
                        status.keys = HdcpSource2XKeys.TestR1
                    elif (status_value >> 15) & 1:
                        status.keys = HdcpSource2XKeys.TestR2
                    elif (status_value >> 9) & 1:
                        status.keys = HdcpSource2XKeys.Production
                    else:
                        status.keys = HdcpSource2XKeys.Unload

                    status.km_is_stored = (status_value >> 13) & 1
                    status.try_authenticate = (status_value >> 18) & 1
                    status.try_encrypt = (status_value >> 19) & 1
                    status.content_level = self.__io.get(TSI_HDCP_2X_CFG, c_uint32)[1]

                status.active = (status_value >> 8) & 1
                status.capable = (status_value >> 10) & 1
                status.authenticated = (status_value >> 12) & 1

                return status
            else:
                warnings.warn(f"Not supported 'HDCP 2.3'")

    def __read_status(self, mode: HdcpMode):
        if mode == HdcpMode.Mode1_4:
            return self.__io.get(TSI_HDCP_1X_STATUS_R, c_int)[1]
        elif mode == HdcpMode.Mode2_3:
            return self.__io.get(self.__ci_status_control, c_int)[1]
        else:
            return 0


class HdcpSourceConfig:

    """
    Class `HdcpSourceConfig` contains information about HDCP 1.4 and 2.3 configurations.
    If you want to set configuration, use function `set`.
    If you want to get current config, use function `get`.
    """

    def __init__(self, port_io: PortIO, caps_1x: HdcpHwSourceCaps, caps_2x: HdcpHwSourceCaps, status: HdcpSourceStatus):
        self.__io = port_io
        self.__caps_1x = caps_1x
        self.__caps_2x = caps_2x
        self.__status = status

    def set(self, config: HdcpTxConfigType):
        """

        This function is used to set the HDCP on Source (TX - transmitter) side.
        Possible to load HDCP keys and enable/disable HDCP.

        Args:
            config (HdcpTxConfigType) - one of the available HDCP config type: Config1x (HDCP 1.4), Config2x (HDCP 2.3)
        """
        if isinstance(config, HdcpTxConfig.Config1x):
            if self.__caps_1x.hw_supported:
                self.__load_keys(config.keys, HdcpMode.Mode1_4)
                self.__set_authenticate(config.authenticate, HdcpMode.Mode1_4)
                self.__set_encryption(config.encryption, HdcpMode.Mode1_4)
            else:
                warnings.warn(f"Not supported 'HDCP 1.4'")
        elif isinstance(config, HdcpTxConfig.Config2x):
            if self.__caps_2x.hw_supported:
                self.__load_keys(config.keys, HdcpMode.Mode2_3)
                self.__set_content_level(config.content_level)
                self.__set_authenticate(config.authenticate, HdcpMode.Mode2_3)
                self.__set_store_km(config.store_km)
                self.__set_encryption(config.encryption, HdcpMode.Mode2_3)
            else:
                warnings.warn(f"Not supported 'HDCP 2.3'")

    def get(self, hdcp_mode: Type[HdcpTxConfigType]) -> HdcpTxConfigType:
        """

        This function is used to get current HDCP configuration on Source (TX - transmitter) side.

        Returns:
            object of HdcpTxConfigType - one of the available HDCP config type: Config1x (HDCP 1.4), Config2x (HDCP 2.3)
        """
        if hdcp_mode == HdcpTxConfig.Config1x:
            if self.__caps_1x.hw_supported:
                config = HdcpTxConfig.Config1x()
                status = self.__status.get(HdcpStatus.Status1x)
                config.keys = status.keys
                config.encryption = status.active
                config.authenticate = status.authenticated
                return config
            else:
                warnings.warn(f"Not supported 'HDCP 1.4'")
        elif hdcp_mode == HdcpTxConfig.Config2x:
            if self.__caps_2x.hw_supported:
                config = HdcpTxConfig.Config2x()
                status = self.__status.get(HdcpStatus.StatusTx2x)
                config.keys = status.keys
                config.encryption = status.try_encrypt
                config.authenticate = status.try_authenticate
                config.store_km = status.km_is_stored
                config.content_level = status.content_level
                return config
            else:
                warnings.warn(f"Not supported 'HDCP 2.3'")

    def __set_encryption(self, value: Optional[bool], hdcp_mode: HdcpMode):
        if value is None:
            return
        if hdcp_mode == HdcpMode.Mode1_4:
            self.__io.set(TSI_HDCP_1X_COMMAND_W, H1_SOURCE_ENABLE_ENCRYPT if value else H1_SOURCE_DISABLE_ENCRYPT)
        elif hdcp_mode == HdcpMode.Mode2_3:
            self.__io.set(TSI_HDCP_2X_COMMAND_W, H2_SOURCE_ENABLE_ENCRYPT if value else H2_SOURCE_DISABLE_ENCRYPT)

    def __set_authenticate(self, value: Optional[bool], hdcp_mode: HdcpMode):
        if value is None:
            return
        if hdcp_mode == HdcpMode.Mode1_4:
            self.__io.set(TSI_HDCP_1X_COMMAND_W, H1_SOURCE_AUTHENTICATE if value else H1_SOURCE_DE_AUTHENTICATE)
        elif hdcp_mode == HdcpMode.Mode2_3:
            self.__io.set(TSI_HDCP_2X_COMMAND_W, H2_SOURCE_AUTHENTICATE if value else H2_SOURCE_DE_AUTHENTICATE)

    def __load_keys(self, keys: Optional[Union[HdcpSource1XKeys, HdcpSource2XKeys]], hdcp_mode: HdcpMode):
        if keys is None:
            return
        if not self.__full_check(hdcp_mode):
            return

        if hdcp_mode == HdcpMode.Mode1_4 and not isinstance(keys, HdcpSource1XKeys):
            raise TypeError("Was selected HDCP 1.4 mode. Need to use 'HdcpSource1XKeys' type.")
        elif hdcp_mode == HdcpMode.Mode1_4 and isinstance(keys, HdcpSource1XKeys):
            if keys == HdcpSource1XKeys.Production and not self.__caps_1x.production_keys_available:
                warnings.warn("Production keys is not hw supported. Please, select another keys.")
            elif keys == HdcpSource1XKeys.Test and not self.__caps_1x.test_keys_available:
                warnings.warn("Test keys is not hw supported. Please, select another keys.")
        elif hdcp_mode == HdcpMode.Mode2_3 and not isinstance(keys, HdcpSource2XKeys):
            raise TypeError("Was selected HDCP 2.3 mode. Need to use 'HdcpSource2XKeys' type.")
        elif hdcp_mode == HdcpMode.Mode2_3 and isinstance(keys, HdcpSource2XKeys):
            if keys == HdcpSource2XKeys.Production and not self.__caps_2x.production_keys_available:
                warnings.warn("Production keys is not hw supported. Please, select another keys.")

        self.__io.set(TSI_HDCP_1X_COMMAND_W if hdcp_mode == HdcpMode.Mode1_4 else TSI_HDCP_2X_COMMAND_W, keys.value)

    def __full_check(self, hdcp_mode: HdcpMode) -> bool:
        if hdcp_mode == HdcpMode.Unknown:
            warnings.warn(f"Did not select HDCP mode. Please, select it from available variants: "
                          f"{'HDCP 1.4 ' if self.__caps_1x.hw_supported else ''}"
                          f"{'HDCP 2.3 ' if self.__caps_2x.hw_supported else ''}")
            return False

        if not self.__availability_mode_check(hdcp_mode):
            warnings.warn(f"Selected HDCP mode is not HW supported. Please, select it from available variants: "
                          f"{'HDCP 1.4 ' if self.__caps_1x.hw_supported else ''}"
                          f"{'HDCP 2.3 ' if self.__caps_2x.hw_supported else ''}")
            return False

        return True

    def __availability_mode_check(self, hdcp_mode: HdcpMode) -> bool:
        if hdcp_mode == HdcpMode.Mode1_4 and self.__caps_1x.hw_supported:
            return True
        elif hdcp_mode == HdcpMode.Mode2_3 and self.__caps_2x.hw_supported:
            return True
        else:
            return False

    def __set_store_km(self, value: Optional[bool]):
        if value is None:
            return
        self.__io.set(TSI_HDCP_2X_COMMAND_W, H2_SOURCE_AUTHENTICATE_STORE_KM if value else H2_SOURCE_CLEAR_STORE_KM)

    def __set_content_level(self, value: Optional[int]):
        if value is None:
            return
        if value not in [0, 1]:
            raise ValueError(f"Incorrect input value of 'Content level' - {value}. Must be from range: 0-1")
        self.__io.set(TSI_HDCP_2X_CFG, value)


class HdcpSource:

    """
    Main class contains info of HDCP on Source (TX - transmitter) side.
    If you need to configurate HDCP, use `config` for getting object responsible for the configuration.
    If you need to read HDCP status, use `status` for getting object responsible for the reading current status.
    """

    def __init__(self, port_io: PortIO, ci_caps_control: int, ci_status_control: int):
        self.__io = port_io
        self.__hw_caps_1x = HdcpHwSourceCaps(self.__read_hw_caps(HdcpMode.Mode1_4), HdcpMode.Mode1_4)
        self.__hw_caps_2x = HdcpHwSourceCaps(self.__read_hw_caps(HdcpMode.Mode2_3, ci_caps_control), HdcpMode.Mode2_3)
        self.__status = HdcpSourceStatus(port_io, ci_status_control, self.__hw_caps_1x, self.__hw_caps_2x)
        self.__config = HdcpSourceConfig(port_io, self.__hw_caps_1x, self.__hw_caps_2x, self.__status)

    @property
    def config(self) -> HdcpSourceConfig:
        """

        Should be used to configure HDCP on Source (TX - transmitter) role.

        Returns:
            object of `HdcpSourceConfig`.
        """
        return self.__config

    @property
    def status(self) -> HdcpSourceStatus:
        """

        Should be used to read HDCP current status onSource (TX - transmitter) role.

        Returns:
            object of `HdcpSourceStatus`.
        """
        return self.__status

    def __read_hw_caps(self, mode: HdcpMode, ci_caps_control: int = 0) -> int:
        if mode == HdcpMode.Mode1_4:
            return self.__io.get(TSI_HDCP_1X_STATUS_R, c_int)[1]
        elif mode == HdcpMode.Mode2_3:
            return self.__io.get(ci_caps_control, c_int)[1]
        else:
            return 0

