#include <cmath>
#include <cstdio>
#include <cctype>
#include <sstream>
#include <string>
using namespace std;

#include "findFile.h"
#include "Options.h"
#include "Planet.h"
#include "xpUtil.h"

#include "libephemeris/ephemerisWrapper.h"

body
Planet::parseBodyName(char *name)
{
    body return_body;
    char *lowercase = name;
    char *ptr = name;
    while (*ptr != '\0') *ptr++ = tolower(*name++);
    
    if (strncmp(lowercase, "above", 2) == 0)
        return_body = ABOVE_ORBIT;
    else if (strncmp(lowercase, body_string[ARIEL], 2) == 0)
        return_body = ARIEL;
    else if (strncmp(lowercase, "below", 1) == 0)
        return_body = BELOW_ORBIT;
    else if (strncmp(lowercase, body_string[CALLISTO], 2) == 0)
        return_body = CALLISTO;
    else if (strncmp(lowercase, body_string[CHARON], 2) == 0)
        return_body = CHARON;
    else if (strncmp(lowercase, "default", 3) == 0)
        return_body = DEFAULT;
    else if (strncmp(lowercase, body_string[DEIMOS], 3) == 0)
        return_body = DEIMOS;
    else if (strncmp(lowercase, body_string[DIONE], 2) == 0)
        return_body = DIONE;
    else if (strncmp(lowercase, body_string[EARTH], 2) == 0)
        return_body = EARTH;
    else if (strncmp(lowercase, body_string[ENCELADUS], 2) == 0)
        return_body = ENCELADUS;
    else if (strncmp(lowercase, body_string[EUROPA], 2) == 0)
        return_body = EUROPA;
    else if (strncmp(lowercase, body_string[GANYMEDE], 1) == 0)
        return_body = GANYMEDE;
    else if (strncmp(lowercase, body_string[HYPERION], 1) == 0)
        return_body = HYPERION;
    else if (strncmp(lowercase, body_string[IAPETUS], 2) == 0)
        return_body = IAPETUS;
    else if (strncmp(lowercase, body_string[IO], 2) == 0)
        return_body = IO;
    else if (strncmp(lowercase, body_string[JUPITER], 1) == 0)
        return_body = JUPITER;
    else if (strncmp(lowercase, "major", 3) == 0)
        return_body = MAJOR_PLANET;
    else if (strncmp(lowercase, body_string[MARS], 3) == 0)
        return_body = MARS;
    else if (strncmp(lowercase, body_string[MERCURY], 2) == 0)
        return_body = MERCURY;
    else if (strncmp(lowercase, body_string[MIMAS], 3) == 0)
        return_body = MIMAS;
    else if (strncmp(lowercase, body_string[MIRANDA], 3) == 0)
        return_body = MIRANDA;
    else if (strncmp(lowercase, body_string[MOON], 2) == 0)
        return_body = MOON;
    else if (strncmp(lowercase, "naif", 2) == 0)
        return_body = NAIF;
    else if (strncmp(lowercase, body_string[NEPTUNE], 3) == 0)
        return_body = NEPTUNE;
    else if (strncmp(lowercase, body_string[NEREID], 3) == 0)
        return_body = NEREID;
    else if (strncmp(lowercase, "norad", 2) == 0)
        return_body = NORAD;
    else if (strncmp(lowercase, body_string[OBERON], 1) == 0)
        return_body = OBERON;
    else if (strncmp(lowercase, "path", 2) == 0)
        return_body = ALONG_PATH;
    else if (strncmp(lowercase, body_string[PHOBOS], 4) == 0)
        return_body = PHOBOS;
    else if (strncmp(lowercase, body_string[PHOEBE], 4) == 0)
        return_body = PHOEBE;
    else if (strncmp(lowercase, body_string[PLUTO], 2) == 0)
        return_body = PLUTO;
    else if (strncmp(lowercase, "random", 2) == 0)
        return_body = RANDOM_BODY;
    else if (strncmp(lowercase, body_string[RHEA], 2) == 0)
        return_body = RHEA;
    else if (strncmp(lowercase, body_string[SATURN], 2) == 0)
        return_body = SATURN;
    else if (strncmp(lowercase, body_string[SUN], 2) == 0)
        return_body = SUN;
    else if (strncmp(lowercase, "system", 2) == 0)
        return_body = SAME_SYSTEM;
    else if (strncmp(lowercase, body_string[TETHYS], 2) == 0)
        return_body = TETHYS;
    else if (strncmp(lowercase, body_string[TITANIA], 6) == 0)
        return_body = TITANIA;
    else if (strncmp(lowercase, body_string[TITAN], 5) == 0)
        return_body = TITAN;
    else if (strncmp(lowercase, body_string[TRITON], 2) == 0)
        return_body = TRITON;
    else if (strncmp(lowercase, body_string[URANUS], 2) == 0)
        return_body = URANUS;
    else if (strncmp(lowercase, body_string[UMBRIEL], 2) == 0)
        return_body = UMBRIEL;
    else if (strncmp(lowercase, body_string[VENUS], 1) == 0)
        return_body = VENUS;
    else
    {
        xpWarn("parseBody: Unknown body specified\n", 
               __FILE__, __LINE__);
        return_body = UNKNOWN_BODY;
    }
    return(return_body);
}

