from . import mosaic as mosaic
from . import current as current
from . import logic_utils as logic_utils
from . import preferences as preferences
from . import material as material
from . import errors as er
from .geom_handler import base_geom_objects as bgo
from .data import numerical_constants as nc

import sympy as sy

# import icecream as ice
# import networkx as nx
from uuid import uuid4, UUID
from ulid import ULID
from collections.abc import Iterable
import shutil

from typing import (
    NoReturn,
    Union,
    List,
    Tuple,
    TypeAlias,
    Self,
)  # * override decorator only exists in typing from python 3.12

# from chemicals import Chemical, Mixture
import thermo as thermo
from math import cos, sin, pi

Equation: TypeAlias = sy.Eq

MajorPortSelection: TypeAlias = UUID

MinorPortSelection: TypeAlias = tuple[UUID, UUID]

"""
O que define a p=numeração da posição de um elemento em umma ramo é a doreção do escoamento naquele ramo.

    EX.: Se temos um ramo de tubos simples na horizontal e o escoamento acontece da esquerda pra direita, o 
    primeiro elemento será o que estiver na posição mais à esquerda do ramo. A partir dele, os elementos são numerados
    em ordem crescente quanto mais à direita eles estão.
        O primeiro será o elemento  de posição zero no ramo.
        O segundo será o elemento será o elemento de posição 1.
        O terceiro será o elemento de posição 2.

O que eu preciso descobrir é como enumerar de forma diferente os diferentes ramos

    Uma forma de fazer isso poderia ser a escolha de um ponto de referência dentro de toda a estrutura ramificada. Esse poderia ser o ramo
    central. Isso exigiria uma escolha inteligente desse ramo, uma vez que todos os demais precisam estar ligados a ele.
        Uma forma de aplicar isso seria escolher um componente do ramo principal como o elemento zero desse ramo, e nomear todas as conexões
        desse ramo a partir (ou ao redor) do elemento zero do ramo central.

    Uma forma de fazer isso na prática seria criando uma classe de Element_Position, e dando alguna tritubtos para ela.
    O que uenão sei é se a classe de Element_Position precisa ser criada dentro da classe Component, ou se ela pode ser criada fora (au antes)
    da classe Component. Eu criaria ela fora pra poder passar um Objeto Element_Position como argumento da Classe Component.

    Para resolver isso, eu poderia excluir a numeração e nomeação das posições dos elementos, e só retornar pro usuário os elementos que se
    ligam ao elemento observado.

Eu preciso colocar também elementados de tanques, ou elementos que só acumulam matéria. Existe a possibilidade de abrir e fechar os tanques,
mas isso pode me dar problemas, por que vai dar trabalho pra avaliar a vazão e a conservação de massa nesses elementos. AINDA ASSIM, É POSSÍVEL
PROGRAMAR ESSA PARTE.

Existe um problema que são os elementos que tem mais de uma saida e mais de uma entrada. O problema é que, se dentro desses elementos
existtirem tubulações, eu preciso fazer o balanço de massa individual pra cada uma delas.(Deixar esses elementos de lado por enquanto)

PRECISO CRIAR OS DIFERENTES FLUIDOS QUE ESCOAM DENTRO DO SISTEMA.
    Pra fazer isso, eu pensei em cirar uma classe de fluido, e passar ela como argumento da classe component.

    O que eu não decidi ainda é se a livraria vai suportar ou não diferentes fluidos escoamento na mesma tubulação ou em tubulações diferentes.
        Pra esses dois casos eu teria um bom trabalho pra codar a parte de bombeamento e mecÂnica dos fluidos.

    A principio, o programa vai rodar só com água, pra facilitar a implementação das coisas.

    Depois que todas as funções básicas e fundamentais estiverem funcionando, ai sim vou adiconar o suporte pra diferentes fluidos escoando
    em diferentes partes do sistema sem se misturar, ou se misturando em algum ponto.

Uma ideia pra parte da função calculate, seria criar uma outra classe (que poderia existir dentro da classe component ou fora) ou fução pra
que essa função pudesse solucionar as equações toda vez que fosse chamada.
    Uma ideia pra essa parte seria implementar vários tipos de solver, ou colocar varias configurações possiveis no solver atual de propriedades
    do escoamento.

    Essas diferentes formas do solver seriam pra casos particulares, como pra resolevr só pra um componente, resolver as propriedades pro sistema
    todo, resolver só uma das propriedades ou resolver todas.

    Eu acho que seria uma boa ideia criar dois ou tres tipos de solver no maximo, por que mais do que isso pode ser um exagero.
        A ideia seria fazer um solver geral, que vai resolver todas as propriedades para todo o sistema.
        o segundo solver seria um solver geral, mas ele só vai resolver as propriedades para um dado componente
        a terceira ideia ser fazer uma solver que pudesse ser "configurado" pra resolver propriedades especificas de partes especificas do sistema.
            Esse solver na verdade pode fazer tudo que os outros fazem, mas a diferença é que ele precisa que as configurações sejam
            declaradas quando ele for chamado, e por isso ele não vai ter elas setadas para o padrão.
                uma forma de implementar issopoderia ser com if e else dentro da declaração de uma classe solve.
                Uma outra forma seria com a criação de uma classe solve geral, e criar as outras classes como classes filhas e alterar as
                caracteristicas de cada uma individualmente.
"""


# MARK: Port Class
class Port:
    port_objects: List[Self] = []

    """
    This Class will be used to create the port objects that each component object and subclass objet has.
    An example of this is that a pipe has two ports, one for the inlet and other for the outlet.
    A "y" connector has one inlet port, ans two outlet ports.
    The objects from this class will store data about geometry, size, fitting (or joint type)
    """

    def __init__(self, geometry, characteristic_length, location):
        # In a cilindrical pipe the geometry wouold be circular
        # In a duct the geometry would be square or rectangle
        # In a pump and other devices, even if the cross section isn't a circle or a rectangle, the ports should have one of these geometries
        self.geometry: str = geometry

        # This is the diameter of a Pipe, measured in meters
        self.characteristic_length: Union[float, int] = characteristic_length
        self.id: UUID = uuid4()

        """
        The location of a Port object must be the ID of the component which owns said port.
        The location must be atributed to the port on the instant of its creation inside some component object, and must
        remain the same throughout the entire lifetime of the Port object.
        """
        self.location: UUID = location
        self.spacial_position: bgo.Point = bgo.Point(None, None, None)

        # This vector must come from the root point of the component, and stop at the spacial position point of the port
        self.position_vector: bgo.Vector = bgo.Vector(None, None, None)
        self.local_current: current.Current = current.Current.generic_current(
            port_id=self.id
        )

        match self.geometry:
            case "circle" | "cylinder":
                pass
            case "square" | "rectangle":
                pass

            case None:
                pass

            case _:
                raise ValueError(
                    f"The geometry of the ports must be circle/cylinder or square/rectangle!"
                )

        match self.characteristic_length:
            case 0:
                pass
            case None:
                pass
            # case -int():

        # This assert statement will be used to assure that the location is not none in anny circunstances

        try:
            assert location is not None

        except AssertionError:
            raise ValueError(f"\nThe location of the {self.id} port must not be None")

        # this command must be the last one inside the __init__ method, so that it loads the object in the correct way into
        # the port_objects list, with all of its data already processed and defined
        Port.port_objects.append(self)

    def cross_sect_area(self) -> float | int:
        """
        This area is calculated in m^2
        """

        if (self.geometry == "circle") or ("cylinder"):
            A = (pi * (self.characteristic_length**2)) / 4

            return A

        else:
            raise NotImplementedError

    @classmethod
    def generic_port(cls, arg):
        """
        This method should be used to create generic Port objects to populate the component_I/O lists and the plugged I/O
        in the moment that the objects are created.

        Those generic ports will recieve the geometry and characteristic length right after they are created.

        I will create those objects just so that the lists doesn't have the correct size when they are created,
        and because of this, the __add__ method from the component class can't put in the correct position the inputs and outputs (
        this positioning if the ports in the lists is needed because there may be some components that have multiple and diferent ports,
        and because of those diferent components, the "plugs"/"additions" between tow components or a component and a current must be
        made specifically in a given position in the lists, so that the user can "select" which port he wants to plug to which
        )
        """

        return Port(geometry=None, characteristic_length=None, location=arg)

    @classmethod
    def is_generic_port(cls, arg) -> bool | None:
        match isinstance(arg, Port):
            case True:
                for obj in Port.port_objects:
                    if arg.id == obj.id:
                        if obj.characteristic_length == None and obj.geometry == None:
                            return True

                        else:
                            return False

                    else:
                        pass

            case True:
                for obj in Port.port_objects:
                    if arg.id == obj.id:
                        if (
                            obj.local_current.composition == None
                            and obj.geometry == None
                        ):
                            return True

                        else:
                            return False

                    else:
                        pass

            case False if isinstance(arg, UUID) == True:
                """ In this case, the argument is a UUID, and the search must be made through ID's"""

                for obj in Port.port_objects:
                    if arg == obj.id:
                        if obj.characteristic_length == None and obj.geometry == None:
                            return True

                        else:
                            return False

                    else:
                        pass

            case (False,) if arg is None:
                return True

            case (False,):
                raise TypeError(
                    f"The argument passed to this function must be an instance of the Port class, but is from {type(arg) = }"
                )

    """def __add__(self, other):
        if isinstance(other, Port) == True:
            if len(mosaic.Mosaic.mosaic_objects) == 0:
                new_mosaic = mosaic.Mosaic(joints_list=[])
                new_mosaic.joint(self.id, other.id)
            
            if len(mosaic.Mosaic.mosaic_objects) != 0:
                #This part has to search for the mosaicto witch one of the two ports belong
                # it also has to check is both parts aren't from different mosaics

                pass
            pass
        elif isinstance(other, current.Current) == True:
           pass 
        else:
            raise TypeError(f'The ports must only be added to Current or other Port objects!')
        
        pass"""

    def __radd__(self, other):
        pass

    def __repr__(self):
        return f"{self.id = },\n{self.location = },\n{self.spacial_position = },\n{self.position_vector = }"

    def __str__(self):
        return f"{self.id = },\n{self.location = },\n{self.spacial_position = },\n{self.position_vector = }"

    def __eq__(self, other) -> bool:
        # if (other is None):
        # if all(coord == None for coord in self.spacial_position.coords) == True:
        #     return True

        # elif all(coord == None for coord in self.spacial_position.coords) == False:
        #     return False
        # #elif any(coord == None)

        # else:
        #     raise ValueError

        if isinstance(other, Port):
            if self.id == other.id:
                return True

            else:
                return False

        if isinstance(other, UUID):
            for p in Port.port_objects:
                if p.id == other:
                    return True

            else:
                # print(
                #     f"\n\nSomething happend, and this misterious thing makes the code crash"
                # )
                return False

        else:
            raise TypeError(
                f"\n\nThe other object is from {type(other) = }, and is not supported by this method"
            )

    def __ne__(self, other) -> bool:
        # if (other is None):
        #     if all(coord == None for coord in self.spacial_position.coords) == False:
        #         return True

        #     elif all(coord == None for coord in self.spacial_position.coords) == True:
        #         return False
        #     #elif any(coord == None)

        #     else:
        #         raise ValueError

        # assert  isinstance(other, Port) == True
        if isinstance(other, Port):
            if self.id == other.id:
                return False

            else:
                return True

        if isinstance(other, UUID):
            for p in Port.port_objects:
                if p.id == other:
                    return False

            else:
                return True

        else:
            raise TypeError(
                f"\n\nThe other object is from {type(other) = }, and is not supported by this method"
            )

    def __format__(self):
        return f"{self}"


