/*
 * This file is part of the GIRAFFE Pipeline
 * Copyright (C) 2002-2019 European Southern Observatory
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif

#include <math.h>

#include <cxtypes.h>

#include <cpl_msg.h>

#include <giastroutils.h>



/**
 * @defgroup giastroutils Astronomy related Utilities
 *
 * TBD
 */

/**@{*/

static const cxdouble TINY = 1.e-12;

static const cxdouble RV_DPI  =
    3.1415926535897932384626433832795028841971693993751;       /* pi      */

static const cxdouble DEG_TO_RAD =
        0.017453292519943295769236907684886127134428718885417; /* pi/180  */

static const cxdouble SEC_TO_DEG = 15. / 3600.;



/*
 * @brief
 *  Compute the zenith distance of a point in the sky
 *
 * @param hourangle  Hour angle in radians
 * @param delta      Declination in radians
 * @param latitude   Latitude of the observatory in radians
 *
 * @return
 *  The secant of the zenith distance, or @c 0. if na error occurred.
 *
 * The function computes the secans of the zenith distance of a point
 * in the sky, given by the angle @em hourangle from the meridian and the
 * declination @em delta. The latitude of the observing site is given by
 * @em latitude.
 *
 * The domain of the hour angle, declination and the latitude of the
 * observing site are [$-\pi$, \pi$], [$-0.5\pi, 0.5\pi$] and [$0, 2\pi$]
 * respectively.
 */

inline static cxdouble
_giraffe_compute_zdistance(cxdouble hourangle, cxdouble delta,
                           cxdouble latitude)
{

    cxdouble p0 = sin(latitude) * sin(delta);
    cxdouble p1 = cos(latitude) * cos(delta);
    cxdouble z  = p0 + cos(hourangle) * p1;

    if (fabs(z) < TINY) {
        z = z < 0. ? -TINY : TINY;
    }

    return 1. / z;

}


/*
 * @brief
 *  Compute approximated airmass value.
 *
 * @param secz  Secant of the zenith distance.
 *
 * @return
 *  The function returns the approximated airmass value.
 *
 * The function uses the approximation given by Young and Irvine
 * (Young A. T., Irvine W. M., 1967, Astron. J. 72, 945) to compute
 * the airmass for a given sec(z) @em secz. This approximation
 * takes into account atmosphere refraction and curvature, but is in
 * principle only valid at sea level.
 */

inline static cxdouble
_giraffe_compute_airmass_young_irvine(cxdouble secz)
{

    return secz * (1. - 0.0012 * (pow(secz, 2.) - 1.));

}


/*
 * @brief
 *  Compute approximated airmass value.
 *
 * @param secz  Secant of the zenith distance.
 *
 * @return
 *  The function returns the approximated airmass value.
 *
 * The function uses the approximation given by Young (Young A. T.,
 * 1994, "Air mass and refraction", Applied Optics, 33, 1108-1110) to
 * compute the airmass for a given sec(z) @em secz, where z is the true
 * zenith angle.
 */

inline static cxdouble
_giraffe_compute_airmass_young(cxdouble secz)
{

    cxdouble z = 1. / secz;    /* cos(zt) cosine of the true zenith angle */
    cxdouble x = 0.;
    cxdouble y = 0.;

    x = 1.002432 * z * z + 0.148386 * z + 0.0096467;
    y = z * z * z + 0.149864 * z * z + 0.0102963 * z + 0.000303978;

    return x / y;

}


/**
 * @brief
 *   Compute the airmass for a given pointing direction and observing site.
 *
 * @param alpha     Right Ascension in degrees.
 * @param delta     Declination in degrees.
 * @param lst       Local sidereal time in seconds elapsed since
 *                  sidereal midnight.
 * @param exptime   Integration time in seconds.
 * @param latitude  Latitude of the observatory site in degrees.
 *
 * @return
 *  The function returns the computed average airmass. In case an
 *  error occurred the returned airmass is set to -1.
 *
 * The function calculates the average airmass for the line of sight
 * given by the right ascension @em alpha and the declination
 * @em delta. The latitude in degrees of the observatory site @em latitude,
 * and the local sidereal time at observation start @em lst has to be given,
 * as well as the duration of the observation, i.e. the exposure time
 * @em exptime.
 *
 * The airmass is computed using the approximations of Young (Young A. T.,
 * 1994, "Air mass and refraction", Applied Optics, 33, 1108-1110) and
 * Stetson (Stetson P., 1987, PASP 99, 191).
 */

cxdouble
giraffe_compute_airmass(cxdouble alpha, cxdouble delta, cxdouble lst,
                        cxdouble exptime, cxdouble latitude)
{

    const cxchar* const fctid = "giraffe_compute_airmass";


    /* Weights for Stetson's formula */

    const cxdouble weights[] = {1. / 6., 2. / 3., 1. / 6.};


   /*
    * Accuracy limit for airmass approximation (cf. Young A. T., Irvine W. M.,
    * 1967, Astron. J. 72, 945).
    */

    const cxdouble airmass_upper_limit = 10.;


    cxdouble z = 0.;
    cxdouble hourangle = 0.;
    cxdouble airmass = 0.;


   /*
    * Compute hour angle of the observation in degrees.
    */

    hourangle = lst * SEC_TO_DEG - alpha;


   /*
    * Range adjustments. Angle between line of sight and the meridian
    * is needed.
    */

    if (hourangle < -180.) {
        hourangle += 360.;
    }

    if (hourangle > 180.) {
        hourangle -= 360.;
    }


   /*
    * Convert angles from degrees to radians
    */

    delta *= DEG_TO_RAD;
    latitude *= DEG_TO_RAD;
    hourangle *= DEG_TO_RAD;


   /*
    * Calculate airmass of the observation using the approximation given
    * by Young (Young A. T., 1994, "Air mass and refraction", Applied
    * Optics, 33, 1108-1110)for the individual airmass values. For finite
    * exposure times these airmass values are averaged using the weights
    * given by Stetson (Stetson P., 1987, PASP 99, 191)
    */

    z = _giraffe_compute_zdistance(hourangle, delta, latitude);

    if (fabs(z) < TINY) {
        cpl_msg_debug(fctid, "Airmass computation failed. Object is "
                      "below the horizon.");
        return -1.;
    }

    airmass = _giraffe_compute_airmass_young(z);

    if (exptime > 0.) {

        const cxint nweights = CX_N_ELEMENTS(weights);

        cxint i = 0;

        cxdouble timestep = exptime / (nweights - 1) * SEC_TO_DEG *
                DEG_TO_RAD;


        airmass *= weights[0];

        for (i = 1; i < nweights; i++) {

            z = _giraffe_compute_zdistance(hourangle + i * timestep,
                                           delta, latitude);

            if (fabs(z) < TINY) {

                cpl_msg_debug(fctid, "Airmass computation failed. Object "
                              "is below the horizon.");
                return -1.;

            }

            airmass += weights[i] * _giraffe_compute_airmass_young(z);

        }

    }


    if (airmass > airmass_upper_limit) {
        cpl_msg_debug(fctid, "Airmass larger than %f", airmass_upper_limit);
    }

    return airmass;

}
/**@}*/
