/*
 * (C) Copyright 1996-2016 ECMWF.
 *
 * This software is licensed under the terms of the Apache Licence Version 2.0
 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
 * In applying this licence, ECMWF does not waive the privileges and immunities
 * granted to it by virtue of its status as an intergovernmental organisation nor
 * does it submit to any jurisdiction.
 */

/*!
    \file SciMethods.cc
    \brief Definition of SciMethods.
    \author Graphics Section, ECMWF

    Started: September 2011
*/

#include <math.h>

#include "SciMethods.h"

namespace magics {

const double KAPPA = 0.285611;  // Rd/cp

/*! Computes the distance on Earth in km */
double geoDistanceInKm(double fi1, double la1, double fi2, double la2) {
    double rfi1 = RAD(fi1);
    double rla1 = RAD(la1);
    double rfi2 = RAD(fi2);
    double rla2 = RAD(la2);

    if (rla1 != rla2) {
        double d = sin(rfi1) * sin(rfi2) + cos(rfi1) * cos(rfi2) * cos(fabs(rla1 - rla2));
        return acos(d) * EarthRadiusInKm;
    }
    else {
        return fabs(rfi1 - rfi2) * EarthRadiusInKm;
    }
}

/*!
  \brief computes potential temperature

   This method computes the potential temperature for a given
   temperature and pressure.

   \param t temperature in K
   \param p pressure in Pa
   \return potential temperature in K
 */
double theta(double t, double p) {
    return t * pow(100000. / p, KAPPA);
}

/*!
  \brief computes temperature from potential temperature

   This method computes the temperature for a given
   potential temperature and pressure.

   \param th potential temperature in K
   \param p pressure in Pa
   \return temperature in K

*/
double temperatureFromTheta(double th, double p) {
    return th * pow(p / 100000., KAPPA);
}

/*!
  \brief computes pressure from potential temperature

   This method computes the pressure for a given
   potential temperature and temperature.

   \param th potential temperature in K
   \param t temperature in K
   \return pressure in Pa
*/
double pressureFromTheta(double th, double t) {
    return 100000. * pow(t / th, 1. / KAPPA);
}

/*!
  \brief computes the dew point temperature

   This method computes the dew point
   temperature for a given temperature
   and relative humidity.

   \param t  temperature in K
   \param rh relative humidity in %
   \return   dew point temperature in K
*/

double tDew(double t, double rh) {
    double td;
    double tc = t - 273.16;
    double a  = 17.27;
    double b  = 237.7;

    if (rh >= 100.)
        td = tc;
    else {
        double gamma = a * tc / (b + tc) + log(rh / 100.);
        td           = b * gamma / (a - gamma);
    }

    return td + 273.16;
}

/*!
  \brief computes the wet bulb temperature

  This method computes the wet bulb temperature.

  \param td dew point temperature in K
  \param t  temperature in K
  \param p  pressure in Pa
  \return   wet bulb temperature in K
*/

double tWet(double td, double t, double p) {
    double r  = mixingRatio(td, p);
    double th = theta(t, p);

    double pSec = p;
    for (int i = 0; i < 10; i++) {
        double x = 0.02 * (temperatureFromMixingRatio(r, pSec) - temperatureFromTheta(th, pSec));
        if (abs(x) < 0.01) {
            break;
        }
        pSec = pow(2., x);
    }

    double tSec = temperatureFromTheta(th, pSec);

    double thSat = thetaEq(tSec, pSec);
    return temperatureFromThetaEq(thSat, p);
}

/*!
  \brief computes the equivalent potential temperature from initial conditions

   \param td  dew point temperature in K
   \param t   temperature in K
   \param p   pressure in Pa
   \return   equivalent potential temperature in K
*/

double thetaEq(double td, double t, double p) {
    double tLCL = temperatureLCL(td, t);
    double th   = theta(t, p);
    double thEq = th * exp(2.6518986 * mixingRatio(td, p) / tLCL);
    return thEq;
}

/*!
  \brief computes the equivalent potential temperature on a saturation adiabat

  This method computes the equivalent potential temperature for a saturation
  adiabat.

  \param t  temperature in K
  \param p  pressure in Pa
  \return   equvalent potential temperature in K
*/

double thetaEq(double t, double p) {
    return theta(t, p) / exp(-2.6518986 * mixingRatio(t, p) / t);
}

/*!
  \brief computes the temperature on saturation adiabat

  This method computes the temperature on a saturation adiabat for
  a given equvalent potential temperature and pressure.

  \param thEq equivalent potential temperature
  \param p    pressure in Pa
  \return     temperature in K
*/

double temperatureFromThetaEq(double thEq, double p) {
    double tq = 253.16;
    double d  = 120.;
    for (int i = 0; i < 12; i++) {
        d /= 2;
        double x = thEq * exp(-2.6518986 * mixingRatio(tq, p) / tq) - theta(tq, p);
        if (abs(x) <= 0.0000001) {
            break;
        }
        tq += ((x < 0) ? -1 : 1) * abs(d);
    }

    return tq;
}

/*!
  \brief computes the saturation vapour pressure over water from temperature

  This method computes the saturation vapour pressure
  over water from temperature.

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

double saturationVapourPressure(double t) {
    double c1 = 23.832241 - 5.02808 * log10(t);
    double c2 = 3.49149 - 1302.8844 / t;
    double c3 = 11.344 - 0.0303998 * t;
    return (::pow(10., c1 - 1.3816E-7 * ::pow(10., c3) + 8.1328E-3 * ::pow(10., c2) - 2949.076 / t));
}

/*!
  \brief computes the saturation  mixing ratio

  This method computes the mixing ratio.

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

double mixingRatio(double t, double p) {
    double e = saturationVapourPressure(t);
    return 622. * e / (0.01 * p - e);
}


double temperatureFromMixingRatio(double r, double p) {
    double pmb = p * 0.01;
    double c   = log10(r * pmb / (622. + r));

    double tmr =
        ::pow(10., 0.0498646455 * c + 2.4082965) - 280.23475 + 38.9114 * ::pow(::pow(10., 0.0915 * c) - 1.2035, 2.);

    return tmr + 273.16;
}

/*!
  \brief computes the temperature of LCL

   This method computes the temperature of
   the lifted condensation level (LCL)

   \param td  dew point temperature in K
   \param tf  temperature in K
   \return   LCL temperature in Pa
*/

double temperatureLCL(double td, double t) {
    double tdc  = td - 273.16;
    double tLCL = tdc - (0.001296 * tdc + 0.1963) * (t - td);

    return tLCL + 273.5;
}

/*!
  \brief computes the pressure of LCL

   This method computes the pressure of
   the lifted condensation level (LCL)

   \param td  dew point temperature in K
   \param t  temperature in K
   \param p   pressure in Pa
   \return   LCL pressure in Pa
*/

double pressureLCL(double td, double t, double p) {
    double pLCL;
    double tLCL = temperatureLCL(td, t);

    if (tLCL == t)
        pLCL = p;
    else {
        pLCL = p * pow(tLCL / t, 3.5);
    }

    return pLCL;
}


}  // namespace magics