# MARK: Component Class
class Component:
    """
    This class is used to declare the general propperties of the components in a piping system.

    Although it can be used instead of using one of its child classes, it's better to use the child classes in most
    of the cases, as the child classes have the specific settings to deal with possible errors and contradictions in
    the code implementation.
    """

    component_objects: list = []

    def __init__(self, port_quantity: int, orientation: float | int | None = 0) -> None:
        # Creation of a random uuid-4 that belongs to the object
        self.id: UUID = uuid4()

        """
        The location of a Component object will be the index of the mosaic which owns some joint with at leas one port
        of the component. This means that said component will be forever linked to the mosaic which has one of its ports
        inside a joint from said mosaic.
        """
        self.location: UUID | None = None

        self.port_quantity: int = port_quantity
        self.component_ports: list[None | Port] = [
            Port.generic_port(self.id)
        ] * self.port_quantity

        """
        The orientation attribute will be used mostly by the Pipe objects, because it sets the orientation of the pipe
        as an angle with the 'floor plane'.

        if the orientation is None, it means that the Pipe is in the 'floor plane'.
        """

        self.orientation: float | int | None = orientation
        # self.vector: Union[None, bgo.Vector, List[Union[None, bgo.Vector]]] = None

        # if ((self.orientation != 90) or (self.orientation != 180) or (self.orientation != 270) or (self.orientation != 0)):
        #     raise ValueError(f'\n\nThe orientation must be paralel to all the cartesian axis (x, y, z)')

        self.KL_Dict: dict[str, int | float] = {}

        # This is the point to where all the other port position vectors are connected
        # self.component_ports[0] = None #!self.component_ports[0]

        """
        The material attribute will be used to determinat the rougthness of the internal surface of the Pipe, and this
        rougthness will be used inside the colebrook correlation, co calculate the Darcy friction factor 'f'.
        """
        self.material: material.Material = material.Material()

        #! This attirbute is only used in some cases, and only in some classes, like the ones in
        #! which the cross_section_area is the same for all ports (Like the Pipe class,
        #! the T_connector class, and the elbow class)
        self.cross_section_area: float = 0

        """
        # These variables define the size of both ports from the component and the ports that are added to it
        #self.inputs_quantity: int = inputs_quantity
        #self.outputs_quantity: int = outputs_quantity

        #The multiplications below should create a list with the element being multiplied in each element

        # These inputs and outputs are the ones that are part of the Component instance
            #The objects inside these lists should be instances from the port class
        #self.component_input_ports: list =  [] #[Port.generic_port()] * self.inputs_quantity
        #self.component_output_ports: list = [] #[Port.generic_port()] * self.outputs_quantity

        # These inputs and outputs are the ones thar are being joined to the Component instance
            #Should the objects inside these lists should be instances from the port class, like the input_ports and output_ports?
        """

        """
        These indexes would be used if the user manually whats to make the joint
        I have to code the part that manages multiple additions to a Component instance
             The automatic addition should be capable to make the forst addition and, in the second addition from one component to another,
             it should make it so that the second ports avaliable in both objects are "joined".

             In this way, for the operation between Component instancies that follows: A + B,
             the automatic order would be that the first output port from A is "plugged" in the first port of B,
             If the A + B is repeated, the second port of A should be "plugged" in the second port of B.
             If, again, the A + B is repeated, the third port of A should be "plugged" in the third port of B

             The automatic addition would only be activated if the user don't especifies the indexes for both Components instances
             addition.

        I need to figure a way that the user could use, if wanted, the manual additon to add the ports of different components in a non
        automatic order.
        """

        # These are the arguments that define the position of the joint between the components instances ports
        self.i = 0
        self.j = 0

        """
        Above is the variable that counts how many times the add was used

        This variable serves to determine if the creation of a new mosaic is needed (the mosaic object is created only if its the first
        time that a component is being added to another).

        In the case that a mosaic is added to a component, the Mosaic should be able to handle with special match case for the addition
        """

        self.add_counter: int = 0

        self.plugged_inputs: list[
            UUID
        ] = []  # [Port.generic_port()] * self.inputs_quantity
        self.plugged_outputs: list[
            UUID
        ] = []  # [Port.generic_port()] * self.outputs_quantity

        # if (len(self.plugged_inputs)) or ():

        match self.i, self.j:
            case i, j if (i <= self.port_quantity - 1) and (
                j <= self.port_quantity - 1
            ):
                pass

            case _:
                raise ValueError(
                    f"The component ({__name__ = }) has no ports avaliable to be plugged to other component or current"
                )

        """
        match self.inputs_quantity, self.outputs_quantity:
            case None:
                pass
            case 0, 0:
                pass

            case x, y if x>=0 and y>=0:
                self.component_input_ports = [Port.generic_port(self.id)] * self.inputs_quantity
                self.plugged_inputs = [Port.generic_port(self.id)] * self.inputs_quantity

                self.component_output_ports = [Port.generic_port(self.id)] * self.outputs_quantity
                self.plugged_outputs = [Port.generic_port(self.id)] * self.outputs_quantity

                self.all_component_ports = self.component_input_ports + self.component_output_ports

            case x,y if x != None:
                self.component_input_ports = [Port.generic_port(self.id)] * self.inputs_quantity
                self.plugged_inputs = [Port.generic_port(self.id)] * self.inputs_quantity

                self.all_component_ports = self.component_input_ports + self.component_output_ports
        
            case x,y if y != None:
                self.component_output_ports = [Port.generic_port(self.id)] * self.outputs_quantity
                self.plugged_outputs = [Port.generic_port(self.id)] * self.outputs_quantity
                
                self.all_component_ports = self.component_input_ports + self.component_output_ports

        # THis list will be used in the find_mosaic method to shorten the quantity of the for loops used in the search
        self.all_component_ports = self.component_input_ports + self.component_output_ports
        """

        if self.location is not None and isinstance(self.location, tuple) is False:
            # The location is made a tuple so that it can't be changed once it's defined as some Mosaics ID.
            self.location = self.location

        """
        Those are some of the Component child classes that i have to create
        # Connector category of component
        if category == 'connector':

        # Measurimg Instrument category of component
        if category == 'measure_instrument':

        # Pump category of component
        if category == 'pump':
        """

        #! Each and every method inside the Component class (or inside its child classes) that
        #! that returns an equation (sy.Basicm, sy.Equation, sy.Ne) must have a statment that
        #! assign the equation (the one being returned by the method) to an ULID, inside the
        #! equations dict below

        # // This equation dict maps all the equations of a Component object, and assign an ULID to each one
        self.equations_dict: dict[ULID, sy.Basic] = {}

        # this must be the last command inside the __init__ method, read the Port Class __init__ to see more information
        Component.component_objects.append(self)

    def __repr__(self) -> str:
        return f"{self.id}"

    def __str__(self) -> str:
        return f"{self.id}"

    def __format__(self) -> str:
        return f"{self}"

    def relational_status(self):
        """
        This method should return two values, Fixed or unfixed.

        This method will be used to determiante if this component is fixed to another component, and if it is part
        of a mosaic.
        """

        def ports_status() -> Union[bool, ValueError]:
            """
            This funtion returns True if the component self has a port joined to another port inside a moisac.
            if the self component is not joined to another port, this function returns false.
            """

            for m in mosaic.Mosaic.mosaic_objects:
                for joint in m.joints_list:
                    for id in joint:
                        for port in self.component_ports:
                            if id == port.id:  # type: ignore
                                return True

            # If the for loop never returns a value, the else block below will be triggered

            else:
                return False

        match self.location, ports_status():
            case (
                loc,
                True,
            ) if loc != None:
                return "fixed"

                # case loc, False, if loc != None:
                #     raise ValueError(f'\nThis scenario must be impossible')
                """
                This scenario should be impossible because if a component has a location != None, some of its ports
                should belong in a joint inside a mosaic
                """

            # * Those tow cases where missing in the first implementation of this part
            case None, True:
                raise ValueError(f"\nThis scenario must be impossible")
                """
                This scenario should be impossible because if a component has a location == None and some of its ports
                is registered inside a joint of some mosaic, said component should have the location != None, and the
                location must be set as the mosaic in which the joint is registered inside a joint.

                Maybe, in this case, i could correct the location issue, and set the self.location as the correct one,
                but this apparent fix could hide errors inside other parts of the code, so i should just fix the root of
                the problem, and do not apply this solution.
                """

            case None, False:
                return "unfixed"

            case (
                loc,
                False,
            ) if loc != None:
                return "unfixed"

            case _:
                # print(f'\n -----------{self.id =}-----------')
                # print(f'\n{self.location = }')

                raise ValueError

    def upload_all_comp_ports_list(self):
        """
        As for every time that some of the ports inside a component are modified, or some of the list of ports that the
        component owns are modified, the all_components_ports list must be updated to have all the ports with the actual
        values in the place of the older
        """

    """
    
    Preciso definir e restringir o usuario a escolher quais vão ser os tipos básciso de componentes
    que podem ser escolhidos.

    Eu pensei em fazer isso com letras das iniciais de cada tipo. Ex: v = valve, p = pipe, d = duct, c = connector,
    mi = measure instrument.
        
    """

    def log_self(self):
        """This method will be used to print the necessary variables about some component"""
        pass

    # mosaic.Mosaic | None
    def __add__(self, other) -> None | mosaic.Mosaic:
        # This method is analogous to the older DownstreamPlug method

        """
        TODO:
        Como a classe Mosaico é a responsável por fiscalizar o comportamento das demais classes, toda essa
        parte de verificação deve ficar dentro da classe Mosaic, por que isso não faz parte do escopo da
        classe Component.

        Por isso, em cada uma das operações, seja a criação de uma junta (com adição ou não), a permissão
        para criar uma junta e a localização de um determinado objeto, a classe mosaico deve ser chamada.

        Preciso passar todos os métodos de __add__ da Component para a classe Mosaic.
        Depois disso, eu preciso chamar esses métodos em Component para usar eles
        """

        match self.i, self.j:
            case i, j if (i <= self.port_quantity - 1) and (
                j <= self.port_quantity - 1
            ):
                pass

            case _:
                raise ValueError(
                    f"The component ({__name__ = }) has no ports avaliable to be plugged to other component or current"
                )

        # this is used to force the user to let one port of the component as an inlet or outlet, if the others are of the
        # opposite type (if all of them are inputs, at leat one should be an output, and vice versa).
        if (len(self.plugged_inputs) >= self.port_quantity) or (
            len(self.plugged_outputs) >= self.port_quantity
        ):
            raise ValueError(
                f"The component needs to have at least one output or input current"
            )

        else:
            pass

        size = len(mosaic.Mosaic.mosaic_objects)

        match size:
            # Checking if the creation of a mosaic is needed
            case 0:
                # Checking if the other object being added is an instance of Component
                if (
                    isinstance(other, Component) == True
                    and logic_utils.has_generic_ports(other) == False
                    and logic_utils.has_generic_ports(self) == False
                ):
                    new_mosaic = mosaic.Mosaic(joints_list=[])

                    # those locations are being made a tuple so that they can't be changed
                    self.location = new_mosaic.id
                    other.location = new_mosaic.id

                    # Pluging the other.comp_input in the self.pluged_outputs
                    # self.plugged_outputs[self.i]=(other.component_ports[self.j].id)

                    # Pluging the self.comp_output in the other.plggued_inputs
                    # other.plugged_inputs[self.j]=(self.component_ports[self.i])

                    # self.position_solver(mosaic_count = size, self_coords = False, other_coords = False)
                    # other.position_solver(mosaic_count = size, self_coords = False, other_coords = False)

                    # The joint will store the ports id and both of the components id
                    new_mosaic.joint(
                        self.component_ports[self.i].id,
                        other.component_ports[self.j].id,  # type: ignore
                    )

                    self.i += 1
                    self.j += 1
                    self.add_counter += 1

                    other.i += 1
                    other.j += 1
                    other.add_counter += 1

                    return new_mosaic

                elif isinstance(other, current.Current) == True:
                    # The index is zero because a current can never be plugged into the outputs of any coponent
                    # logic_utils.has_available_ports(self)[0] > 0 and
                    if other.location == None:
                        new_mosaic = mosaic.Mosaic(joints_list=[])

                        new_mosaic.joint(self.component_ports[self.i].id, other.id)  # type: ignore

                        # print(f'\n* The {self.i = } port from the {self} is receaving the {other} as its current')

                        # * place_holder_composition = thermo.Chemical(
                        # *    str(other.composition.ID),
                        # *   T=float(other.composition.T),
                        # *  P=float(other.composition.P),
                        # *)

                        place_holder_composition = other.composition

                        place_holder_cur = current.Current(place_holder_composition)

                        self.component_ports[
                            self.i
                        ].local_current.composition = place_holder_cur.composition
                        self.component_ports[
                            self.i
                        ].local_current.pressure = place_holder_cur.pressure
                        self.component_ports[
                            self.i
                        ].local_current.temperature = place_holder_cur.temperature

                        self.component_ports[
                            self.i
                        ].local_current.composition.P = place_holder_cur.composition.P

                        self.component_ports[
                            self.i
                        ].local_current.composition.T = place_holder_cur.composition.T

                        self.component_ports[
                            self.i
                        ].local_current.composition.ID = place_holder_cur.composition.ID

                        del place_holder_composition
                        del place_holder_cur

                        other.location = (self.id, self.component_ports[self.i].id)

                        self.location = new_mosaic.id

                        self.i += 1
                        self.j += 1
                        self.add_counter += 1

                        return new_mosaic

                    else:
                        raise ValueError(
                            f"\nthe control flow does not enter the if statement above\n"
                        )

                try:
                    assert isinstance(other, Component) or isinstance(
                        other, current.Current
                    )
                except AssertionError:
                    raise TypeError(
                        f"The object being added is from a type ({type(other) = }) not alowed to add to a Component"
                    )

            case size if size != 0:
                """
                This block inside the if statement is used to "find" if one of the components being added have some of its ports registrated
                in the joints list of some of the existing Mosaic objects created.

                Than, if one of them have some of its ports registrated in the joints_list of some mosaic object, this statement will make the
                joint method to be aplied for both elements, and the joint to be made in the mosaic that already have some of the ports from one or
                both the Components being added.

                If both components are from different mosaic objects, that means that some of the ports of the self component belong in the m_1 
                mosaic, and some of the ports from other component belong in m_2 mosaic (read the * note to clarify this case). In this case, the code
                must raise an error telling the user that components from two different mosaics can not be added. If the clone feature (described bellow)
                is implemented, the error raised should indicate that the user could create a clone of one of the components, so that the clone can be
                added to the component in the other mosaic.
                
                *
                May it be registered that the ports of one component must only belong in one mosaic, and not to multiple mosaics. This is because 
                each component object is unique, and made to be used in just one mosaic. If the user intend to use one component in multiple mosaic
                i'm planning to create a feature to "clone" one speccific object of component, so that the clone have the exact samme propperties as
                the original. This way, the clone can be used in a different mosaic as the original object without the user having to manually declare
                the creation of another component with the same propperties as the original component object.
                
                The first task is to find to whitch mosaic each one of the components belong.
                After this, we need to check if the components belong to the same mosaic
                """
                """
                        This statement will be used to check if:

                        1 - self has avaliable ports
                        2 - other has avaliable ports
                        3 - other has ports in only one mosaic
                            This means that all the ports location must return a id of just one mosaic, or it must return that
                            a port has no mosaic
                        4 - self has ports only in one mosaic
                            This means that all the ports location must return a id of just one mosaic, or it must return that
                           a port has no mosaic
                        """

                # Checking if the other object being added is an instance of Component
                match isinstance(other, Component):
                    case True if (
                        (not logic_utils.has_generic_ports(other))
                        and (not logic_utils.has_generic_ports(self))
                    ):
                        # if other.location == None and self.location != None or self.location == None and other.location != None:
                        match (
                            self.location,
                            other.location,
                        ):  # , logic_utils.has_available_ports(other)[0], logic_utils.has_available_ports(self)[1]:
                            case (
                                None,
                                None,
                            ):  # other_plug_input_ports, self_plug_out_ports) if (other_plug_input_ports > 0) and (self_plug_out_ports > 0):
                                new_mosaic = mosaic.Mosaic(joints_list=[])

                                if other.location is None:
                                    other.location = new_mosaic.id

                                if self.location is None:
                                    self.location = new_mosaic.id

                                # Pluging the other.comp_input in the self.pluged_outputs
                                # self.plugged_outputs[self.i]=(other.component_ports[self.j])

                                # Pluging the self.comp_output in the other.plggued_inputs
                                # other.plugged_inputs[self.j]=(self.component_ports[self.i])

                                # self.position_solver(mosaic_count = size, self_coords = False, other_coords = False)
                                # other.position_solver(mosaic_count = size, self_coords = False, other_coords = False)

                                new_mosaic.joint(
                                    self.component_ports[self.i].id,
                                    other.component_ports[other.i].id,
                                )

                                self.i += 1
                                self.j += 1
                                self.add_counter += 1

                                other.i += 1
                                other.j += 1
                                other.add_counter += 1

                                return new_mosaic

                            case (None, other_loc) if (
                                other_loc is not None
                            ):  # , other_plug_input_ports, self_plug_out_ports) if (other_loc != None and \
                                # other_plug_input_ports > 0 and \
                                # self_plug_out_ports >0):

                                mos: mosaic.Mosaic | None = None
                                if mos is not None:
                                    self.location = mos.id

                                for m in mosaic.Mosaic.mosaic_objects:
                                    if m.id == self.location:
                                        mos = m

                                        m.joint(
                                            self.component_ports[self.i].id,
                                            other.component_ports[other.i].id,
                                        )

                                self.i += 1
                                self.j += 1
                                self.add_counter += 1

                                other.i += 1
                                other.j += 1
                                other.add_counter += 1

                                return mos

                            case (self_loc, None) if (
                                self_loc is not None
                            ):  # , other_plug_input_ports, self_plug_out_ports) if (self_loc != None and \
                                # other_plug_input_ports > 0 and \
                                # self_plug_out_ports >0):

                                mo: mosaic.Mosaic | None = None
                                for m in mosaic.Mosaic.mosaic_objects:
                                    if m.id == self.location:
                                        mo = m
                                        m.joint(
                                            self.component_ports[self.i].id,
                                            other.component_ports[other.i].id,
                                        )

                                if (
                                    (mo is not None)
                                    and (mo != None)
                                    and (mo.id == self.location)
                                ):
                                    other.location = mo.id

                                self.i += 1
                                self.j += 1
                                self.add_counter += 1

                                other.i += 1
                                other.j += 1
                                other.add_counter += 1

                                return mo

                            case (
                                s_loc,
                                o_loc,
                            ) if s_loc == o_loc and s_loc != None:  # \
                                #:#, other_plug_input_ports, self_plug_out_ports)  and \
                                # other_plug_input_ports > 0 and \
                                # self_plug_out_ports >0):

                                mo: Union[mosaic.Mosaic, None] = None

                                for m in mosaic.Mosaic.mosaic_objects:
                                    if m.id == self.location:
                                        mo = m
                                        m.joint(
                                            self.component_ports[self.i].id,
                                            other.component_ports[other.i].id,
                                        )

                                    # else:
                                    #     raise NotImplementedError(f'Generic Error on __add__ from Component class')

                                self.i += 1
                                self.j += 1
                                self.add_counter += 1

                                other.i += 1
                                other.j += 1
                                other.add_counter += 1

                                return mo

                            case (se_loc, ot_loc) if (
                                (ot_loc != None)
                                and (se_loc != None)
                                and (ot_loc != se_loc)
                            ):  # , other_plug_input_ports, self_plug_out_ports) if (ot_loc != None and \
                                # se_loc != None and \
                                # ot_loc != se_loc and \
                                # other_plug_input_ports > 0 and \
                                # self_plug_out_ports > 0 ):
                                raise ValueError(
                                    f"\nTwo Components with different locations({self.location = } , {other.location = }) can't be added"
                                )

                                """
                                Revisar essa parte, acho que posso ter errado ou esquecido alguma coisa!!
                                """
                                # # Pluging the other.comp_input in the self.pluged_outputs
                                # self.plugged_outputs[self.i]=(other.component_input_ports[self.j].id)

                                # # Pluging the self.comp_output in the other.plggued_inputs
                                # other.plugged_inputs[self.j]=(self.component_output_ports[self.i].id)

                            case _:
                                print(
                                    f"\n\n{self.location = }, \n{other.location = }, \n{logic_utils.has_available_ports(other)[0] = }, \n{logic_utils.has_available_ports(self)[1] = }"
                                )
                                raise ValueError(
                                    f"No patern matching, something must be whrong"
                                )

                    # The index is zero because a current can never be plugged into the outputs of any coponent
                    # logic_utils.has_available_ports(self)[0] > 0 and \
                    case False if (
                        isinstance(other, current.Current) == True
                        and other.location == None
                    ):
                        if self.location == None:
                            new_mosaic = mosaic.Mosaic(joints_list=[])

                            new_mosaic.joint(self.component_ports[self.i].id, other.id)  # type: ignore

                            # * place_holder_composition = thermo.Chemical(
                            # *     str(other.composition.ID),
                            # *     T=float(other.composition.T),
                            # *     P=float(other.composition.P),
                            # * )

                            place_holder_composition = other.composition

                            place_holder_cur = current.Current(place_holder_composition)

                            self.component_ports[
                                self.i
                            ].local_current.composition = place_holder_cur.composition
                            self.component_ports[
                                self.i
                            ].local_current.pressure = place_holder_cur.pressure
                            self.component_ports[
                                self.i
                            ].local_current.temperature = place_holder_cur.temperature

                            self.component_ports[
                                self.i
                            ].local_current.composition.P = (
                                place_holder_cur.composition.P
                            )

                            self.component_ports[
                                self.i
                            ].local_current.composition.T = (
                                place_holder_cur.composition.T
                            )

                            self.component_ports[
                                self.i
                            ].local_current.composition.ID = (
                                place_holder_cur.composition.ID
                            )

                            del place_holder_composition
                            del place_holder_cur

                            other.location = (self.id, self.component_ports[self.i].id)

                            self.i += 1
                            self.j += 1
                            self.add_counter += 1

                            return new_mosaic

                        elif self.location != None:
                            mo: mosaic.Mosaic | None = None

                            for m in mosaic.Mosaic.mosaic_objects:
                                if m.id == self.location:
                                    mo = m

                            if mo is not None:
                                mo.joint(self.component_ports[self.i].id, other.id)

                            # place_holder_composition = thermo.Chemical(
                            #     str(other.composition.ID),
                            #     T=float(other.composition.T),
                            #     P=float(other.composition.P),
                            # )

                            place_holder_composition = other.composition

                            place_holder_cur = current.Current(place_holder_composition)

                            for port in self.component_ports:
                                port.local_current.composition = other.composition

                            self.component_ports[
                                self.i
                            ].local_current.composition = place_holder_cur.composition

                            self.component_ports[
                                self.i
                            ].local_current.pressure = place_holder_cur.pressure

                            self.component_ports[
                                self.i
                            ].local_current.temperature = place_holder_cur.temperature

                            self.component_ports[
                                self.i
                            ].local_current.composition.P = (
                                place_holder_cur.composition.P
                            )

                            self.component_ports[
                                self.i
                            ].local_current.composition.T = (
                                place_holder_cur.composition.T
                            )

                            self.component_ports[
                                self.i
                            ].local_current.composition.ID = (
                                place_holder_cur.composition.ID
                            )

                            del place_holder_composition
                            del place_holder_cur

                            other.location = (self.id, self.component_ports[self.i].id)

                            self.i += 1
                            self.j += 1
                            self.add_counter += 1

                            return mo

                        else:
                            raise NotImplementedError

        # self.i += 1
        # self.j += 1
        # self.add_counter += 1

    def __radd__(self, other: current.Current):  # -> mosaic.Mosaic | None
        """
        TODO:
        Como a classe Mosaico é a responsável por fiscalizar o comportamento das demais classes, toda essa
        parte de verificação deve ficar dentro da classe Mosaic, por que isso não faz parte do escopo da
        classe Component.

        Por isso, em cada uma das operações, seja a criação de uma junta (com adição ou não), a permissão
        para criar uma junta e a localização de um determinado objeto, a classe mosaico deve ser chamada.

        Preciso passar todos os métodos de __add__ da Component para a classe Mosaic.
        Depois disso, eu preciso chamar esses métodos em Component para usar eles
        """
        try:
            assert (
                isinstance(other, Component) == True
                or isinstance(other, current.Current) == True
            )
            pass
        except AssertionError as a:
            raise TypeError(
                f"The object being added is from a type ({type(other) = }) not alowed to add to a Component"
            )

        match self.i, self.j:
            case i, j if (i <= self.port_quantity - 1) and (
                j <= self.port_quantity - 1
            ):
                pass

            case _:
                raise ValueError(
                    f"The component ({__name__ = }) has no ports avaliable to be plugged to other component or current"
                )

        # this is used to force the user to let one port of the component as an inlet or outlet, if the others are of the
        # opposite type (if all of them are inputs, at leat one should be an output, and vice versa).
        if (len(self.plugged_inputs) >= self.port_quantity) or (
            len(self.plugged_outputs) >= self.port_quantity
        ):
            raise ValueError(
                f"The component needs to have at least one output or input current"
            )

        else:
            pass

        size: int = len(mosaic.Mosaic.mosaic_objects)

        raise DeprecationWarning("""This method is deprecated, and it needs to be refactored. Some 
                                 commands have wrong variable names and commands, in a way that the
                                 entire method can cause serious problems""")

        match size:
            # Checking if the creation of a mosaic is needed
            case 0:
                if isinstance(other, current.Current) == True:
                    # The index is zero because a current can never be plugged into the outputs of any component
                    # logic_utils.has_available_ports(self)[0] > 0 and
                    #

                    print(f"\n\n{other = }")
                    print(f"{logic_utils.has_generic_ports(self) = }")

                    if (
                        other.location == None
                        and logic_utils.has_generic_ports(self) == False
                    ):
                        new_mosaic = mosaic.Mosaic(joints_list=[])

                        print(f"\n\ncreation of the new_mosaic {new_mosaic.id = }")

                        new_mosaic.joint(self.component_ports[self.i].id, other.id)

                        assert self.component_ports[self.i] is not None

                        self.component_ports[self.i].local_current = other

                        other.location = self.component_ports[self.i].id

                        # print(f'\n\n-> {self.i = }')

                        self.plugged_inputs.append(other.id)

                        self.i += 1
                        self.j += 1
                        self.add_counter += 1

                        return new_mosaic

            case size if size != 0:
                """
                This block inside the if statement is used to "find" if one of the components being added have some of its ports registrated
                in the joints list of some of the existing Mosaic objects created.

                Than, if one of them have some of its ports registrated in the joints_list of some mosaic object, this statement will make the
                joint method to be aplied for both elements, and the joint to be made in the mosaic that already have some of the ports from one or
                both the Components being added.

                If both components are from different mosaic objects, that means that some of the ports of the self component belong in the m_1 
                mosaic, and some of the ports from other component belong in m_2 mosaic (read the * note to clarify this case). In this case, the code
                must raise an error telling the user that components from two different mosaics can not be added. If the clone feature (described bellow)
                is implemented, the error raised should indicate that the user could create a clone of one of the components, so that the clone can be
                added to the component in the other mosaic.
                
                *
                May it be registered that the ports of one component must only belong in one mosaic, and not to multiple mosaics. This is because 
                each component object is unique, and made to be used in just one mosaic. If the user intend to use one component in multiple mosaic
                i'm planning to create a feature to "clone" one speccific object of component, so that the clone have the exact samme propperties as
                the original. This way, the clone can be used in a different mosaic as the original object without the user having to manually declare
                the creation of another component with the same propperties as the original component object.
                
                The first task is to find to whitch mosaic each one of the components belong.
                After this, we need to check if the components belong to the same mosaic
                """
                """
                        This statement will be used to check if:

                        1 - self has avaliable ports
                        2 - other has avaliable ports
                        3 - other has ports in only one mosaic
                            This means that all the ports location must return a id of just one mosaic, or it must return that
                            a port has no mosaic
                        4 - self has ports only in one mosaic
                            This means that all the ports location must return a id of just one mosaic, or it must return that
                           a port has no mosaic
                        """

                # Checking if the other object being added is an instance of Component
                match isinstance(other, Component):
                    case False if (
                        isinstance(other, current.Current) == True
                        and other.location == None
                    ):
                        if self.location != None:
                            mo: mosaic.Mosaic | None = None
                            for m in mosaic.Mosaic.mosaic_objects:
                                if m.id == self.location:
                                    mo = m
                                    m.joint(self.plugged_inputs[self.i].id, other.id)

                            if mo is not None:
                                self.plugged_inputs[self.i] = other.id

                                self.component_ports[self.i].local_current = other

                                other.location = (
                                    self.id,
                                    self.component_ports[self.i].id,
                                )

                                self.i += 1
                                self.j += 1
                                self.add_counter += 1

                                return mo

                        elif self.location == None:
                            new_mosaic = mosaic.Mosaic(joints_list=[])

                            other.location = (self.id, self.component_ports[self.i].id)

                            self.plugged_inputs[self.i] = other.id

                            self.component_ports[self.i].local_current = other

                            self.location = new_mosaic.id

                            new_mosaic.joint(self.plugged_inputs[self.i].id, other.id)

                            self.i += 1
                            self.j += 1
                            self.add_counter += 1

                            return new_mosaic

                    # logic_utils.has_available_ports(self)[0] > 0 and \
                    case False if (
                        isinstance(other, current.Current) == True
                        and other.location != None
                    ):
                        raise ValueError(f"""The current ({other.id = } already has a location ({other.location = }, and can\'t)
                                         be added to another component or arranged to a new mosaic.""")

                    case _:
                        raise ValueError(
                            f"\nThe {logic_utils.has_available_ports(self)[0] = } and {other.location = }"
                        )

    def mass_conservation(self) -> sy.Basic:
        """
        This method is the updated one, that will be used inside the Mosaic to help the Newton-Raphson method of
        mass-energy coupled solving for the system
        """

        self.variables_list: list = []

        for port_index, port in enumerate(self.component_ports):
            assert port is not None

            # * In this case, the component does not have a root port
            if len(self.component_ports) == self.port_quantity:
                id: UUID = port.id

                # print(f'\n\n{self.id = }')
                # print(f'\n\n{port.id = }')
                # print(f'\n{port.local_current.id = }')

                str_name = str(id)
                id = sy.symbols(str_name)

                self.variables_list.append(id)

            # * In this case, the component does have a root port (i.e. the T_connector objects)
            if (len(self.component_ports) != self.port_quantity) and (
                (port_index != len(self.component_ports) - 1)
                or (port != self.component_ports[-1])
            ):
                # print(f"\n{(port != self.component_ports[-1]) = }")
                # print(f"\n{(port_index != -1) = }")
                # print(f"{len(self.component_ports) = }")
                # print(f"{self.port_quantity = }")
                # print(f"\n{self.id =}\n")
                # print(f"\n{type(self) =}\n")

                id: UUID = port.id

                # print(f'\n\n{self.id = }')
                # print(f'\n\n{port.id = }')
                # print(f'\n{port.local_current.id = }')

                str_name = str(id)
                id = sy.symbols(str_name)

                self.variables_list.append(id)

        # if isinstance(self, T_connector):
        #     print(f"\n{len(self.variables_list) = }")
        #     print(f"\n{self.variables_list = }")

        current_sum = sy.Add(*self.variables_list)

        self.mass_conservation_eq: Equation = sy.Eq(current_sum, 0)

        assert self.mass_conservation_eq is not None
        assert isinstance(self.mass_conservation_eq, sy.Basic)

        if self.mass_conservation_eq not in self.equations_dict.values():
            self.equations_dict[ULID()] = self.mass_conservation_eq

        if self.mass_conservation_eq in self.equations_dict.values():
            ul_id_associated_with_self_mass_eq = [
                ul_id
                for ul_id, equation in self.equations_dict.items()
                if equation == self.mass_conservation_eq
            ]

            assert len(ul_id_associated_with_self_mass_eq) == 1

            return self.equations_dict[ul_id_associated_with_self_mass_eq[0]]

        return self.mass_conservation_eq

    """
    def head_pressure_loss_eq(self, arg) -> sy.Basic:
        # raise NotImplementedError(""" """#This method needs to be overwritten by its child classes
        #                           head_pressure_loss_methods! If this method is being called, it
        #                           means that some class hasn\'t had its head_pressure_loss
        #                           method propperly created, or it hasn\'t overwritten the
        #                           parent HPL method from its parent, or grandparent
        #                           (in this case, the component Class is the parent/grandparent
        #                           class))
        
    """

    def position_solver(self) -> None:
        """
        This method will use the orientation and length as input to find the 3D spatial positions
        of all the ports of the component using vectorial algebra, so that the heigt of the inlet
        and outlet ports of a component can be fed to the energy equation.

        if a component already has the definitions of some of its ports, those ports will serve as
        a base to the calculation of the other ports coordinates.

        The port position in space can not be directly manipulated by the user, as this could cause
        errors and inconsistencies inside the software.

        Each class that inherits from the Component Class needs to declare this method, because
        if it is not declared, the error below will be raised
        """

        raise NotImplementedError(
            """\n The object you called ({self = })the position solver does not have the position
            solver implemented"""
        )

    # // this part is temporarily deativated

    def energy_conservation(self) -> None:
        raise NotImplementedError("""This method should be overwritten by the energy_conservation
            method inside each one of its child classes""")

    def reynolds(self, port_id: MajorPortSelection | MinorPortSelection) -> float | int:
        """
        This method will be used to calculate the reynolds number in one of the ports os the component

        In a Pipe, the reynolds number will be calculated in one of its ports

        In components with diferent numbers of input and output ports, i have to think of a strategy to calculate the
        velocity in one place, but having the equation accounting for the mechanical energy on the other ones.
        """
        port_uuid_map: dict[UUID, Port] = {port.id: port for port in Port.port_objects}

        # print(f"\n\nReynolds: {port_id = }\n")

        chosen_port: Port = port_uuid_map[port_id]

        # print(
        #     f"\n{round(chosen_port.local_current.velocity, 6) = }\
        #     \n{round(chosen_port.local_current.ab_viscosity(), 6) = }\
        #     \n{round(chosen_port.local_current.density(), 2) = }\
        #     \n{round(chosen_port.characteristic_length, 2) = }"
        # )
        if (
            (
                (chosen_port.local_current.volume_flow is not None)
                and (chosen_port.local_current.volume_flow != 0)
            )
            and (chosen_port.local_current.velocity is None)
            or (chosen_port.local_current.velocity == 0)
            or (chosen_port.local_current.mass_flow is None)
        ):
            chosen_port.local_current.velocity = (
                chosen_port.local_current.volume_flow / chosen_port.cross_sect_area()
            )  # type: ignore
        """
        rounded_vel: float = round(chosen_port.local_current.velocity, 6)  # type: ignore
        rounded_abs_visc: float = round(chosen_port.local_current.ab_viscosity(), 9)
        rounded_density: float = round(chosen_port.local_current.density(), 2)
        rounded_length: float = round(chosen_port.characteristic_length, 2)

        Re: float = (rounded_length * rounded_vel * rounded_density) / rounded_abs_visc

        return round(Re, 2)
        """

        # // re_p1_1 = ((4 * rho) / (mi * pi * d_p1_1)) * p1_1_q , this formula comes from
        # //he sympy_tests_file
        local_rho = chosen_port.local_current.density()
        local_mi = chosen_port.local_current.ab_viscosity()

        #! I also need to change the returned type anotation for the resynolds method
        # raise NotImplemented(
        #     "I need to set the name os the volume flow variable correctly"
        # )
        local_d = chosen_port.characteristic_length
        local_q = sy.symbols(str(chosen_port.id))

        Re = ((4 * local_rho) / (local_mi * pi * local_d)) * local_q

        return Re

        # # (position, UUID) == True):
        # for port in Port.port_objects:
        #     if port.id == port_id and port in self.component_ports:
        #         print(f"\n{self.id = }\n")
        #         print(f"\n{port.id = }\n")
        #         print(f"{port.local_current.velocity = }")
        #         print(f"{port.local_current.mass_flow = }")
        #         print(f"{port.local_current.volume_flow = }")

        #     if (port.id == port_id and port in self.component_ports) and (
        #         (port.local_current.velocity is not None)
        #         and (
        #             (port.local_current.mass_flow is not None)
        #             or (port.local_current.volume_flow is not None)
        #         )
        #     ):
        #         rounded_vel: float = round(port.local_current.velocity, 6)
        #         rounded_abs_visc: float = round(port.local_current.ab_viscosity(), 6)
        #         rounded_density: float = round(port.local_current.density(), 2)
        #         rounded_length: float = round(port.characteristic_length, 2)

        #         try:
        #             Re: float = (
        #                 rounded_length * rounded_vel * rounded_density
        #             ) / rounded_abs_visc

        #         except ValueError as VE:
        #             numeric_error_message: str = r"""This port is laking values to calculate the Reynolds number,
        #                                             please run the mass conservation method"""

        #             raise ValueError(f"{numeric_error_message}")

        #         except TypeError as TE:
        #             raise TypeError(
        #                 f"\n\nSomething went wrong with some object type inside the reynolds calculation"
        #             )

        #         return round(Re, 2)

        # else:
        #     # If the for loop never finds the port, the ValueError will be raised

        #     raise ValueError("Port not found")

    def darcy_perssure_drop_coefficient(
        self, port_id: MajorPortSelection
    ) -> sy.Piecewise:
        """
        This method will use the reynolds number, the rougthness of the inner surface of the tube and the inner diameter
        to calculate darcy pressure drop factor known as 'f'
        """

        # // assert self.material.rougthnes is not None, only if f is a function
        # // of self.material.rougthnes

        # // Essa equação veio do livro: WHITE, Frank M. Mecânica dos fluidos.
        # // 8. ed. Porto Alegre: ArtMed, 2018. E-book. p.357. ISBN 9788580556070.
        # // Disponível em: https://integrada.minhabiblioteca.com.br/reader/books/9788580556070/.
        # // Acesso em: 17 out. 2025.
        # // Na biblioteca digital de UNILA
        blasius = 0.316 / (self.reynolds(port_id) ** (1 / 4))

        f_laminar = 64 / self.reynolds(port_id)

        f = sy.Piecewise(
            (f_laminar, self.reynolds(port_id) < 2300),
            (
                0.002,
                sy.And(self.reynolds(port_id) > 2300, self.reynolds(port_id) <= 4000),
            ),
            (blasius, self.reynolds(port_id) > 4000),
        )

        return f

        """def f_initial_guess(port_id: MajorPortSelection) -> float:
            """
        # The function used to give the initial guess of the friction factor
        # is the Swamee-Jain equation
        """

            assert self.material.rougthnes is not None
            assert self.component_ports[0] is not None

            epsilon: float = self.material.rougthnes

            #! This part considers that all the diameters of the ports are equal inside a given component
            D: Union[float, int] = self.component_ports[0].characteristic_length

            f: float = (0.25) / (
                (
                    log(
                        ((epsilon) / (3.7 * D))
                        + ((5.74) / (self.reynolds(port_id=port_id) ** 0.9))
                    )
                )
                ** 2
            )

            return f

        Re: float | int | None = None

        D: float | int | None = None

        epsilon: float = self.material.rougthnes

        if port_id is None:
            # print(f'\n\nPosition is None')

            for port in self.component_ports:
                vel_list = []
                max_vel_port = None
                if port.local_current.velocity is not None:
                    vel_list.append(port.local_current.velocity)
                    # max_vel_port = port

            max_vel: list[float] = max(vel_list)
            for port in self.component_ports:
                if port.local_current.velocity == max_vel:
                    max_vel_port: Port = port

            Re = round(self.reynolds(port_id=max_vel_port.id), 2)

        if port_id is not None:
            # print(f'\n\nPosition is not None')

            if isinstance(port_id, UUID) is True:
                for port in Port.port_objects:
                    if port.id == port_id:
                        D = port.characteristic_length

                        Re = round(self.reynolds(port_id=port.id), 2)

        match Re:
            case (x,) if ((x is not None) and (x <= 2300.0)):
                return 64 / Re

            case y if ((y is not None) and (y > 2300.0) and (y < 4000.0)):
                raise NotImplementedError(
                    f"\nThis range of Reynolds number is out of the scope of this library, because this is the transient flow region"
                )

            case z if ((z is not None) and (z > 4000.0)):
                # this is the tolerance, and it means that this is the acceptable difference between new_f and f
                tol: float = 1e-6
                new_f: float = 0
                f: float = f_initial_guess(port_id=port_id)

                while (f - new_f) >= tol:
                    epsilon = self.material.rougthnes

                    # some math should happen here
                    new_f = sqrt(
                        1
                        / (
                            -2
                            * log(
                                (epsilon / (3.7 * D))  # type: ignore
                                + (2.51 / (self.reynolds(port_id=port_id) * sqrt(f)))
                            )
                        )
                    )

                    f = new_f

                return f

            case _:
                print(f"\n\n{Re = }")
                print(f"\n\n{port_id = }")
                raise NotImplementedError"""

    def E_constant(self, port_id: MajorPortSelection) -> float | int:
        """
        This constant is the one that will be used inside the bernoulloi equation, in the energy
        conservation method, as listed bellow:

        (P1/(rho*g)) + ((V1^2)/(2*g)) + (z1) + h_bomba = (P2/(rho*g)) + ((V2^2)/(2*g)) + (z2) + h_Loss

        A = pi * (R^2) -> D = 2R -> R = D/2

        A = (pi*(D^2))/4

        Q = V * A -> V = Q / A

        V = (4*(Q))/(pi*(D^2))

        P1 + ((16*(Q1^2)) / ((2*g)*(pi^2)*(D1^4))) + (z1) + h_bomba = ...

        E_constant = (16) / ((2*g)*(pi^2)*(D^4)))

        P1 + (E * (Q1^2)) + z1 + h_bomba = ...

        """

        e_constant: str = str(port_id) + "_e"

        e_constant = sy.symbols(e_constant)

        g = 9.81  # // measured in m/s^2

        # // This is the internal_diam inside the port_id recieved from outside of this method
        D: int | float = 0

        for port in Port.port_objects:
            if (
                (port.id == port_id)
                and (port.location == self.id)
                and (port in self.component_ports)
            ):
                assert port.characteristic_length is not None
                assert port.characteristic_length != 0

                D = port.characteristic_length

        assert D != 0

        E_eq = sy.Eq(e_constant, (16) / ((2 * g) * (pi**2) * (D**4)))

        E_equation_solved = sy.solve(E_eq, dict=True)

        # print(f"\n{E_equation_solved = }\n")
        numeric_e_constant: int | float = E_equation_solved[0][e_constant]

        # print(f"\n{numeric_e_constant = }\n")
        # print(f"\n{type(numeric_e_constant) = }\n")

        numeric_e_constant = float(numeric_e_constant)

        # print(f"\n{numeric_e_constant = }\n")
        # print(f"\n{type(numeric_e_constant) = }\n")

        # assert (numeric_e_constant is int) or (numeric_e_constant is float)

        # print(f"\n{self.id = }")
        # print(f"\n{numeric_e_constant = }")

        return numeric_e_constant


