/**
 *  @file MolalityVPSSTP.cpp
 *   Definitions for intermediate ThermoPhase object for phases which
 *   employ molality based activity coefficient formulations
 *  (see @ref thermoprops
 * and class @link Cantera::MolalityVPSSTP MolalityVPSSTP@endlink).
 */

// This file is part of Cantera. See License.txt in the top-level directory or
// at https://cantera.org/license.txt for license and copyright information.

#include "cantera/thermo/MolalityVPSSTP.h"
#include "cantera/base/stringUtils.h"
#include "cantera/base/utilities.h"

#include <fstream>

namespace Cantera
{

MolalityVPSSTP::MolalityVPSSTP()
{
    // Change the default to be that charge neutrality in the phase is necessary
    // condition for the proper specification of thermodynamic functions within
    // the phase
    m_chargeNeutralityNecessary = true;
}

// -------------- Utilities -------------------------------

void MolalityVPSSTP::setpHScale(const int pHscaleType)
{
    m_pHScalingType = pHscaleType;
    if (pHscaleType != PHSCALE_PITZER && pHscaleType != PHSCALE_NBS) {
        throw CanteraError("MolalityVPSSTP::setpHScale",
                           "Unknown scale type: {}", pHscaleType);
    }
}

int MolalityVPSSTP::pHScale() const
{
    return m_pHScalingType;
}

void MolalityVPSSTP::setMoleFSolventMin(double xmolSolventMIN)
{
    if (xmolSolventMIN <= 0.0) {
        throw CanteraError("MolalityVPSSTP::setMoleFSolventMin ", "trouble");
    } else if (xmolSolventMIN > 0.9) {
        throw CanteraError("MolalityVPSSTP::setMoleFSolventMin ", "trouble");
    }
    m_xmolSolventMIN = xmolSolventMIN;
}

double MolalityVPSSTP::moleFSolventMin() const
{
    return m_xmolSolventMIN;
}

void MolalityVPSSTP::calcMolalities() const
{
    getMoleFractions(m_molalities.data());
    double xmolSolvent = std::max(m_molalities[0], m_xmolSolventMIN);
    double denomInv = 1.0/ (m_Mnaught * xmolSolvent);
    for (size_t k = 0; k < m_kk; k++) {
        m_molalities[k] *= denomInv;
    }
}

void MolalityVPSSTP::getMolalities(double* const molal) const
{
    calcMolalities();
    for (size_t k = 0; k < m_kk; k++) {
        molal[k] = m_molalities[k];
    }
}

void MolalityVPSSTP::setMolalities(const double* const molal)
{
    double Lsum = 1.0 / m_Mnaught;
    for (size_t k = 1; k < m_kk; k++) {
        m_molalities[k] = molal[k];
        Lsum += molal[k];
    }
    double tmp = 1.0 / Lsum;
    m_molalities[0] = tmp / m_Mnaught;
    double sum = m_molalities[0];
    for (size_t k = 1; k < m_kk; k++) {
        m_molalities[k] = tmp * molal[k];
        sum += m_molalities[k];
    }
    if (sum != 1.0) {
        tmp = 1.0 / sum;
        for (size_t k = 0; k < m_kk; k++) {
            m_molalities[k] *= tmp;
        }
    }
    setMoleFractions(m_molalities.data());

    // Essentially we don't trust the input: We calculate the molalities from
    // the mole fractions that we just obtained.
    calcMolalities();
}

void MolalityVPSSTP::setMolalitiesByName(const Composition& mMap)
{
    // HKM -> Might need to be more complicated here, setting neutrals so that
    //        the existing mole fractions are preserved.

    // Get a vector of mole fractions
    vector<double> mf(m_kk, 0.0);
    getMoleFractions(mf.data());
    double xmolSmin = std::max(mf[0], m_xmolSolventMIN);
    for (size_t k = 0; k < m_kk; k++) {
        double mol_k = getValue(mMap, speciesName(k), 0.0);
        if (mol_k > 0) {
            mf[k] = mol_k * m_Mnaught * xmolSmin;
        }
    }

    // check charge neutrality
    size_t largePos = npos;
    double cPos = 0.0;
    size_t largeNeg = npos;
    double cNeg = 0.0;
    double sum = 0.0;
    for (size_t k = 0; k < m_kk; k++) {
        double ch = charge(k);
        if (mf[k] > 0.0) {
            if (ch > 0.0 && ch * mf[k] > cPos) {
                largePos = k;
                cPos = ch * mf[k];
            }
            if (ch < 0.0 && fabs(ch) * mf[k] > cNeg) {
                largeNeg = k;
                cNeg = fabs(ch) * mf[k];
            }
        }
        sum += mf[k] * ch;
    }
    if (sum != 0.0) {
        if (sum > 0.0) {
            if (cPos > sum) {
                mf[largePos] -= sum / charge(largePos);
            } else {
                throw CanteraError("MolalityVPSSTP:setMolalitiesbyName",
                                   "unbalanced charges");
            }
        } else {
            if (cNeg > (-sum)) {
                mf[largeNeg] -= (-sum) / fabs(charge(largeNeg));
            } else {
                throw CanteraError("MolalityVPSSTP:setMolalitiesbyName",
                                   "unbalanced charges");
            }
        }
    }
    sum = 0.0;
    for (size_t k = 0; k < m_kk; k++) {
        sum += mf[k];
    }
    sum = 1.0/sum;
    for (size_t k = 0; k < m_kk; k++) {
        mf[k] *= sum;
    }
    setMoleFractions(mf.data());

    // After we formally set the mole fractions, we calculate the molalities
    // again and store it in this object.
    calcMolalities();
}

void MolalityVPSSTP::setMolalitiesByName(const string& x)
{
    Composition xx = parseCompString(x, speciesNames());
    setMolalitiesByName(xx);
}

// - Activities, Standard States, Activity Concentrations -----------

int MolalityVPSSTP::activityConvention() const
{
    return cAC_CONVENTION_MOLALITY;
}

void MolalityVPSSTP::getActivityConcentrations(double* c) const
{
    throw NotImplementedError("MolalityVPSSTP::getActivityConcentrations");
}

double MolalityVPSSTP::standardConcentration(size_t k) const
{
    throw NotImplementedError("MolalityVPSSTP::standardConcentration");
}

void MolalityVPSSTP::getActivities(double* ac) const
{
    throw NotImplementedError("MolalityVPSSTP::getActivities");
}

void MolalityVPSSTP::getActivityCoefficients(double* ac) const
{
    getMolalityActivityCoefficients(ac);
    double xmolSolvent = std::max(moleFraction(0), m_xmolSolventMIN);
    for (size_t k = 1; k < m_kk; k++) {
        ac[k] /= xmolSolvent;
    }
}

void MolalityVPSSTP::getMolalityActivityCoefficients(double* acMolality) const
{
    getUnscaledMolalityActivityCoefficients(acMolality);
    applyphScale(acMolality);
}

double MolalityVPSSTP::osmoticCoefficient() const
{
    // First, we calculate the activities all over again
    vector<double> act(m_kk);
    getActivities(act.data());

    // Then, we calculate the sum of the solvent molalities
    double sum = 0;
    for (size_t k = 1; k < m_kk; k++) {
        sum += std::max(m_molalities[k], 0.0);
    }
    double oc = 1.0;
    if (sum > 1.0E-200) {
        oc = - log(act[0]) / (m_Mnaught * sum);
    }
    return oc;
}

void MolalityVPSSTP::setState_TPM(double t, double p, const double* const molalities)
{
    setMolalities(molalities);
    setState_TP(t, p);
}

void MolalityVPSSTP::setState_TPM(double t, double p, const Composition& m)
{
    setMolalitiesByName(m);
    setState_TP(t, p);
}

void MolalityVPSSTP::setState_TPM(double t, double p, const string& m)
{
    setMolalitiesByName(m);
    setState_TP(t, p);
}

void MolalityVPSSTP::setState(const AnyMap& state) {
    AnyValue molalities;
    if (state.hasKey("molalities")) {
        molalities = state["molalities"];
    } else if (state.hasKey("M")) {
        molalities = state["M"];
    }

    if (molalities.is<string>()) {
        setMolalitiesByName(molalities.asString());
    } else if (molalities.is<AnyMap>()) {
        setMolalitiesByName(molalities.asMap<double>());
    }

    VPStandardStateTP::setState(state);
}

void MolalityVPSSTP::initThermo()
{
    VPStandardStateTP::initThermo();

    // Find the Cl- species
    m_indexCLM = findCLMIndex();
}

void MolalityVPSSTP::getUnscaledMolalityActivityCoefficients(double* acMolality) const
{
    throw NotImplementedError("MolalityVPSSTP::getUnscaledMolalityActivityCoefficients");
}

void MolalityVPSSTP::applyphScale(double* acMolality) const
{
    throw NotImplementedError("MolalityVPSSTP::applyphScale");
}

size_t MolalityVPSSTP::findCLMIndex() const
{
    size_t indexCLM = npos;
    size_t eCl = npos;
    size_t eE = npos;
    size_t ne = nElements();
    for (size_t e = 0; e < ne; e++) {
        string sn = elementName(e);
        if (sn == "Cl" || sn == "CL") {
            eCl = e;
            break;
        }
    }
    // We have failed if we can't find the Cl element index
    if (eCl == npos) {
        return npos;
    }
    for (size_t e = 0; e < ne; e++) {
        string sn = elementName(e);
        if (sn == "E" || sn == "e") {
            eE = e;
            break;
        }
    }
    // We have failed if we can't find the E element index
    if (eE == npos) {
        return npos;
    }
    for (size_t k = 1; k < m_kk; k++) {
        double nCl = nAtoms(k, eCl);
        if (nCl != 1.0) {
            continue;
        }
        double nE = nAtoms(k, eE);
        if (nE != 1.0) {
            continue;
        }
        for (size_t e = 0; e < ne; e++) {
            if (e != eE && e != eCl) {
                double nA = nAtoms(k, e);
                if (nA != 0.0) {
                    continue;
                }
            }
        }
        string sn = speciesName(k);
        if (sn != "Cl-" && sn != "CL-") {
            continue;
        }

        indexCLM = k;
        break;
    }
    return indexCLM;
}

bool MolalityVPSSTP::addSpecies(shared_ptr<Species> spec)
{
    bool added = VPStandardStateTP::addSpecies(spec);
    if (added) {
        if (m_kk == 1) {
            // The solvent defaults to species 0
            m_weightSolvent = molecularWeight(0);
            m_Mnaught = m_weightSolvent / 1000.;
        }
        m_molalities.push_back(0.0);
    }
    return added;
}

string MolalityVPSSTP::report(bool show_thermo, double threshold) const
{
    fmt::memory_buffer b;
    try {
        if (name() != "") {
            fmt_append(b, "\n  {}:\n", name());
        }
        fmt_append(b, "\n");
        fmt_append(b, "       temperature    {:12.6g}  K\n", temperature());
        fmt_append(b, "          pressure    {:12.6g}  Pa\n", pressure());
        fmt_append(b, "           density    {:12.6g}  kg/m^3\n", density());
        fmt_append(b, "  mean mol. weight    {:12.6g}  amu\n", meanMolecularWeight());

        double phi = electricPotential();
        fmt_append(b, "         potential    {:12.6g}  V\n", phi);

        vector<double> x(m_kk);
        vector<double> molal(m_kk);
        vector<double> mu(m_kk);
        vector<double> muss(m_kk);
        vector<double> acMolal(m_kk);
        vector<double> actMolal(m_kk);
        getMoleFractions(&x[0]);
        getMolalities(&molal[0]);
        getChemPotentials(&mu[0]);
        getStandardChemPotentials(&muss[0]);
        getMolalityActivityCoefficients(&acMolal[0]);
        getActivities(&actMolal[0]);

        size_t iHp = speciesIndex("H+", false);
        if (iHp != npos) {
            double pH = -log(actMolal[iHp]) / log(10.0);
            fmt_append(b,
                "                pH    {:12.4g}\n", pH);
        }

        if (show_thermo) {
            fmt_append(b, "\n");
            fmt_append(b, "                          1 kg            1 kmol\n");
            fmt_append(b, "                       -----------      ------------\n");
            fmt_append(b, "          enthalpy    {:12.6g}     {:12.4g}     J\n",
                       enthalpy_mass(), enthalpy_mole());
            fmt_append(b, "   internal energy    {:12.6g}     {:12.4g}     J\n",
                       intEnergy_mass(), intEnergy_mole());
            fmt_append(b, "           entropy    {:12.6g}     {:12.4g}     J/K\n",
                       entropy_mass(), entropy_mole());
            fmt_append(b, "    Gibbs function    {:12.6g}     {:12.4g}     J\n",
                       gibbs_mass(), gibbs_mole());
            fmt_append(b, " heat capacity c_p    {:12.6g}     {:12.4g}     J/K\n",
                       cp_mass(), cp_mole());
            try {
                fmt_append(b, " heat capacity c_v    {:12.6g}     {:12.4g}     J/K\n",
                           cv_mass(), cv_mole());
            } catch (NotImplementedError&) {
                fmt_append(b, " heat capacity c_v    <not implemented>\n");
            }
        }

        fmt_append(b, "\n");
        int nMinor = 0;
        double xMinor = 0.0;
        if (show_thermo) {
            fmt_append(b, "                           X        "
                "   Molalities         Chem.Pot.    ChemPotSS    ActCoeffMolal\n");
            fmt_append(b, "                                    "
                "                      (J/kmol)      (J/kmol)\n");
            fmt_append(b, "                     -------------  "
                "  ------------     ------------  ------------    ------------\n");
            for (size_t k = 0; k < m_kk; k++) {
                if (x[k] > threshold) {
                    if (x[k] > SmallNumber) {
                        fmt_append(b,
                                   "{:>18s}  {:12.6g}     {:12.6g}     {:12.6g}   "
                                   "{:12.6g}   {:12.6g}\n", speciesName(k),
                                   x[k], molal[k], mu[k], muss[k], acMolal[k]);
                    } else {
                        fmt_append(b,
                                   "{:>18s}  {:12.6g}     {:12.6g}          N/A      "
                                   "{:12.6g}   {:12.6g}\n", speciesName(k),
                                   x[k], molal[k], muss[k], acMolal[k]);
                    }
                } else {
                    nMinor++;
                    xMinor += x[k];
                }
            }
        } else {
            fmt_append(b, "                           X            Molalities\n");
            fmt_append(b, "                     -------------     ------------\n");
            for (size_t k = 0; k < m_kk; k++) {
                if (x[k] > threshold) {
                    fmt_append(b, "{:>18s}   {:12.6g}     {:12.6g}\n",
                            speciesName(k), x[k], molal[k]);
                } else {
                    nMinor++;
                    xMinor += x[k];
                }
            }
        }
        if (nMinor) {
            fmt_append(b, "     [{:+5d} minor] {:12.6g}\n", nMinor, xMinor);
        }
    } catch (CanteraError& err) {
        return to_string(b) + err.what();
    }
    return to_string(b);
}

}
