# -*- coding: utf-8 -*-
"""
Created on Mon May 22 21:09:12 2023

@author: atakan
"""

import os
import copy
import ctREFPROP.ctREFPROP as ct
from ctREFPROP.ctREFPROP import REFPROPFunctionLibrary
import numpy as np
import carbatpy
from dataclasses import dataclass, field
from typing import Optional, List

VERBOSE = False
DEFAULTS = carbatpy.CB_DEFAULTS
TREND = DEFAULTS['Fluid_Defaults']['TREND']

# verify installation
if TREND["TREND_INSTALLED"]:
    try:
        import TRENDfluid.fluid as tr_fl  # TREND fluids
    except ImportError as e:
        print(f"Import error for 'TRENDfluid.fluid': {e}")

_PROPS = DEFAULTS["Fluid_Defaults"]['PROPS']  # "REFPROP"  # or "CoolProp"

_fl_properties_names = DEFAULTS["Fluid_Defaults"]['Property_Names']
_fl_properties_names_trend = DEFAULTS["Fluid_Defaults"]['Property_Names_Trend']
_THERMO_STRING = DEFAULTS["Fluid_Defaults"]['THERMO_STRING']
# careful density not volume
_THERMO_TREND = request = DEFAULTS["Fluid_Defaults"]['THERMO_TREND']
_TRANS_STRING = DEFAULTS["Fluid_Defaults"]['TRANS_STRING']
_TRANS_TREND = DEFAULTS["Fluid_Defaults"]['TRANS_TREND']
_TRANS_TREND_MIX = DEFAULTS["Fluid_Defaults"]['TRANS_TREND_MIX']

@dataclass
class FluidModelDefaults:
    units = 21
    props = DEFAULTS["Fluid_Defaults"]['PROPS']
    rp_inst = False
    # TREND
    eos_ind: Optional[List[int]] = None
    eos_ind_default: int = 1
    mix_ind = 1
    trendmodel = ""
    path = carbatpy.CB_DEFAULTS['Fluid_Defaults']['TREND']["TREND_PATH"]
    dll_path = carbatpy.CB_DEFAULTS['Fluid_Defaults']['TREND']["TREND_DLL"]
    # CUSTOM
    props_func = None


