/*
 * CoolPropFluid.h
 *
 *  Created on: 20 Dec 2013
 *      Author: jowr
 */

#ifndef INCOMPRESSIBLEFLUID_H_
#define INCOMPRESSIBLEFLUID_H_

#include "DataStructures.h"
#include "Helmholtz.h"
#include "Solvers.h"

#include <numeric>
#include <string>
#include <vector>
#include <map>
#include <cassert>
#include <iterator>

#include <Eigen/Core>
#include "PolyMath.h"
#include "MatrixMath.h"

namespace CoolProp {

struct IncompressibleData
{
    enum IncompressibleTypeEnum
    {
        INCOMPRESSIBLE_NOT_SET,
        INCOMPRESSIBLE_POLYNOMIAL,
        INCOMPRESSIBLE_EXPPOLYNOMIAL,
        INCOMPRESSIBLE_EXPONENTIAL,
        INCOMPRESSIBLE_LOGEXPONENTIAL,
        INCOMPRESSIBLE_POLYOFFSET
    };
    IncompressibleTypeEnum type;
    Eigen::MatrixXd coeffs;  //TODO: Can we store the Eigen::Matrix objects more efficiently?
    //std::vector<std::vector<double> > coeffs;
    IncompressibleData() {
        type = INCOMPRESSIBLE_NOT_SET;
    };
};

/// A property provider for incompressible solutions and pure fluids
/**
This fluid instance is populated using an entry from a JSON file
*/
class IncompressibleFluid
{

   protected:
    bool strict;

    std::string name;
    std::string description;
    std::string reference;

    double Tmin, Tmax;
    double xmin, xmax;
    composition_types xid;

    double TminPsat;
    double xbase, Tbase;

    /// These are the objects that hold the coefficients
    /** Note that all polynomials require a 2-dimensional array
     *  of coefficients. This array may have only one row or
     *  column, but the structure should be 2D. This behaviour is
     *  hard-coded in the JSON file reader that resides inside
     *  the IncompressibleLibrary.cpp
     *  All other functions, also polyoffset, can only handle 1D
     *  input and throw an error if you feed them other coefficients.
     */

    /// Density coefficients
    /** If 2D, the rows are temperature and the columns are concentration.
     *  If 1D, should be a column vector of temperature coefficients
     */
    IncompressibleData density;
    /// Specific heat coefficients
    /** If 2D, the rows are temperature and the columns are concentration.
     *  If 1D, should be a column vector of temperature coefficients
     *  Fails for all other forms than polynomial due to the automatic
     *  integration for internal energy and entropy.
     */
    IncompressibleData specific_heat;
    /// Viscosity coefficients
    /** If 2D, the rows are temperature and the columns are concentration.
     *  If 1D, should be a column vector of temperature coefficients
     */
    IncompressibleData viscosity;
    /// Conductivity coefficients
    /** If 2D, the rows are temperature and the columns are concentration.
     *  If 1D, should be a column vector of temperature coefficients
     */
    IncompressibleData conductivity;
    /// Saturation pressure coefficients
    /** If 2D, the rows are temperature and the columns are concentration.
     *  If 1D, should be a column vector of temperature coefficients
     */
    IncompressibleData p_sat;
    /// Freezing temperature coefficients
    /** If 2D, the rows are concentration and the columns are pressure.
     *  If 1D, should be a column vector of concentration coefficients
     */
    IncompressibleData T_freeze;

    /// Mass fraction conversion coefficients
    /** If the fluid type is mass-based, it does not do anything. Otherwise,
     *  it converts the mass fraction to the required input.
     */
    IncompressibleData mass2input;
    /// Volume fraction conversion coefficients
    /** If the fluid type is volume-based, it does not do anything. Otherwise,
     *  it converts the volume fraction to the required input.
     */
    IncompressibleData volume2input;
    /// Mole fraction conversion coefficients
    /** If the fluid type is mole-based, it does not do anything. Otherwise,
     *  it converts the mole fraction to the required input.
     */
    IncompressibleData mole2input;

    Polynomial2DFrac poly;

    // Forward declaration of the some internal functions
    //double h_u(double T, double p, double x);
    //double u_h(double T, double p, double x);

   public:
    IncompressibleFluid() : Tmin(_HUGE), Tmax(_HUGE), xmin(_HUGE), xmax(_HUGE), TminPsat(_HUGE), xbase(_HUGE), Tbase(_HUGE) {
        strict = true;
        xid = IFRAC_UNDEFINED;
    };
    virtual ~IncompressibleFluid(){};

    std::string getName() const {
        return name;
    }
    std::string get_name() const {
        return getName();
    }  // For backwards-compatibility.
    std::string getDescription() const {
        return description;
    }
    std::string getReference() const {
        return reference;
    }

