from . import components
from . import current
from . import mosaic
from . import logic_utils
from .geom_handler import base_geom_objects as bgo
from .components import Component, Port

# from . import data
from .data import numerical_constants as nc

from uuid import uuid4, UUID
from ulid import ULID
import sympy as sy

from typing import TypeAlias, Union

"""
This file will be used to declare the pump class, as a class that inherits from
Pipe (because it also only has one inlet and one outlet).
"""

MajorPortSelection: TypeAlias = UUID

MinorPortSelection: TypeAlias = tuple[UUID, UUID]


class Pump(components.MechanicalPowerInputComponent):
    pump_objects: list = []

    def __init__(
        self,
        internal_diam: float | int,
        mechanical_power: float | int,
        efficiency: float | int,
    ):
        super().__init__(port_quantity=2)  # , orientation=orientation
        # internal_diam=internal_diam

        self.internal_diam: float | int = internal_diam

        if logic_utils.has_generic_ports(self) == True:
            # this should erase all the item inside the component_ports_list
            self.component_ports.clear()

        for n in range(self.port_quantity):
            self.component_ports.append(
                Port(
                    geometry="cylinder",
                    characteristic_length=self.internal_diam,
                    location=self.id,
                )
            )

        # This part make the pipe instance limited to have just one input and one output connected to itself from other Component Objects
        if len(self.plugged_inputs) >= self.port_quantity:
            self.plugged_inputs.pop(self.port_quantity)

        if len(self.plugged_outputs) >= self.port_quantity:
            self.plugged_outputs.pop(self.port_quantity)

        if self.component_ports[0] is None:
            raise ValueError(f"\n\nThis port must be different than None")

        # * super().__init__(internal_diam, 0)  # //port_quantity=2
        # components.Pipe
        self.power_name = ULID()
        # This is the mechanical power that the pump recieves, and this power is
        # transformed directly in mechanical flow energy
        self.mechanical_power = mechanical_power

        # this is the efficiency of the pump in transforming the mechanical shaft power
        # in flow mechanical power
        self.efficiency = efficiency

        # this comand should be the last one inside of the __init__ method, because it has to run after all the others so
        # that all the informations are astored correctlly on the pump_objects list.
        Pump.pump_objects.append(self)

    def position_solver(self) -> None:
        """
        This component must be analogous to a Pipe, by having the root point in one of its ports.

        The lateral outlet must be fixated perpendicular to the main strigth tube path and, its positional vector must
        also be positioned in the middle of the straigth path vector length.

        """

        """
        In this case, the orientation of this component should be fixed using three points as a plane, and the angle of
        this plane relative to the 'ground plane' (xy plane) should be the orientation of this component.

        The three points would be the coordinates from each one of its three ports.

        Another option should be that the orientation of this component relative to the 'ground plane' could be the angle
        between the direct path vector on the 'T' connector
        """

        try:
            first_none_coord_index: Union[None, int] = None
            for coord_index, coord in enumerate(
                self.component_ports[0].spacial_position.coords
            ):  # type: ignore
                first_none_coord_index = coord_index
                assert coord != None

        except:
            raise ValueError(
                f"\n\nThe {self.id = } Pipe has the None coord at the {first_none_coord_index} element of the root point coordinates"
            )

        # // Eu preciso criar o ponto central do mosaico, mas eu não sei qual das opções abaixo escolher
        # // - Criar esse ponto como um ponto no espaço
        # // - Criar o ponto como uma quarta porta dentro da lista de portas do mosaico
        # // - Criar o ponto como uma porta que vai ser um atributo especial, e vai ficar armazenado
        # // em um lugar diferente das demais portas do componente

        # // Essa alteração vai influenciar muito a parte do método greate_graph do mosaico, que lida
        # // com a criação de arestas e nós para componentes com 3 portas dento da lista
        # // self.component_ports

        if self.component_ports[0].spacial_position != None:  # type: ignore
            self.component_ports[0].spacial_position = self.component_ports[
                0
            ].spacial_position  # type: ignore

            self.component_ports[0].position_vector = bgo.Vector(
                self.component_ports[0].spacial_position.coords[0],  # type: ignore
                self.component_ports[0].spacial_position.coords[1],  # type: ignore
                self.component_ports[0].spacial_position.coords[2],
            )  # type: ignore

            self.component_ports[1].spacial_position = self.component_ports[
                0
            ].spacial_position  # type: ignore
            self.component_ports[1].position_vector = bgo.Vector(
                self.component_ports[0].spacial_position.coords[0],  # type: ignore
                self.component_ports[0].spacial_position.coords[1],  # type: ignore
                self.component_ports[0].spacial_position.coords[2],
            )  # type: ignore

    def r_constant(self) -> float | int:
        """
        This method calculates the R_constant of some_pump.

        R = (mechanical_power * pump_eficiency) / (rho * g)

        hb = R * (1/Q)
        """

        assert (
            (self.mechanical_power is not None)
            and (self.mechanical_power != 0)
            and (self.mechanical_power > 0)
        )
        assert (
            (self.efficiency is not None)
            and (self.efficiency > 0)
            and (self.efficiency <= 100)
        )

        if self.efficiency <= 1:
            pass

        elif (self.efficiency <= 100) and (self.efficiency > 1):
            self.efficiency = self.efficiency / 100

        rho_internal_delta = (
            self.component_ports[0].local_current.density()
            - self.component_ports[1].local_current.density()
        )

        assert rho_internal_delta < 0.000001

        r = (self.mechanical_power * self.efficiency) / (
            self.component_ports[0].local_current.density() * nc.g
        )

        return r

    # def head_pressure_loss_eq(self, port_id: UUID) -> float | int:
    #     """
    #     The pump components have no pressure loss, so this function must return 0
    #     """

    #     return 0

    def HB(self, port_id: MajorPortSelection) -> sy.Basic:
        """
        This method will generate the Head Mechanical Pressure Input term,
        that will be used inside the energy conservation equation, inside the
        mosaic
        """

        other_port = None
        for port in self.component_ports:
            if port.id != port_id:
                other_port = port
        assert other_port is not None

        assert len(self.component_ports) == 2

        arg_port: Port | None = None
        arg_port_id: UUID | None = None

        port_uuid_map: dict[UUID, Port] = {port.id: port for port in Port.port_objects}

        arg_port = port_uuid_map[port_id]
        arg_port_id = arg_port.id
        arg_port_id = sy.symbols(str(arg_port_id))

        other_port = port_uuid_map[other_port.id]
        other_port_id = other_port.id
        other_port_id = sy.symbols(str(other_port_id))

        assert arg_port_id is not None
        assert arg_port is not None

        volume_flow_internal_delta = (
            self.component_ports[0].local_current.volume_flow
            - self.component_ports[1].local_current.volume_flow
        )  # type: ignore

        assert volume_flow_internal_delta < 0.000001

        # * comp_port_1_volume_flow = sy.symbols(str(self.component_ports[0].id))

        self_power_name = sy.symbols(str(self.power_name))
        # // This part is the original one used for declaring the hb term
        # //hb: sy.Basic = sy.Eq(self.r_constant() * (1 / arg_port_id), self_power_name)

        hb: sy.Basic = sy.Eq(self.r_constant() * (1 / other_port_id), self_power_name)

        return hb.lhs