class FluidModel:
    """
    Fluid model to be used Refprop or TREND

    Only model, chemical compunds units and for TREND some more values are set here. The
    mixture composition, state etc. is set in Fluid.
    """

    def __init__(self, fluid, composition=[1], **kwargs):
        """
        For Class Fluidmodell a fluid (mixture) must be defined, the evaluation
        takes place with props, units can be set and an instance can be set,
        the latter is important, if more than one fluid is used.
        For Refprop: Initiates new instance for each fluid (mixture).

        Parameters
        ----------
        fluid : String
            as defined in the props (Model), for RefProp it is
            "fluid1 * fluid2 * fluid3".
        composition: array
            of working fluid (mixture), at this level only used for TREND
        **kwargs: dict
            units, props, rp_inst, eos_ind, mix_ind, trendmodel, props_func
            (see dataclass FluidModelDefaults). For props="CUSTOM", props_func must be
            a callable with signature func(values, given) -> list[T, P, H, V, S, Q, U, ...]
            returning SI values (K, Pa, J/kg, m³/kg, J/kg/K, -, J/kg).


        Returns
        -------
        None.

        """

        cfg = FluidModelDefaults()
        for k, v in kwargs.items():
            if not hasattr(cfg, k):
                raise TypeError(f"Unknown keyword '{k}' for FluidModel")
            setattr(cfg, k, v)

        self.fluid = fluid
        self.props = cfg.props
        self.units = cfg.units
        if self.props == "TREND":
            self.comp = composition
            if cfg.eos_ind is None:
                self.eos_ind = [cfg.eos_ind_default] * len(self.comp)
            else:
                self.eos_ind = list(cfg.eos_ind)  # Kopie
            self.mix_ind = cfg.mix_ind
            self.trendmodel = cfg.trendmodel
            self.trend_path = cfg.path
            self.dll_path = cfg.dll_path
        if self.props == "REFPROP":
            if cfg.rp_inst == False:
                self._init_default_rp_instance()
            elif cfg.rp_inst == True:
                self._new_rp_instance()
            else:
                raise ValueError("Unknown instance flag. Chose 0 or 1.")
        elif self.props == "TREND":
            self._set_tr_fluid()
        elif self.props == "CUSTOM":
            if cfg.props_func is None:
                raise ValueError("For props='CUSTOM', a callable 'props_func' must be provided.")
            self.props_func = cfg.props_func

    def _init_default_rp_instance(self):
        if "RPPREFIX" not in os.environ:
            raise EnvironmentError("Environment variable 'RPPREFIX' is not set.")
        self.fl_instance = REFPROPFunctionLibrary(os.environ['RPPREFIX'])
        self.fl_instance.SETPATHdll(os.environ['RPPREFIX'])
        ierr = self.fl_instance.SETFLUIDSdll(self.fluid)
        if ierr != 0:
            print(f"Error in setfluid {ierr}")
            print(self.fl_instance.ERRMSGdll(ierr))

    def _new_rp_instance(self):
        """
        A new instance of Refpropdll for the given fluid. Requires installation of REFPROPLibraryManager.
        REFPROPLibraryManager enables threadsafe handling of different fluids.
        More information: https://github.com/usnistgov/REFPROP-manager
        """

        try:
            manager = ct.REFPROPLibraryManager(os.environ["REFPROPLibraryManager"])
        except:
            raise ValueError("REFPROPLibraryManager could not be imported.")

        self.fl_instance = manager.get_instance(os.environ['RPPREFIX'], "REFPRP64.dll")

        if self.fl_instance.managed_handle >= 100:
            raise Warning(">100th Refprop instance has been created. Working memory overflow expected shortly.")

        self.fl_instance.SETPATHdll(os.environ['RPPREFIX'])
        ierr = self.fl_instance.SETFLUIDSdll(self.fluid)
        if ierr != 0:
            print(f"Fehler in setfluid {ierr}")
            print(self.fl_instance.ERRMSGdll(ierr))

    def _set_tr_fluid(self):
        """ Set the state of a fluid in TREND. """

        self.fluid = self.fluid_to_list()
        if self.units == 21:
            self.units = "specific"
        self._trend_sanity_check()

        trend_path_incl_subpath = self.trend_path + self.trendmodel
        _trend_dict = {"Input": 'HP', # just for initialization
                           'calctype': 'S', # just for initialization
                           'fluids': self.fluid,
                           "moles": self.comp,
                           "eos_ind": self.eos_ind,
                           'mix_ind': self.mix_ind,
                           'path': trend_path_incl_subpath,
                           'unit': self.units,
                           'dll_path': self.dll_path,
                           'libhandle': None}

        self.fl_instance = tr_fl.Fluid(*_trend_dict.values())
        err_flag = self.fl_instance.errorflag.value
        if err_flag > 0:
            print(f"fluid-Trend problem, errorflag:{err_flag}")
        return self.fl_instance

    def _trend_sanity_check(self):
        """
        checks if length of composition matches fluid string and length of eos_ind
        """
        n_compounds = len(self.fluid)

        if len(self.comp) != n_compounds:
            raise ValueError(
                f"Composition array wrong length, must be {n_compounds}, got {len(self.comp)}"
            )
        if len(self.eos_ind) != n_compounds:
            raise ValueError(
                f"Eos_ind array wrong length, must be {n_compounds}, got {len(self.eos_ind)}"
            )
        if self.trendmodel != "":
            if 1 not in self.eos_ind:
                raise ValueError(f"Verify TREND inputs: eos_ind {self.eos_ind} and trendmodel {self.trendmodel} both set.")
            if self.mix_ind != 1:
                raise ValueError(f"Verify TREND inputs: mix_ind {self.mix_ind} and trendmodel {self.trendmodel} both set.")
    def fluid_to_list(self):
        """
        Converts the fluid names string from Refprop to a list for TREND

        Returns
        -------
        List of strings
            the fluid names in a list.

        """

        no_blank = self.fluid.replace(" ", "")
        return no_blank.split("*")


class FluidState:
    """
    The thermodynamic state of a fluid is stored here.

    Which properties (T,p,h,v,s,q,...), depends on:
    carbatpy.CB_DEFAULTS["Fluid_Defaults"]["Property_Names_Short"]
    """

    def __init__(self, state, what, **kwargs):
        self.props = kwargs.get("props", _PROPS)
        self. verbose = kwargs.get("verbose", False)
        self.liq_x = kwargs.get("x", None)
        self.vap_y = kwargs.get("y", None)
        self.total_z = kwargs.get("z", None)

        if self.props == "REFPROP":
            self.no_val = what.count(";")+1
        elif self.props in ("TREND", "CUSTOM"):
            self.no_val = len(state)
        if self.props in ('REFPROP', 'CUSTOM'):
            names = carbatpy.CB_DEFAULTS["Fluid_Defaults"]["Property_Names_Short"]
        elif self.props == 'TREND':
            names = carbatpy.CB_DEFAULTS["Fluid_Defaults"]["Property_Names_Short_Trend"]
        self.state = state[:self.no_val]
        for ii, key in enumerate(names.keys()):
            if ii < self.no_val:
                setattr(self,key , state[ii])



    def state_to_dict(self):
        """Convert the state properties to a dictionary name:value."""
        if self.props in ('REFPROP', 'CUSTOM'):
            return_dict = dict(
                zip(_fl_properties_names[:self.no_val], self.state[:self.no_val]))
        elif self.props == 'TREND':
            return_dict = dict(
                zip(_fl_properties_names_trend[:self.no_val], self.state[:self.no_val]))
        return_dict["x"] = self.liq_x
        return_dict["y"] = self.vap_y
        return_dict["z"] = self.total_z
        return return_dict