# MARK: MajorLossComponent
class MajorLossComponent(Component):
    """
    This class is the parent class of all the Components that have their head pressure loss as Major.

    i.e.: Pipes, Ducts, .

    Those classes will be childs from this class because all of them have their pressure loss calculated using
    the Darcy-Weisbach friction factor (f), the reynolds number and other parameters.
    """

    def B_constant(self, port_id: MajorPortSelection) -> sy.Basic:
        if hasattr(self, "KL_Dict") and callable(getattr(self, "KL_Dict")):
            del self.KL_Dict

        b_constant_name = str(self.id) + "_B"

        b_constant: str = str(self.id) + "_B"

        b_constant = sy.symbols(b_constant)

        f = self.darcy_perssure_drop_coefficient(port_id=port_id)

        # // nc.g = 9.81  # // mesaured in m/s^2

        assert f is not None

        b_eq = sy.Eq(
            b_constant,
            (f * self.length * 8) / ((self.internal_diam**5) * (pi**2) * nc.g),
        )

        # print(f"\n{sy.solve(b_eq, dict=True) = }\n")
        # print(f"{type(sy.solve(b_eq, dict=True)) = }\n")
        # print(f"{sy.solve(b_eq, dict=True)[0][B_constant] = }\n")

        #! self.b_cons_value = sy.solve(b_eq, dict=True)[0][b_constant]

        # print(f"\n{self.id = }")
        # print(f"\n{self.b_cons_value = }")

        return b_eq.rhs  #!self.b_cons_value

    def C_constant(self) -> None:
        raise er.ConduitForgeError("""\nThis method doesn\'t belong to this class, Try using the
                                   B_constant method, for local losses""")

    def energy_conservation(self, port_id: MajorPortSelection) -> None | sy.Basic:
        # // This part is a safety measure, and ensures that the inputs are passed to this method correctly

        assert len(self.component_ports) == self.port_quantity
        assert len(self.component_ports) == 2

        try:
            assert all(port.local_current is not None for port in self.component_ports)
        except AssertionError:
            for port_index, port in enumerate(self.component_ports):
                assert port is not None
                if port.local_current is None:
                    raise ValueError(
                        f"\n The {port.id = }, {port_index = } has a local_current = None"
                    )

                else:
                    pass

            raise ValueError(
                "\nThe mass conservation method need to be run before the energy conservation method"
            )

        try:
            for comp_port in self.component_ports:
                assert comp_port is not None
                for coord in comp_port.spacial_position.coords:
                    assert coord != None
        except:
            raise ValueError(f"\nSomething went wrong with the position solver")

        current_uuid_map: dict[UUID, current.Current] = {
            cur.id: cur for cur in current.Current.current_objects
        }

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

        component_uuid_map: dict[UUID, Component] = {
            comp.id: comp for comp in Component.component_objects
        }

        mosaic_uuid_map: dict[UUID, mosaic.Mosaic] = {
            mos.id: mos for mos in mosaic.Mosaic.mosaic_objects
        }

        # * given_port = port_uuid_map[port_id]
        given_port = self.component_ports[
            self.component_ports.index(port_uuid_map[port_id])
        ]
        # print(f"\n{given_port.id = }\n")
        # print(f"\n{given_port.location = }\n")
        # print(f"\n{self.id = }\n")

        assert (
            self.component_ports[self.component_ports.index(port_uuid_map[port_id])]
            in self.component_ports
        )
        assert (
            self.component_ports[
                self.component_ports.index(port_uuid_map[port_id])
            ].location
            == self.id
        )

        # // This is the UUID of the other port that will be used to compose the energy conservation
        # //eq, with the data colected from the port_id port (given port)
        # alternative_port: None | Port = None

        # for p in self.component_ports:
        #     if p is not given_port:
        #         alternative_port = p

        alternative_port = self.component_ports[
            1 - self.component_ports.index(port_uuid_map[port_id])
        ]

        assert (
            self.component_ports[1 - self.component_ports.index(port_uuid_map[port_id])]
            is not None
        )
        assert (
            self.component_ports[self.component_ports.index(port_uuid_map[port_id])]
            is not None
        )

        assert (
            self.component_ports[1 - self.component_ports.index(port_uuid_map[port_id])]
            in self.component_ports
        )
        assert (
            self.component_ports[
                1 - self.component_ports.index(port_uuid_map[port_id])
            ].location
            == self.id
        )

        # print(f"\n{alternative_port.id = }\n")
        # print(f"\n{alternative_port.location = }\n")
        assert (
            self.component_ports[
                self.component_ports.index(port_uuid_map[port_id])
            ].location
            == self.component_ports[
                1 - self.component_ports.index(port_uuid_map[port_id])
            ].location
        )

        try:
            assert (
                self.component_ports[
                    self.component_ports.index(port_uuid_map[port_id])
                ].local_current.composition.ID
                == self.component_ports[
                    1 - self.component_ports.index(port_uuid_map[port_id])
                ].local_current.composition.ID
            )

            assert (
                self.component_ports[
                    self.component_ports.index(port_uuid_map[port_id])
                ].local_current.composition.T
                == self.component_ports[
                    1 - self.component_ports.index(port_uuid_map[port_id])
                ].local_current.composition.T
            )

            # assert (
            #     self.component_ports[
            #         self.component_ports.index(port_uuid_map[port_id])
            #     ].local_current.composition.P
            #     == self.component_ports[
            #         1 - self.component_ports.index(port_uuid_map[port_id])
            #     ].local_current.composition.P
            # )

        except:
            print(
                f"\n{self.component_ports[
            self.component_ports.index(port_uuid_map[port_id])
        ] = }\n"
            )

            print(
                f"\n{self.component_ports[
                self.component_ports.index(port_uuid_map[port_id])
            ].local_current.composition.ID = }"
            )

            print(
                f"\n{self.component_ports[
            self.component_ports.index(port_uuid_map[port_id])
        ].local_current.composition.P = }\n"
            )

            print(
                f"\n{self.component_ports[
            self.component_ports.index(port_uuid_map[port_id])
        ].local_current.composition.T = }\n"
            )

            print(
                f"\n{self.component_ports[
            1 - self.component_ports.index(port_uuid_map[port_id])
        ] = }\n"
            )

            print(
                f"\n{self.component_ports[
                1 - self.component_ports.index(port_uuid_map[port_id])
            ].local_current.composition.ID = }"
            )

            print(
                f"\n{self.component_ports[
            1 - self.component_ports.index(port_uuid_map[port_id])
        ].local_current.composition.P = }\n"
            )
            print(
                f"\n{self.component_ports[
            1 - self.component_ports.index(port_uuid_map[port_id])
        ].local_current.composition.T = }\n"
            )

            raise AssertionError
        # try:
        #     assert (
        #         self.component_ports[
        # self.component_ports.index(port_uuid_map[port_id])
        # given_port.local_current.composition.P
        #         == alternative_port.local_current.composition.P
        #     )
        # except:
        #     print(f"\n{given_port = }\n")
        #     print(f"\n{given_port.location = }\n")

        #     print(f"\n{alternative_port = }\n")
        #     print(f"\n{alternative_port.location = }\n")

        #     print(f"\n{given_port.local_current.composition.P = }\n")
        #     print(f"\n{alternative_port.local_current.composition.P = }\n")

        #     raise ValueError

        assert (
            self.component_ports[
                self.component_ports.index(port_uuid_map[port_id])
            ].local_current.composition.ID
            == self.component_ports[
                1 - self.component_ports.index(port_uuid_map[port_id])
            ].local_current.composition.ID
        )

        # print(
        #     f"\n\n{(
        #     self.component_ports[
        #     self.component_ports.index(port_uuid_map[port_id])
        # ].local_current.composition.ID
        #     == self.component_ports[
        #     1 - self.component_ports.index(port_uuid_map[port_id])
        # ].local_current.composition.ID
        # ) = }\n\n"
        # )

        try:
            assert (
                self.component_ports[
                    self.component_ports.index(port_uuid_map[port_id])
                ].local_current.density()
                == self.component_ports[
                    1 - self.component_ports.index(port_uuid_map[port_id])
                ].local_current.density()
            )

        except AssertionError:
            print(
                "\nEntered the Except AssertionError Block inside the try for density verification\n"
            )

            print(
                f"\n{self.component_ports[
                    self.component_ports.index(port_uuid_map[port_id])
                ].local_current.density() = }\n"
            )

            print(
                f"\n{self.component_ports[
                    1 - self.component_ports.index(port_uuid_map[port_id])
                ].local_current.density() = }\n"
            )

            component_uuid_map = {comp.id: comp for comp in Component.component_objects}

            given_prt_index_inside_comp_ports_list = self.component_ports.index(
                self.component_ports[self.component_ports.index(port_uuid_map[port_id])]
            )
            alternative_prt_index_inside_comp_ports_list = self.component_ports.index(
                self.component_ports[
                    1 - self.component_ports.index(port_uuid_map[port_id])
                ]
            )

            print(
                f"\n{self.component_ports[
            self.component_ports.index(port_uuid_map[port_id])
            ].local_current.density() = }"
            )
            print(
                f"\n{self.component_ports[
            1 - self.component_ports.index(port_uuid_map[port_id])
            ].local_current.density() = }\n"
            )

            print(
                f"\n{self.component_ports[
            self.component_ports.index(port_uuid_map[port_id])
            ].local_current.id = }"
            )
            print(f"\n{given_prt_index_inside_comp_ports_list = }")
            print(
                f"\n{self.component_ports[
            self.component_ports.index(port_uuid_map[port_id])
            ].local_current.composition = }"
            )
            print(
                f"\n{self.component_ports[
            self.component_ports.index(port_uuid_map[port_id])
            ].location = }"
            )
            print(
                f"\n{component_uuid_map[self.component_ports[
            self.component_ports.index(port_uuid_map[port_id])
            ].location] = }"
            )
            print(
                f"\n{len(component_uuid_map[self.component_ports[
            self.component_ports.index(port_uuid_map[port_id])
            ].location].component_ports) = }\n"
            )

            print(
                f"\n{self.component_ports[
            1 - self.component_ports.index(port_uuid_map[port_id])
            ].local_current.id = }"
            )
            print(f"\n{alternative_prt_index_inside_comp_ports_list = }")
            print(
                f"\n{self.component_ports[
            1 - self.component_ports.index(port_uuid_map[port_id])
            ].local_current.composition = }"
            )
            print(
                f"\n{self.component_ports[
            1 - self.component_ports.index(port_uuid_map[port_id])
            ].location = }"
            )
            print(
                f"\n{component_uuid_map[self.component_ports[
            1 - self.component_ports.index(port_uuid_map[port_id])
            ].location] = }"
            )
            print(
                f"\n{len(component_uuid_map[self.component_ports[
            1 - self.component_ports.index(port_uuid_map[port_id])
            ].location].component_ports) = }\n"
            )

            # // In this section, the compositions inside all the mosaic will be
            # // setted to be homogenous (all equal, with equal T)
            comp_uuid_map = {comp.id: comp for comp in Component.component_objects}

            curr_uuid_map = {cur.id: cur for cur in current.Current.current_objects}

            mosaic_uuid_map = {
                mosaic.id: mosaic for mosaic in mosaic.Mosaic.mosaic_objects
            }

            currs_inside_self_mosaic: list[UUID] = []

            comps_inside_self: list[UUID] = []

            # // finding the comps and currents in self

            for j in mosaic_uuid_map[self.location].joints_list:
                for some_obj in j:
                    if isinstance(some_obj, Iterable):
                        raise ValueError(
                            "i dont remeber if this is possible and allowed to happen"
                        )

                    if some_obj in curr_uuid_map.keys():
                        # * Search for another original current inside the self mosaic
                        currs_inside_self_mosaic.append(some_obj)

                    if some_obj in comp_uuid_map.keys():
                        # * Search for another original current inside the self mosaic
                        comps_inside_self.append(some_obj)

            for curr_id in currs_inside_self_mosaic:
                for some_orig_self_curr_id in currs_inside_self_mosaic:
                    if curr_id != some_orig_self_curr_id:
                        try:
                            assert (
                                self.component_ports[
                                    self.component_ports.index(port_uuid_map[port_id])
                                ].local_current.composition.T
                                == self.component_ports[
                                    1
                                    - self.component_ports.index(port_uuid_map[port_id])
                                ].local_current.composition.T
                            )

                            assert (
                                self.component_ports[
                                    self.component_ports.index(port_uuid_map[port_id])
                                ].local_current.composition.P
                                == self.component_ports[
                                    1
                                    - self.component_ports.index(port_uuid_map[port_id])
                                ].local_current.composition.P
                            )

                            assert (
                                self.component_ports[
                                    self.component_ports.index(port_uuid_map[port_id])
                                ].local_current.composition.ID
                                == self.component_ports[
                                    1
                                    - self.component_ports.index(port_uuid_map[port_id])
                                ].local_current.composition.ID
                            )

                        except:
                            print(
                                f"\n{(
                                self.component_ports[
                                    self.component_ports.index(port_uuid_map[port_id])
                                ].local_current.composition.ID
                                == "water"
                            ) and (
                                self.component_ports[
                                    1
                                    - self.component_ports.index(port_uuid_map[port_id])
                                ].local_current.composition.ID
                                != "water"
                            ) = }"
                            )

                            print(
                                f"\n{(
                            self.component_ports[
                                1
                                - self.component_ports.index(port_uuid_map[port_id])
                            ].local_current.composition.ID
                            == "water"
                            ) and (
                                self.component_ports[
                                    self.component_ports.index(port_uuid_map[port_id])
                                ].local_current.composition.ID
                                != "water"
                            ) = }"
                            )

                            print(
                                f"\n{(
                                self.component_ports[
                                    self.component_ports.index(port_uuid_map[port_id])
                                ].local_current.composition.ID
                                == "water"
                            ) and (
                                self.component_ports[
                                    1
                                    - self.component_ports.index(port_uuid_map[port_id])
                                ].local_current.composition.ID
                                == "water"
                            ) = }"
                            )

                            raise ValueError

                            if (
                                self.component_ports[
                                    self.component_ports.index(port_uuid_map[port_id])
                                ].local_current.composition.ID
                                == "water"
                            ) and (
                                self.component_ports[
                                    1
                                    - self.component_ports.index(port_uuid_map[port_id])
                                ].local_current.composition.ID
                                != "water"
                            ):
                                self.component_ports[
                                    self.component_ports.index(port_uuid_map[port_id])
                                ].local_current.composition = curr_uuid_map[
                                    some_orig_self_curr_id
                                ].composition

                                # curr_uuid_map[curr_id].composition.ID = curr_uuid_map[
                                #     some_orig_self_curr_id
                                # ].composition.ID

                                # curr_uuid_map[curr_id].composition.T = curr_uuid_map[
                                #     some_orig_self_curr_id
                                # ].composition.T

                                # curr_uuid_map[curr_id].composition.P = curr_uuid_map[
                                #     some_orig_self_curr_id
                                # ].composition.P

                            elif (
                                self.component_ports[
                                    1
                                    - self.component_ports.index(port_uuid_map[port_id])
                                ].local_current.composition.ID
                                == "water"
                            ) and (
                                self.component_ports[
                                    self.component_ports.index(port_uuid_map[port_id])
                                ].local_current.composition.ID
                                != "water"
                            ):
                                curr_uuid_map[
                                    some_orig_self_curr_id
                                ].composition = self.component_ports[
                                    self.component_ports.index(port_uuid_map[port_id])
                                ].local_current.composition

                                # curr_uuid_map[
                                #     some_orig_self_curr_id
                                # ].composition.ID = curr_uuid_map[curr_id].composition.ID

                                # curr_uuid_map[
                                #     some_orig_self_curr_id
                                # ].composition.T = curr_uuid_map[curr_id].composition.T

                                # curr_uuid_map[
                                #     some_orig_self_curr_id
                                # ].composition.P = curr_uuid_map[curr_id].composition.P

                            elif (
                                self.component_ports[
                                    self.component_ports.index(port_uuid_map[port_id])
                                ].local_current.composition.ID
                                == "water"
                            ) and (
                                self.component_ports[
                                    1
                                    - self.component_ports.index(port_uuid_map[port_id])
                                ].local_current.composition.ID
                                == "water"
                            ):
                                evaluated_curr_pair: list[UUID] = [
                                    curr_id,
                                    some_orig_self_curr_id,
                                ]
                                standard_composition = nc.pure_water

                                if (
                                    self.component_ports[
                                        self.component_ports.index(
                                            port_uuid_map[port_id]
                                        )
                                    ].local_current.composition.T
                                    == curr_uuid_map[
                                        some_orig_self_curr_id
                                    ].composition.T
                                ):
                                    for c_uuid in evaluated_curr_pair:
                                        if (
                                            curr_uuid_map[c_uuid].composition.T
                                            != standard_composition.T
                                        ):
                                            curr_uuid_map[
                                                evaluated_curr_pair[
                                                    1
                                                    - evaluated_curr_pair.index(c_uuid)
                                                ]
                                            ].composition = curr_uuid_map[
                                                c_uuid
                                            ].composition

                                            # curr_uuid_map[
                                            #     evaluated_curr_pair[
                                            #         1 - evaluated_curr_pair.index(c_uuid)
                                            #     ]
                                            # ].composition.ID = curr_uuid_map[
                                            #     c_uuid
                                            # ].composition.ID

                                            # curr_uuid_map[
                                            #     evaluated_curr_pair[
                                            #         1 - evaluated_curr_pair.index(c_uuid)
                                            #     ]
                                            # ].composition.P = curr_uuid_map[
                                            #     c_uuid
                                            # ].composition.P

                                        elif (
                                            curr_uuid_map[
                                                evaluated_curr_pair[
                                                    1
                                                    - evaluated_curr_pair.index(c_uuid)
                                                ]
                                            ].composition.T
                                            != standard_composition.T
                                        ):
                                            curr_uuid_map[
                                                c_uuid
                                            ].composition = curr_uuid_map[
                                                evaluated_curr_pair[
                                                    1
                                                    - evaluated_curr_pair.index(c_uuid)
                                                ]
                                            ].composition

                                            # curr_uuid_map[
                                            #     c_uuid
                                            # ].composition.ID = curr_uuid_map[
                                            #     evaluated_curr_pair[
                                            #         1 - evaluated_curr_pair.index(c_uuid)
                                            #     ]
                                            # ].composition.ID

                                            # curr_uuid_map[
                                            #     c_uuid
                                            # ].composition.T = curr_uuid_map[
                                            #     evaluated_curr_pair[
                                            #         1 - evaluated_curr_pair.index(c_uuid)
                                            #     ]
                                            # ].composition.T

                                elif (
                                    self.component_ports[
                                        self.component_ports.index(
                                            port_uuid_map[port_id]
                                        )
                                    ].local_current.composition.P
                                    == curr_uuid_map[
                                        some_orig_self_curr_id
                                    ].composition.P
                                ):
                                    for c_uuid in evaluated_curr_pair:
                                        if (
                                            curr_uuid_map[c_uuid].composition.P
                                            != standard_composition.P
                                        ):
                                            curr_uuid_map[
                                                evaluated_curr_pair[
                                                    1
                                                    - evaluated_curr_pair.index(c_uuid)
                                                ]
                                            ].composition = curr_uuid_map[
                                                c_uuid
                                            ].composition

                                            # curr_uuid_map[
                                            #     evaluated_curr_pair[
                                            #         1 - evaluated_curr_pair.index(c_uuid)
                                            #     ]
                                            # ].composition.ID = curr_uuid_map[
                                            #     c_uuid
                                            # ].composition.ID

                                            # curr_uuid_map[
                                            #     evaluated_curr_pair[
                                            #         1 - evaluated_curr_pair.index(c_uuid)
                                            #     ]
                                            # ].composition.T = curr_uuid_map[
                                            #     c_uuid
                                            # ].composition.T

                                        elif (
                                            curr_uuid_map[
                                                evaluated_curr_pair[
                                                    1
                                                    - evaluated_curr_pair.index(c_uuid)
                                                ]
                                            ].composition.P
                                            != standard_composition.P
                                        ):
                                            curr_uuid_map[
                                                c_uuid
                                            ].composition = curr_uuid_map[
                                                evaluated_curr_pair[
                                                    1
                                                    - evaluated_curr_pair.index(c_uuid)
                                                ]
                                            ].composition

                                            # curr_uuid_map[
                                            #     c_uuid
                                            # ].composition.ID = curr_uuid_map[
                                            #     evaluated_curr_pair[
                                            #         1 - evaluated_curr_pair.index(c_uuid)
                                            #     ]
                                            # ].composition.ID

                                            # curr_uuid_map[
                                            #     c_uuid
                                            # ].composition.T = curr_uuid_map[
                                            #     evaluated_curr_pair[
                                            #         1 - evaluated_curr_pair.index(c_uuid)
                                            #     ]
                                            # ].composition.T

                            else:
                                raise ValueError(
                                    r"\nThis is a scenario that wasnt planned,"
                                )

                raise AssertionError
            try:
                assert (
                    self.component_ports[
                        self.component_ports.index(port_uuid_map[port_id])
                    ].local_current.density()
                    == self.component_ports[
                        1 - self.component_ports.index(port_uuid_map[port_id])
                    ].local_current.density()
                )

            except AssertionError:
                raise ValueError

        assert (
            self.component_ports[
                1 - self.component_ports.index(port_uuid_map[port_id])
            ].local_current.density
            is not None
        )
        assert (
            self.component_ports[
                self.component_ports.index(port_uuid_map[port_id])
            ].local_current.density
            is not None
        )

        given_rho: float = float(
            self.component_ports[
                self.component_ports.index(port_uuid_map[port_id])
            ].local_current.density()
        )
        alternative_rho: float = float(
            self.component_ports[
                1 - self.component_ports.index(port_uuid_map[port_id])
            ].local_current.density()
        )

        # // Método alternativo de homogenização de composições em correntes locais

        self.local_curs_in_self = [port.local_current for port in self.component_ports]
        self.local_curs_composition_in_comp: set[tuple] = {
            (
                local_cur.composition.ID,
                local_cur.composition.T,
                local_cur.composition.P,
                local_cur.composition,
            )
            for local_cur in self.local_curs_in_self
        }

        try:
            assert len(self.local_curs_composition_in_comp) <= 2
            assert len(self.local_curs_composition_in_comp) >= 1
        except:
            raise ValueError

        nc_pure_water_tuple = (
            nc.pure_water.ID,
            nc.pure_water.T,
            nc.pure_water.P,
            nc.pure_water,
        )
        # self.generic_orig_self_curs: list[current.Current] = []
        self.generic_orig_self_curs = []

        # // Homogenization of the original currents inside self mosaic
        if len(self.local_curs_composition_in_comp) == 2:
            assert nc_pure_water_tuple in self.local_curs_composition_in_comp

            a, b = self.local_curs_composition_in_comp
            dif_comp = b if nc_pure_water_tuple == a else a

            for orig_cur in self.local_curs_in_self:
                if (
                    (orig_cur.composition.ID == nc.pure_water.ID)
                    and (orig_cur.composition.T == nc.pure_water.T)
                    and (orig_cur.composition.P == nc.pure_water.P)
                ):
                    orig_cur.composition = dif_comp[3]

        # print(f"\n{given_port.local_current.density() = }\n")
        # print(f"\n{alternative_port.local_current.density() = }\n")
        delta_rho = (
            self.component_ports[
                self.component_ports.index(port_uuid_map[port_id])
            ].local_current.density()
            - self.component_ports[
                1 - self.component_ports.index(port_uuid_map[port_id])
            ].local_current.density()
        )

        # print(f"\n{delta_rho = }\n")

        #!This section is deactivated, but just for a while, until i fix some other problems
        if False:
            if delta_rho >= 0.000001:
                for p in self.component_ports:
                    print(f"\n{self.id = }\n")
                    print(f"\n{p.id = }\n")

                    print(f"\n{p.local_current.composition = }\n")
                    print(f"\n{p.local_current.composition.T = }\n")

                #     print(f"\n{p.local_current.temperature = }\n")
                #     print(f"\n{p.local_current.pressure = }\n")
                #     print(f"\n{p.local_current.mass_flow = }\n")
                #     print(f"\n{p.local_current.volume_flow = }\n")
                #     print(f"\n{p.local_current.density() = }\n")

                # print(f"\n{given_port.id = }\n")
                # print(f"\n{alternative_port.id = }\n")

                # print(f"\n{given_rho = }\n")
                # print(f"\n{alternative_rho = }\n")

        #! Essa alteração parece ter resolvido o problema da composição para um unico
        #! Componente, mas o codigo quebrou na etapa do solver, então ainda existem
        #! Erros (e talvez sejam erros na composição) acontecendo, talvez nesse componente
        #! mesmo, ou em outros.
        # * self.component_ports[
        # *    self.component_ports.index(port_uuid_map[port_id])
        # * ].local_current.composition = self.component_ports[
        # *    1 - self.component_ports.index(port_uuid_map[port_id])
        # * ].local_current.composition

        try:
            assert delta_rho <= 0.000001
        except AssertionError:
            print(
                f"{f'{self.component_ports[
            self.component_ports.index(port_uuid_map[port_id])
        ].local_current.composition = }':^{shutil.get_terminal_size().columns}}"
            )
            print(
                f"\n\n{self.component_ports[
            1 - self.component_ports.index(port_uuid_map[port_id])
        ].local_current.composition = }\n\n"
            )
            raise ValueError(f"""
                The rho values differ a considerable amount:\n{given_rho = }\n{alternative_rho = }""")

        definitive_rho: None | float = given_rho

        # //local_current_pressure_in_port_id
        given_cur_pressure = sy.symbols(
            str(
                self.component_ports[
                    self.component_ports.index(port_uuid_map[port_id])
                ].local_current.pressure_name
            )
        )
        alternative_cur_pressure = sy.symbols(
            str(alternative_port.local_current.pressure_name)
        )

        #! This code must be refactroed as soon as possible, because this method
        #! does return the energy conservation equation, and it can
        #! not be used as such, before being refactored. If this method is being used in some
        #! part of the  library, it should be refactored,to compose the energy conservation equation
        #! of the self component, and so that the unknows of the equation are defined as either
        #! UUID's or ULID's. The equation returned must be added to
        #! the self.equations_dict, if the equation returned are not already
        #! in said dictionary

        error_msg: str = """The declaration of p2 is wrong, because it needed to be declared using
        the uuids or ulids, using the id of the current or the port. The declaration of the Symbols
        must be fixed before the section below is activated againd"""

        # raise ValueError(error_msg)  # None

        # // local current (inside the given port) volumetric flow, Q, in (m^3)/s
        given_port_id = sy.symbols(str(given_port.id))

        # // local current (inside the alternative port, the one that is in self.comp_ports, and is
        # // not the given_port) volumetric flow, Q, in (m^3)/s
        # * In a pipe, if the given port is self.comp_ports[0], the q would be the volumetric flow in
        # * theself.comp_ports[1], and vice versa
        alternative_port_id = sy.symbols(
            str(
                self.component_ports[
                    1 - self.component_ports.index(port_uuid_map[port_id])
                ].id
            )
        )

        # // local elevation of the center of the given port, measured in meters
        z1: int | float = self.component_ports[
            self.component_ports.index(port_uuid_map[port_id])
        ].spacial_position.coords[2]

        # // local elevation of the center of the alternative port, measured in meters
        # * In a pipe, if the given port is self.comp_ports[0], the z2 would be the elevation in the
        # * self.comp_ports[1], and vice versa
        z2: int | float = alternative_port.spacial_position.coords[2]

        # // This value (hl) is the input energy, if the component has
        # // such feature (like major and minor loss components)

        hl: sy.Basic | float | int = 0

        # // This value (hb) is the input energy, if the component has such feature (like bombs)

        hb: sy.Basic | float | int = 0

        # // h_l is the head_pressure_loss inside the self component (in the path from the given_port
        # // to the alternative port)
        if (hasattr(self, "head_pressure_loss_eq")) and (
            callable(getattr(self, "head_pressure_loss_eq"))
        ):
            # // If the self object has the method to calculate the hl,
            # // hl = head_pressure_loss_eq()
            hl = self.head_pressure_loss_eq(alternative_port.id)

            assert hl is not None
            # * assert hl != 0

        if (hasattr(self, "HB")) and (callable(getattr(self, "HB"))):
            # // If the self object has the method to calculate the hb, hb = HB()
            hb = self.HB(port_id)

            assert hb != 0
            assert hb is not None

        assert z1 is not None

        if len(self.component_ports) == 2:
            for p_index, p in enumerate(self.component_ports):
                assert p is not None

                if p.id == port_id:
                    # *This part is to make sure that the iteration does not selet p2 and z2 values
                    # * if the port in the given port, and not the other one
                    continue

                for port in Port.port_objects:
                    if (
                        (port in self.component_ports)
                        and (port.id != port_id)
                        and (port.id == alternative_port)
                    ):
                        assert port.local_current is not None
                        assert port.local_current.pressure is not None
                        assert port.local_current.pressure != 0

                        assert port.spacial_position.coords is not None

                        assert port.local_current.volume_flow is not None

                        assert port.local_current.volume_flow != 0
                        assert port.local_current.velocity != 0

                        assert port.local_current.volume_flow != 0
                        assert port.local_current.velocity != 0

        else:
            raise NotImplementedError

        assert z2 is not None

        e1: float | int = self.E_constant(given_port.id)

        e2: float | int = self.E_constant(alternative_port.id)

        assert definitive_rho is not None

        energy_cons_eq: sy.Basic = sy.Eq(
            (given_cur_pressure / (definitive_rho * nc.g))
            + (e1 * (given_port_id**2))
            + z1
            + hb,
            (alternative_cur_pressure / (definitive_rho * nc.g))
            + (e2 * (alternative_port_id**2))
            + z2
            + hl,  # ,
        )

        return energy_cons_eq


