/**
 *  @file WaterSSTP.cpp
 * Definitions for a %ThermoPhase class consisting of  pure water (see \ref thermoprops
 * and class \link Cantera::WaterSSTP WaterSSTP\endlink).
 */
/*
 * Copyright (2006) Sandia Corporation. Under the terms of
 * Contract DE-AC04-94AL85000 with Sandia Corporation, the
 * U.S. Government retains certain rights in this software.
 */

#include "cantera/thermo/WaterSSTP.h"
#include "cantera/thermo/WaterPropsIAPWS.h"
#include "cantera/thermo/WaterProps.h"
#include "cantera/thermo/ThermoFactory.h"
#include "cantera/base/xml.h"
#include "cantera/base/stringUtils.h"

#include <cmath>
#include <fstream>

using namespace std;

namespace Cantera
{
/**
 * Basic list of constructors and duplicators
 */

WaterSSTP::WaterSSTP() :
    SingleSpeciesTP(),
    m_sub(0),
    m_waterProps(0),
    m_mw(0.0),
    EW_Offset(0.0),
    SW_Offset(0.0),
    m_ready(false),
    m_allowGasPhase(false)
{
    //constructPhase();
}


WaterSSTP::WaterSSTP(std::string inputFile, std::string id) :
    SingleSpeciesTP(),
    m_sub(0),
    m_waterProps(0),
    m_mw(0.0),
    EW_Offset(0.0),
    SW_Offset(0.0),
    m_ready(false),
    m_allowGasPhase(false)
{
    constructPhaseFile(inputFile, id);
}


WaterSSTP::WaterSSTP(XML_Node& phaseRoot, std::string id) :
    SingleSpeciesTP(),
    m_sub(0),
    m_waterProps(0),
    m_mw(0.0),
    EW_Offset(0.0),
    SW_Offset(0.0),
    m_ready(false),
    m_allowGasPhase(false)
{
    constructPhaseXML(phaseRoot, id) ;
}



WaterSSTP::WaterSSTP(const WaterSSTP& b) :
    SingleSpeciesTP(b),
    m_sub(0),
    m_waterProps(0),
    m_mw(b.m_mw),
    EW_Offset(b.EW_Offset),
    SW_Offset(b.SW_Offset),
    m_ready(false),
    m_allowGasPhase(b.m_allowGasPhase)
{
    m_sub = new WaterPropsIAPWS(*(b.m_sub));
    m_waterProps =  new WaterProps(m_sub);

    /*
     * Use the assignment operator to do the brunt
     * of the work for the copy constructor.
     */
    *this = b;
}

/*
 * Assignment operator
 */
WaterSSTP& WaterSSTP::operator=(const WaterSSTP& b)
{
    if (&b == this) {
        return *this;
    }
    m_sub->operator=(*(b.m_sub));

    if (!m_waterProps) {
        m_waterProps = new WaterProps(m_sub);
    }
    m_waterProps->operator=(*(b.m_waterProps));


    m_mw = b.m_mw;
    m_ready = b.m_ready;
    m_allowGasPhase = b.m_allowGasPhase;
    return *this;
}


ThermoPhase* WaterSSTP::duplMyselfAsThermoPhase() const
{
    WaterSSTP* wtp = new WaterSSTP(*this);
    return (ThermoPhase*) wtp;
}

WaterSSTP::~WaterSSTP()
{
    delete m_sub;
    delete m_waterProps;
}




/*
 * @param infile XML file containing the description of the
 *        phase
 *
 * @param id  Optional parameter identifying the name of the
 *            phase. If none is given, the first XML
 *            phase element will be used.
 */
void WaterSSTP::constructPhaseXML(XML_Node& phaseNode, std::string id)
{

    /*
     * Call the Cantera importPhase() function. This will import
     * all of the species into the phase. This will also handle
     * all of the solvent and solute standard states.
     */
    bool m_ok = importPhase(phaseNode, this);
    if (!m_ok) {
        throw CanteraError("initThermo","importPhase failed ");
    }

}

/*
 * constructPhaseFile
 *
 *
 * This routine is a precursor to constructPhaseXML(XML_Node*)
 * routine, which does most of the work.
 *
 * @param inputFile XML file containing the description of the
 *        phase
 *
 * @param id  Optional parameter identifying the name of the
 *            phase. If none is given, the first XML
 *            phase element will be used.
 */
void WaterSSTP::constructPhaseFile(std::string inputFile, std::string id)
{

    if (inputFile.size() == 0) {
        throw CanteraError("WaterSSTP::constructPhaseFile",
                           "input file is null");
    }
    std::string path = findInputFile(inputFile);
    std::ifstream fin(path.c_str());
    if (!fin) {
        throw CanteraError("WaterSSTP::constructPhaseFile","could not open "
                           +path+" for reading.");
    }
    /*
     * The phase object automatically constructs an XML object.
     * Use this object to store information.
     */
    XML_Node& phaseNode_XML = xml();
    XML_Node* fxml = new XML_Node();
    fxml->build(fin);
    XML_Node* fxml_phase = findXMLPhase(fxml, id);
    if (!fxml_phase) {
        throw CanteraError("WaterSSTP::constructPhaseFile",
                           "ERROR: Can not find phase named " +
                           id + " in file named " + inputFile);
    }
    fxml_phase->copy(&phaseNode_XML);
    constructPhaseXML(*fxml_phase, id);
    delete fxml;
}



void WaterSSTP::initThermo()
{
    SingleSpeciesTP::initThermo();
}

void WaterSSTP::
initThermoXML(XML_Node& phaseNode, std::string id)
{

    /*
     * Do initializations that don't depend on knowing the XML file
     */
    initThermo();
    if (m_sub) {
        delete m_sub;
    }
    m_sub = new WaterPropsIAPWS();
    if (m_sub == 0) {
        throw CanteraError("WaterSSTP::initThermo",
                           "could not create new substance object.");
    }
    /*
     * Calculate the molecular weight. Note while there may
     * be a very good calculated weight in the steam table
     * class, using this weight may lead to codes exhibiting
     * mass loss issues. We need to grab the elemental
     * atomic weights used in the Element class and calculate
     * a consistent H2O molecular weight based on that.
     */
    size_t nH = elementIndex("H");
    if (nH == npos) {
        throw CanteraError("WaterSSTP::initThermo",
                           "H not an element");
    }
    double mw_H = atomicWeight(nH);
    size_t nO = elementIndex("O");
    if (nO == npos) {
        throw CanteraError("WaterSSTP::initThermo",
                           "O not an element");
    }
    double mw_O = atomicWeight(nO);
    m_mw = 2.0 * mw_H + mw_O;
    setMolecularWeight(0,m_mw);
    double one = 1.0;
    setMoleFractions(&one);

    /*
     * Set the baseline
     */
    doublereal T = 298.15;
    Phase::setDensity(7.0E-8);
    Phase::setTemperature(T);

    doublereal presLow = 1.0E-2;
    doublereal oneBar = 1.0E5;
    doublereal dd = m_sub->density(T, presLow, WATER_GAS, 7.0E-8);
    setDensity(dd);
    setTemperature(T);
    SW_Offset = 0.0;
    doublereal s = entropy_mole();
    s -=  GasConstant * log(oneBar/presLow);
    if (s != 188.835E3) {
        SW_Offset = 188.835E3 - s;
    }
    s = entropy_mole();
    s -=  GasConstant * log(oneBar/presLow);
    //printf("s = %g\n", s);

    doublereal h = enthalpy_mole();
    if (h != -241.826E6) {
        EW_Offset = -241.826E6 - h;
    }
    h = enthalpy_mole();

    //printf("h = %g\n", h);


    /*
     * Set the initial state of the system to 298.15 K and
     * 1 bar.
     */
    setTemperature(298.15);
    double rho0 = m_sub->density(298.15, OneAtm, WATER_LIQUID);
    setDensity(rho0);

    m_waterProps =  new WaterProps(m_sub);


    /*
     * We have to do something with the thermo function here.
     */
    if (m_spthermo) {
        delete m_spthermo;
        m_spthermo = 0;
    }

    /*
     * Set the flag to say we are ready to calculate stuff
     */
    m_ready = true;
}

void WaterSSTP::
setParametersFromXML(const XML_Node& eosdata)
{
    eosdata._require("model","PureLiquidWater");
}

/*
 * Return the molar dimensionless enthalpy
 */
void WaterSSTP::getEnthalpy_RT(doublereal* hrt) const
{
    double T = temperature();
    doublereal h = m_sub->enthalpy();
    *hrt = (h + EW_Offset)/(GasConstant*T);
}

/*
 * Calculate the internal energy in mks units of
 * J kmol-1
 */
void WaterSSTP::getIntEnergy_RT(doublereal* ubar) const
{
    doublereal u = m_sub->intEnergy();
    *ubar = (u + EW_Offset)/GasConstant;
}

/*
 * Calculate the dimensionless entropy
 */
void WaterSSTP::getEntropy_R(doublereal* sr) const
{
    doublereal s = m_sub->entropy();
    sr[0] = (s + SW_Offset) / GasConstant;
}

/*
 * Calculate the Gibbs free energy in mks units of
 * J kmol-1 K-1.
 */
void WaterSSTP::getGibbs_RT(doublereal* grt) const
{
    double T = temperature();
    doublereal g = m_sub->Gibbs();
    *grt = (g + EW_Offset - SW_Offset*T) / (GasConstant * T);
    if (!m_ready) {
        throw CanteraError("waterSSTP::", "Phase not ready");
    }
}

/*
 * Calculate the Gibbs free energy in mks units of
 * J kmol-1 K-1.
 */
void WaterSSTP::getStandardChemPotentials(doublereal* gss) const
{
    double T = temperature();
    doublereal g = m_sub->Gibbs();
    *gss = (g + EW_Offset - SW_Offset*T);
    if (!m_ready) {
        throw CanteraError("waterSSTP::", "Phase not ready");
    }
}

void WaterSSTP::getCp_R(doublereal* cpr) const
{
    doublereal cp = m_sub->cp();
    cpr[0] = cp / GasConstant;
}

/*
 * Calculate the constant volume heat capacity
 * in mks units of J kmol-1 K-1
 */
doublereal WaterSSTP::cv_mole() const
{
    doublereal cv = m_sub->cv();
    return cv;
}

// @name Thermodynamic Values for the Species Reference State


void WaterSSTP::getEnthalpy_RT_ref(doublereal* hrt) const
{
    doublereal p = pressure();
    double T = temperature();
    double dens = density();
    int waterState = WATER_GAS;
    double rc = m_sub->Rhocrit();
    if (dens > rc) {
        waterState = WATER_LIQUID;
    }
    doublereal dd = m_sub->density(T, OneAtm, waterState, dens);
    if (dd <= 0.0) {
        throw CanteraError("setPressure", "error");
    }
    doublereal h = m_sub->enthalpy();
    *hrt = (h + EW_Offset) / (GasConstant * T);
    dd = m_sub->density(T, p, waterState, dens);
}

void WaterSSTP::getGibbs_RT_ref(doublereal* grt) const
{
    doublereal p = pressure();
    double T = temperature();
    double dens = density();
    int waterState = WATER_GAS;
    double rc = m_sub->Rhocrit();
    if (dens > rc) {
        waterState = WATER_LIQUID;
    }
    doublereal dd = m_sub->density(T, OneAtm, waterState, dens);
    if (dd <= 0.0) {
        throw CanteraError("setPressure", "error");
    }
    m_sub->setState_TR(T, dd);
    doublereal g = m_sub->Gibbs();
    *grt = (g + EW_Offset - SW_Offset*T)/ (GasConstant * T);
    dd = m_sub->density(T, p, waterState, dens);

}

void WaterSSTP::getGibbs_ref(doublereal* g) const
{
    getGibbs_RT_ref(g);
    doublereal rt = _RT();
    for (size_t k = 0; k < m_kk; k++) {
        g[k] *= rt;
    }
}

void WaterSSTP::getEntropy_R_ref(doublereal* sr) const
{
    doublereal p = pressure();
    double T = temperature();
    double dens = density();
    int waterState = WATER_GAS;
    double rc = m_sub->Rhocrit();
    if (dens > rc) {
        waterState = WATER_LIQUID;
    }
    doublereal dd = m_sub->density(T, OneAtm, waterState, dens);

    if (dd <= 0.0) {
        throw CanteraError("setPressure", "error");
    }
    m_sub->setState_TR(T, dd);

    doublereal s = m_sub->entropy();
    *sr = (s + SW_Offset)/ (GasConstant);
    dd = m_sub->density(T, p, waterState, dens);

}

void WaterSSTP::getCp_R_ref(doublereal* cpr) const
{
    doublereal p = pressure();
    double T = temperature();
    double dens = density();
    int waterState = WATER_GAS;
    double rc = m_sub->Rhocrit();
    if (dens > rc) {
        waterState = WATER_LIQUID;
    }
    doublereal dd = m_sub->density(T, OneAtm, waterState, dens);
    m_sub->setState_TR(T, dd);
    if (dd <= 0.0) {
        throw CanteraError("setPressure", "error");
    }
    doublereal cp = m_sub->cp();
    *cpr = cp / (GasConstant);
    dd = m_sub->density(T, p, waterState, dens);
}

void WaterSSTP::getStandardVolumes_ref(doublereal* vol) const
{
    doublereal p = pressure();
    double T = temperature();
    double dens = density();
    int waterState = WATER_GAS;
    double rc = m_sub->Rhocrit();
    if (dens > rc) {
        waterState = WATER_LIQUID;
    }
    doublereal dd = m_sub->density(T, OneAtm, waterState, dens);
    if (dd <= 0.0) {
        throw CanteraError("setPressure", "error");
    }
    *vol = meanMolecularWeight() /dd;
    dd = m_sub->density(T, p, waterState, dens);
}

/*
 * Calculate the pressure (Pascals), given the temperature and density
 *  Temperature: kelvin
 *  rho: density in kg m-3
 */
doublereal WaterSSTP::pressure() const
{
    doublereal p = m_sub->pressure();
    return p;
}

void WaterSSTP::
setPressure(doublereal p)
{
    double T = temperature();
    double dens = density();
    int waterState = WATER_GAS;
    double rc = m_sub->Rhocrit();
    if (dens > rc) {
        waterState = WATER_LIQUID;
    }
    doublereal dd = m_sub->density(T, p, waterState, dens);
    if (dd <= 0.0) {
        throw CanteraError("setPressure", "error");
    }
    setDensity(dd);
}

// Returns  the isothermal compressibility. Units: 1/Pa.
/*
 * The isothermal compressibility is defined as
 * \f[
 * \kappa_T = -\frac{1}{v}\left(\frac{\partial v}{\partial P}\right)_T
 * \f]
 *  or
 * \f[
 * \kappa_T = \frac{1}{\rho}\left(\frac{\partial \rho}{\partial P}\right)_T
 * \f]
 */
doublereal WaterSSTP::isothermalCompressibility() const
{
    doublereal val = m_sub->isothermalCompressibility();
    return val;
}

// Return the volumetric thermal expansion coefficient. Units: 1/K.
/*
 * The thermal expansion coefficient is defined as
 * \f[
 * \beta = \frac{1}{v}\left(\frac{\partial v}{\partial T}\right)_P
 * \f]
 */
doublereal WaterSSTP::thermalExpansionCoeff() const
{
    doublereal val = m_sub->coeffThermExp();
    return val;
}

doublereal WaterSSTP::dthermalExpansionCoeffdT() const
{
    doublereal pres = pressure();
    doublereal dens_save = density();
    double T = temperature();
    double tt = T - 0.04;
    doublereal dd = m_sub->density(tt, pres, WATER_LIQUID, dens_save);
    if (dd < 0.0) {
        throw CanteraError("WaterSSTP::dthermalExpansionCoeffdT",
                           "Unable to solve for the density at T = " + fp2str(tt) + ", P = " + fp2str(pres));
    }
    doublereal vald = m_sub->coeffThermExp();
    m_sub->setState_TR(T, dens_save);
    doublereal val2 = m_sub->coeffThermExp();
    doublereal val = (val2 - vald) / 0.04;
    return val;
}


// critical temperature
doublereal WaterSSTP::critTemperature() const
{
    return m_sub->Tcrit();
}

// critical pressure
doublereal WaterSSTP::critPressure() const
{
    return m_sub->Pcrit();
}

// critical density
doublereal WaterSSTP::critDensity() const
{
    return m_sub->Rhocrit();
}


void WaterSSTP::setTemperature(const doublereal temp)
{
    Phase::setTemperature(temp);
    doublereal dd = density();
    m_sub->setState_TR(temp, dd);
}

void WaterSSTP::setDensity(const doublereal dens)
{
    Phase::setDensity(dens);
    doublereal temp = temperature();
    m_sub->setState_TR(temp, dens);
}

// saturation pressure
doublereal WaterSSTP::satPressure(doublereal t) const
{
    doublereal tsave = temperature();
    doublereal dsave = density();
    doublereal pp = m_sub->psat(t);
    m_sub->setState_TR(tsave, dsave);
    return pp;
}

// Return the fraction of vapor at the current conditions
doublereal WaterSSTP::vaporFraction() const
{
    if (temperature() >= m_sub->Tcrit()) {
        double dens = density();
        if (dens >= m_sub->Rhocrit()) {
            return 0.0;
        }
        return 1.0;
    }
    /*
     * If below tcrit we always return 0 from this class
     */
    return 0.0;
}


}
