/**
 *  @file VPSSMgrFactory.cpp
 *    Definitions for factory to build instances of classes that manage the
 *    calculation of standard state properties for all the species in a phase
 *    (see \ref spthermo and class
 *    \link Cantera::VPSSMgrFactory VPSSMgrFactory\endlink);
 */
/*
 * Copyright (2005) 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/SpeciesThermo.h"

#include "cantera/thermo/VPSSMgr.h"
#include "VPSSMgrFactory.h"

#include "cantera/thermo/VPStandardStateTP.h"

#include "cantera/thermo/VPSSMgr_IdealGas.h"
#include "cantera/thermo/VPSSMgr_ConstVol.h"
#include "cantera/thermo/VPSSMgr_Water_ConstVol.h"
#include "cantera/thermo/VPSSMgr_Water_HKFT.h"
#include "cantera/thermo/VPSSMgr_General.h"

#include "cantera/thermo/VPSSMgr_types.h"

#include "cantera/thermo/SpeciesThermoMgr.h"
#include "cantera/thermo/speciesThermoTypes.h"
#include "cantera/thermo/SpeciesThermo.h"
#include "cantera/thermo/SpeciesThermoFactory.h"
#include "cantera/thermo/GeneralSpeciesThermo.h"

#include "cantera/thermo/mix_defs.h"

#include "cantera/base/xml.h"
#include "cantera/base/ctml.h"

using namespace ctml;
using namespace std;

namespace Cantera
{

VPSSMgrFactory* VPSSMgrFactory::s_factory = 0;

#if defined(THREAD_SAFE_CANTERA)
// Defn of the static mutex variable that locks the
// %VPSSMgr factory singelton
boost::mutex VPSSMgrFactory::vpss_species_thermo_mutex;
#endif


//! Examine the types of species thermo parameterizations, and return a flag indicating the type of parameterization
//! needed by the species.
/*!
 *
 *  @param spDataNodeList            Species Data XML node. This node contains a list
 *                                     of species XML nodes underneath it.
 *  @param has_nasa_idealGas         Boolean indicating that one species has a nasa ideal gas standard state
 *  @param has_nasa_constVol         Boolean indicating that one species has a nasa ideal solution standard state
 *  @param has_shomate_idealGas      Boolean indicating that one species has a shomate ideal gas standard state
 *  @param has_shomate_constVol      Boolean indicating that one species has a shomate ideal solution standard state
 *  @param has_simple_idealGas       Boolean indicating that one species has a simple ideal gas standard state
 *  @param has_simple_constVol       Boolean indicating that one species has a simple ideal solution standard state
 *  @param has_water                 Boolean indicating that one species has a water standard state
 *  @param has_tpx                   Boolean indicating that one species has a tpx standard state
 *  @param has_hptx                  Boolean indicating that one species has a htpx standard state
 *  @param has_other                 Boolean indicating that one species has different standard state than the ones listed above
 *
 * @todo Make sure that spDadta_node is species Data XML node by checking
 *      its name is speciesData
 */