# MARK: MinorLossComponent
class MinorLossComponent(Component):
    """
    This class is the parent class of all the Components that have their head pressure loss as Minor Losses.

    i.e.: Fittings, Elbows, Connectors, Adapters, Reducers, Expansors.

    Those classes will be childs from this class because all of them have their pressure loss calculated using specific
    coeficients.
    """

    def C_constant(self, ports_id: MinorPortSelection) -> float | int:
        # print(
        #     "\n\n------------ This method need to have its description upddated ------------\n\n"
        # )

        """
        This method calculates the C coeficient for a given component and port inside said component.

        This coeficient is used to calculate the head pressure loss inside said component.

        For Pipes, this head pressure loss is in the form Delta Hl = C * Q * |Q|
        """

        # print(f'\n\n{self.id = }')
        # print(f'\n{ports_id = }\n')

        # self.cross_section_area: float = 0

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

        for port in self.component_ports:
            for obj in ports_id:
                try:
                    assert obj in port_uuid_map.keys()
                    assert port_uuid_map[obj].location == self.id
                    assert port_uuid_map[obj] in self.component_ports

                except AssertionError:
                    raise er.ConduitForgeError
                    # print(f"\n\n{self.id = }")
                    # print(f"\n-----------{port_uuid_map[obj].id = }-----------")
                    # print(
                    #     f"\n**********{(port_uuid_map[obj].location == self.id) = }**********"
                    # )
                    # print(f"\n**********{port_uuid_map[obj].location = }**********\n")

        ports_as_key: str = ""

        g: float = 9.81  # * m/(s^2)

        component_ports_id: list[UUID] = []

        if len(self.component_ports) != self.port_quantity:
            for port in self.component_ports:
                if port != self.component_ports[-1] and (
                    port.id != self.component_ports[-1].id
                ):
                    assert port is not None
                    component_ports_id.append(port.id)

            for some_port_id in ports_id:
                assert port_uuid_map[some_port_id].location == self.id
                assert some_port_id != self.component_ports[-1].id

        elif (len(self.component_ports) == self.port_quantity) and (
            self.port_quantity == 2
        ):
            for port in self.component_ports:
                assert port is not None
                component_ports_id.append(port.id)
        # print(f"\n{component_ports_id = }\n")
        # print(f"\n{ports_id = }\n")

        for arg_port_id in ports_id:
            # print(f"\n\n{ports_as_key = }\n")

            for port_index, port_id in enumerate(component_ports_id):
                # * // Eu posso adicionar uma outra condicional a esse bloco if, para permitir que o bloco só seja
                # * //executado se a string ports_as_key tiver menos de 2 caracteres, mas isso pode gerar outros problemas
                if str(port_index) not in ports_as_key and (port_id == arg_port_id):
                    # print(f"\n{str(port_index) = }\n")

                    ports_as_key += str(port_index)

        assert ports_as_key != ""
        print(f"\n{ports_as_key = } Chosen !\n")
        assert len(ports_as_key) == 2
        # print(f"\n{ports_as_key = }\n")
        # print(f"\n{(ports_as_key in self.KL_Dict.keys()) = }\n")
        assert ports_as_key in self.KL_Dict.keys()
        assert self.KL_Dict[ports_as_key] is not None
        assert self.cross_section_area is not None
        assert self.cross_section_area != 0

        c_coefficient: Union[float, int] = (self.KL_Dict[ports_as_key]) / (
            (2 * g) * (self.cross_section_area**2)
        )

        #! raise NotImplementedError(f"Essa parte da definição de ports_as_key"\
        #!  "parece estar com problemas, por que os números sempre aparecem em ordem, e"\
        #!  "eu percebi que os números sempre são 0 e 1")

        # print(f'\n{c_coefficient = }\n')

        assert c_coefficient is not None

        # print(f"\n{self.id = }")
        # print(f"\n{c_coefficient = }")

        return c_coefficient

    # * @override
    def darcy_perssure_drop_coefficient(self, port_id: MajorPortSelection) -> None:
        raise er.ConduitForgeError(
            f"\nThis method doesn't belong to this class, Try using the C_constant method, for major losses"
        )

    def B_Constant(self) -> NoReturn:
        raise er.ConduitForgeError(
            f"\nThis method doesn't belong to this class, Try using the C_constant method, for major losses"
        )

    def energy_conservation(self, ports_id: MinorPortSelection) -> sy.Basic:
        # // This part is a safety measure, and ensures that the inputs are passed to this method correctly
        # //

        try:
            assert all(port.local_current is not None for port in self.component_ports)

        except AssertionError as e:
            for port_index, port in enumerate(self.component_ports):
                assert port is not None
                if port.local_current is None:
                    raise ValueError(
                        f"\n The {port.id = }, {port_index = } has a local_current = None"
                    )

                else:
                    pass

            raise ValueError(
                "\nThe mass conservation method need to be run before the energy conservation method"
            )

        try:
            for comp_port in self.component_ports:
                assert comp_port is not None
                for coord in comp_port.spacial_position.coords:
                    assert coord != None
        except:
            raise ValueError("\nSomething went wrong with the position solver")

        # first_port = sy.symbols(f'{first_port}')

        # print(f'{first_port = }')

        current_uuid_map: dict[UUID, current.Current] = {
            cur.id: cur for cur in current.Current.current_objects
        }

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

        component_uuid_map: dict[UUID, Component] = {
            comp.id: comp for comp in Component.component_objects
        }

        mosaic_uuid_map: dict[UUID, mosaic.Mosaic] = {
            mos.id: mos for mos in mosaic.Mosaic.mosaic_objects
        }

        # // This is the UUID of the first port that will be used to compose the energy conservation
        # //eq
        first_port: Port = port_uuid_map[ports_id[0]]

        # // This is the UUID of the other port that will be used to compose the energy conservation
        # //eq, with thedata colected from the first_port port (given port)
        second_port: Port = port_uuid_map[ports_id[1]]

        assert first_port.local_current.density is not None
        assert second_port.local_current.density is not None

        first_rho: float = float(first_port.local_current.density())
        second_rho: float = float(second_port.local_current.density())

        delta_rho = first_rho - second_rho

        if delta_rho >= 0.000001:
            for p in self.component_ports:
                print(f"\n{self.id = }\n")
                print(f"\n{p.id = }\n")

                print(f"\n{p.local_current.composition = }\n")
                print(f"\n{p.local_current.composition.T = }\n")

                print(f"\n{p.local_current.temperature = }\n")
                print(f"\n{p.local_current.pressure = }\n")
                print(f"\n{p.local_current.mass_flow = }\n")
                print(f"\n{p.local_current.volume_flow = }\n")

        try:
            assert delta_rho <= 0.000001

        except AssertionError:
            definitive_rho = first_rho
            # print(f"\n{ports_id = }\n")
            # print(f"\n{(first_port.location == self.id) = }\n")
            # print(f"\n{(second_port.location == self.id) = }\n")
            # raise ValueError(f"""
            #     The rho values differ a considerable amount:\n\n{first_rho = }\n{second_rho = }""")

        definitive_rho: None | float = first_rho

        # //local_current_pressure_in_first_port
        first_cur_pressure: None | UUID = sy.symbols(
            str(first_port.local_current.pressure_name)
        )

        # //local_current_pressure_in the other port of the Major Loss Component
        # * In a pipe, if the given port is self.comp_ports[0], the p2 would be the pressure in the
        # * self.comp_ports[1], and vice versa
        second_cur_pressure: None | UUID = sy.symbols(
            str(second_port.local_current.pressure_name)
        )

        # // local current (inside the given port) volumetric flow, Q, in (m^3)/s
        first_port_id = sy.symbols(str(first_port.id))

        # // local current (inside the alternative port, the one that is in self.comp_ports, and is
        # // not the given_port) volumetric flow, Q, in (m^3)/s
        # * In a pipe, if the given port is self.comp_ports[0], the q would be the volumetric flow in
        # * theself.comp_ports[1], and vice versa
        second_port_id = sy.symbols(str(second_port.id))

        # // local elevation of the center of the given port, measured in meters
        z1: int | float = first_port.spacial_position.coords[2]

        # // local elevation of the center of the alternative port, measured in meters
        # * In a pipe, if the given port is self.comp_ports[0], the z2 would be the elevation in the
        # * self.comp_ports[1], and vice versa
        z2: int | float = second_port.spacial_position.coords[2]

        # // This value (hb) is the input energy, if the component has such feature (like bombs)

        hb: sy.Basic | int | float = 0

        # // h_l is the head_pressure_loss inside the self component (in the path from the given_port
        # to the alternative port)

        hl: int | float = self.head_pressure_loss_eq(ports_id)

        assert hl is not None
        assert hl != 0

        if (hasattr(self, "HB")) and (callable(getattr(self, "HB"))):
            # // If the self object has the method to calculate the hb, hb = HB()
            hb = self.HB()

            assert hb != 0
            assert hb is not None

        assert z1 is not None

        assert z2 is not None

        e1: float | int = self.E_constant(ports_id[0])

        e2: float | int = self.E_constant(ports_id[1])

        assert definitive_rho is not None

        energy_cons_eq: sy.Basic = sy.Eq(
            (first_cur_pressure / (definitive_rho * nc.g))
            + (e1 * (first_port_id**2))
            + z1
            + hb,
            (second_cur_pressure / (definitive_rho * nc.g))
            + (e2 * (second_port_id**2))
            + z2
            + hl,
        )

        return energy_cons_eq


