/***************************** LICENSE START ***********************************

 Copyright 2018 ECMWF and INPE. This software is distributed under the terms
 of the Apache License version 2.0. In applying this license, ECMWF does not
 waive the privileges and immunities granted to it by virtue of its status as
 an Intergovernmental Organization or submit itself to any jurisdiction.

 ***************************** LICENSE END *************************************/

#include "MvSci.h"

#include <math.h>

#if defined(METVIEW_AIX_XLC)
inline double exp10(const double r)
{
    return exp(r * 2.302585093);
}  // exp(r*ln(10))
#endif

const double cTMELT  = 273.16;
const double cR3LES  = 17.502;
const double cR3IES  = 22.587;
const double cR4LES  = 32.19;
const double cR4IES  = -0.7;
const double cRD     = 287.05;
//specific heat of air on constant volume
const double cRV     = 461.51;
//specific heat of air on constant pressure
const double cRP     = 1005.46;
const double cKAPPA  = cRD/cRP;
const double cEP     = cRD/cRV;
const double cCI     = 2710.;
const double cRESTT  = 611.21;
const double cR2ES   = cRESTT * cRD / cRV;
const double cVTMPC1 = cRV / cRD - 1.;
const double RAD     = 180. / (2. * asin(1));

/*!
  \brief computes the saturation  mixing ratio

  This method computes the saturation mixing ratio.

  \param t  temperature in K
  \param p  pressure in Pa
  \return   saturation mixing ratio in kg/kg
*/

double MvSci::saturationMixingRatio(double t, double p)
{
    double e = saturationVapourPressure(t);
    return 0.621981 * e / (p - e);
}

/*!
  \brief computes the relative humidity

  This method computes the relative humidity.

  \param t  temperature in K
  \param p  pressure in Pa
  \param q  specific humidity in kg/kg
  \return   relative humidity (0-1)
*/

double MvSci::relativeHumidity(double t, double p, double q)
{
    double es = saturationVapourPressure(t);
    double e  = vapourPressure(p, q);

    return e / es;
}

/*!
  \brief computes the relative humidity from dewpoint temperature

  This method computes the relative humidity from dewpoint temperature.

  \param t  temperature in K
  \param td dewpoint temperature in K
  \return   relative humidity (0-1)
*/

double MvSci::relativeHumidityFromTd(double t, double td)
{
    double es = saturationVapourPressure(td) / saturationVapourPressure(t);
    return es;
}

/*!
  \brief computes the specific humidity from relative humidity

  This method computes specific humidity from relative humidity

  \param t  temperature in K
  \param p  pressure in Pa
  \param r  relative humidity (0-1)
  \return   specific humidity in kg/kg
*/

double MvSci::specificHumidity(double t, double p, double r)
{
    double ws = saturationMixingRatio(t, p);
    double w  = r * ws;

    return w / (1. + w);
}

/*!
  \brief computes the specific humidity from dewpoint temperature

  This method computes specific humidity from dewpoint temperature

  \param t  temperature in K
  \param p  pressure in Pa
  \param td  dewpoint temperature in K
  \return   specific humidity in kg/kg
*/

double MvSci::specificHumidityFromTd(double t, double p, double td)
{
    double r = relativeHumidityFromTd(t, td);
    return specificHumidity(t, p, r);
}

/*!
  \brief computes the saturation vapour pressure for mixed-phase from temperature

  This method computes the saturation vapour pressure
  for mixed-phase from temperature.

  \param t temperature in K
  \return pressure in Pa
*/

double MvSci::saturationVapourPressure(double t)
{
    double c1  = 611.21;
    double c3l = 17.502;
    double c4l = 32.19;
    double c3i = 22.587;
    double c4i = -0.7;
    double t0  = 273.16;
    double ti  = 250.16;

    //Saturation vapour pressure over water
    double es_water = c1 * exp(c3l * (t - t0) / (t - c4l));

    //Saturation vapour pressure over ice
    double es_ice = c1 * exp(c3i * (t - t0) / (t - c4i));

    //fraction of liquid water
    double alpha;
    if (t <= ti)
        alpha = 0.;
    else if (t > ti && t < t0)
        alpha = pow((t - ti) / (t0 - ti), 2.);
    else
        alpha = 1.;

    return alpha * es_water + (1. - alpha) * es_ice;
}

/*!
  \brief computes the vapour pressure from pressure and spec humidity

  This method computes the vapour pressure
  from pressure and specific humidity.

  \param p pressure in Pa
  \param q specific humidity in kg/kg
  \return vapour pressure in Pa
*/
double MvSci::vapourPressure(double p, double q)
{
    double w = q / (1 - q);
    return p * w / (0.621981 + w);
}

/*!
  \brief computes the water vapour ppmv from pressure and specific humidity

  This method computes the water vapour ppmv
  from pressure and specific humidity.

  \param p pressure in Pa
  \param q specific humidity in kg/kg
  \return humidity in ppm
*/

double MvSci::vapourPPMV(double p, double q)
{
    double pw = vapourPressure(p, q);
    return pw / (p - pw) * 1000000;
}