/*
  Rotational parameters are from Seidelmann et al. (2002), Celestial
  Mechanics 82, 83--110.
*/
Planet::Planet(const double jd, const body this_body) 
    : index_(this_body), 
      julianDay_(jd), 
      needRotationMatrix_(true), 
      period_(0),
      needShadowCoeffs_(true)
{
    Options *options = Options::getInstance();
    if (options->UniversalTime())
        julianDay_ += delT(julianDay_) / 86400;

    d2000_ = (julianDay_ - 2451545.0);
    T2000_ = d2000_ / 36525;

    switch (index_)
    {
    case SUN:
        primary_ = SUN;

        alpha0_ = 286.13;
        delta0_ = 63.87;

        nullMeridian0_ = 84.10;
        wdot_ = 14.1844;

        flipped_ = 1;

        period_ = 0;
        radiusEq_ = 696000;
        flattening_ = 0;

        break;
    case MERCURY:
        primary_ = SUN;

        alpha0_ = 281.01 - 0.033 * T2000_;
        delta0_ = 61.45 - 0.005 * T2000_;

        nullMeridian0_ = 329.548;
        wdot_ = 6.1385025;

        flipped_ = -1; 

        period_ = 87.969;

        radiusEq_ = 2439;
        flattening_ = 0;

        break;
    case VENUS:
        primary_ = SUN;

        alpha0_ = 272.76;
        delta0_ = 67.16;

        nullMeridian0_ = 160.20;
        wdot_ = -1.4813688;

        flipped_ = 1; 

        period_ = 224.701;

        radiusEq_ = 6051.9;
        flattening_ = 0;

        break;
    case EARTH:
        primary_ = SUN;
        
        alpha0_ = 0 - 0.641 * T2000_;
        delta0_ = 90 - .557 * T2000_;
        
        nullMeridian0_ = 190.147;
        wdot_ = 360.9856235;
        
        flipped_ = 1;

        period_ = 365.256;
        
        // WGS84 ellipsoid
        radiusEq_ = 6378.137;
        flattening_ = 1/298.257223563; 
        
        break;
    case MOON:
    {
        primary_ = EARTH;

        const double E1 = (125.045 -  0.0529921 * d2000_) * deg_to_rad;
        const double E2 = (250.089 -  0.1059842 * d2000_) * deg_to_rad;
        const double E3 = (260.008 + 13.0120009 * d2000_) * deg_to_rad;
        const double E4 = (176.625 + 13.3407154 * d2000_) * deg_to_rad;
        const double E5 = (357.529 +  0.9856003 * d2000_) * deg_to_rad;
        const double E6 = (311.589 + 26.4057084 * d2000_) * deg_to_rad;
        const double E7 = (134.963 + 13.0649930 * d2000_) * deg_to_rad;
        const double E8 = (276.617 +  0.3287146 * d2000_) * deg_to_rad;
        const double E9 = ( 34.226 +  1.7484877 * d2000_) * deg_to_rad;
        const double E10 = ( 15.134 -  0.1589763 * d2000_) * deg_to_rad;
        const double E11 = (119.743 +  0.0036096 * d2000_) * deg_to_rad;
        const double E12 = (239.961 +  0.1643573 * d2000_) * deg_to_rad;
        const double E13 = ( 25.053 + 12.9590088 * d2000_) * deg_to_rad;

        alpha0_ = 269.9949 + (0.0031 * T2000_        - 3.8787 * sin(E1)
                              - 0.1204 * sin(E2) + 0.0700 * sin(E3)
                              - 0.0172 * sin(E4) + 0.0072 * sin(E6)
                              - 0.0052 * sin(E10) + 0.0043 * sin(E13));
        delta0_ =  66.5392 + (0.0130 * T2000_        + 1.5419 * cos(E1)
                              + 0.0239 * cos(E2) - 0.0278 * cos(E3)
                              + 0.0068 * cos(E4) - 0.0029 * cos(E6)
                              + 0.0009 * cos(E7) + 0.0008 * cos(E10)
                              - 0.0009 * cos(E13));
        nullMeridian0_ = 38.3213 + (d2000_ * (13.17635815 - 1.4E-12 * d2000_) 
                                    + 3.5610 * sin(E1) 
                                    + 0.1208 * sin(E2)
                                    - 0.0642 * sin(E3)
                                    + 0.0158 * sin(E4)
                                    + 0.0252 * sin(E5)
                                    - 0.0066 * sin(E6)
                                    - 0.0047 * sin(E7)
                                    - 0.0046 * sin(E8)
                                    + 0.0028 * sin(E9)
                                    + 0.0052 * sin(E10)
                                    + 0.0040 * sin(E11)
                                    + 0.0019 * sin(E12)
                                    - 0.0044 * sin(E13));
        wdot_ = 0;

        flipped_ = 1;
        
        period_ = 27.321661;

        radiusEq_ = 1737.5;
        flattening_ = 0.002;

    }
    break;
    case MARS:
    case PHOBOS:
    case DEIMOS:
    {
        flipped_ = -1;

        const double M1 = (169.51 - 0.4357640 * d2000_) * deg_to_rad;
        const double M2 = (192.93 + 1128.4096700 * d2000_) * deg_to_rad;
        const double M3 = (53.47 - 0.0181510 * d2000_) * deg_to_rad;

        switch (index_)
        {
        case MARS:
            primary_ = SUN;
            
            alpha0_ = 317.68143 - 0.1061 * T2000_;
            delta0_ = 52.88650 - 0.0609 * T2000_;
            
            nullMeridian0_ = 176.630;
            wdot_ = 350.89198226;
            
            period_ = 686.980;
            
            radiusEq_ = 3397;
            flattening_ = 0.0065;

            break;
        case PHOBOS:
            primary_ = MARS;

            alpha0_ = 317.68 - 0.108 * T2000_ + 1.79 * sin(M1);
            delta0_ = 52.90 - 0.061 * T2000_ - 1.08 * cos(M1);

            nullMeridian0_ = (35.06 + 8.864 * T2000_ * T2000_ - 1.42 * sin(M1) 
                              - 0.78 * sin(M2));
            wdot_ = 1128.8445850;

            period_ = 0.31891023;

            radiusEq_ = 10;  // non-spherical
            flattening_ = 0;

            break;
        case DEIMOS:
            primary_ = MARS;

            alpha0_ = 316.65 - 0.108 * T2000_ + 2.98 * sin(M3);
            delta0_ = 53.52 - 0.061 * T2000_ - 1.78 * cos(M3);

            nullMeridian0_ = (79.41 - 0.520 * T2000_ * T2000_ 
                              - 2.58 * sin(M3) + 0.19 * cos(M3));
            wdot_ = 285.1618970;

            period_ = 1.2624407;
            radiusEq_ = 6;  // non-spherical
            flattening_ = 0;

            break;
        default:
            break;
        }
    }
    break;
    case JUPITER:
    case IO:
    case EUROPA:
    case GANYMEDE:
    case CALLISTO:
    {
        flipped_ = -1;

        const double J3 = (283.90 +  4850.7 * T2000_) * deg_to_rad;
        const double J4 = (355.80 +  1191.3 * T2000_) * deg_to_rad;
        const double J5 = (119.90 +   262.1 * T2000_) * deg_to_rad;
        const double J6 = (229.80 +    64.3 * T2000_) * deg_to_rad;
        const double J7 = (352.25 +  2382.6 * T2000_) * deg_to_rad;
        const double J8 = (113.35 +  6070.0 * T2000_) * deg_to_rad;

        switch (index_)
        {
        case JUPITER:
            primary_ = SUN;

            alpha0_ = 268.05 - 0.009 * T2000_;
            delta0_ = 64.49 + 0.003 * T2000_;

            // System III (magnetic field rotation)
            nullMeridian0_ = 284.95;
            wdot_ = 870.5366420;

            if (options->GRSSet())
            {
                // System II
                nullMeridian0_ = 43.3;
                wdot_ = 870.270;
            }

            period_ = 4332.589;

            radiusEq_ = 71492;
            flattening_ = 0.06487;

            break;
        case IO:
            primary_ = JUPITER;
            
            alpha0_ = (268.05 - 0.009 * T2000_ + 0.094 * sin(J3) 
                       + 0.024 * sin(J4));
            delta0_ = (64.50 + 0.003 * T2000_ + 0.040 * cos(J3)
                       + 0.011 * cos(J4));
            nullMeridian0_ = 200.39 - 0.085 * sin(J3) - 0.022 * sin(J4);
            wdot_ = 203.4889538;

            period_ = 1.769137786;

            radiusEq_ = 1821.6;
            flattening_ = 0;

            break;
        case EUROPA:
            primary_ = JUPITER;
            
            alpha0_ = (268.08 - 0.009 * T2000_ + 1.086 * sin(J4)
                       + 0.060 * sin(J5) + 0.015 * sin(J6)
                       + 0.009 * sin(J7));
            delta0_ = (64.51 + 0.003 * T2000_ + 0.468 * cos(J4)
                       + 0.026 * cos(J5) + 0.007 * cos(J6)
                       + 0.002 * cos(J7));
            nullMeridian0_ = (35.67 - 0.980 * sin(J4) - 0.054 * sin(J5)
                              - 0.014 * sin(J6) - 0.008 * sin(J7));
            wdot_ = 101.3747235;

            period_ = 3.551181041;

            radiusEq_ = 1560.8;
            flattening_ = 0;

            break;
        case GANYMEDE:
            primary_ = JUPITER;
            
            alpha0_ = (268.20 - 0.009 * T2000_ - 0.037 * sin(J4) 
                       + 0.431 * sin(J5) + 0.091 * sin(J6));
            delta0_ = (64.57 + 0.003 * T2000_ - 0.016 * cos(J4)
                       + 0.186 * cos(J5) + 0.039 * cos(J6));
            nullMeridian0_ = (44.04 + 0.033 * sin(J4) - 0.389 * sin(J5)
                              - 0.082 * sin(J6));
            wdot_ = 50.3176081;

            period_ = 7.15455296;

            radiusEq_ = 2631.2;
            flattening_ = 0;
 
            break;
        case CALLISTO:
            primary_ = JUPITER;
            
            alpha0_ = (268.72 - 0.009 * T2000_ - 0.068 * sin(J5)
                       + 0.590 * sin(J6) + 0.010 * sin(J8));
            delta0_ = (64.83 + 0.003 * T2000_ - 0.029 * cos(J5)
                       + 0.254 * cos(J6) - 0.004 * cos(J8));
            nullMeridian0_ = (259.73 + 0.061 * sin(J5) - 0.533 * sin(J6)
                              - 0.009 * sin(J8));
            wdot_ = 21.5710715;

            period_ = 16.6890184;

            radiusEq_ = 2410.3; 
            flattening_ = 0;

            break;
        default:
            break;
        }
    }
    break;
    case SATURN:
    case MIMAS:
    case ENCELADUS:
    case TETHYS:
    case DIONE:
    case RHEA:
    case TITAN:
    case HYPERION:
    case IAPETUS:
    case PHOEBE:
    {
        flipped_ = -1;

        const double S3 = (177.40 - 36505.5 * T2000_) * deg_to_rad;
        const double S4 = (300.00 -  7225.9 * T2000_) * deg_to_rad;
        const double S5 = (316.45 +   506.2 * T2000_) * deg_to_rad;
        const double S6 = (345.20 -  1016.3 * T2000_) * deg_to_rad;
        const double S7 = ( 29.80 -    52.1 * T2000_) * deg_to_rad;
        switch (index_)
        {
        case SATURN:
            primary_ = SUN;

            alpha0_ = 40.589 - 0.036 * T2000_;
            delta0_ = 83.537 - 0.004 * T2000_;
            nullMeridian0_ = 38.90;
            wdot_ = 810.7939024;

            period_ = 10759.22;

            radiusEq_ = 60268;
            flattening_ = 0.09796;

            break;
        case MIMAS:
            primary_ = SATURN;

            alpha0_ = 40.66 - 0.036 * T2000_ + 13.56 * sin(S3);
            delta0_ = 83.52 - 0.004 * T2000_ - 1.53 * cos(S3);
            nullMeridian0_ = 337.46 - 13.48 * sin(S3) - 44.85 * sin(S5);
            wdot_ = 381.994550;

            period_ = 0.942421813;
            
            radiusEq_ = 198.6;
            flattening_ = 0;

            break;
        case ENCELADUS:
            primary_ = SATURN;

            alpha0_ = 40.66 - 0.036 * T2000_;
            delta0_ = 83.52 - 0.004 * T2000_;

            nullMeridian0_ = 2.82;
            wdot_ = 262.7318996;

            period_ = 1.370217855;

            radiusEq_ = 249.4;
            flattening_ = 0;

            break;
        case TETHYS:
            primary_ = SATURN;

            alpha0_ = 40.66 - 0.036 * T2000_ + 9.66 * sin(S4);
            delta0_ = 83.52 - 0.004 * T2000_ - 1.09 * cos(S4);

            nullMeridian0_ = 10.45 - 9.60 * sin(S4) + 2.23 * sin(S5);
            wdot_ = 190.6979085;

            period_ = 1.887802160;

            radiusEq_ = 529.8;
            flattening_ = 0;

            break;
        case DIONE:
            primary_ = SATURN;

            alpha0_ = 40.66 - 0.036 * T2000_;
            delta0_ = 83.52 - 0.004 * T2000_;

            nullMeridian0_ = 357.00;
            wdot_ = 131.5349316;

            period_ = 2.736914742;

            radiusEq_ = 559;
            flattening_ = 0;

            break;
        case RHEA:
            primary_ = SATURN;

            alpha0_ = 40.38 - 0.036 * T2000_ + 3.10 * sin(S6);
            delta0_ = 83.55 - 0.004 * T2000_ - 0.35 * cos(S6);
            nullMeridian0_ = 235.16 - 3.08 * sin(S6);
            wdot_ = 79.6900478;

            period_ = 4.517500436;

            radiusEq_ = 764;
            flattening_ = 0;

            break;
        case TITAN:
            primary_ = SATURN;

            alpha0_ = 36.41 - 0.036 * T2000_ + 2.66 * sin(S7);
            delta0_ = 83.94 - 0.004 * T2000_ - 0.30 * cos(S7);
            nullMeridian0_ = 189.64 - 2.64 * sin(S7);
            wdot_ = 22.5769768;

            period_ = 15.94542068;

            radiusEq_ = 2575;
            flattening_ = 0;

            break;
        case HYPERION:
            primary_ = SATURN;

            alpha0_ = 40.589 - 0.036 * T2000_;
            delta0_ = 83.537 - 0.004 * T2000_;

            nullMeridian0_ = 0;
            wdot_ = 0;

            period_ = 21.2766088;

            radiusEq_ = 141.5;   // non-spherical
            flattening_ = 0;

            break;
        case IAPETUS:
            primary_ = SATURN;

            alpha0_ = 318.16 - 3.949 * T2000_;
            delta0_ = 75.03 - 1.143 * T2000_;
            nullMeridian0_ = 350.20;
            wdot_ = 4.5379572;

            period_ = 79.3301825;

            radiusEq_ = 718;
            flattening_ = 0;

            break;
        case PHOEBE:
            primary_ = SATURN;
            
            alpha0_ = 355.00;
            delta0_ = 68.70;

            nullMeridian0_ = 304.70;
            wdot_ = 930.8338720;

            period_ = 550.48;

            radiusEq_ = 110;
            flattening_ = 0;

            break;
        default:
            break;
        }
    }
    break;
    case URANUS:
    case MIRANDA:
    case ARIEL:
    case UMBRIEL:
    case TITANIA:
    case OBERON:
    {
        flipped_ = 1;

        const double U11 = (141.69 + 41887.66 * T2000_) * deg_to_rad;
        const double U12 = (316.41 + 2863.96 * T2000_) * deg_to_rad;
        const double U13 = (304.01 - 51.94 * T2000_) * deg_to_rad;
        const double U14 = (308.71 - 93.17 * T2000_) * deg_to_rad;
        const double U15 = (340.82 - 75.32 * T2000_) * deg_to_rad;
        const double U16 = (259.14 - 504.81 * T2000_) * deg_to_rad;

        switch (index_)
        {
        case URANUS:
            primary_ = SUN; 
            
            alpha0_ = 257.311;
            delta0_ = -15.175;
            nullMeridian0_ = 203.81;
            wdot_ = -501.1600928;

            period_ = 30685.4;
            
            radiusEq_ = 25559;
            flattening_ = 0.02293;

            break;
        case MIRANDA:
            primary_ = URANUS;

            alpha0_ = 257.42 + 4.41 * sin(U11) - 0.04 * sin(2*U11);
            delta0_ = -15.08 + 4.25 * cos(U11) - 0.02 * cos(2*U11);
            nullMeridian0_ = (30.70 - 1.27 * sin(U12) + 0.15 * sin(2*U12)
                              + 1.15 * sin(U11) - 0.09 * sin(2*U11));
            wdot_ = -254.6906892;

            period_ = 1.41347925;

            radiusEq_ = 235.8;
            flattening_ = 0;

            break;
        case ARIEL:
            primary_ = URANUS;

            alpha0_ = 257.43 + 0.29 * sin(U13);
            delta0_ = -15.10 + 0.28 * cos(U13);
            nullMeridian0_ = 156.22 + 0.05 * sin(U12) + 0.08 * sin(U13);
            wdot_ = -142.8356681;

            period_ = 2.52037935;

            radiusEq_ = 578.9;
            flattening_ = 0;

            break;
        case UMBRIEL:
            primary_ = URANUS;

            alpha0_ = 257.43 + 0.21 * sin(U14);
            delta0_ = -15.10 + 0.20 * cos(U14);
            nullMeridian0_ = 108.05 - 0.09 * sin(U12) + 0.06 * sin(U14);
            wdot_ = -86.8688923;

            period_ = 4.1441772;

            radiusEq_ = 584.7;
            flattening_ = 0;

            break;
        case TITANIA:
            primary_ = URANUS;

            alpha0_ = 257.43 + 0.29 * sin(U15);
            delta0_ = -15.10 + 0.28 * cos(U15);
            nullMeridian0_ = 77.74 + 0.08 * sin(U15);
            wdot_ = -41.3514316;

            period_ = 8.7058717;

            radiusEq_ = 788.9;
            flattening_ = 0;

            break;
        case OBERON:
            primary_ = URANUS;

            alpha0_ = 257.43 + 0.16 * sin(U16);
            delta0_ = -15.10 + 0.16 * cos(U16);
            nullMeridian0_ = 6.77 + 0.04 * sin(U16);
            wdot_ = -26.7394932;

            period_ = 13.4632389;

            radiusEq_ = 761.4;
            flattening_ = 0;

            break;
        default:
            break;
        }
    }
    break;
    case NEPTUNE:
    case TRITON:
    case NEREID:
    {
        flipped_ = -1;

        switch (index_)
        {
        case NEPTUNE:
        {
            primary_ = SUN; 
            
            double N = (357.85 + 52.316 * T2000_) * deg_to_rad;
            alpha0_ = 299.36 + 0.70 * sin(N);
            delta0_ = 43.46 - 0.51 * cos(N);
            
            nullMeridian0_ = 253.18 - 0.48 * sin(N);
            wdot_ = 536.3128492;

            period_ = 60189.0;
            
            radiusEq_ = 24764;
            flattening_ = 0.0171;

        }
        break;
        case TRITON:
        {
            primary_ = NEPTUNE;

            const double N7 = (177.85 + 52.316 * T2000_) * deg_to_rad;
            alpha0_ = (299.36 - 32.35 * sin(N7) - 6.28 * sin(2*N7)
                       - 2.08 * sin(3*N7) - 0.74 * sin(4*N7) 
                       - 0.28 * sin(5*N7) - 0.11 * sin(6*N7)
                       - 0.07 * sin(7*N7) - 0.02 * sin(8*N7)
                       - 0.01 * sin(9*N7));
            delta0_ = (43.46 + 22.55 * cos(N7) + 2.10 * cos(2*N7)
                       + 0.55 * cos(3*N7) + 0.16 * cos(4*N7) 
                       + 0.05 * cos(5*N7) + 0.02 * cos(6*N7)
                       + 0.01 * cos(7*N7));
            
            nullMeridian0_ = (296.53 + 22.25 * sin(N7) + 6.73 * sin(2*N7)
                              + 2.05 * sin(3*N7) + 0.74 * sin(4*N7)
                              + 0.28 * sin(5*N7) + 0.11 * sin(6*N7) 
                              + 0.05 * sin(7*N7) + 0.02 * sin(8*N7)
                              + 0.01 * sin(9*N7));

            wdot_ = -61.2572637;

            period_ = 5.8768541;

            radiusEq_ = 1353;
            flattening_ = 0;
        }
        break;
        case NEREID:
        {
            primary_ = NEPTUNE;

            alpha0_ = 299.36;
            delta0_ = 43.46;
            
            nullMeridian0_ = 0;
            wdot_ = 0;

            period_ = 360.13619;

            radiusEq_ = 170;
            flattening_ = 0;
        }
        break;
        default:
            break;
        }
    }
    break;
    case PLUTO:
    case CHARON:
    {
        flipped_ = 1;
        
        alpha0_ = 313.02;
        delta0_ = 9.09;
        
        wdot_ = -56.3623195;
        
        switch (index_)
        {
        case PLUTO:
            primary_ = SUN; 
            
            nullMeridian0_ = 236.77;
            
            period_ = 90465.0;
            
            radiusEq_ = 1151;
            flattening_ = 0;
            break;
        case CHARON:
            primary_ = PLUTO;

            nullMeridian0_ = 56.77;

            period_ = 6.38723;

            radiusEq_ = 593;
            flattening_ = 0;
            break;
        default:
            break;
        }
    }
    break;
    default:
        xpExit("Planet: Unknown body specified.\n", __FILE__, __LINE__);
    }

    alpha0_ *= deg_to_rad;
    delta0_ *= deg_to_rad;

    radiusEq_ /= AU_to_km;

    nullMeridian_ = fmod(nullMeridian0_ + wdot_ * d2000_, 360.0);
    nullMeridian_ *= deg_to_rad;

    radiusPol_ = radiusEq_ * (1 - flattening_);
    omf2_ = (1 - flattening_) * (1 - flattening_);
}