class MechanicalPowerInputComponent(Component):
    """
    This class is the Parent class of all the components that give mechanical
    energy to the stream of fluid, like pumps, for exemple
    """

    def head_pressure_loss_eq(self, arg) -> sy.Basic:
        return 0

    def energy_conservation(self, port_id: MajorPortSelection) -> None | sy.Basic:
        # // This part is a safety measure, and ensures that the inputs are passed to this method correctly

        assert len(self.component_ports) == self.port_quantity
        assert len(self.component_ports) == 2

        try:
            assert all(port.local_current is not None for port in self.component_ports)

        except AssertionError as e:
            for port_index, port in enumerate(self.component_ports):
                assert port is not None
                if port.local_current is None:
                    raise ValueError(
                        f"\n The {port.id = }, {port_index = } has a local_current = None"
                    )

                else:
                    pass

            raise ValueError(
                "\nThe mass conservation method need to be run before the energy conservation method"
            )

        try:
            for comp_port in self.component_ports:
                assert comp_port is not None
                for coord in comp_port.spacial_position.coords:
                    assert coord != None
        except:
            raise ValueError(f"\nSomething went wrong with the position solver")

        current_uuid_map: dict[UUID, current.Current] = {
            cur.id: cur for cur in current.Current.current_objects
        }

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

        component_uuid_map: dict[UUID, Component] = {
            comp.id: comp for comp in Component.component_objects
        }

        mosaic_uuid_map: dict[UUID, mosaic.Mosaic] = {
            mos.id: mos for mos in mosaic.Mosaic.mosaic_objects
        }

        assert len(self.component_ports) == self.port_quantity
        assert len(self.component_ports) == 2

        given_port = port_uuid_map[port_id]

        for port_index, port in enumerate(self.component_ports):
            if port.id == port_id:
                print(f"\n{port_id = }")
                print(f"\n{port_index = }\n")

        # print(f"\n{given_port.id = }\n")
        # print(f"\n{given_port.location = }\n")
        # print(f"\n{self.id = }\n")

        assert given_port in self.component_ports
        assert given_port.location == self.id

        # // This is the UUID of the other port that will be used to compose the energy conservation
        # //eq, with the data colected from the port_id port (given port)
        alternative_port: None | Port = None

        for p in self.component_ports:
            if p is not given_port:
                alternative_port = p

        assert alternative_port is not None
        assert given_port is not None

        assert alternative_port in self.component_ports
        assert alternative_port.location == self.id

        # print(f"\n{alternative_port.id = }\n")
        # print(f"\n{alternative_port.location = }\n")
        assert given_port.location == alternative_port.location

        # // assert (
        # //     given_port.local_current.composition.T
        # //     == alternative_port.local_current.composition.T
        # // )

        try:
            assert (
                given_port.local_current.composition.T
                == alternative_port.local_current.composition.T
            )
        except:
            print(f"\n{given_port = }\n")
            print(f"\n{given_port.location = }\n")

            print(f"\n{alternative_port = }\n")
            print(f"\n{alternative_port.location = }\n")

            print(f"\n{given_port.local_current.composition.T = }\n")
            print(f"\n{alternative_port.local_current.composition.T = }\n")

            raise ValueError

        # try:
        #     assert (
        #         given_port.local_current.composition.P
        #         == alternative_port.local_current.composition.P
        #     )
        # except:
        #     print(f"\n{given_port = }\n")
        #     print(f"\n{given_port.location = }\n")

        #     print(f"\n{alternative_port = }\n")
        #     print(f"\n{alternative_port.location = }\n")

        #     print(f"\n{given_port.local_current.composition.P = }\n")
        #     print(f"\n{alternative_port.local_current.composition.P = }\n")

        #     raise ValueError

        assert (
            given_port.local_current.composition.ID
            == alternative_port.local_current.composition.ID
        )
        assert (
            given_port.local_current.density()
            == alternative_port.local_current.density()
        )

        assert alternative_port.local_current.density is not None
        assert given_port.local_current.density is not None

        given_rho: float = float(given_port.local_current.density())
        alternative_rho: float = float(alternative_port.local_current.density())

        # print(f"\n{given_port.local_current.density() = }\n")
        # print(f"\n{alternative_port.local_current.density() = }\n")
        delta_rho = given_rho - alternative_rho

        # print(f"\n{delta_rho = }\n")

        #!This section is deactivated, but just for a while, until i fix some other problems
        if False:
            if delta_rho >= 0.000001:
                for p in self.component_ports:
                    print(f"\n{self.id = }\n")
                    print(f"\n{p.id = }\n")

                    print(f"\n{p.local_current.composition = }\n")
                    print(f"\n{p.local_current.composition.T = }\n")

                #     print(f"\n{p.local_current.temperature = }\n")
                #     print(f"\n{p.local_current.pressure = }\n")
                #     print(f"\n{p.local_current.mass_flow = }\n")
                #     print(f"\n{p.local_current.volume_flow = }\n")
                #     print(f"\n{p.local_current.density() = }\n")

                # print(f"\n{given_port.id = }\n")
                # print(f"\n{alternative_port.id = }\n")

                # print(f"\n{given_rho = }\n")
                # print(f"\n{alternative_rho = }\n")

        try:
            assert delta_rho <= 0.000001
        except AssertionError:
            raise ValueError(f"""
                The rho values differ a considerable amount:\n\n{given_rho = }\n{alternative_rho = }""")

        definitive_rho: None | float = given_rho

        # //local_current_pressure_in_port_id
        given_cur_pressure = sy.symbols(str(given_port.local_current.pressure_name))
        alternative_cur_pressure = sy.symbols(
            str(alternative_port.local_current.pressure_name)
        )

        #! This code must be refactroed as soon as possible, because this method
        #! does return the energy conservation equation, and it can
        #! not be used as such, before being refactored. If this method is being used in some
        #! part of the  library, it should be refactored,to compose the energy conservation equation
        #! of the self component, and so that the unknows of the equation are defined as either
        #! UUID's or ULID's. The equation returned must be added to
        #! the self.equations_dict, if the equation returned are not already
        #! in said dictionary

        error_msg: str = """The declaration of p2 is wrong, because it needed to be declared using
        the uuids or ulids, using the id of the current or the port. The declaration of the Symbols
        must be fixed before the section below is activated againd"""

        # raise ValueError(error_msg)  # None

        # // local current (inside the given port) volumetric flow, Q, in (m^3)/s
        given_port_id = sy.symbols(str(given_port.id))

        # // local current (inside the alternative port, the one that is in self.comp_ports, and is
        # // not the given_port) volumetric flow, Q, in (m^3)/s
        # * In a pipe, if the given port is self.comp_ports[0], the q would be the volumetric flow in
        # * theself.comp_ports[1], and vice versa
        alternative_port_id = sy.symbols(str(alternative_port.id))

        # // local elevation of the center of the given port, measured in meters
        z1: int | float = given_port.spacial_position.coords[2]

        # // local elevation of the center of the alternative port, measured in meters
        # * In a pipe, if the given port is self.comp_ports[0], the z2 would be the elevation in the
        # * self.comp_ports[1], and vice versa
        z2: int | float = alternative_port.spacial_position.coords[2]

        # // This value (hl) is the input energy, if the component has
        # // such feature (like major and minor loss components)

        hl: sy.Basic | float | int = 0

        # // This value (hb) is the input energy, if the component has such feature (like bombs)

        hb: sy.Basic | float | int = 0

        # // h_l is the head_pressure_loss inside the self component (in the path from the given_port
        # // to the alternative port)
        if (hasattr(self, "head_pressure_loss_eq")) and (
            callable(getattr(self, "head_pressure_loss_eq"))
        ):
            # // If the self object has the method to calculate the hl,
            # // hl = head_pressure_loss_eq()
            hl = self.head_pressure_loss_eq(alternative_port.id)

            assert hl is not None
            # * assert hl != 0

        if (hasattr(self, "HB")) and (callable(getattr(self, "HB"))):
            # // If the self object has the method to calculate the hb, hb = HB()
            hb = self.HB(port_id)

            assert hb != 0
            assert hb is not None

        assert z1 is not None

        if len(self.component_ports) == 2:
            for p_index, p in enumerate(self.component_ports):
                assert p is not None

                if p.id == port_id:
                    # *This part is to make sure that the iteration does not selet p2 and z2 values
                    # * if the port in the given port, and not the other one
                    continue

                for port in Port.port_objects:
                    if (
                        (port in self.component_ports)
                        and (port.id != port_id)
                        and (port.id == alternative_port)
                    ):
                        assert port.local_current is not None
                        assert port.local_current.pressure is not None
                        assert port.local_current.pressure != 0

                        assert port.spacial_position.coords is not None

                        assert port.local_current.volume_flow is not None

                        assert port.local_current.volume_flow != 0
                        assert port.local_current.velocity != 0

                        assert port.local_current.volume_flow != 0
                        assert port.local_current.velocity != 0

        else:
            raise NotImplementedError

        assert z2 is not None

        e1: float | int = self.E_constant(given_port.id)

        e2: float | int = self.E_constant(alternative_port.id)

        assert definitive_rho is not None

        energy_cons_eq: sy.Basic = sy.Eq(
            (given_cur_pressure / (definitive_rho * nc.g))
            + (e1 * (given_port_id**2))
            + z1
            + hb,
            (alternative_cur_pressure / (definitive_rho * nc.g))
            + (e2 * (alternative_port_id**2))
            + z2
            + hl,  # ,
        )

        return energy_cons_eq


