// Reaktoro is a unified framework for modeling chemically reactive systems.
//
// Copyright © 2014-2021 Allan Leal
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this library. If not, see <http://www.gnu.org/licenses/>.

#pragma once

// Reaktoro includes
#include <Reaktoro/Common/Constants.hpp>
#include <Reaktoro/Equilibrium/EquilibriumSpecs.hpp>

namespace Reaktoro {

// Forward declarations
class ChemicalState;

/// The class used to define conditions to be satisfied at chemical equilibrium.
class EquilibriumConditions
{
public:
    /// Construct an EquilibriumConditions object.
    explicit EquilibriumConditions(const EquilibriumSpecs& specs);

    //=================================================================================================
    //
    // METHODS TO SPECIFY THERMODYNAMIC CONDITIONS
    //
    //=================================================================================================

    /// Specify the **temperature** of the system at chemical equilibrium.
    /// @param value The temperature of the system
    /// @param unit The unit of the temperature value (must be convertible to K)
    auto temperature(real value, String unit="K") -> void;

    /// Specify the **pressure** of the system at chemical equilibrium.
    /// @param value The pressure of the system
    /// @param unit The unit of the pressure value (must be convertible to Pa)
    auto pressure(real value, String unit="Pa") -> void;

    /// Specify the **volume** of the system at chemical equilibrium.
    /// @param value The volume of the system
    /// @param unit The unit of the volume value (must be convertible to m@sup{3})
    auto volume(real value, String unit="m3") -> void;

    /// Specify the **internal energy** of the system at chemical equilibrium.
    /// @param value The internal energy of the system
    /// @param unit The unit of the internal energy value (must be convertible to J)
    auto internalEnergy(real value, String unit="J") -> void;

    /// Specify the **enthalpy** of the system at chemical equilibrium.
    /// @param value The enthalpy of the system
    /// @param unit The unit of the enthalpy value (must be convertible to J)
    auto enthalpy(real value, String unit="J") -> void;

    /// Specify the **Gibbs energy** of the system at chemical equilibrium.
    /// @param value The Gibbs energy of the system
    /// @param unit The unit of the Gibbs energy value (must be convertible to J)
    auto gibbsEnergy(real value, String unit="J") -> void;

    /// Specify the **Helmholtz energy** of the system at chemical equilibrium.
    /// @param value The Helmholtz energy of the system
    /// @param unit The unit of the Helmholtz energy value (must be convertible to J)
    auto helmholtzEnergy(real value, String unit="J") -> void;

    /// Specify the **entropy** of the system at chemical equilibrium.
    /// @param value The entropy of the system
    /// @param unit The unit of the entropy value (must be convertible to J/K)
    auto entropy(real value, String unit="J/K") -> void;

    //=================================================================================================
    //
    // METHODS TO SPECIFY STARTING COMPOSITIONAL CONDITIONS
    //
    //=================================================================================================

    /// Specify an initial temperature condition for the chemical system (in K).
    /// @param value The initial temperature of the system.
    auto startWithTemperature(real value) -> void;

    /// Specify an initial temperature condition for the chemical system.
    /// @param value The initial temperature of the system.
    /// @param unit The temperature unit (must be convertible to K).
    auto startWithTemperature(real value, String unit) -> void;

    /// Specify an initial pressure condition for the chemical system.
    /// @param value The initial pressure of the system.
    auto startWithPressure(real value) -> void;

    /// Specify an initial pressure condition for the chemical system.
    /// @param value The initial pressure of the system.
    /// @param unit The pressure unit (must be convertible to Pa).
    auto startWithPressure(real value, String unit) -> void;

    /// Specify an initial condition for the species amounts.
    /// @param n The array with the initial amounts of the species (in mol).
    auto startWithSpeciesAmounts(ArrayXrConstRef n) -> void;

    /// @param species The name of the species in the chemical system.
    /// @param value The amount or mass value of the species.
    /// @param unit The amount or mass unit (must be convertible to mol or kg).
    /// @warning An error is thrown if the chemical system has no species with name @p species.
    auto startWith(String species, real value, String unit) -> void;

    /// Specify an initial condition for the abundance of a chemical species.
    /// @param ispecies The index of the species in the chemical system.
    /// @param value The amount or mass value of the species.
    /// @param unit The amount or mass unit (must be convertible to mol or kg).
    auto startWith(Index ispecies, real value, String unit) -> void;

    /// Specify an initial condition for temperature, pressure, and species amounts with a given chemical state.
    /// @note This method overwrites all previous `startWith`, `startWithTemperature`,
    /// @note `startWithPressure`, and `startWithSpeciesAmounts` method calls.
    /// @param state The chemical state representing the initial conditions of the system.
    auto startWithState(const ChemicalState& state) -> void;

    /// Specify the initial condition for the amounts of the conservative components.
    /// These component amounts are conserved at chemical equilibrium only if
    /// the system is closed. If the system is open to one or more substances,
    /// these given initial component amounts will differ from those computed
    /// at chemical equilibrium. The difference correspond to how much each
    /// titrant (i.e., the substance for which the system is open to) entered
    /// or leaved the system.
    ///
    /// @note This method has higher priority than the other methods that
    /// specify initial conditions for the abundance of chemical species:
    ///
    ///    * @ref EquilibriumConditions::startWith
    ///    * @ref EquilibriumConditions::startWithSpeciesAmounts
    ///    * @ref EquilibriumConditions::startWithState
    ///
    /// By using this method, the chemical equilibrium calculation will use the
    /// specified component amounts instead of computing them from the initial
    /// condition for the species amounts. In this case, the provided
    /// conditions for species amounts will be solely used as initial guess for
    /// the equilibrium computation. The provided species amounts may not be
    /// consistent with the given component amounts here (i.e., there is mass
    /// imbalance between initial condition for species amounts and the amounts
    /// of the components that constitute the species). At the end of a
    /// successfull calculation, this imbalance is resolved, and the species
    /// amounts are consistent with the component amounts in terms of mass
    /// conservation. This is true provided the chemical system is not open to
    /// other substances, in which case, mass conservation also considers
    /// these substances as well.
    ///
    /// @param b The array with the initial amounts of the components (in mol)
    auto startWithComponentAmounts(ArrayXrConstRef b) -> void;