Planet::~Planet()
{
}

// Return the distance from the surface to the center, in units of
// equatorial radius.  Input latitude is assumed to be planetographic.
double
Planet::Radius(const double lat) const
{
    double returnValue = 1;
    if (index_ == JUPITER ||
        index_ == SATURN)
    {
        double tmpLat = lat;
        double tmpLon = 0;
        PlanetographicToPlanetocentric(tmpLat, tmpLon);
        
        const double ReSinLat = radiusEq_  * sin(tmpLat);
        const double RpCosLat = radiusPol_ * cos(tmpLat);
        
        // from http://mathworld.wolfram.com/Ellipse.html
        returnValue = radiusPol_ / sqrt(RpCosLat * RpCosLat 
                                        + ReSinLat * ReSinLat);
    }

    return(returnValue);
}

// Compute heliocentric equatorial coordinates of the planet
void
Planet::calcHeliocentricEquatorial()
{
    calcHeliocentricEquatorial(true);
}

void
Planet::calcHeliocentricEquatorial(const bool relativeToSun)
{
    GetHeliocentricXYZ(index_, primary_, julianDay_, 
                       relativeToSun, X_, Y_, Z_);
}

// Return rectangular coordinates in heliocentric equatorial frame
void
Planet::getPosition(double &X, double &Y, double &Z) const
{
    X = X_;
    Y = Y_;
    Z = Z_;
}