class Fluid:
    """
    The Fluid class is used to set, get, and print states of  a fluid with a given

    model (e.g. RefProp). The compounds are set in the fluidmodel, while the
    composition is also set here. The calculated values depend also on
    carbatpy.CB_DEFAULTS["Fluid_Defaults"]["Property_Names_Short"].

    """

    def __init__(self, fluidmodel, composition=None, option=1):
        """
        Generate a Fluid instance

        a FluidModel instance, the composition and an unused option has to be passed.

        Parameters
        ----------
        fluidmodel : FluidModel
            the instance of the property model to be used.
        composition : list, optional
            mole fractions of all compounds. The default is [1.0].
        option : TYPE, optional
            unused. The default is 1.

        Returns
        -------
        None.

        """

        self.fluidmodel = fluidmodel
        if composition is None:
            composition = [1.0]
        self.composition = copy.copy(composition)
        self.properties = None
        self.state_v = None
        self.option = option
        self.no_compounds = len(composition)
        self.comp_mass = np.zeros(self.no_compounds)
        self.herr = 0
        self.props = fluidmodel.props
        # BA something is wrong here (probably 2024-07-10
        self.set_composition(composition)
        self.val_dict = {}

    def set_composition(self, composition, **kwargs):
        """
        Mole fractions are set and mass fractions are calculated.

        Sets the self.composition used in the Fluid settings and the self.comp_mass
        TREND needs the mass fractions for specific (/kg) calculations. Thus
        calc_mass_fraction() is called.

        Parameters
        ----------
        composition : list/numpy.array
            mole fractions must sum up to 1.
        kwargs : dictionary, optional.
            - verbose : boolean, optional. Generate additional printig. The default is
              False.

        Returns
        -------
        None.

        """

        verbose = kwargs.get("verbose", False)
        if verbose:
            print("set_comp", composition, self.composition, self.comp_mass)
        self.composition = composition
        if self.fluidmodel.props == "TREND":
            self.comp_mass = np.zeros((self.no_compounds))
            self.calc_mass_fraction()
            self.fluidmodel.fl_instance.set_moles(self.comp_mass)
            # here is the mass fraction to internal mol fraction conversion
        elif self.fluidmodel.props == "REFPROP":
            self.comp_mass = self._rp_mass_from_mole(composition)
        if verbose:
            print("set_comp-after", composition, self.composition, self.comp_mass)

    def calc_mass_fraction(self):
        """ mole to mass fractions for TREND"""

        mass_frac = np.zeros(self.no_compounds)
        for n_c in range(self.no_compounds):
            mass_frac[n_c] = self.fluidmodel.fl_instance.get_mw(n_c) * \
                             self.composition[n_c]
        self.comp_mass = mass_frac/mass_frac.sum()
        return self.comp_mass

    def calc_mole_fraction(self, comp_mass):
        """
        needed for x_liq and y_vap in TREND, therefore mass fraction passed as function argument.
        """
        mole_frac = np.zeros(self.no_compounds)
        for n_c in range(self.no_compounds):
            mole_frac[n_c] = comp_mass[n_c] / self.fluidmodel.fl_instance.get_mw(n_c)
        comp_mole = mole_frac/mole_frac.sum()
        return comp_mole

    def set_state(self, values, given="TP", wanted=_THERMO_STRING, composition=None,
                  **kwargs):
        """
        Sets the state of a fluid and calculates the wanted properties.

        Parameters
        ----------
        values : list[2]
            the two state parameter values.
        given : String, optional
            What are the two values (T,P,H,S,Q,V). The default is "TP".
        wanted : String, optional
            Which parameters shall be calculated? The default is _THERMO_STRING.
        composition : list, optional
            mole fraction of each compound, must sum up to 1. When empty, the last/actual
            value  will be used. The default is [].
        kwargs : dictionary, optional
            -dll_select : is passed to Refprop.
             dll_select is the only important one the standard value is "2dll" using the
             faster REFPROP2dll function, not calculating the composition of different
             phases. The alternative "dll" is slower but does it. Throughout it is expected that
             compoitions are mole fractions and qualities in mass/mass.
           - i_mass : Flag (0/1), optional, should not be used,  not teste.
           - i_flag : Flag (0/1), optional, should not be used,  not tested.
           - verbose : Boolean, optional, leads to additional (printing) output. The default
             value is False.
           - output : String, optional, what shall be the return value, the "state" as list
             or an instance of the "FluidState". default is "state"



        Raises
        ------
        Exception
            DESCRIPTION.

        Returns
        -------
        TYPE
            DESCRIPTION.
        """

        composition = self._update_composition(composition)
        values = [_scalar(values[0]), _scalar(values[1])]
        self. output =kwargs.get("output", "state")

        if self.fluidmodel.props == "REFPROP":
            self._set_state_refprop(values, given, wanted, **kwargs)
        elif self.fluidmodel.props == "TREND":
            if wanted == _THERMO_STRING:
                wanted = _THERMO_TREND
            self._set_state_trend(values, given, wanted, **kwargs)
        elif self.fluidmodel.props == "CUSTOM":
            self._set_state_custom(values, given, wanted, **kwargs)
        else:
            raise NotImplementedError(
                f"Property model {self.fluidmodel.props} not implemented yet!")
        if self.output =="FluidState":
            return self.properties
        return np.array([*self.properties.state])

    def _update_composition(self, composition):
        if composition is None:  # mole fractions only!
            if self.props =='TREND':
                return self.comp_mass
            else:
                return self.composition

        self.set_composition(composition)  # important for trend mass->mole
        if VERBOSE:
            print("comp-set", composition, self.composition,
                  self.comp_mass, self.composition)
        return composition

    def _set_state_refprop(self, values, given, wanted, **kwargs):
        i_mass = kwargs.get("iMass", 0)
        i_flag = kwargs.get("iFlag", 0)  # if this is 1 most tests fail!
        dll_select = kwargs.get("dll_select", "2dll") # "2dll"
        verbose = kwargs.get("verbose", False)

        if given.count("Q") > 0:
            q_act = values[given.find("Q")]
            if 0 < q_act < 1:  # only then x and y are meaningful/needed
                dll_select = "dll"

        if self.fluidmodel.fl_instance.managed_handle == None:
            # single REFPROP instance, thus fluid string needs to be forwarded
            fluid = self.fluidmodel.fluid
        else:
            # properly set instance is used, thus faster calls by omitting fluid string evaluation
            fluid = ""
        match dll_select:  # noqa
            case "2dll":
                i_mass = 0
                state = self.fluidmodel.fl_instance.REFPROP2dll(
                    fluid, given, wanted,
                    self.fluidmodel.units,
                    i_flag, values[0], values[1],
                    copy.copy(self.composition)
                )
                # additional calls implemented that are used if flash calculation failed (error code 226)
                # which may happen close to saturation lines. flag 1 is single phase assumption, flag 2 is two-phase assumption
                if state.ierr == 226:
                    state = self.fluidmodel.fl_instance.REFPROP2dll(
                        fluid, given, wanted,
                        self.fluidmodel.units,
                        2, values[0], values[1],
                        copy.copy(self.composition)
                    )
                    if verbose:
                        print("iflag 2 called")

                if state.ierr == 226:
                    state = self.fluidmodel.fl_instance.REFPROP2dll(
                        fluid, given, wanted,
                        self.fluidmodel.units,
                        1, values[0], values[1],
                        copy.copy(self.composition)
                    )
                    if verbose:
                        print("iflag 1 called")
            case "dll":
                # super fishy, imass usage in REFPROPdll from REFPROPFunctionLibrary most likely erroneous
                # imass = 0 is supposed to use composition and quality on molar basis and does so
                # imass = 1 is supposed to use composition and quality on mass basis, but uses composition on molar basis!
                # really messy: REFPROPLibraryManager seems to use imass correctly

                i_mass = 1
                if self.fluidmodel.fl_instance.managed_handle != None:
                    to_use_composition = copy.copy(self.comp_mass)
                else:
                    to_use_composition = copy.copy(self.composition)

                state = self.fluidmodel.fl_instance.REFPROPdll(
                    fluid, given, wanted,
                    self.fluidmodel.units,
                    i_mass,
                    i_flag, values[0], values[1],
                    to_use_composition
                )

                if verbose:
                    print(state.z[:5], self.composition, self.comp_mass)
            case _:
                raise ValueError(f"This iMass value is not allowed {i_mass}")

        if state.ierr == 0: # for estimated mixing parameters, add: or state.ierr == -117:
            state_val = state.Output
            z_act = state.z[:self.no_compounds]
            x_act = y_act = None

            if state.q > 0 and state.q < 1:  # mole -> mass quality problem of Refprop
                # state_val[5] = state.q
                if dll_select == "dll":
                    x_act = state.x[:self.no_compounds]
                    y_act = state.y[:self.no_compounds]
                    if self.fluidmodel.units == 21:  # convert back to mole fractions
                        x_act = np.array(self._rp_mole_from_mass(state.x))
                        y_act = np.array(self._rp_mole_from_mass(state.y))
                        z_act = np.array(self._rp_mole_from_mass(state.z))
            self.properties = FluidState(state_val, wanted, x=x_act, y=y_act, z=z_act)
            self.val_dict = self.properties.state_to_dict()

        else:
            self.herr = state.herr
            raise NotImplementedError(f"Property-Refprop problem: {state.herr}, {state.ierr}!")
    def _rp_mass_from_mole(self, composition):
        return self.fluidmodel.fl_instance.XMASSdll(composition).xkg[:self.no_compounds]

    def _rp_mole_from_mass(self, composition):
        return self.fluidmodel.fl_instance.XMOLEdll(composition).xmol[:self.no_compounds]

    def _set_state_trend(self, values, given, wanted, **kwargs):
        verbose = kwargs.get("verbose", False)
        self.fluidmodel.fl_instance.set_moles(self.comp_mass)

        if verbose:
            print("in TREND set state")

        values, given = self._adjust_trend_values(values, given)
        all_values = self.fluidmodel.fl_instance.ALLPROP(given, values[0], values[1])
        all_val_initial = copy.copy(all_values)
        phase_flag = (self.no_compounds > 1 and 1 > all_values["QEOS"] > 0)

        if phase_flag:
            self.fluidmodel.fl_instance.set_moles(self.comp_mass)
            self.fluidmodel.fl_instance.set_input("TP")
            res_flash = self.fluidmodel.fl_instance.FLASH(all_values["T"], all_values["P"])
            phasetype  = list(res_flash["phasetype"])  # codes: 1=VAP, 2=LIQ1, 3=LIQ2
            x_phase    = np.array(res_flash["x_phase"])
            x_phase = x_phase[:, :self.no_compounds]
            ivap = phasetype.index(1)
            if 2 in phasetype: #liquid marked either as lighter or heavier liquid phase
                iliq = phasetype.index(2)
            elif 3 in phasetype:
                iliq = 2

            vap_y = self.calc_mole_fraction(x_phase[ivap, :])
            liq_x = self.calc_mole_fraction(x_phase[iliq, :])
            if all_values["QEOS"] > 0: #transfer q_mole to q_mass
                all_values["QEOS"] = self.calc_q_mass(all_values["QEOS"],liq_x, vap_y)

        if all_values["T"] > 0:  # on error it is -8888
            all_values["P"] = all_values["P"] * 1e6  # MPa
            all_values["D"] = 1 / all_values["D"]  # V (spec. volume)

            all_values = [all_values[key] for key in _THERMO_TREND]
            if len(wanted) > len(_THERMO_TREND):

                trans_dict = {}
                speed_sound = all_val_initial["WS"]
                for tr_want in wanted[len(_THERMO_TREND):]:
                    self.fluidmodel.fl_instance.set_moles(self.comp_mass)
                    self.fluidmodel.fl_instance.set_calctype(tr_want)
                    self.fluidmodel.fl_instance.set_input(given)

                    trans_dict[tr_want], err = self.fluidmodel.fl_instance.TREND_EOS(
                        *values)
                    if verbose:
                        print(tr_want, ":", trans_dict[tr_want], err)
                    if trans_dict[tr_want] < 0:
                        print(f"WARNING: Problem / NOT IMPLEMENTED IN TREND! {tr_want}")
                try:
                    trans_dict['ETA_RES'] = trans_dict['ETA_RES'] * 10**-6 #Pa s
                except:
                    pass
                try:
                    trans_dict['ETA_ECS'] = trans_dict['ETA_ECS'] * 10**-6 #Pa s
                except:
                    pass
                try:
                    trans_dict['ETA_BERLIN'] = trans_dict['ETA_BERLIN'] * 10**-6 #Pa s
                except:
                    pass
                try:
                    trans_dict['ST'] = trans_dict['ST'] / 10 ** 3 #TREND gives back 10 ** 3 N/m
                except:
                    pass
                all_values.extend(trans_dict.values())

            if phase_flag:
                self.properties = FluidState(all_values, wanted, props="TREND", x=liq_x, y=vap_y)
            else:
                self.properties = FluidState(all_values, wanted, props="TREND")

            self.val_dict = self.properties.state_to_dict()
        else:
            raise NotImplementedError(
                f"property calculation in TREND failed, probably not implemented please look up the TREND error code : {all_values['T']}")

    def _set_state_custom(self, values, given, wanted=_THERMO_STRING, **kwargs):
        """
        Evaluate fluid properties using a user-supplied callable (props="CUSTOM").

        The callable stored in self.fluidmodel.props_func is called with the two
        input values and the 'given' string (e.g. "TP", "PH") and must return a
        list or array of property values in SI units (mass basis) in the order:
            [T (K), P (Pa), H (J/kg), V (m³/kg), S (J/kg/K), Q (-), U (J/kg)]
        Transport properties may follow optionally in the same order as
        CB_DEFAULTS["Fluid_Defaults"]["Property_Names_Short"].

        Parameters
        ----------
        values : list[float, float]
            The two thermodynamic input values.
        given : str
            Identifier for the two inputs (e.g. "TP", "PH", "TQ").
        wanted : str, optional
            Which parameters shall be calculated? The default is _THERMO_STRING.

        The callable receives additionally:
            fluid       = self.fluidmodel.fluid   (str)
            composition = self.composition        (list)
            wanted      = wanted                  (str)
        as keyword arguments, so its signature should be
        ``func(values, given, fluid=..., composition=..., wanted=...)``.
        """
        state = list(self.fluidmodel.props_func(values, given,
                                                wanted=wanted,
                                                fluid=self.fluidmodel.fluid,
                                                composition=self.composition))
        self.properties = FluidState(state, wanted, props="CUSTOM")
        self.val_dict = self.properties.state_to_dict()

    def calc_q_mass(self, q, liq_x, liq_y):
        vap_mass_i = np.zeros(self.no_compounds)
        liq_mass_i = np.zeros(self.no_compounds)
        for n_c in range(self.no_compounds):
            vap_mass_i[n_c] = self.fluidmodel.fl_instance.get_mw(n_c) * \
                             liq_y[n_c]
            liq_mass_i[n_c] = self.fluidmodel.fl_instance.get_mw(n_c) * \
                                liq_x[n_c]
        q_mass = q * vap_mass_i.sum() / (q* vap_mass_i.sum() +( 1-q)*liq_mass_i.sum())
        return q_mass

    def _adjust_trend_values(self, values, given):
        pressure_in = given[:2].find("P")
        quality_in = given[:2].find("Q")
        temp_in = given[:2].find("T")

        if pressure_in >= 0:
            values[pressure_in] = values[pressure_in] / 1e6  # MPa in Trend
        if quality_in > 0:
            if pressure_in >= 0:
                values, given = self._adjust_values_for_quality_pressure(
                    values, quality_in, pressure_in)
            elif temp_in >= 0:
                values, given = self._adjust_values_for_quality_temperature(
                    values, quality_in, temp_in)
            else:
                raise NotImplementedError(
                    f'Error: Qualities cannot be combined with {given}!')
        return values, given

    def _adjust_values_for_quality_pressure(self, values, quality_in, pressure_in):
        match values[quality_in]:
            case 0.:
                given = "PLIQ"
            case 1.:
                given = "PVAP"
            case _:
                raise ValueError('Error: only qualities 0 or 1 for mixtures in TREND!')
        values[0] = values[pressure_in]
        values[1] = 10
        return values, given

    def _adjust_values_for_quality_temperature(self, values, quality_in, temp_in):
        match values[quality_in]:
            case 0.:
                given = "TLIQ"
            case 1.:
                given = "TVAP"
            case _:
                raise ValueError('Error: only qualities 0 or 1 for mixtures!')
        values[0] = values[temp_in]
        values[1] = 10
        return values, given

    def set_state_v(self, values, given="TP", wanted=_THERMO_STRING, **kwargs):
        """
        set many Fluid states with the values given as array(vector)

        Parameters
        ----------
        values : numpy.array((N,2))
            the N value pairs to fix the states.
        given : String, optional
            what are the input values?(T,P, Q, S,...). The default is "TP".
        wanted : String, optional
            with all the outputs wanted. The default is _THERMO_STRING.
        kwargs : dictionary optional
            This is optional and passed to REFPROP or TREND, depending on the props
            settings / chosen property model. (see set_state())


        Returns
        -------
        output : numpy.array(N, n_wanted)
            all the wanted properties for the N inputs.

        """
        dimension = np.shape(values)
        number_wanted = wanted.count(";")+1
        output = np.zeros((dimension[0], number_wanted))
        for count, value in enumerate(values):
            output[count, :] = self.set_state(value, given, wanted, **kwargs)
        self.state_v = output
        return output



    def print_state(self):
        """ Print the actual state of the Fluid"""
        pr = self.properties
        flmod = self.fluidmodel
        print(f"\n{flmod.fluid}, composition: {self.composition}")

        if pr.quality >= 0:
            print(f"Quality: {pr.quality :.3f}")
        for key, value in self.val_dict.items():
            print(f"{key}    : {value}")

    def calc_temp_mean(self, h_final):
        """
        Calculate the thermodynamic mean temperature between the actual state

        and the final enthalpy along an isobaric (Delta h /Delta s)

        Parameters
        ----------
        h_final : float
            enthalpy of he final stat.

        Returns
        -------
        temp_mean : float
            the thermodynamic mean temperature.

        """

        actual_props = self.properties.state
        final_props = self.set_state([h_final, actual_props[1]], "HP")
        temp_mean = (final_props[2] - actual_props[2]) \
            / (final_props[4] - actual_props[4])
        return temp_mean