# MARK: Pipe(MajorLossComponent)
class Pipe(MajorLossComponent):
    """
    This is a mandatory feture
    """

    def __init__(
        self,
        internal_diam: Union[float, int],
        length: Union[float, int],
        orientation: int = 0,
    ) -> None:
        super().__init__(port_quantity=2, orientation=orientation)

        # Both of those are measured in meters
        self.internal_diam: Union[float, int] = internal_diam
        self.length: Union[float, int] = length

        r"""
        #ports_location = self.component_input_ports[0].location

        # This block should catch any of the location errors of the ports
        try:
            assert ports_location == self.id

        except AssertionError:
            raise ValueError(f'\nThe location from the ports (={ports_location}) doesn\'t match the ID of the Pipe Object (={self.id})')

        try:
            for locked_port in self.all_component_ports:
                for any_port in self.all_component_ports:
                    assert locked_port.location == any_port.location
        except AssertionError:
             raise ValueError(f'\nThe location from the ports (={ports_location}) doesn\'t match the location from the ports (={ports_location})')
        """

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

        """
        while logic_utils.has_generic_ports(self) == True:
            for p_index, p in enumerate(self.component_ports):
                if Port.is_generic_port(p) == True:
                    self.component_ports.pop(p_index)

                elif Port.is_generic_port(p) == True:
                    print(f'--Pipe __init__-- {p = }')
                
                else:
                    raise ValueError(f'something went wrong inside the Pipe __init__')
        """

        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")

        """
        # This part make the pipe instance limites to have just one input and output ports on its own body
        if len(self.component_input_ports)>=self.port_quantity:
            self.component_input_ports.pop(self.inputs_quantity)
            
        if len(self.component_output_ports)>=self.outputs_quantity:
            self.component_output_ports.pop(self.inputs_quantity)
        """

    # * @override
    def position_solver(self) -> None:
        """
        This version fo the position solver must only take care of its own ports coordinates, based on the root point
        cordinates, each component must have this method coded this way, because each position solver will be called
        individually from the Joint method from the Mosaic object it belongs to.

        From the root point, the position solver will also use the positional vectors from the ports to calculate the
        ports positions
        """

        try:
            first_none_coord_index: Union[None, int] = None
            for coord_index, coord in enumerate(
                self.component_ports[0].spacial_position.coords
            ):
                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"
            )

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

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

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

            # * This conversion from degrees to radians is needed because the sin and cos functions only recieve inputs in radians

            assert self.orientation is not None
            self.orientation_radians = self.orientation * (pi / 180)

            assert self.component_ports[0].spacial_position.coords[0] is not None
            assert self.component_ports[0].spacial_position.coords[2] is not None

            x = (
                cos(self.orientation_radians) * self.length
                + self.component_ports[0].spacial_position.coords[0]
            )
            z = (
                sin(self.orientation_radians) * self.length
                + self.component_ports[0].spacial_position.coords[2]
            )

            self.component_ports[1].spacial_position = bgo.Point(x, 0, z)
            self.component_ports[1].position_vector = bgo.Vector(
                points=[
                    self.component_ports[0].spacial_position,
                    self.component_ports[1].spacial_position,
                ]
            )

        # elif (self.component_ports[0].spacial_position == None):
        else:
            print(f"\n{self.component_ports[0] = }\n")
            raise ValueError(
                f"\n\nThis error comes from the position solver method from the {self.id} component"
            )

        assert self.component_ports[0].spacial_position is not None
        assert self.component_ports[1].spacial_position is not None

    """
    def __add__(self, other: Union[Self, current.Current]) -> mosaic.Mosaic | TypeError | None:

        assert ((isinstance(other, Component) == True) or (isinstance(other, current.Current) == True)) == True

        super().__add__(other)

        

        #self.component_ports[0].spacial_position = self.component_ports[0].spacial_position

        #self.vector = bgo.Vector(points = [self.component_ports[0].spacial_position, self.component_ports[1].spacial_position])

        
        if isinstance(other, Component) == True:
            other.component_ports[0].spacial_position = self.

        elif isinstance(other, current.Current) == True:
            pass
        
        else:
            raise TypeError()
        """

    # * @override
    def head_pressure_loss_eq(self, port_id: MajorPortSelection) -> sy.Basic:
        """
        This method should return the equation of the head pressure loss inside a pipe
        """

        # assert self.component_ports[0] is not None
        assert self.component_ports[1] is not None

        arg_port: None | Port = None
        arg_port_id: None | UUID | sy.Basic = 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

        # for port in Port.port_objects:
        #     # print(f'\n{port.id == port_id = }\n')
        #     # print(f'\n{port in self.component_ports = }\n')

        #     # if port.id == port_id:
        #     # print(f'\n{self.id =}\n')
        #     # print(f'\n{port.location = }\n')

        #     if (port.id == port_id) and (port in self.component_ports):
        #         arg_port_id = port.id

        # # print(f'\n{port_id = }\n')
        # # print(f'\n{type(port_id) = }\n')

        assert arg_port_id is not None
        assert arg_port is not None

        # self_pressure_loss: str = f'{self.id}' + '_pressure_loss'

        arg_port_id = sy.symbols(str(arg_port_id))

        self_b_port_id = self.B_constant(port_id)

        self_hl_str: str = f"{self.id}" + "_hl"

        self_hl = sy.symbols(self_hl_str)

        # * pipe_head_pressure_loss_eq = sy.Eq(self_pressure_loss, self.B_constant * first_port_id * abs(first_port_id))

        pipe_head_pressure_loss_eq = sy.Eq(
            self_hl, self_b_port_id * arg_port_id * ((arg_port_id ** (1 / 2)) ** 2)
        )

        # print(f"\n\n{self.id = }\n\n")
        # print(f"{port_id = }")

        # print(f"\n{pipe_head_pressure_loss_eq = }\n")
        # print(f"\n{type(pipe_head_pressure_loss_eq) = }\n")
        # print(f"\n{pipe_head_pressure_loss_eq.rhs = }\n")
        # print(f"\n{pipe_head_pressure_loss_eq.lhs = }\n")

        return pipe_head_pressure_loss_eq.rhs