    double getTmax() const {
        return Tmax;
    }
    double getTmin() const {
        return Tmin;
    }
    double getxmax() const {
        return xmax;
    }
    double getxmin() const {
        return xmin;
    }
    composition_types getxid() const {
        return xid;
    }
    double getTminPsat() const {
        return TminPsat;
    }
    double getTbase() const {
        return Tbase;
    }
    double getxbase() const {
        return xbase;
    }

    void setName(const std::string& name) {
        this->name = name;
    }
    void setDescription(const std::string& description) {
        this->description = description;
    }
    void setReference(const std::string& reference) {
        this->reference = reference;
    }
    void setTmax(double Tmax) {
        this->Tmax = Tmax;
    }
    void setTmin(double Tmin) {
        this->Tmin = Tmin;
    }
    void setxmax(double xmax) {
        this->xmax = xmax;
    }
    void setxmin(double xmin) {
        this->xmin = xmin;
    }
    void setxid(composition_types xid) {
        this->xid = xid;
    }
    void setTminPsat(double TminPsat) {
        this->TminPsat = TminPsat;
    }
    void setTbase(double Tbase) {
        this->Tbase = Tbase;
    }
    void setxbase(double xbase) {
        this->xbase = xbase;
    }

    /// Setters for the coefficients
    void setDensity(IncompressibleData density) {
        this->density = density;
    }
    void setSpecificHeat(IncompressibleData specific_heat) {
        this->specific_heat = specific_heat;
    }
    void setViscosity(IncompressibleData viscosity) {
        this->viscosity = viscosity;
    }
    void setConductivity(IncompressibleData conductivity) {
        this->conductivity = conductivity;
    }
    void setPsat(IncompressibleData p_sat) {
        this->p_sat = p_sat;
    }
    void setTfreeze(IncompressibleData T_freeze) {
        this->T_freeze = T_freeze;
    }

    /// Setters for the concentration conversion coefficients
    void setMass2input(IncompressibleData mass2input) {
        this->mass2input = mass2input;
    }
    void setVolume2input(IncompressibleData volume2input) {
        this->volume2input = volume2input;
    }
    void setMole2input(IncompressibleData mole2input) {
        this->mole2input = mole2input;
    }

    /// A function to check coefficients and equation types.
    void validate();
    /// A function to test the density coefficients for 1D or 2D
    bool is_pure();

   protected:
    /// Base functions that handle the custom function types
    double baseExponential(IncompressibleData data, double y, double ybase);
    double baseLogexponential(IncompressibleData data, double y, double ybase);
    double baseExponentialOffset(IncompressibleData data, double y);
    double basePolyOffset(IncompressibleData data, double y, double z = 0.0);

   public:
    /* All functions need T and p as input. Might not
     * be necessary, but gives a clearer structure.
     */
    /// Density as a function of temperature, pressure and composition.
    double rho(double T, double p, double x);
    /// Heat capacities as a function of temperature, pressure and composition.
    double c(double T, double p, double x);
    double cp(double T, double p, double x) {
        throw ValueError(format("%s (%d): Please use the c-function instead.", __FILE__, __LINE__));
    }
    double cv(double T, double p, double x) {
        throw ValueError(format("%s (%d): Please use the c-function instead.", __FILE__, __LINE__));
    }
    /// Entropy as a function of temperature, pressure and composition.
    double s(double T, double p, double x) {
        throw ValueError(format("%s (%d): The internal calculations have changed, use the backend to calculate entropy from the partial derivatives.",
                                __FILE__, __LINE__));
    }
    /// Internal energy as a function of temperature, pressure and composition.
    double u(double T, double p, double x) {
        throw ValueError(
          format("%s (%d): The internal calculations have changed, use the backend to calculate internal energy from enthalpy.", __FILE__, __LINE__));
    }
    /// Enthalpy as a function of temperature, pressure and composition.
    double h(double T, double p, double x) {
        throw ValueError(
          format("%s (%d): The internal calculations have changed, use the backend to calculate enthalpy from the partial derivatives.", __FILE__,
                 __LINE__));
    }
    /// Viscosity as a function of temperature, pressure and composition.
    double visc(double T, double p, double x);
    /// Thermal conductivity as a function of temperature, pressure and composition.
    double cond(double T, double p, double x);
    /// Saturation pressure as a function of temperature and composition.
    double psat(double T, double x);
    /// Freezing temperature as a function of pressure and composition.
    double Tfreeze(double p, double x);

    /* Below are direct calculations of the derivatives. Nothing
     * special is going on, we simply use the polynomial class to
     * derive the different functions with respect to temperature.
     */
    /// Partial derivative of density
    //  with respect to temperature at constant pressure and composition
    double drhodTatPx(double T, double p, double x);
    ///// Partial derivative of entropy
    ////  with respect to temperature at constant pressure and composition
    //double dsdTatPx  (double T, double p, double x){return c(T,p,x)/T;};
    ///// Partial derivative of enthalpy
    ////  with respect to temperature at constant pressure and composition
    //double dhdTatPx  (double T, double p, double x){return c(T,p,x);};
    /// Partial derivative of entropy
    //  with respect to temperature at constant pressure and composition
    //  integrated in temperature
    double dsdTatPxdT(double T, double p, double x);
    /// Partial derivative of enthalpy
    //  with respect to temperature at constant pressure and composition
    //  integrated in temperature
    double dhdTatPxdT(double T, double p, double x);