    //=================================================================================================
    //
    // METHODS TO SPECIFY CHEMICAL POTENTIAL CONDITIONS
    //
    //=================================================================================================

    /// Specify the **chemical potential** of a substance at chemical equilibrium.
    /// @param substance The chemical formula of the substance.
    /// @param value The constrained chemical potential value.
    /// @param unit The unit for the constrained chemical potential value (must be convertible to J/mol).
    /// @warning An error is thrown if the specifications for the chemical equilibrium calculation
    /// @warning do not consider a chemical potential constraint for the substance.
    auto chemicalPotential(String substance, real value, String unit="J/mol") -> void;

    /// Specify the **ln activity** of a species at chemical equilibrium.
    /// @param species The name of the chemical species.
    /// @param value The constrained ln activity value.
    /// @warning An error is thrown if the specifications for the chemical equilibrium calculation
    /// @warning do not consider an activity constraint for the species.
    auto lnActivity(String species, real value) -> void;

    /// Specify the **lg activity** of a species at chemical equilibrium.
    /// @param species The name of the chemical species.
    /// @param value The constrained lg activity value.
    /// @warning An error is thrown if the specifications for the chemical equilibrium calculation
    /// @warning do not consider an activity constraint for the species.
    auto lgActivity(String species, real value) -> void;

    /// Specify the **activity** of a species at chemical equilibrium.
    /// @param species The name of the chemical species.
    /// @param value The constrained activity value.
    /// @warning An error is thrown if the specifications for the chemical equilibrium calculation
    /// @warning do not consider an activity constraint for the species.
    auto activity(String species, real value) -> void;

    /// Specify the **fugacity** of a gaseous species at chemical equilibrium.
    /// @param species The name of the gaseous species.
    /// @param value The constrained fugacity value.
    /// @param unit The unit for the constrained fugacity value (must be convertible to Pa).
    /// @warning An error is thrown if the specifications for the chemical equilibrium calculation
    /// @warning do not consider a fugacity constraint for the gas.
    auto fugacity(String species, real value, String unit) -> void;

    /// Specify the *pH* at chemical equilibrium.
    /// @param value The constrained value for pH.
    /// @warning An error is thrown if the specifications for the chemical equilibrium calculation
    /// @warning do not consider a pH constraint.
    auto pH(real value) -> void;

    /// Specify the *pMg* at chemical equilibrium.
    /// @param value The constrained value for pMg.
    /// @warning An error is thrown if the specifications for the chemical equilibrium calculation
    /// @warning do not consider a pMg constraint.
    auto pMg(real value) -> void;

    /// Specify the *pE* at chemical equilibrium.
    /// @param value The constrained value for pE.
    /// @warning An error is thrown if the specifications for the chemical equilibrium calculation
    /// @warning do not consider a pE constraint.
    auto pE(real value) -> void;

    /// Specify the *Eh* at chemical equilibrium.
    /// @param value The constrained value for Eh.
    /// @param unit The unit of the constrained value for Eh (must be convertible to V).
    /// @warning An error is thrown if the specifications for the chemical equilibrium calculation
    /// @warning do not consider an Eh constraint.
    auto Eh(real value, String unit="V") -> void;

    //=================================================================================================
    //
    // MISCELLANEOUS METHODS
    //
    //=================================================================================================

    /// Set the input variable with name @p input to the value in @p val.
    /// @warning An error is thrown if there are no input variables with name @p input.
    auto set(const String& input, const real& val) -> void;

    /// Return the initial temperature of the system in the equilibrium calculation (in K).
    auto initialTemperature() const -> real;

    /// Return the initial pressure of the system in the equilibrium calculation (in Pa).
    auto initialPressure() const -> real;

    /// Return the initial amounts of the species in the equilibrium calculation (in mol).
    auto initialSpeciesAmounts() const -> ArrayXrConstRef;

    /// Return the initial amounts of the conservative components in the equilibrium calculation (in mol).
    auto initialComponentAmounts() const -> ArrayXr;

    /// Return the chemical system associated with the equilibrium conditions.
    auto system() const -> const ChemicalSystem&;

    /// Return the names of the input variables associated with the equilibrium conditions.
    auto inputNames() const -> const Strings&;

    /// Return the values of the input variables associated with the equilibrium conditions.
    auto inputValues() const -> VectorXrConstRef;

private:
    /// The chemical system associated with the equilibrium problem specifications.
    const ChemicalSystem m_system;

    /// The names of the input variables in the equilibrium problem specifications.
    const Strings m_inputs;

    /// The current values of the input variables.
    VectorXr m_inputs_values;

    /// The initial temperature of the system in the equilibrium calculation (in K).
    real m_initial_temperature = NaN;

    /// The initial pressure of the system in the equilibrium calculation (in Pa).
    real m_initial_pressure = NaN;

    /// The initial amounts of the species in the equilibrium calculation (in mol).
    ArrayXr m_initial_species_amounts;

    /// The initial amounts of the conservative components in the equilibrium calculation (in mol).
    ArrayXr m_initial_component_amounts;
};

} // namespace Reaktoro
