// nutation.cpp
//
// Calculate nutation angles for Earth.
//
// Copyright (C) 2008, the Celestia Development Team
// Initial version by Chris Laurel, claurel@gmail.com
//
// 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.

#include "nutation.h"

#include <array>

#include <celmath/mathlib.h>

namespace celestia::ephem
{

namespace
{

struct NutationTableEntry
{
    // Multiples of arguments
    int lMult;
    int l_Mult;
    int FMult;
    int DMult;
    int OmMult;

    double l1; // longitude, sin
    double l2; // longitude, t*sin
    double o1; // obliquity, cos
    double o2; // obliquity, t*cos
    double l3; // longitude, cos
    double o3; // obliquity, sin
};


// Luni-Solar nutation coefficients, units 0.1 microarcsec:
// longitude (sin, t*sin, cos), obliquity (cos, t*cos, sin)

constexpr std::array IAU2000BNutationTable =
{
    NutationTableEntry { 0,   0,   0,   0,   1,-172064161, -174666, 92052331,  9086, 33386, 15377 },
    NutationTableEntry { 0,   0,   2,  -2,   2, -13170906,   -1675,  5730336, -3015,-13696, -4587 },
    NutationTableEntry { 0,   0,   2,   0,   2,  -2276413,    -234,   978459,  -485,  2796,  1374 },
    NutationTableEntry { 0,   0,   0,   0,   2,   2074554,     207,  -897492,   470,  -698,  -291 },
    NutationTableEntry { 0,   1,   0,   0,   0,   1475877,   -3633,    73871,  -184, 11817, -1924 },
    NutationTableEntry { 0,   1,   2,  -2,   2,   -516821,    1226,   224386,  -677,  -524,  -174 },
    NutationTableEntry { 1,   0,   0,   0,   0,    711159,      73,    -6750,     0,  -872,   358 },
    NutationTableEntry { 0,   0,   2,   0,   1,   -387298,    -367,   200728,    18,   380,   318 },
    NutationTableEntry { 1,   0,   2,   0,   2,   -301461,     -36,   129025,   -63,   816,   367 },
    NutationTableEntry { 0,  -1,   2,  -2,   2,    215829,    -494,   -95929,   299,   111,   132 },
    NutationTableEntry { 0,   0,   2,  -2,   1,    128227,     137,   -68982,    -9,   181,    39 },
    NutationTableEntry {-1,   0,   2,   0,   2,    123457,      11,   -53311,    32,    19,    -4 },
    NutationTableEntry {-1,   0,   0,   2,   0,    156994,      10,    -1235,     0,  -168,    82 },
    NutationTableEntry { 1,   0,   0,   0,   1,     63110,      63,   -33228,     0,    27,    -9 },
    NutationTableEntry {-1,   0,   0,   0,   1,    -57976,     -63,    31429,     0,  -189,   -75 },
    NutationTableEntry {-1,   0,   2,   2,   2,    -59641,     -11,    25543,   -11,   149,    66 },
    NutationTableEntry { 1,   0,   2,   0,   1,    -51613,     -42,    26366,     0,   129,    78 },
    NutationTableEntry {-2,   0,   2,   0,   1,     45893,      50,   -24236,   -10,    31,    20 },
    NutationTableEntry { 0,   0,   0,   2,   0,     63384,      11,    -1220,     0,  -150,    29 },
    NutationTableEntry { 0,   0,   2,   2,   2,    -38571,      -1,    16452,   -11,   158,    68 },
    NutationTableEntry {-2,   0,   0,   2,   0,    -47722,       0,      477,     0,   -18,   -25 },
    NutationTableEntry { 2,   0,   2,   0,   2,    -31046,      -1,    13238,   -11,   131,    59 },
    NutationTableEntry { 1,   0,   2,  -2,   2,     28593,       0,   -12338,    10,    -1,    -3 },
    NutationTableEntry {-1,   0,   2,   0,   1,     20441,      21,   -10758,     0,    10,    -3 },
    NutationTableEntry { 2,   0,   0,   0,   0,     29243,       0,     -609,     0,   -74,    13 },
    NutationTableEntry { 0,   0,   2,   0,   0,     25887,       0,     -550,     0,   -66,    11 },
    NutationTableEntry { 0,   1,   0,   0,   1,    -14053,     -25,     8551,    -2,    79,   -45 },
    NutationTableEntry {-1,   0,   0,   2,   1,     15164,      10,    -8001,     0,    11,    -1 },
    NutationTableEntry { 0,   2,   2,  -2,   2,    -15794,      72,     6850,   -42,   -16,    -5 },
    NutationTableEntry { 0,   0,  -2,   2,   0,     21783,       0,     -167,     0,    13,    13 },
    NutationTableEntry { 1,   0,   0,  -2,   1,    -12873,     -10,     6953,     0,   -37,   -14 },
    NutationTableEntry { 0,  -1,   0,   0,   1,    -12654,      11,     6415,     0,    63,    26 },
    NutationTableEntry {-1,   0,   2,   2,   1,    -10204,       0,     5222,     0,    25,    15 },
    NutationTableEntry { 0,   2,   0,   0,   0,     16707,     -85,      168,    -1,   -10,    10 },
    NutationTableEntry { 1,   0,   2,   2,   2,     -7691,       0,     3268,     0,    44,    19 },
    NutationTableEntry {-2,   0,   2,   0,   0,    -11024,       0,      104,     0,   -14,     2 },
    NutationTableEntry { 0,   1,   2,   0,   2,      7566,     -21,    -3250,     0,   -11,    -5 },
    NutationTableEntry { 0,   0,   2,   2,   1,     -6637,     -11,     3353,     0,    25,    14 },
    NutationTableEntry { 0,  -1,   2,   0,   2,     -7141,      21,     3070,     0,     8,     4 },
    NutationTableEntry { 0,   0,   0,   2,   1,     -6302,     -11,     3272,     0,     2,     4 },
    NutationTableEntry { 1,   0,   2,  -2,   1,      5800,      10,    -3045,     0,     2,    -1 },
    NutationTableEntry { 2,   0,   2,  -2,   2,      6443,       0,    -2768,     0,    -7,    -4 },
    NutationTableEntry {-2,   0,   0,   2,   1,     -5774,     -11,     3041,     0,   -15,    -5 },
    NutationTableEntry { 2,   0,   2,   0,   1,     -5350,       0,     2695,     0,    21,    12 },
    NutationTableEntry { 0,  -1,   2,  -2,   1,     -4752,     -11,     2719,     0,    -3,    -3 },
    NutationTableEntry { 0,   0,   0,  -2,   1,     -4940,     -11,     2720,     0,   -21,    -9 },
    NutationTableEntry {-1,  -1,   0,   2,   0,      7350,       0,      -51,     0,    -8,     4 },
    NutationTableEntry { 2,   0,   0,  -2,   1,      4065,       0,    -2206,     0,     6,     1 },
    NutationTableEntry { 1,   0,   0,   2,   0,      6579,       0,     -199,     0,   -24,     2 },
    NutationTableEntry { 0,   1,   2,  -2,   1,      3579,       0,    -1900,     0,     5,     1 },
    NutationTableEntry { 1,  -1,   0,   0,   0,      4725,       0,      -41,     0,    -6,     3 },
    NutationTableEntry {-2,   0,   2,   0,   2,     -3075,       0,     1313,     0,    -2,    -1 },
    NutationTableEntry { 3,   0,   2,   0,   2,     -2904,       0,     1233,     0,    15,     7 },
    NutationTableEntry { 0,  -1,   0,   2,   0,      4348,       0,      -81,     0,   -10,     2 },
    NutationTableEntry { 1,  -1,   2,   0,   2,     -2878,       0,     1232,     0,     8,     4 },
    NutationTableEntry { 0,   0,   0,   1,   0,     -4230,       0,      -20,     0,     5,    -2 },
    NutationTableEntry {-1,  -1,   2,   2,   2,     -2819,       0,     1207,     0,     7,     3 },
    NutationTableEntry {-1,   0,   2,   0,   0,     -4056,       0,       40,     0,     5,    -2 },
    NutationTableEntry { 0,  -1,   2,   2,   2,     -2647,       0,     1129,     0,    11,     5 },
    NutationTableEntry {-2,   0,   0,   0,   1,     -2294,       0,     1266,     0,   -10,    -4 },
    NutationTableEntry { 1,   1,   2,   0,   2,      2481,       0,    -1062,     0,    -7,    -3 },
    NutationTableEntry { 2,   0,   0,   0,   1,      2179,       0,    -1129,     0,    -2,    -2 },
    NutationTableEntry {-1,   1,   0,   1,   0,      3276,       0,       -9,     0,     1,     0 },
    NutationTableEntry { 1,   1,   0,   0,   0,     -3389,       0,       35,     0,     5,    -2 },
    NutationTableEntry { 1,   0,   2,   0,   0,      3339,       0,     -107,     0,   -13,     1 },
    NutationTableEntry {-1,   0,   2,  -2,   1,     -1987,       0,     1073,     0,    -6,    -2 },
    NutationTableEntry { 1,   0,   0,   0,   2,     -1981,       0,      854,     0,     0,     0 },
    NutationTableEntry {-1,   0,   0,   1,   0,      4026,       0,     -553,     0,  -353,  -139 },
    NutationTableEntry { 0,   0,   2,   1,   2,      1660,       0,     -710,     0,    -5,    -2 },
    NutationTableEntry {-1,   0,   2,   4,   2,     -1521,       0,      647,     0,     9,     4 },
    NutationTableEntry {-1,   1,   0,   1,   1,      1314,       0,     -700,     0,     0,     0 },
    NutationTableEntry { 0,  -2,   2,  -2,   1,     -1283,       0,      672,     0,     0,     0 },
    NutationTableEntry { 1,   0,   2,   2,   1,     -1331,       0,      663,     0,     8,     4 },
    NutationTableEntry {-2,   0,   2,   2,   2,      1383,       0,     -594,     0,    -2,    -2 },
    NutationTableEntry {-1,   0,   0,   0,   2,      1405,       0,     -610,     0,     4,     2 },
    NutationTableEntry { 1,   1,   2,  -2,   2,      1290,       0,     -556,     0,     0,     0 },
    NutationTableEntry {-2,   0,   2,   4,   2,     -1214,       0,      518,     0,     5,     2 },
    NutationTableEntry {-1,   0,   4,   0,   2,      1146,       0,     -490,     0,    -3,    -1 },
};



constexpr double arcsecToRad(double as)
{
    return math::degToRad(as / 3600.0);
}


constexpr double milliarcsecToRad(double as)
{
    return math::degToRad(as / 3600000.0);
}


constexpr double microarcsecToRad(double as)
{
    return math::degToRad(as / 3600000000.0);
}

} // end unnamed namespace

/*! Calculate nutation angles using the IAU2000B model. This model is a
 *  truncated version of the IAU2000A model. It uses 77 terms for lunisolar
 *  nutation and just a single constant term for planetary precession.
 *
 *  T is a time in Julian centuries (day number / 36525) from J2000 TT. The
 *  angles returned are in radians. Note the use of Terrestrial Time instead
 *  of TDB: this will not result in any meaningful discrepancy.
 *
 *  For further information, see IERS Technical Note 32:
 *  http://www.iers.org/documents/publications/tn/tn32/tn32_033.pdf
 */
NutationAngles
Nutation_IAU2000B(double T)
{
    double T2 = T * T;
    double T3 = T2 * T;
    double T4 = T3 * T;

    // Mean anomaly of the Moon
    double l  = arcsecToRad(134.96340251
                            + 1717915923.2178 * T
                            + 31.8792         * T2
                            + 0.051635        * T3
                            - 0.00024470      * T4);
    // Mean anomaly of the Sun.
    double l_ = arcsecToRad(357.52910918
                            + 129596581.0481  * T
                            - 0.5532          * T2
                            + 0.000136        * T3
                            - 0.00001149      * T4);
    // Mean longitude of the Moon minus the mean longitude of the Moon's
    // node.
    double F  = arcsecToRad(93.27209062
                            + 1739527262.8478 * T
                            - 12.7512         * T2
                            - 0.001037        * T3
                            + 0.00000417      * T4);

    // Mean elongation of the Moon from the Sun
    double D  = arcsecToRad(29.785019547
                            + 1602961601.2090 * T
                            - 6.3706          * T2
                            + 0.006593        * T3
                            - 0.00003169      * T4);

    // Longitude of the ascending node of the Moon's orbit on the ecliptic
    // measured from the mean equinox of date.
    double Om = arcsecToRad(125.04455501
                            - 6962890.5431    * T
                            + 7.4722          * T2
                            + 0.007702        * T3
                            - 0.00005939      * T4);

    double obliquity = 0.0;
    double longitude = 0.0;

    for (const NutationTableEntry& ent : IAU2000BNutationTable)
    {
        double arg = (l  * ent.lMult   +
                      l_ * ent.l_Mult  +
                      F  * ent.FMult   +
                      D  * ent.DMult   +
                      Om * ent.OmMult);
        double S;
        double C;
        math::sincos(arg, S, C);

        longitude += (ent.l1 + ent.l2 * T) * S + ent.l3 * C;
        obliquity += (ent.o1 + ent.o2 * T) * C + ent.o3 * S;
    }

    // These constant terms account for the missing long-period planetary
    // terms in the truncated nutation model.
    double oblPlanetary  = milliarcsecToRad(-0.135);
    double longPlanetary = milliarcsecToRad(+0.388);

    NutationAngles nutation;

    // Convert to radians from units of 0.1 microarcsec
    nutation.obliquity = microarcsecToRad(obliquity * 0.1);
    nutation.longitude = microarcsecToRad(longitude * 0.1);

    // Add planetary nutation
    nutation.obliquity += oblPlanetary;
    nutation.longitude += longPlanetary;

    return nutation;
}

} // end namespace celestia::ephem
