# *   import sympy as sy
import thermo as thermo
from typing import Optional, Self, Union, List

# import icecream as ice
from uuid import uuid4, UUID

# from . import components
# from . import mosaic
from . import errors
from .data import numerical_constants as nc

"""
Esse arquivo serve pra definir a classe Corrente, que vai ser a classe que gera instancias com propriedades fisicas de uma corrente de um dado fluido.
Algumas dessas propriedadaes são:
- Vazão mássica
- Densidade
- Temperatura
- Pressão
- Velocidade

Algumas dessas propriedades e das demais podem não ser dadas pelo usuario mas, se esse for o caso, deverá existir a opção de a livraria
identificar isso e se ajustar para calcular as variaveis que está faltando no sistema
"""


class Current:
    current_objects: list[Self] = []

    """
        exemplo de composição pura:
        composition = thermo.Chemical('metane')
        
        exemplo de composição de mistura:
        OBS:
        - zs = fração molar
        - ws = fração mássica
        - vfls = fração volumétrica
        composition = thermo.Mixture*=(['metane', 'water'], )


        O Usuário pode criar varias composições e dar diferentes nomes a cada uma delas, e substituir em composition um dos nomes de misturas que eles ja declararam
        ex:
        vodka = thermo.Mixture(['water','etanol'],Vfls = [0.6, 0.4], T=300, P=500)

        corrente_1 = current(name=corrente_1, composition=vodka,)

        Uma coisa que eu preciso acertar é a pressão e a temperatura. Eu preciso definir essa classe pra que ela seja capaz de entender que se o usuario não definiu
        a pressão nem a temperatura, é necessário pegar a pressão e temperatura e a pressão da mistura que foi definida na biblioteca thermo.

        Além disso, eu preciso que, quando o usuário  definir a temperatura e/ou a pressão, a mistura definida na biblioteca thermo precisa assumir os valores definidos
        pelo usuario.

        Uma forma de resolver isso seria deixar por padrão que o usuário defina a temperatura e a pressão da corrente somente na criação do objeto corrente, e nao na
        composição. Eu acredito que essa seria a melhor forma de organizar, por que é possível que o usuário queira utilizar a mesma composição em diferentes correntes,
        e caso ele não defina ambas com temp e press diferentes, é possível que o programa use temp e press iguais para as correntes diferentes, e isso poderia ser um problema
        que pode dar trabalho pra descobrir o motivo.

        Uma forma de simplificar isso seria habilitar a opção de o usuário optar pela forma padrão (com a definição das propriedades somente na criação da corrente), ou pela forma automatica (com a 
        definição das propriedades tanto na composição quanto na criação da corrente).
        Essa opção viria por padrão ativada para a forma padrão, o usuário teria que digitar algum comando para que a forma automatica seja ativada.
        ex: nome_da_livraria.thermo_prop = 'std'
        nome_da_livraria.thermo_prop = 'auto'
    """

    def __init__(
        self,
        composition: thermo.Mixture | thermo.Chemical,
        location: UUID | None = None,
    ) -> None:
        # P: float | int | None = None,
        # T: float | int | None = None,
        self.id: UUID = uuid4()

        self.location: UUID | None = location

        """Preciso encontrar uma forma de linkar a composição com uma livraria de propriedades termodinamicas, por que a
        composição vai servir justamente pra calcular as proprieaddes termodinamicas do fluido.
        """

        self.composition: thermo.Mixture | thermo.Chemical = composition
        self.pressure: float | int | None = (
            self.composition.P if self.composition.P is not None else None
        )

        self.original_pressure: float | int = self.composition.P

        self.temperature: float | int | None = (
            self.composition.T if self.composition.T is not None else None
        )

        # // This attribute declares the id that will be used as unknown inside the algebraic
        # // equations of the piping system (aka, mosaic)
        self.pressure_name: UUID = uuid4()

        # * The mass flow is measured in kg/s
        self.mass_flow: float | None = None

        # *the volume flow is measured in m^3/s
        self.volume_flow: float | int | None = None

        # this propperty is left blank because it will be calculed when and if a sepparated method is called, so that
        # the code doesn't waste time calculating something a long time before it's used
        # self.absolute_viscosity: float

        # The units for the velocity are m/s
        self.velocity: float | int | None = None

        """
        match self.mass_flow, self.volume_flow, self.velocity:
            case None, None, None:
            case None, None, v if v != None:
            case None, vf, v if ((vf != None) and (v != None)):
            case mf, vf, v if ((mf != None) and (vf != None) and (v != None)):
            case mf, None, v:
            case mf, None, None:
            case mf, vf, None:
        """

        """
        This part above full of if's needs to be changed by match case statements for organization and to add more mandatory
        robust functionalities to this class further on the project.

        The match wouldbe on the isinstance(composition, thermo.Chemical) booblean.
        in the case True, a bunch of things would happen, including error handling and pressure, temperature, volume and
        mass flow, density, viscosity and other propperties would be calculated calling different methods from thermo.Chemical
        or from thermo.Mixture.
        """

        if (self.pressure is not None) and (
            (self.mass_flow is not None) or (self.volume_flow is not None)
        ):
            raise errors.ConduitForgeError(f"""The pressure and the flow (volume and/or mass)
                                           should not be declared together in an original inlet
                                           current, because if the pressure is declared, the library
                                           solves the system for the volume flow. And if the volume
                                           flow is declared, i will add the functionality (I do not
                                           know when) so that the library solves the system for
                                           pressure""")

        match isinstance(composition, thermo.Chemical):
            case True:
                # The statement below is used to check if in some point there is phase separation, so that it raises a warning,
                # because the library can not handle more than one phase flow
                if composition.phase != "l":
                    raise Warning(
                        f"""\n{composition.phase = }is happening in some point of the mosaic and the library can not handle multiple phase flow or solid phase"""
                    )

                match self.temperature:
                    case None:
                        self.temperature = composition.T

                    case (t,) if t is int:
                        pass
                    case (t,) if t is float:
                        pass
                    # case t:
                    #     raise ValueError(f' The temperature is neither an int, nor a float, it is in the wrong type {type(t) = } ')

                """        
            case False if (isinstance(composition, thermo.Mixture) == True):
                
                # The statement below is used to check if in some point there is phase separation, so that it raises a warning,
                # because the library can not handle more than one phase flow
                if ('s' in composition.phase_STPs or 'l/g' in composition.phase_STPs or 'g' in composition.phase_STPs):
                    raise Warning(f'{composition.phase_STPs = }is happening in some point of the mosaic and the library can not handle multiple phase flow or solid phase')

               
                #This part needs to be adapted to fit the needs of the mixture object
                

                match self.pressure, self.temperature:
                    case None, None:
                        # Atmosferic Pressure, in Pa
                        self.pressure = composition.P

                        # Tempeerature in Kelvin
                        self.temperature = composition.T

                    case None, x if x != None:
                        self.pressure  = composition.P

                    case y, None if y != None:
                        self.temperature = composition.T

                    case _:
                        raise ValueError(f' The pressure or the temperature is not declared ')
            """

            case False if composition == None:
                raise ValueError("The composition argument must be declared!")

            case False if (
                isinstance(composition, thermo.Mixture) == False
                and isinstance(composition, thermo.Chemical) == False
            ):
                raise ValueError(
                    "The composition must be defined as a Mixture or Chemical object from the Thermo library"
                )

        # this must be the last command inside the Current class __init__, read the Port __init__ documentation to know more.
        Current.current_objects.append(self)

    # @property
    def ab_viscosity(self) -> float:
        """
        This is made so that an alternate composition is created to evaluate the absolute viscosity using the P and T
        from the current, not from the composition.
        """

        alternate_composition: Union[thermo.Mixture, thermo.Chemical] = self.composition
        alternate_composition.T = self.temperature
        alternate_composition.P = self.pressure

        if (self.composition != None) and (hasattr(Current, "ab_viscosity") == False):
            # This part does two things, it sets the atribute with the correct value, but it returns this attribute, so
            # the user can print it in one command, without having to type 2 different comands to receive the value they want

            assert alternate_composition.mu is not None

            absolute_viscosity_mu: float | int = alternate_composition.mu

            setattr(Current, "ab_viscosity", absolute_viscosity_mu)

            assert absolute_viscosity_mu is not None

            # * the viscosity is given in Pa/s
            return round(absolute_viscosity_mu, 6)

        # this part should check if the propperty already exists
        elif hasattr(Current, "ab_viscosity") == True:
            # In a simmilar forma of the if above, this part sets the new value if needed and return it to the user

            old_viscosity: float = getattr(Current, "ab_viscosity")

            # if this method was already used and the current or its propperties where changed, the part below will check
            # and return the 'new' viscosity if needed

            if old_viscosity != alternate_composition.mu:
                self._ab_viscosity: float = alternate_composition.mu
            else:
                self._ab_viscosity: float = old_viscosity

            return round(self._ab_viscosity, 6)
        else:
            raise ValueError(
                f"The composition ({type(self.composition) = }) must be declared as a thermo.Chemical or thermo.Mixture object!"
            )

    # @property
    def density(self):
        """
        This method is used to calculate the density in the same way the absolute_viscosity is calculed
        """

        alternate_composition = self.composition
        alternate_composition.T = self.temperature
        alternate_composition.P = self.pressure

        if (self.composition is not None) and (not hasattr(Current, "density")):
            # This part does two things, it sets the atribute with the correct value, but it returns this attribute, so
            # the user can print it in one command, without having to type 2 different comands to receive the value they want

            dens: float = alternate_composition.rho

            setattr(Current, "density", dens)

            # the viscosity is given in Pa/s
            return round(dens, 2)

        # this part should check if the propperty already exists
        elif hasattr(Current, "density") == True:
            # In a simmilar forma of the if above, this part sets the new value if needed and return it to the user

            old_density: float = getattr(Current, "density")

            # if this method was already used and the current or its propperties where changed, the part below will check
            # and return the 'new' viscosity if needed
            if old_density != alternate_composition.rho:
                self._density: float = alternate_composition.rho
            else:
                self._density: float = old_density

            return round(self._density, 2)
        else:
            raise ValueError(
                f"The composition ({type(self.composition) = }) must be declared as a thermo.Chemical or thermo.Mixture object!"
            )

    @classmethod
    def generic_current(cls, port_id) -> "Current":
        """
        This method should be used to create generic Current objects to populate component ports
        in the moment that the objects are created.

        The standard temperature and pressure are the Standard Temperature and Pressure (P = 101_325, T = 273.15)
        """

        # pure_water = thermo.Chemical(
        #     "water", T=298.15, P=nc.absolute_atm_pressure
        # )  # temperature in Kelvin

        pure_water = nc.pure_water
        return Current(composition=pure_water, location=(port_id))

    """

    The current and the Component classes can not have the __add__ method at the same time.
    This happens because python would run just one of them, and not read the __radd__ that is declared in the component
    class. Because of that, the verifications of type and availability should be made in just one element of the addition,
    or inside the mosaic, right after it's creation.

    def __add__(self,other) -> None:
        
        # Check if two Current instances are being added
        if isinstance(other, Current) == True:
            raise TypeError(f'Two Curret instances can not be added')

        elif isinstance(other, mosaic.Mosaic) == True:
            raise TypeError(f'A Current object can\'t be added to a Mosaic object')

        elif isinstance(other, components.Component) == True
    """

    """
    def __radd__(self,other):

        # Check if two Current instances are being added
        if isinstance(other, Current):
            raise TypeError('Two Curret instances can not be added')

        pass
    """