# MARK: Fitting(MinorLossComponent)
class Fitting(MinorLossComponent):
    def __init__(self, internal_diam: Union[float, int]) -> None:
        """
        This class is responsible for creating the objects that go between one port from a component,
        and other port from other component. The classes that will be child from this one are:

        - Elbow
        - Expander (from a small diameter to a big diameter)
        - Reducer (from a big diameter to a small diameter)
        - Straight Fitting (For coupling two ports, both not in the same component)

        - Custom Fitting (* OPTIONAL FEATURE *)
        - Custom Expander (* OPTIONAL FEATURE *)
        - Custom Reducer (* OPTIONAL FEATURE *)


        """

        super().__init__(port_quantity=2, orientation=None)

        # Both of those are measured in meters
        self.internal_diam: Union[float, int] = internal_diam

        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")

    def positions_solver(self) -> None:
        """
        This method comes from the T_connector class, and it will be moidified
        to be used inside the fitting class.
        """

        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"
            )

        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

        else:
            raise ValueError(
                f"\n\nThis error comes from the position solver method from the {self.id} component"
            )

    def head_pressure_loss_eq(self, ports_id: MinorPortSelection) -> sy.Basic:
        """
        This method is a modifyed copy from the T_connector
        """
        try:
            assert len(ports_id) == 2

            for element in ports_id:
                assert isinstance(element, UUID) == True

        except:
            print(f"\n{ports_id = }")
            raise ValueError(
                f"\n\n The position list should only have 2 elements, and those elements must be UUIDs!"
            )

        first_port_id: sy.Symbol = sy.symbols(str(ports_id[0]))
        second_port_id: sy.Symbol = sy.symbols(str(ports_id[1]))

        self_hb = sy.Integer(0)

        c: float | int = self.C_constant(ports_id=ports_id)

        fitting_head_pressure_loss_eq = sy.Eq(
            self_hb, second_port_id * (((second_port_id) ** 2) ** (1 / 2)) * c
        )

        assert fitting_head_pressure_loss_eq is not None

        return fitting_head_pressure_loss_eq.rhs