void
Planet::CreateRotationMatrix()
{
    /* 
       These equations are from "Astronomy on the Personal Computer,
       3rd Edition", by Montenbruck and Pfleger, Chapter 7.
    */
    const double cosW = cos(nullMeridian_);
    const double sinW = sin(nullMeridian_);

    const double cosA = cos(alpha0_);
    const double sinA = sin(alpha0_);

    const double cosD = cos(delta0_);
    const double sinD = sin(delta0_);

    rot_[0][0] = -cosW * sinA - sinW * sinD * cosA;
    rot_[0][1] =  cosW * cosA - sinW * sinD * sinA;
    rot_[0][2] =  sinW * cosD;

    rot_[1][0] =  sinW * sinA - cosW * sinD * cosA;
    rot_[1][1] = -sinW * cosA - cosW * sinD * sinA;
    rot_[1][2] =  cosW * cosD;

    rot_[2][0] =  cosD * cosA;
    rot_[2][1] =  cosD * sinA;
    rot_[2][2] =  sinD;

    invertMatrix(rot_, invRot_);

    needRotationMatrix_ = false;
}

void
Planet::PlanetocentricToXYZ(double &X, double &Y, double &Z,
                            const double lat, const double lon, 
                            const double rad)
{
    if (needRotationMatrix_) CreateRotationMatrix();

    double r[3];
    r[0] = cos(lat) * cos(lon);
    r[1] = cos(lat) * sin(lon);
    r[2] = sin(lat);

    double newrad = rad * radiusEq_;
    X = dot(invRot_[0], r) * newrad;
    Y = dot(invRot_[1], r) * newrad;
    Z = dot(invRot_[2], r) * newrad;

    X += X_;
    Y += Y_;
    Z += Z_;
}