static void getVPSSMgrTypes(std::vector<XML_Node*> & spDataNodeList,
                            int& has_nasa_idealGas,
                            int& has_nasa_constVol,
                            int& has_shomate_idealGas,
                            int& has_shomate_constVol,
                            int& has_simple_idealGas,
                            int& has_simple_constVol,
                            int& has_water,
                            int& has_tpx,
                            int& has_hptx,
                            int& has_other)
{

    XML_Node* ss_ptr = 0;
    string ssModel = "idealGas";
    size_t ns = spDataNodeList.size();
    for (size_t n = 0; n < ns; n++) {
        bool ifound = false;
        XML_Node* spNode = spDataNodeList[n];
        if (spNode->hasChild("standardState")) {
            const XML_Node& ssN = spNode->child("standardState");
            string mm = ssN["model"];
            if (mm == "waterIAPWS" || mm == "waterPDSS") {
                has_water++;
                ifound = true;
            }
            if (mm == "HKFT") {
                has_hptx++;
                ifound = true;
            }
        }
        if (!ifound) {
            if (spNode->hasChild("thermo")) {
                const XML_Node& th = spNode->child("thermo");
                if (spNode->hasChild("standardState")) {
                    ss_ptr = &(spNode->child("standardState"));
                    ssModel = ss_ptr->attrib("model");
                }
                if (th.hasChild("NASA")) {
                    if (ssModel == "idealGas") {
                        has_nasa_idealGas++;
                    } else if (ssModel == "constant_incompressible" ||
                               ssModel == "constantVolume") {
                        has_nasa_constVol++;
                    } else if (ssModel == "temperature_polynomial" ||
                               ssModel == "density_temperature_polynomial"  ||
                               ssModel == "constant") {
                        has_other++;
                    } else {
                        throw UnknownVPSSMgrModel("getVPSSMgrTypes:",
                                                  spNode->attrib("name"));
                    }
                    ifound = true;
                }
                if (th.hasChild("Shomate")) {
                    if (ssModel == "idealGas") {
                        has_shomate_idealGas++;
                    } else if (ssModel == "constant_incompressible" ||
                               ssModel == "constantVolume") {
                        has_shomate_constVol++;
                    } else if (ssModel == "temperature_polynomial" ||
                               ssModel == "density_temperature_polynomial"  ||
                               ssModel == "constant") {
                        has_other++;
                    } else {
                        throw UnknownVPSSMgrModel("getVPSSMgrTypes:",
                                                  spNode->attrib("name"));
                    }
                    ifound = true;
                }
                if (th.hasChild("const_cp")) {
                    if (ssModel == "idealGas") {
                        has_simple_idealGas++;
                    } else if (ssModel == "constant_incompressible" ||
                               ssModel == "constantVolume") {
                        has_simple_constVol++;
                    } else if (ssModel == "temperature_polynomial" ||
                               ssModel == "density_temperature_polynomial"  ||
                               ssModel == "constant") {
                        has_other++;
                    } else {
                        throw UnknownVPSSMgrModel("getVPSSMgrTypes:",
                                                  spNode->attrib("name"));
                    }
                    ifound = true;
                }
                if (th.hasChild("poly")) {
                    if (th.child("poly")["order"] == "1") {
                        has_simple_constVol = 1;
                        ifound = true;
                    } else throw CanteraError("newSpeciesThermo",
                                                  "poly with order > 1 not yet supported");
                }
                if (th.hasChild("Mu0")) {
                    has_other++;
                    ifound = true;
                }
                if (th.hasChild("NASA9")) {
                    has_other++;
                    ifound = true;
                }
                if (th.hasChild("NASA9MULTITEMP")) {
                    has_other++;
                    ifound = true;
                }
                if (th.hasChild("adsorbate")) {
                    has_other++;
                    ifound = true;
                }
                if (th.hasChild("HKFT")) {
                    has_hptx++;
                    ifound = true;
                }
            } else {
                throw UnknownVPSSMgrModel("getVPSSMgrTypes:",
                                          spNode->attrib("name"));
            }
        }
    }
}

// Delete static instance of this class
/*
 * If it is necessary to explicitly delete the factory before
 * the process terminates (for example, when checking for
 * memory leaks) then this method can be called to delete it.
 */
void VPSSMgrFactory::deleteFactory()
{

#if defined(THREAD_SAFE_CANTERA)
    boost::mutex::scoped_lock lock(vpss_species_thermo_mutex);
#endif
    if (s_factory) {
        delete s_factory;
        s_factory = 0;
    }
}

VPSSMgrFactory::~VPSSMgrFactory()
{
}

VPSSMgr_enumType
VPSSMgrFactory::VPSSMgr_StringConversion(std::string ssModel) const
{
    std::string lssModel = lowercase(ssModel);
    VPSSMgr_enumType type;
    if (lssModel == "idealgas") {
        type = cVPSSMGR_IDEALGAS;
    } else if (lssModel == "constvol") {
        type = cVPSSMGR_CONSTVOL;
    } else if (lssModel == "purefuild") {
        type = cVPSSMGR_PUREFLUID;
    } else if (lssModel == "water_constvol") {
        type = cVPSSMGR_WATER_CONSTVOL;
    } else if (lssModel == "water_hkft") {
        type = cVPSSMGR_WATER_HKFT;
    } else if (lssModel == "general") {
        type = cVPSSMGR_GENERAL;
    } else {
        type = cVPSSMGR_UNDEF;
    }
    return type;
}