/*!
  \brief computes the ozone ppmv from mixing ratio

  This method computes the ozone ppmv
  from from mixing ratio.

  \param r ozone mixing ratio in kg/kg
  \return ozone in ppmv
*/

double MvSci::ozonePPMV(double r)
{
    double mmr2vmr = 28.97 / 48.0;
    return 1000000 * r * mmr2vmr;
}

double MvSci::dewPointFromQ(double p, double t, double q, const std::string& formula)
{
    const std::string cMixedFormula("MIXED_PHASE_0_TO_-23");
    const std::string cSaturationIce("SATURATION_OVER_ICE");
    double ZCVM3, ZCVM4;

    if (formula == cMixedFormula) {
        double rtwat   = cTMELT;
        double rtice   = cTMELT - 23.0;
        double foealfa = std::min(1., pow(((std::max(rtice, std::min(rtwat, t)) - rtice) / (rtwat - rtice)), 2.0));

        ZCVM3 = foealfa * cR3LES + (1. - foealfa) * cR3IES;
        ZCVM4 = foealfa * cR4LES + (1. - foealfa) * cR4IES;
    }
    else if (formula == cSaturationIce) {
        if (t > cTMELT) {
            ZCVM3 = cR3LES;
            ZCVM4 = cR4LES;
        }
        else {
            ZCVM3 = cR3IES;
            ZCVM4 = cR4IES;
        }
    }
    else  // SATURATION OVER WATER
    {
        ZCVM3 = cR3LES;
        ZCVM4 = cR4LES;
    }

    double ZFRAC = log(p * q / (cR2ES * (1. + cVTMPC1 * q))) / ZCVM3;

    return (cTMELT - ZFRAC * ZCVM4) / (1. - ZFRAC);
}

double MvSci::speed(double u, double v)
{
    return sqrt(pow(u, 2) + pow(v, 2));
}

double MvSci::direction(double u, double v)
{
    return (-90.0 - atan2(v, u) * RAD);
}

// Used in Pott.cc
// t temperature in K
// p pressure in Pa
double MvSci::saturationSpecHumidity(double t, double p, bool flag)
{
    double A = 0.;
    double B = 0.;

    if (t >= cTMELT || flag) {
        A = 17.269;
        B = 35.86;
    } else {
        A = 21.875;
        B = 7.66;
    }

    double v = 610.78*exp(A*(t-cTMELT)/(t-B));
    return (flag)?
                (cEP*v/p):
                ((cEP*v)/(p - (1.-cEP)*v));
}


// As used in Pott.cc
// t temperature in K
// p pressure in Pa
// q specific humidity in kg/kg
bool MvSci::saturationLevel(double t, double p, double q, double &tSat, double &pSat, bool flag)
{
    int count = 0;
    const int maxIter = 20;

    if (q < 0.000002 && p > 10000.)
        q=0.000002;

    double pSatLimit=p*0.2;
    pSat = p*0.98;
    tSat = t;

    while ( count < maxIter ) {
        tSat = t*pow(pSat/p, cKAPPA) ;
        double qSat = saturationSpecHumidity(tSat, pSat, flag);
        double tSatTop = t*pow((pSat-100.)/p, cKAPPA);
        double qSatTop = saturationSpecHumidity(tSatTop, (pSat-100.),flag);
        double ratio =(qSat-q)/(qSat-qSatTop);
        //dp = p - pSat;
        if (fabs(ratio) < 1.) {
            return true;
        }
        pSat -= ratio*100;
        if (pSat < pSatLimit) {
            pSat += ratio*100;
            return true;
        }

        count++;
    }

    //error
    return false;
}

// t temperature in K
// p pressure in Pa
double MvSci::potentialTemperature(double t, double p)
{
    return t*pow(100000./p, cKAPPA);
}

double MvSci::potentialTemperaturePressureTerm(double p)
{
    return pow(100000./p, cKAPPA);
}

// As used in Pott.cc
// t temperature in K
// p pressure in Pa
// q specific humidity in kg/kg
double MvSci::equivalentPotentialTemperature(double t, double p, double q, bool flag)
{
    double pSat, tSat;
    if (saturationLevel(t, p, q, tSat, pSat, flag)) {
        double qSat = saturationSpecHumidity(tSat, pSat, flag);
        return tSat * pow(100000./pSat, cKAPPA)*exp(qSat*cCI/tSat);
    }

    return -1.;
}

// As used in Pott.cc
// t temperature in K
// p pressure in Pa
double MvSci::saturatedEquivalentPotentialTemperature(double t, double p, bool flag)
{
    double qSat = saturationSpecHumidity(t, p, flag);
    return t * pow(100000./p, cKAPPA)*exp(qSat*cCI/t);
}

// As used in Pott.cc
// t temperature in K
// p pressure in Pa
// pTerm precomputed potentialTemperaturePressureTerm
double MvSci::saturatedEquivalentPotentialTemperatureWithPTerm(double t, double p, double pTerm, bool flag)
{
    double qSat = saturationSpecHumidity(t, p, flag);
    return t * pTerm*exp(qSat*cCI/t);
}