def init_fluid(fluid, composition, **keywords):
    """
    short way to define a Fluid and a FluidModel

    Parameters
    ----------
    fluid : string
        The species within the fluid.
    composition : List
        mole fraction for each fluid.
    **keywords : TYPE
        all keywords needed for the FluidModel, if non-defaults shall be set.

    Returns
    -------
    actual_fluid : Fluid
        Instance of the actually set Fluid.

    """

    flmod = FluidModel(fluid, composition, **keywords)
    actual_fluid = Fluid(flmod, composition)
    return actual_fluid

def _scalar(x):
    x = np.asarray(x)
    if x.size != 1:
        raise ValueError(f"Expected scalar, got shape {x.shape}")
    return float(x.reshape(()))


if __name__ == "__main__":
    import time

    FLUID = "Propane * Pentane"
    comp = [.50, 0.5]
    P_ACT = 1e5
    T_ACT = 370
    flm = FluidModel(FLUID)
    myFluid = Fluid(flm, comp)
    st00 = myFluid.set_state([T_ACT, P_ACT], "TP")
    st1 = myFluid.set_state([T_ACT, P_ACT], "TP",
                            _TRANS_STRING)
    print(st00, st1)
    myFluid.print_state()
    myFluid.set_composition([.2, .8])
    t0 = time.time()
    for ii in range(500):
        st0 = myFluid.set_state([T_ACT, P_ACT], "TP", composition=[.35, .65])
    t1 = time.time()
    myFluid.print_state()

    mean_temp_act = myFluid.calc_temp_mean(st0[2] + 1e5)
    print(f"Mean Temperature {mean_temp_act} K")

    # for testing water:
    water = init_fluid("H2O", [1.0])
    T_WATER = 300.

    water.set_state([T_WATER, P_ACT], "TP", _TRANS_STRING)
    print(f"""\nWater at {T_WATER} K, {P_ACT / 1e5} bar.
              Cp ={water.val_dict['isobaric_heat_capacity']} ,
              Pr: {water.val_dict['Prandtl_number']}\n""")

    # value_vec = np.array([[300, 1e5], [400, 1e5], [500, 1e5]])
    # stv = myFluid.set_state_v(value_vec, "TP")

    # print(myFluid.set_state_v(value_vec, "TP"))
    # print(myFluid.set_state([300., 1.], "TQ"))

    # New simple way to get an instance of Fluid
    myFluid2 = init_fluid(FLUID, comp)

    # Refprop with mass fractions as input
    print('\n---------------- Refprop Mass fraction Input')
    FLUID = "Propane * Pentane"
    comp = [.5, .5]
    myFluid = init_fluid(FLUID, comp)
    myFluidb = init_fluid("H2O", [1])

    QUALITY = 0.5  # is interpreted by carbatpy as mol/mol to calculate state
    # but given back in string as kg/kg
    ta0 = time.time()
    for ii in range(7):
        stold = st0
        st0 = myFluid.set_state([300., QUALITY], "TQ")  # , dll_select="dll")
        sth2o = myFluidb.set_state([310, 1e5], "TP")
        # print (st0-stold)
        stold = st0
    ta1 = time.time()

    print(f"Durations all: {ta1 - ta0}, 2dll: {t1 - t0}")
    myFluid.print_state()
    st0n = myFluid.set_state([st0[1], st0[2]], "PH", dll_select="dll")
    myFluid.print_state()

    print(f"quality prescribed: {QUALITY}")
    print(f"resulting quality from carbatpy in st0: {st0[5]}")

    print("\n------------\n")


    # Trend: --------------------------------------
    my_dict = {#"Input": "TP",
               #'calctype': "H",
               #'fluids': FLUID,
               #"moles": comp,
               "eos_ind": [1, 1],
               'mix_ind': 1,
               #'path': trend_path,
               'units': 'specific',
               #'dll_path': trend_dll
        }
    comp =[.5,0.5]

    my_tr_fl = init_fluid(FLUID, composition=comp, props="TREND")#, **my_dict)
    # [0.6206645653468038, 0.3793354346531962])
    state_trend = my_tr_fl.set_state([T_ACT, P_ACT], "TP")
    print("\nComparison Refprop vs. Trend\n", st00, "\n", state_trend,
          my_tr_fl.properties.enthalpy)
    state_tr_trans = my_tr_fl.set_state([T_ACT, P_ACT], "TP", wanted=_TRANS_TREND_MIX)
    print(f"Trans-String TREND: {state_tr_trans}")

    # shortcut to generate a trend fluid
    flt0 = carbatpy.init_fluid(FLUID, comp, props="TREND", **my_dict)

    # saturated mixture:
    state_trend_satp = my_tr_fl.set_state([P_ACT, 0], "PQ")
    print("\nSaturated mixture, liq:\n", state_trend_satp, my_tr_fl.properties.enthalpy)
    state_trend_satp = my_tr_fl.set_state([P_ACT, 1], "PQ")
    print("Saturated mixture, vap:\n", state_trend_satp, my_tr_fl.properties.enthalpy)

    # TREND two-phase
    print("---------------------two-phase TREND------------------------------")
    state_2p = my_tr_fl.set_state([267, 1e5], "TP")
    my_tr_fl.print_state()

    # some temperature dependence
    state_trend_c = my_tr_fl.set_state([267., P_ACT], "TP")

    enth = np.linspace(state_trend_c[2], state_trend[2], 25)
    all_props_act = []
    for h_act in enth:
        all_props_act.append(my_tr_fl.set_state([P_ACT, h_act], "PH"))
    all_props_act = np.array(all_props_act)

    import matplotlib.pyplot as plt

    fi, ax = plt.subplots(1)
    ax.plot(all_props_act[:, 2], all_props_act[:, 0])

    # return a FluidState
    my_prop = my_tr_fl.set_state([T_ACT, P_ACT], "TP", output="FluidState")
    my_prop2 = my_tr_fl.set_state([T_ACT + 25, P_ACT], "TP", output="FluidState")
    print(f'Fluidstate as output: {my_prop.enthalpy, my_prop2.enthalpy}')
    my_prop = myFluid.set_state([T_ACT, P_ACT], "TP", output="FluidState")
    my_prop2 = myFluid.set_state([T_ACT + 25, P_ACT], "TP", output="FluidState")
    print(f'Fluidstate as output: {my_prop.enthalpy, my_prop2.enthalpy}')

    # test new multiREFPROPstructure
    fluids = ["water", "Butane * Propane"]
    instances = [False, True]
    printing_handles = ["---0---",
                        "---1---"]
    # one instance, new init called each time
    for i, rp_inst in enumerate(instances):
        print(f"\n {printing_handles[i]}")
        water = init_fluid(fluids[0], [1], rp_inst=rp_inst)
        mixture = init_fluid(fluids[1], [0.2, 0.8], rp_inst=rp_inst)
        t0 = time.time()
        for i in range(1):
            water.set_state([350, 0.5], "TQMASS")
            #print(water.properties.pressure)
        #for i in range(1000):
            mixture.set_state([310, 1e6], "TP")
            #print(mixture.properties.pressure)
        t1 = time.time()
        print(f"thought for {t1 - t0} seconds")
        print(water.properties.pressure)
        print(mixture.properties.entropy)
        print(f"Hi! I am Refprop Instance no: {water.fluidmodel.fl_instance.managed_handle}!")
        print(f"Hi! I am Refprop Instance no: {mixture.fluidmodel.fl_instance.managed_handle}!")

    # test set_composition
    print("\n")
    print("---test setup mixture composition---")
    fluidname = "Butane * Propane"
    comps = np.random.dirichlet(alpha=[1, 1], size=100)
    #Variante 1: eine Instanz und set composition
    t0 = time.time()
    mixture = init_fluid(fluidname, [0.1,0.9])#comps[0,:])
    for comp in comps:
        mixture.set_composition(comp)
        mixture.set_state([350, 0.5], "TQ")
    t1 = time.time()
    print(f"thought for {t1 - t0} seconds")

    #Variante 2: immer neue Init mit einer Instanz
    fluidname = "Butane * Propane"
    instances = [0, 1]
    # Variante 1: eine Instanz
    t0 = time.time()
    for comp in comps:
        mixture = init_fluid(fluidname, comps[0])
        mixture.set_state([350, 0.5], "TQ")
    t1 = time.time()
    print(f"thought for {t1 - t0} seconds")

    # #Variante 3: Viele Instanzen, nicht empfohlen
    # fluidname = "Butane * Propane"
    # instances = [0, 1]
    # # Variante 1: eine Instanz
    # t0 = time.time()
    # for comp in comps:
    #     mixture = init_fluid(fluidname, comps[0], rp_inst=1)
    #     mixture.set_state([350, 0.5], "TQ")
    # t1 = time.time()
    # print(f"thought for {t1 - t0} seconds")

    # use TREND
    fluid = "Propane * Butane"
    comp = [0.2, 0.8]
    # easy with default:
    trend_easy = init_fluid(fluid, comp, props="TREND")

    fluid_models = {
        "DEF": {"model": "", "no": 1},
        "Gerg": {"model": "\\gerg-2008", "no": 1},
        "PR": {"model": "", "no": 3},
    }

    for key, items in fluid_models.items():
        my_dict = {"eos_ind": [items["no"], items["no"]],
                   'mix_ind': items["no"],
                   'trendmodel': items['model'],
                   'props': "TREND"}
        tr_fluid = init_fluid(fluid, comp, **my_dict)

        tr_fluid.set_state([310, 0.6e5], "TP")
        print(tr_fluid.properties.sp_volume)