// Chose the variable pressure standard state manager
// and the reference standard state manager
VPSSMgr*
VPSSMgrFactory::newVPSSMgr(VPStandardStateTP* vp_ptr,
                           XML_Node* phaseNode_ptr,
                           std::vector<XML_Node*> & spDataNodeList)
{

    std::string ssManager="";
    std::string vpssManager="";
    VPSSMgr* vpss = 0;

    // First look for any explicit instructions within the XML Database
    // for the standard state manager and the variable pressure
    // standard state manager
    if (phaseNode_ptr) {
        if (phaseNode_ptr->hasChild("thermo")) {
            const XML_Node& thermoNode = phaseNode_ptr->child("thermo");
            if (thermoNode.hasChild("standardStateManager")) {
                const XML_Node& ssNode = thermoNode.child("standardStateManager");
                ssManager = ssNode["model"];
            }
            if (thermoNode.hasChild("variablePressureStandardStateManager")) {
                const XML_Node& vpssNode = thermoNode.child("variablePressureStandardStateManager");
                vpssManager = vpssNode["model"];
            }
        }
    }

    // first get the reference state handler. If we have explicit instructions,
    // use them to spawn the object.
    SpeciesThermo* spth = 0;
    if (ssManager != "") {
        spth = newSpeciesThermoMgr(ssManager);
    } else {
        spth = newSpeciesThermoMgr(spDataNodeList);
    }
    vp_ptr->setSpeciesThermo(spth);

    // Next, if we have specific directions, use them to get the VPSSSMgr object
    // and return immediately
    if (vpssManager != "") {
        VPSSMgr_enumType type = VPSSMgr_StringConversion(vpssManager);
        vpss = newVPSSMgr(type, vp_ptr);
        return vpss;
    }

    // Handle special cases based on the VPStandardState types
    if (vp_ptr->eosType() == cVPSS_IdealGas) {
        vpss = new VPSSMgr_IdealGas(vp_ptr, spth);
        return vpss;
    } else if (vp_ptr->eosType() == cVPSS_ConstVol) {
        vpss = new VPSSMgr_ConstVol(vp_ptr, spth);
        return vpss;
    }


    int inasaIG = 0, inasaCV = 0, ishomateIG = 0, ishomateCV = 0,
        isimpleIG = 0, isimpleCV = 0,
        iwater = 0, itpx = 0, iother = 0;
    int ihptx = 0;

    try {
        getVPSSMgrTypes(spDataNodeList, inasaIG, inasaCV, ishomateIG, ishomateCV,
                        isimpleIG, isimpleCV, iwater, itpx, ihptx, iother);
    } catch (UnknownSpeciesThermoModel) {
        iother = 1;
        popError();
    }

    if (iwater == 1) {
        if (ihptx == 0) {
            if (inasaIG ||  ishomateIG || isimpleIG) {
                throw CanteraError("newVPSSMgr", "Ideal gas with liquid water");
            } else {
                vpss = new VPSSMgr_Water_ConstVol(vp_ptr, spth);
            }
        } else {
            if (inasaIG ||  ishomateIG || isimpleIG) {
                throw CanteraError("newVPSSMgr", "Ideal gas with liquid water");
            } else if (inasaCV || ishomateCV ||  isimpleCV) {
                vpss = new VPSSMgr_General(vp_ptr, spth);
            } else {
                vpss = new VPSSMgr_Water_HKFT(vp_ptr, spth);
            }
        }
    }
    if (vpss == 0) {
        if (inasaCV || ishomateCV || isimpleCV) {
            if (!inasaIG && !ishomateIG && !isimpleIG && !itpx && !ihptx && !iother) {
                vpss = new VPSSMgr_ConstVol(vp_ptr, spth);
            }
        }
    }
    if (vpss == 0) {
        vpss = new VPSSMgr_General(vp_ptr, spth);
    }
    return vpss;
}



// I don't think this is currently used. However, this is a virtual
// function where additional capabilities may be added.
VPSSMgr*
VPSSMgrFactory::newVPSSMgr(VPSSMgr_enumType type, VPStandardStateTP* vp_ptr)
{
    SpeciesThermo& spthermoRef = vp_ptr->speciesThermo();
    switch (type) {
    case cVPSSMGR_IDEALGAS:
        return new VPSSMgr_IdealGas(vp_ptr, &spthermoRef);
    case cVPSSMGR_CONSTVOL:
        return new VPSSMgr_ConstVol(vp_ptr, &spthermoRef);
    case cVPSSMGR_PUREFLUID:
        throw CanteraError("VPSSMgrFactory::newVPSSMgr",
                           "unimplemented");
    case cVPSSMGR_WATER_CONSTVOL:
        return new VPSSMgr_Water_ConstVol(vp_ptr, &spthermoRef);
    case cVPSSMGR_WATER_HKFT:
        return new VPSSMgr_Water_HKFT(vp_ptr, &spthermoRef);
    case cVPSSMGR_GENERAL:
        return new VPSSMgr_General(vp_ptr, &spthermoRef);
    case cVPSSMGR_UNDEF:
    default:
        throw UnknownVPSSMgrModel("VPSSMgrFactory::newVPSSMgr", int2str(type));
        return 0;
    }
}

// I don't think this is currently used
VPSSMgr* newVPSSMgr(VPSSMgr_enumType type, VPStandardStateTP* vp_ptr,
                    Cantera::VPSSMgrFactory* f)
{
    if (f == 0) {
        f = VPSSMgrFactory::factory();
    }
    VPSSMgr* vpsssptherm = f->newVPSSMgr(type, vp_ptr);
    return vpsssptherm;
}


VPSSMgr* newVPSSMgr(VPStandardStateTP* tp_ptr,
                    XML_Node* phaseNode_ptr,
                    std::vector<XML_Node*> & spDataNodeList,
                    VPSSMgrFactory* f)
{
    if (f == 0) {
        f = VPSSMgrFactory::factory();
    }
    VPSSMgr* vpsssptherm = f->newVPSSMgr(tp_ptr, phaseNode_ptr, spDataNodeList);
    return vpsssptherm;
}


}
