from typing import Type

from UniTAP.dev.ports.modules.pdc.pdo_utils import interpret_pdo_value
from UniTAP.dev.ports.modules.pdc.pdo_types import PdoSide, PdoType, FixedPdoSink, FixedPdoSource, BatteryPdo, \
    VariablePdo, PdoTypeEnum


class Pdo:

    """
    Class `Pdo` describes power delivery object in PDC module. Contains information about side of PDO `PdoSide` and
    main PDO data. May have one of the available type `PdoType`: `FixedPdoSink`, `FixedPdoSource`, `BatteryPdo`,
    `VariablePdo`.
    - Get PDO type `pdo_type`, type of `PdoTypeEnum`.
    - Get PDO side `pdo_side`, type of `PdoSide`.
    - Set and get main PDO object `pdo_object`, type of `PdoType`.
    - Get pdo object as selected PDO type `PdoType` `get_pdo_as_selected_type`, type of `PdoType`.
    - Convert (interpret) from one PDO type to another `interpret_pdo_as_selected_type`.
    """

    def __init__(self, pdo: PdoType, side: PdoSide):
        self.__pdo = pdo
        self.__side = side

    @property
    def pdo_type(self) -> PdoTypeEnum:
        """
        Returns current PDO type.

        Returns:
            object of `PdoTypeEnum` type
        """
        return self.__pdo.pdo_type

    @property
    def pdo_side(self) -> PdoSide:
        """
        Returns current PDO side.

        Returns:
            object of `PdoSide` type
        """
        return self.__side

    @property
    def pdo_object(self) -> PdoType:
        """
        Returns current PDO object.

        Returns:
            object of `PdoType` type
        """
        return self.__pdo

    @pdo_object.setter
    def pdo_object(self, pdo: PdoType):
        self.__pdo = pdo

    def disable_pdo(self):
        """
        Disable PDO. Will be filled with zeros.
        """
        self.__pdo = FixedPdoSink(disable=True) if self.__side == PdoSide.Sink else FixedPdoSource(disable=True)

    def get_pdo_as_selected_type(self, pdo: Type[PdoType]) -> PdoType:
        """
        Returns PDO object as selected new PDO type.

        Args:
            pdo (`PdoType`) - type of PDO

        Returns:
            object of `PdoType` type
        """
        self.interpret_pdo_as_selected_type(pdo)
        return self.__pdo

    def interpret_pdo_as_selected_type(self, pdo: Type[PdoType]):
        """
        Convert (interpret) from one PDO type to another.

        Args:
            pdo (`PdoType`) - type of PDO
        """
        if pdo == FixedPdoSink and self.__side == PdoSide.Sink:
            self.__pdo = FixedPdoSink(interpret_pdo_value(self.__pdo, pdo))
        elif pdo == FixedPdoSource and self.__side == PdoSide.Source:
            self.__pdo = FixedPdoSource(interpret_pdo_value(self.__pdo, pdo))
        elif pdo == BatteryPdo:
            self.__pdo = BatteryPdo(interpret_pdo_value(self.__pdo, pdo))
        elif pdo == VariablePdo:
            self.__pdo = VariablePdo(interpret_pdo_value(self.__pdo, pdo))
        else:
            raise ValueError(f"Incorrect PDO type: {pdo}. Available types: BatteryPdo, VariablePdo, "
                             f"{'FixedPdoSink' if self.__side == PdoSide.Sink else 'FixedPdoSource'}")

    def __str__(self):
        return f'PDO type: {self.pdo_type.name}\n' \
               f'{self.__pdo.__str__()}'