void
Planet::PlanetographicToXYZ(double &X, double &Y, double &Z,
                            double lat, double lon, const double rad)
{
    PlanetographicToPlanetocentric(lat, lon);
    PlanetocentricToXYZ(X, Y, Z, lat, lon, rad);
}

void
Planet::XYZToPlanetocentric(const double X, const double Y, const double Z,
                            double &lat, double &lon)
{
    double rad = 0;
    XYZToPlanetocentric(X, Y, Z, lat, lon, rad);
}

void
Planet::XYZToPlanetocentric(const double X, const double Y, const double Z,
                            double &lat, double &lon, double &rad)
{
    if (needRotationMatrix_) CreateRotationMatrix();

    const double r[3] = { X - X_, Y - Y_, Z - Z_ };

    const double sx = dot(rot_[0], r);
    const double sy = dot(rot_[1], r);
    const double sz = dot(rot_[2], r);

    rad = sqrt(sx * sx + sy*sy);
    if (rad > 0)
        lat = atan(sz/rad);
    else
        lat = 0;

    if (cos(lat) > 1e-5)
        lon = atan2(sy, sx);
    else
        lon = 0;

    if (lon < 0) lon += TWO_PI;

    rad = sqrt(r[0]*r[0] + r[1]*r[1] + r[2]*r[2]);
    rad /= radiusEq_;
}