    /// Mass fraction conversion function
    /** If the fluid type is mass-based, it does not do anything. Otherwise,
     *  it converts the mass fraction to the required input. */
    double inputFromMass(double T, double x);
    /// Volume fraction conversion function
    /** If the fluid type is volume-based, it does not do anything. Otherwise,
     *  it converts the volume fraction to the required input. */
    double inputFromVolume(double T, double x);
    /// Mole fraction conversion function
    /** If the fluid type is mole-based, it does not do anything. Otherwise,
     *  it converts the mole fraction to the required input. */
    double inputFromMole(double T, double x);

    /* Some functions can be inverted directly, those are listed
     * here. It is also possible to solve for other quantities, but
     * that involves some more sophisticated processing and is not
     * done here, but in the backend, T(h,p) for example.
     */
    /// Temperature as a function of density, pressure and composition.
    double T_rho(double Dmass, double p, double x);
    /// Temperature as a function of heat capacities as a function of temperature, pressure and composition.
    double T_c(double Cmass, double p, double x);
    /// Temperature as a function of entropy as a function of temperature, pressure and composition.
    double T_s(double Smass, double p, double x) {
        throw NotImplementedError(format("%s (%d): T from entropy is not implemented in the fluid, use the backend.", __FILE__, __LINE__));
    }
    /// Temperature as a function of internal energy as a function of temperature, pressure and composition.
    double T_u(double Umass, double p, double x) {
        throw NotImplementedError(format("%s (%d): T from internal energy is not implemented in the fluid, use the backend.", __FILE__, __LINE__));
    }
    /// Temperature as a function of enthalpy, pressure and composition.
    double T_h(double Hmass, double p, double x) {
        throw NotImplementedError(format("%s (%d): T from enthalpy is not implemented in the fluid, use the backend.", __FILE__, __LINE__));
    }
    /// Viscosity as a function of temperature, pressure and composition.
    double T_visc(double visc, double p, double x) {
        throw NotImplementedError(format("%s (%d): T from viscosity is not implemented.", __FILE__, __LINE__));
    }
    /// Thermal conductivity as a function of temperature, pressure and composition.
    double T_cond(double cond, double p, double x) {
        throw NotImplementedError(format("%s (%d): T from conductivity is not implemented.", __FILE__, __LINE__));
    }
    /// Saturation pressure as a function of temperature and composition.
    double T_psat(double psat, double x) {
        throw NotImplementedError(format("%s (%d): T from psat is not implemented.", __FILE__, __LINE__));
    }
    /// Composition as a function of freezing temperature and pressure.
    double x_Tfreeze(double Tfreeze, double p) {
        throw NotImplementedError(format("%s (%d): x from T_freeze is not implemented.", __FILE__, __LINE__));
    }

   protected:
    /* Define internal energy and enthalpy as functions of the
     * other properties to provide data in case there are no
     * coefficients.
     */
    /// Enthalpy from u, p and rho.
    /** Calculate enthalpy as a function of temperature and
     *  pressure employing functions for internal energy and
     *  density. Provides consistent formulations. */
    double h_u(double T, double p, double x) {
        return u(T, p, x) + p / rho(T, p, x);
    };

    /// Internal energy from h, p and rho.
    /** Calculate internal energy as a function of temperature
     *  and pressure employing functions for enthalpy and
     *  density. Provides consistent formulations. */
    double u_h(double T, double p, double x) {
        return h(T, p, x) - p / rho(T, p, x);
    };

    /*
     * Some more functions to provide a single implementation
     * of important routines.
     * We start with the check functions that can validate input
     * in terms of pressure p, temperature T and composition x.
     */
    /// Check validity of temperature input.
    /** Compares the given temperature T to the result of a
     *  freezing point calculation. This is not necessarily
     *  defined for all fluids, default values do not cause errors. */
    bool checkT(double T, double p, double x);

    /// Check validity of pressure input.
    /** Compares the given pressure p to the saturation pressure at
     *  temperature T and throws and exception if p is lower than
     *  the saturation conditions.
     *  The default value for psat is -1 yielding true if psat
     *  is not redefined in the subclass.
     *  */
    bool checkP(double T, double p, double x);

   public:
    /// Check validity of composition input.
    /** Compares the given composition x to a stored minimum and
     *  maximum value. Enforces the redefinition of xmin and
     *  xmax since the default values cause an error. */
    bool checkX(double x);

    /// Check validity of temperature, pressure and composition input.
    bool checkTPX(double T, double p, double x) {
        return (checkT(T, p, x) && checkP(T, p, x) && checkX(x));
    };
};

} /* namespace CoolProp */
#endif /* INCOMPRESSIBLEFLUID_H_ */