#! this class needs to be refactored, so that it is a child of the Fitting or Minor Loss Component
class Elbow(MinorLossComponent):
    #! I need to refac the Elbow class so that it will be a child of the MinorLossComponent

    def __init__(self, internal_diam: float) -> None:
        #! I need to refac the Elbow class so that it will be a child of the MinorLossComponent

        """This method comes from the T_connector class"""

        super().__init__(port_quantity=2)

        self.internal_diam: float | int = internal_diam

        self.cross_section_area = (pi * (internal_diam**2)) / 4  # // Measured in m^2

        # // # // Esses valores são do Çengel (pg 304, edição de 2007), para conexões FLANGEADAS
        # // self.KL_Dict["01"] =
        # // self.KL_Dict["10"] = self.KL_Dict["01"]

        if logic_utils.has_generic_ports(self):
            # 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=internal_diam,
                    location=self.id,
                )
            )

        # * raise NotImplementedError

    def position_solver(self) -> None:
        """
        This method should overwrite the position solver method from the Pipe class. The Pipe Class is the Parent of the
        Elbow class
        """

        try:
            first_none_coord_index: Union[None, int] = None
            for coord, coord_index in enumerate(
                self.component_ports[0].spacial_position.coords
            ):
                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"
            )

        if self.component_ports[0].spacial_position != None:
            self.component_ports[0].spacial_position = self.component_ports[
                0
            ].spacial_position
            self.component_ports[0].position_vector = bgo.Vector(
                self.component_ports[0].spacial_position.coords[0],
                self.component_ports[0].spacial_position.coords[1],
                self.component_ports[0].spacial_position.coords[2],
            )

            self.component_ports[1].spacial_position = self.component_ports[
                0
            ].spacial_position
            self.component_ports[1].position_vector = bgo.Vector(
                self.component_ports[0].spacial_position.coords[0],
                self.component_ports[0].spacial_position.coords[1],
                self.component_ports[0].spacial_position.coords[2],
            )
        # elif (self.component_ports[0].spacial_position == None):
        else:
            print(f"\n{self.component_ports[0].spacial_position = }\n")
            raise ValueError(
                f"\n\nThis error comes from the position solver method from the {self.id} component"
            )

        print(f"\nEnd of the Positional solver")
        print(f"\n{self.id = }\n{self.relational_status() = }")

    def head_pressure_loss_eq(self, ports_id: MinorPortSelection) -> sy.Basic:
        """
        This method is a modifyed copy from the T_connector
        """
        try:
            assert len(ports_id) == 2

            for element in ports_id:
                assert isinstance(element, UUID) == True

        except:
            print(f"\n{ports_id = }")
            raise ValueError(
                f"\n\n The position list should only have 2 elements, and those elements must be UUIDs!"
            )

        first_port_id: sy.Symbol = sy.symbols(str(ports_id[0]))
        second_port_id: sy.Symbol = sy.symbols(str(ports_id[1]))

        self_hb = sy.Integer(0)

        c: float | int = self.C_constant(ports_id=ports_id)

        fitting_head_pressure_loss_eq = sy.Eq(
            self_hb, second_port_id * (((second_port_id) ** 2) ** (1 / 2)) * c
        )

        assert fitting_head_pressure_loss_eq is not None

        return fitting_head_pressure_loss_eq.rhs


class Elbow180DegFlanged(Elbow):
    def __init__(self, internal_diam: float) -> None:
        super().__init__(internal_diam)

        # // Esses valores são do Çengel (pg 304, edição de 2007),
        # // para conexões FLANGEADAS
        self.KL_Dict["01"] = 0.2
        self.KL_Dict["01"] = self.KL_Dict["01"]

    def position_solver(self) -> None:
        return super().position_solver()

    def head_pressure_loss_eq(self, ports_id: Tuple[UUID]) -> sy.Basic:
        return super().head_pressure_loss_eq(ports_id)


class Elbow90DegSmoothFlanged(Elbow):
    def __init__(self, internal_diam: float) -> None:
        super().__init__(internal_diam)

        # // Esses valores são do Çengel (pg 304, edição de 2007),
        # // para conexões FLANGEADAS
        self.KL_Dict["01"] = 0.3
        self.KL_Dict["10"] = self.KL_Dict["01"]

    def position_solver(self) -> None:
        return super().position_solver()

    def head_pressure_loss_eq(self, ports_id: Tuple[UUID]) -> sy.Basic:
        return super().head_pressure_loss_eq(ports_id)


class Elbow90DegChanferFlanged(Elbow):
    def __init__(self, internal_diam: float) -> None:
        super().__init__(internal_diam)

        # // Esses valores são do Çengel (pg 304, edição de 2007),
        # // para conexões FLANGEADAS
        self.KL_Dict["01"] = 1.1
        self.KL_Dict["01"] = self.KL_Dict["01"]

    def position_solver(self) -> None:
        return super().position_solver()

    def head_pressure_loss_eq(self, ports_id: Tuple[UUID]) -> sy.Basic:
        return super().head_pressure_loss_eq(ports_id)


class Elbow45DegThreaded(Elbow):
    def __init__(self, internal_diam: float) -> None:
        super().__init__(internal_diam)

        # // Esses valores são do Çengel (pg 304, edição de 2007),
        # // para conexões ROSQUEADAS
        self.KL_Dict["01"] = 0.4
        self.KL_Dict["01"] = self.KL_Dict["01"]

    def position_solver(self) -> None:
        return super().position_solver()

    def head_pressure_loss_eq(self, ports_id: Tuple[UUID]) -> sy.Basic:
        return super().head_pressure_loss_eq(ports_id)


class Straight_Fitting(Elbow):
    def __init__(self, internal_diam: float) -> None:
        super().__init__(internal_diam)

        # // Esses valores são do Çengel (pg 304, edição de 2007),
        # // para conexões ROSQUEADAS
        self.KL_Dict["01"] = 0.08
        self.KL_Dict["01"] = self.KL_Dict["01"]

    def position_solver(self) -> None:
        return super().position_solver()

    def head_pressure_loss_eq(self, ports_id: Tuple[UUID]) -> sy.Basic:
        return super().head_pressure_loss_eq(ports_id)


class Valve(Component):
    """
    This is a mandatory feature
    """

    def __init__(self): ...


# MARK: Connector
class Connector(MinorLossComponent):
    """
    This is a mandatory feture
    """

    ...


# MARK: T_connector
class T_connector(MinorLossComponent):
    """
    This is a mandatory feture

    This class will be used as an abstraction layer between the Component class and all the classes that will inherit
    from the Y_connector class. This glass will be the basis to create other classes, and those others will be used directly
    by the user
    """

    #! ULTRA IMPORTANTE PRECISO CONSERTAR ESSA FALHA O QUANTO ANTES

    # raise NotImplementedError(f'\n\nPreciso alterar a criação de T urgente. Pelo que eu vi, no Çengel, um T não pode ter 3 diâmetros diferentes.
    #                           Por isso, eu precido fixar a opção pra que todos os diametros sejam iguais, por que o calculo da perda de carga
    #                           localizada só leva em consideração um diâmetro. Caso o usuário queira colocar tres diametros diferentes, ele vai
    #                           precisar plugar expandores em cada uma das saídas do T. Além disso, eu acredito que na vida real deve ser desse
    #                           jeito tbm, já que eu nunca vi um T com 3 diâmetros diferentes')

    def __init__(self, internal_diam: int | float) -> None:
        assert internal_diam is not None
        # assert internal_diam is int or internal_diam is float
        assert internal_diam > 0

        super().__init__(port_quantity=3)

        self.internal_diam: float | int = internal_diam

        self.cross_section_area = (pi * (internal_diam**2)) / 4  # // Measured in m^2

        # * Eu preciso fixar que, cada uma das portas (Virtuais) de um T é, e sempre será uma porta (real) do T
        # * Isso significa que eu tenho que dizer qual é qual. Como geralmente um T é usado para dividir ou
        # *unir duas correntes, eu pensei em fixar que a porta de indice 0 é a porta que está perpendicular à
        # *linha reta do T, como mostrado no desenho a seguir.

        # *  Porta 0 <-----------   Porta 3 (root_port)       -----------> Porta 2
        # *                           ^
        # *                           |
        # *                           |
        # *                           |
        # *                       Porta 1

        # // Esses valores são do Çengel (pg 304, edição de 2007), para conexões FLANGEADAS
        self.KL_Dict["01"] = 1
        self.KL_Dict["10"] = self.KL_Dict["01"]

        self.KL_Dict["21"] = self.KL_Dict["01"]
        self.KL_Dict["12"] = self.KL_Dict["01"]

        self.KL_Dict["02"] = 0.2
        self.KL_Dict["20"] = self.KL_Dict["02"]

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

        # ! The additional port inside the T Component is the root port, and it should be the fourth
        # ! port of the component ports list, and its index inside said list should be 3

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

        r"""
        #ports_location = self.component_input_ports[0].location

        # This block should catch any of the location errors of the ports
        try:
            assert ports_location == self.id

        except AssertionError:
            raise ValueError(f'\nThe location from the ports (={ports_location}) doesn\'t match the ID of the Pipe Object (={self.id =})')

        try:
            for locked_port in self.all_component_ports:
                for any_port in self.all_component_ports:
                    assert locked_port.location == any_port.location
        except AssertionError:
             raise ValueError(f'\nThe location from the ports (={ports_location}) doesn\'t match the location from the ports (={ports_location =})')
        """

        # This part make the Connector instance limited to have the rigth number of components plugged to itself
        # 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)

        """
        # This part make the pipe instance limites to have just one input and output ports on its own body
        if len(self.component_input_ports)>=self.port_quantity:
            self.component_input_ports.pop(self.inputs_quantity)
            
        if len(self.component_output_ports)>=self.outputs_quantity:
            self.component_output_ports.pop(self.inputs_quantity)
        """

    # * @Override
    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 create_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

            self.component_ports[2].spacial_position = self.component_ports[
                0
            ].spacial_position  # type: ignore
            self.component_ports[2].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

            # // This is the root port, or the central port.
            # // This port is the one that will be linked (in the mosaic graph)to every
            # // other ports of the component
            self.component_ports[3].spacial_position = self.component_ports[
                0
            ].spacial_position  # type: ignore
            self.component_ports[3].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

        else:
            raise ValueError(
                f"\n\nThis error comes from the position solver method from the {self.id} component"
            )

    # * @override
    def head_pressure_loss_eq(self, ports_id: MinorPortSelection) -> sy.Basic:
        """
        I do not know how to return the pressure loss equation based on the trajectory chosen by the mosaic

        Maybe i should recieve an argument from thee mosaic for what ports is wants the pressure loss equation.

        The two ports it gives me should be used to know which path is used to calculate the pressure loss.

        Maybe, if the mosaic already knows the cicle it is analysing, i could take the arguments of the T part of the cicle
        and see if the path is the direct or curved path. based in this, i could select between the two pressure loss
        coefficients Ka and Kb.
        """

        try:
            assert len(ports_id) == 2

            for element in ports_id:
                assert isinstance(element, UUID) == True

        except:
            print(f"\n{ports_id = }")
            raise ValueError(
                f"\n\n The position list should only have 2 elements, and those elements must be UUIDs!"
            )

        # // A primeira porta da lista é a porta pela qual o fluido entra (pelo menos é o que eu vou fazer o código
        # // considerar), e a segunda porta é a que o escoamento vai considerar que o fluido sai.

        """
        first_position: Optional[Port] = None

        second_position: Optional[Port] = None

        for port in Port.port_objects:
            if ((port.id == ports_id[0]) and (port.location == self.id)):
                first_position = port

            if ((port.id == ports_id[1] and (port.location == self.id))):
                second_position = port
            
            else:
                pass
        
        position_as_index: List[Optional[int]] = [None, None]

        assert first_position is not None
        assert second_position is not None

        for port_index, port in enumerate(self.component_ports):

            assert port is not None

            if port.id == first_position.id:

                position_as_index[0] = port_index

            if port.id == second_position.id:

                position_as_index[1] = port_index
        
        assert len(position_as_index) == 2
        """

        # * Eu preciso fixar que, cada uma das portas (Virtuais) de um T é, e sempre será uma porta (real) do T
        # * Isso significa que eu tenho que dizer qual é qual. Como geralmente um T é usado para dividir ou
        # *unir duas correntes, eu pensei em fixar que a porta de indice 0 é a porta que está perpendicular à
        # *linha reta do T, como mostrado no desenho a seguir.

        # *  Porta 1 <-----------   (Porta 3)   -----------> Porta 2
        # *                           ^
        # *                           |
        # *                           |
        # *                           |
        # *                       Porta 0

        # assert self.component_ports[0] is not None
        # assert self.component_ports[1] is not None

        first_port_id: sy.Symbol = sy.symbols(str(ports_id[0]))
        second_port_id: sy.Symbol = sy.symbols(str(ports_id[1]))

        # second_port_id: Union[UUID, Equation] = ports_id[1]

        self_hb = sy.Integer(0)  #!str = f'{self.id}' + '_pressure_loss'

        # * pipe_head_pressure_loss_eq = sy.Eq(self_pressure_loss, self.B_constant * first_port_id * abs(first_port_id))

        # print(f"\n{type(self_pressure_loss) = }\n")

        # print(f"\n{type(first_port_id) = }\n")

        # print(f"\n{type((((first_port_id)**2)**(1/2))) = }\n")

        # print(f"\n{type(self.C_constant) = }\n")

        # print(f"\n{type(float(self.C_constant(ports_id = ports_id))) = }\n")

        c: float | int = self.C_constant(ports_id=ports_id)

        # // The equation below should return the head pressure loss as a length, measured in meters.

        # // Using the Fluid Mechanics (Çengel, pg 304), the Q should com from the first port of the
        # // port pair. The V is defined in the calculations of the book as the velocity in the entry
        # // port. In this case, the entry port should be the first one

        t_head_pressure_loss_eq = sy.Eq(
            self_hb, second_port_id * (((second_port_id) ** 2) ** (1 / 2)) * c
        )

        assert t_head_pressure_loss_eq is not None

        return t_head_pressure_loss_eq.rhs


class X_connector(Component):
    """
    This is an optional feature

    Further on this project, i intend to transforma this class in to a tank class, that should have multiple inlets
    and/or outlets

    This class will be used as an abstraction layer between the Component class and all the classes that will inherit
    from the Y_connector class. This glass will be the basis to create other classes, and those others will be used directly
    by the user
    """

    def __init__(self, internal_diam) -> None:
        super().__init__(port_quantity=3)

        self.internal_diam: list[float | None] = 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()

        match self.internal_diam:
            case [x, None, None] if x != 0:
                for n in range(self.port_quantity):
                    self.component_ports.append(
                        Port(
                            geometry="cylinder",
                            characteristic_length=x,
                            location=self.id,
                        )
                    )

            case [x, y, None] if (x != 0) and (y != 0):
                raise ValueError(f"The Three diamters should be decalared in this case")

            case [x, y, z, w] if (x != 0) and (y != 0) and (z != 0) and (w != 0):
                self.component_ports.append(
                    Port(geometry="cylinder", characteristic_length=x, location=self.id)
                )
                self.component_ports.append(
                    Port(geometry="cylinder", characteristic_length=y, location=self.id)
                )
                self.component_ports.append(
                    Port(geometry="cylinder", characteristic_length=z, location=self.id)
                )
                self.component_ports.append(
                    Port(geometry="cylinder", characteristic_length=w, location=self.id)
                )

            case []:
                raise ValueError(f"At least one internal diameter should be determined")
                # self.internal_diam = [None] * self.port_quantity

        r"""
        #ports_location = self.component_input_ports[0].location

        # This block should catch any of the location errors of the ports
        try:
            assert ports_location == self.id

        except AssertionError:
            raise ValueError(f'\nThe location from the ports (={ports_location}) doesn\'t match the ID of the Pipe Object (={self.id =})')

        try:
            for locked_port in self.all_component_ports:
                for any_port in self.all_component_ports:
                    assert locked_port.location == any_port.location
        except AssertionError:
             raise ValueError(f'\nThe location from the ports (={ports_location}) doesn\'t match the location from the ports (={ports_location =})')
        """

        # This part make the Connector instance limited to have the rigth number of components plugged to itself
        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)

        """
        # This part make the pipe instance limites to have just one input and output ports on its own body
        if len(self.component_input_ports)>=self.port_quantity:
            self.component_input_ports.pop(self.inputs_quantity)
            
        if len(self.component_output_ports)>=self.outputs_quantity:
            self.component_output_ports.pop(self.inputs_quantity)
        """


class MeasurementComponent(Component):
    """
    This feature will be the last one in priority order, because i have to finish the others, and if i have time, i'll
    code this one.

    The base of this class should be very simmilar to the base of the connectors or valves, and i can make this class to
    have inheritance from valve and/or from connector. This aproach should help me with the time.
    """


class OneWayValve(Valve):
    r"""
    *Opt Feature!
    """

    pass


class Duct(Pipe):
    r"""
    *Opt Feature!
    """

    pass


class EndCap(Component):
    r"""
    *Opt Feature!
    """

    def __init__(self):
        super().__init__(port_quantity=1)


class Custom_Connector(Component):
    r"""
    *Opt Feature!
    """

    """
    This class will be used to create custom connectors with arbitrary geometry and input/output ports number

    There should be away of determinating the pressure loss coeffiecient for custom and not standardized connectors i.e. 3 input  cilindrical
    ports and 8 output square ports.
    """
    pass