void
Planet::XYZToPlanetographic(const double X, const double Y, const double Z,
                            double &lat, double &lon)
{
    double rad = 0;
    XYZToPlanetographic(X, Y, Z, lat, lon, rad);
}

void
Planet::XYZToPlanetographic(const double X, const double Y, const double Z,
                            double &lat, double &lon, double &rad)
{
    XYZToPlanetocentric(X, Y, Z, lat, lon, rad);
    PlanetocentricToPlanetographic(lat, lon);
}

void
Planet::XYZToPlanetaryXYZ(const double X, const double Y, const double Z,
                          double &pX, double &pY, double &pZ)
{
    double lat, lon, rad;
    XYZToPlanetocentric(X, Y, Z, lat, lon, rad);

    pX = rad * cos(lat) * cos(lon);
    pY = rad * cos(lat) * sin(lon);
    pZ = rad * sin(lat);
}

void
Planet::PlanetaryXYZToXYZ(const double pX, const double pY, const double pZ,
                          double &X, double &Y, double &Z)
{
    const double lat = atan(pZ / sqrt(pX * pX + pY * pY));
    double lon;
    if (cos(lat) > 1e-5)
        lon = atan2(pY, pX);
    else
        lon = 0;

    const double rad = sqrt(pX * pX + pY * pY + pZ * pZ);

    PlanetocentricToXYZ(X, Y, Z, lat, lon, rad);
}

