#include <cmath>
#include <cstdio>
#include <cstdlib>
using namespace std;

#include "elp82b.h"

#include "xpUtil.h"

void
moon(const double jd, double &X, double &Y, double &Z)
{
    const double sec_to_rad = deg_to_rad / 3600;

    double R[3] = { 0, 0, 0 };

    // w[0] is the mean mean longitude of the moon
    // w[1] is the mean longitude of the lunar perigee
    // w[2] is the mean longitude of the lunar ascending node
    // Units are seconds of arc
    double w[3][5];
    w[0][0] = (3600 * 218. + 60 * 18. + 59.95571);
    w[1][0] = (3600 *  83. + 60 * 21. + 11.67475);
    w[2][0] = (3600 * 125. + 60 *  2. + 40.39816);

    w[0][1] = 1732559343.73604;
    w[1][1] =   14643420.2632;
    w[2][1] =   -6967919.3622;

    w[0][2] =  -5.8883;
    w[1][2] = -38.2776;
    w[2][2] =   6.3622;

    w[0][3] =  0.6604e-2;
    w[1][3] = -0.45047e-1;
    w[2][3] =  0.7625e-2;

    w[0][4] = -0.3169e-4;
    w[1][4] =  0.21301e-3;
    w[2][4] = -0.3586e-4;

    // T and omega are angles of the inertial mean ecliptic of J2000
    // referred to the inertial mean equinox of J2000
    double T[5];
    T[0] = (3600 * 100. + 60 * 27. + 59.22059);
    T[1] = 129597742.2758;
    T[2] = -0.0202;
    T[3] = 0.9e-5;
    T[4] = 0.15e-6;

    double omega[5];
    omega[0] = (3600 * 102. + 60 * 56. + 14.42753);
    omega[1] = 1161.2283;
    omega[2] = 0.5327;
    omega[3] = -0.138e-3;
    omega[4] = 0;

    // precession between J2000 and the date
    double preces[5];
    preces[0] = 0;
    preces[1] = 5029.0966;
    preces[2] = 1.1120;
    preces[3] = .77e-4;
    preces[4] = -.2353e-4;

    // planetary longitudes in J2000
    double plon[8][2];
    plon[0][0] = (3600 * 252. + 60 * 15. + 3.25986);
    plon[1][0] = (3600 * 181. + 60 * 58. + 47.28305);
    plon[2][0] = T[0];
    plon[3][0] = (3600 * 355. + 60 * 25. + 59.78866);
    plon[4][0] = (3600 *  34. + 60 * 21. + 5.34212);
    plon[5][0] = (3600 *  50. + 60 *  4. + 38.89694);
    plon[6][0] = (3600 * 314. + 60 *  3. + 18.01841);
    plon[7][0] = (3600 * 304. + 60 * 20. + 55.19575);

    plon[0][1] = 538101628.68898;
    plon[1][1] = 210664136.43355;
    plon[2][1] = T[1];
    plon[3][1] = 68905077.59284;
    plon[4][1] = 10925660.42861;
    plon[5][1] = 4399609.65932;
    plon[6][1] = 1542481.19393;
    plon[7][1] = 786550.32074;

    // Convert to radians
    for (int i = 0; i < 8; i++)
    {
        plon[i][0] *= sec_to_rad;
        plon[i][1] *= sec_to_rad;
    }

    // Corrections of the constants (fit to DE200/LE200)
    double delnu =  0.55604 / w[0][1];
    double dele  =  0.01789 * sec_to_rad;
    double delg  = -0.08066 * sec_to_rad;
    double delnp = -0.06424 / w[0][1];
    double delep = -0.12879 * sec_to_rad;

    // Delaunay's arguments
    double del[4][5];
    for (int i = 0; i < 5; i++)
    {
        del[0][i] = w[0][i] - T[i];
        del[1][i] = T[i] - omega[i];
        del[2][i] = w[0][i] - w[1][i];
        del[3][i] = w[0][i] - w[2][i];
        for (int j = 0; j < 4; j++) 
            del[j][i] *= sec_to_rad;
    }
    del[0][0] += M_PI;

    double zeta[2];
    zeta[0] = w[0][0] * sec_to_rad;
    zeta[1] = (w[0][1] + preces[1]) * sec_to_rad;

    // precession matrix
    double p[5], q[5];
    p[0] =  0.10180391e-4;
    p[1] =  0.47020439e-6;
    p[2] = -0.5417367e-9;
    p[3] = -0.2507948e-11;
    p[4] =  0.463486e-14;
    q[0] = -0.113469002e-3;
    q[1] =  0.12372674e-6;
    q[2] =  0.1265417e-8;
    q[3] = -0.1371808e-11;
    q[4] = -0.320334e-14;
    
    double t[5];
    t[0] = 1;
    t[1] = (jd - 2451545.0) / 36525;
    t[2] = t[1] * t[1];
    t[3] = t[2] * t[1];
    t[4] = t[2] * t[2];

    double ath = 384747.9806743165;
    double a0 = 384747.9806448954; 
    double am = 0.074801329518;
    double alfa = 0.002571881335;
    double dtasm = 2 * alfa / (3 * am);

    // Main problem
    for (int i = 1; i < 4; i++)
    {
        int iv = ((i - 1) % 3);

        const int *ilu = ILU[i-1];
        const double *p_coef = COEF[i-1];

        for (int ii = 0; ii < NUM[i-1]; ii++)
        {
            if (ii > 0) 
            {
                ilu += 4;
                p_coef += 7;
            }

            double coef[7];
            for (int j = 0; j < 7; j++)
                coef[j] = p_coef[j];

            double x = coef[0];

            double tgv = coef[1] + dtasm * coef[5];

            if (i == 3) 
                coef[0] -= 2 * coef[0] * delnu / 3;

            x = (coef[0] + tgv * (delnp - am * delnu) 
                 + coef[2] * delg + coef[3] * dele 
                 + coef[4] * delep);
            double y = 0;

            for (int k = 0; k < 5; k++)
            {
                for (int j = 0; j < 4; j++)
                {
                    y += ilu[j] * del[j][k] * t[k];
                }
            }

            if (i == 3) y += M_PI_2;
            y = fmod(y, 2 * M_PI);

            R[iv] += x * sin(y);

        }
    }

    // Figures - Tides - Relativity - Solar eccentricity
    for (int i = 4; i < 37; i++)
    {
        if (i > 9 && i < 22) continue;
        int iv = ((i - 1) % 3);

        const int *ilu = ILU[i-1];
        const int *p_iz = IZ[i-1];
        const double *p_pha = PHA[i-1];
        const double *p_x = XX[i-1];

        for (int ii = 0; ii < NUM[i-1]; ii++)
        {
            if (ii > 0)
            {
                ilu += 4;
                p_iz++;
                p_pha++;
                p_x++;
            }
            
            int iz = *p_iz;
            double pha = *p_pha;
            double x = *p_x;

            if ((i > 6 && i < 10) || (i > 24 && i < 28)) x *= t[1];
            if (i > 33 && i < 37) x *= t[2];

            double y = pha * deg_to_rad;

            for (int k = 0; k < 2; k++)
            {
                y += iz * zeta[k] * t[k];
                for (int l = 0; l < 4; l++)
                {
                    y += ilu[l] * del[l][k] * t[k];
                }
            }
            y = fmod(y, 2*M_PI);
            R[iv] += x * sin(y);
        }
    }

    // Planetary perturbations
    for (int i = 10; i < 22; i++)
    {
        int iv = ((i - 1) % 3);

        const int *ipla = IPLA[i-1];
        const double *p_pha = PHA[i-1];
        const double *p_x = XX[i-1];

        for (int ii = 0; ii < NUM[i-1]; ii++)
        {
            if (ii > 0)
            {
                ipla += 11;
                p_pha++;
                p_x++;
            }

            double pha = *p_pha;
            double x = *p_x;

            if ((i > 12 && i < 16) || (i > 18 && i < 22)) x *= t[1];

            double y = pha * deg_to_rad;

            if (i < 16)
            {
                for (int k = 0; k < 2; k++)
                {
                    y += (ipla[8] * del[0][k] + ipla[9] * del[2][k] 
                          + ipla[10] * del[3][k]) * t[k];
                    for (int l = 0; l < 8; l++)
                    {
                        y += ipla[l] * plon[l][k] * t[k];
                    }
                }
            }
            else
            {
                for (int k = 0; k < 2; k++)
                {
                    for (int l = 0; l < 4; l++)
                        y += ipla[l+7] * del[l][k] * t[k];
                    for (int l = 0; l < 7; l++)
                    {
                        y += ipla[l] * plon[l][k] * t[k];
                    }
                }
            }

            y = fmod(y, 2*M_PI);
            R[iv] += x * sin(y);
        }
    }

    // Change of coordinates
    for (int i = 0; i < 5; i++)
        R[0] += w[0][i] * t[i];
    R[0] *= sec_to_rad;
    R[1] *= sec_to_rad;
    R[2] *= a0/ath;

    double x1 = R[2] * cos(R[1]);
    double x2 = x1 * sin(R[0]);
    x1 *= cos(R[0]);
    double x3 = R[2] * sin(R[1]);

    double pw = 0;
    double qw = 0;
    for (int i = 0; i < 5; i++)
    {
        pw += p[i] * t[i];
        qw += q[i] * t[i];
    }

    pw *= t[1];
    qw *= t[1];

    double ra = 2 * sqrt(1 - pw * pw - qw * qw);
    double pwqw = 2 * pw * qw;
    double pw2 = 1 - 2 * pw * pw;
    double qw2 = 1 - 2 * qw * qw;
    pw *= ra;
    qw *= ra;

    R[0] = pw2*x1+pwqw*x2+pw*x3;
    R[1] = pwqw*x1+qw2*x2-qw*x3;
    R[2] = -pw*x1+qw*x2+(pw2+qw2-1)*x3;

    X = R[0] / AU_to_km;
    Y = R[1] / AU_to_km;
    Z = R[2] / AU_to_km;

    // rotate to earth equator J2000
    const double eps = 23.4392911 * deg_to_rad;
    rotateX(X, Y, Z, -eps);
} 


syntax highlighted by Code2HTML, v. 0.9.1