void
Planet::PlanetocentricToPlanetographic(double &lat, double &lon) const
{
    if (flattening_ > 0)
        lat = atan(tan(lat) / omf2_);

    if (index_ == EARTH || index_ == SUN) lon *= -1;
    if (wdot_ > 0) lon *= -1;
    if (lon < 0) lon += TWO_PI;
}

void
Planet::PlanetographicToPlanetocentric(double &lat, double &lon) const
{
    if (flattening_ > 0)
        lat = atan(omf2_ * tan(lat));

    if (index_ == EARTH || index_ == SUN) lon *= -1;
    if (wdot_ > 0) lon *= -1;
}

void
Planet::ComputeShadowCoeffs()
{
    XYZToPlanetaryXYZ(0, 0, 0, sunX_, sunY_, sunZ_);

    ellipseCoeffC_ = sunZ_ * sunZ_ / omf2_;
    ellipseCoeffC_ += sunY_ * sunY_;
    ellipseCoeffC_ += sunX_ * sunX_;
    ellipseCoeffC_ -= 1;

    needShadowCoeffs_ = false;
}

// x, y, z must be in planetary XYZ frame
bool
Planet::IsInMyShadow(const double x, const double y, const double z)
{
    if (needShadowCoeffs_) ComputeShadowCoeffs();

    double ellipseCoeffA = (z - sunZ_) * (z - sunZ_) / omf2_;
    ellipseCoeffA += (y - sunY_) * (y - sunY_);
    ellipseCoeffA += (x - sunX_) * (x - sunX_);

    double ellipseCoeffB = (z - sunZ_) * sunZ_ / omf2_;
    ellipseCoeffB += (y - sunY_) * sunY_;
    ellipseCoeffB += (x - sunX_) * sunX_;

    const double determinant = (ellipseCoeffB * ellipseCoeffB 
                                - ellipseCoeffA * ellipseCoeffC_);

    return(determinant > 0);
}

void
Planet::getOrbitalNorth(double &X, double &Y, double &Z) const
{
    if (index_ == SUN)
    {
        X = cos(delta0_) * cos(alpha0_);
        Y = cos(delta0_) * sin(alpha0_);
        Z = sin(delta0_);
        return;
    }

    // cross product of position and velocity vectors points to the
    // orbital north pole
    double pos[3] = { X_, Y_, Z_ };

    double pX0, pY0, pZ0;
    double pX1, pY1, pZ1;

    GetHeliocentricXYZ(index_, primary_, julianDay_ - 0.5, 
                       true, pX0, pY0, pZ0);
    GetHeliocentricXYZ(index_, primary_, julianDay_ + 0.5, 
                       true, pX1, pY1, pZ1);

    double vel[3] = { pX1 - pX0, pY1 - pY0, pZ1 - pZ0 };

    double north[3];
    cross(pos, vel, north);

    double mag = sqrt(dot(north, north));
    
    X = north[0]/mag;
    Y = north[1]/mag;
    Z = north[2]/mag;
}

void
Planet::getGalacticNorth(double &X, double &Y, double &Z) const
{
    // B1950.0 coordinates of the galactic north pole
    const double GN_LAT = 27.4 * deg_to_rad;
    const double GN_LON = 192.25 * deg_to_rad;

    X = cos(GN_LAT) * cos(GN_LON);
    Y = cos(GN_LAT) * sin(GN_LON);
    Z = sin(GN_LAT);              
}

void
Planet::getBodyNorth(double &X, double &Y, double &Z) const
{
    // get direction of the rotational axis
    X = cos(alpha0_) * cos(delta0_);
    Y = sin(alpha0_) * cos(delta0_);
    Z = sin(delta0_);
}
