
/*  JavaScript support routines for pacalc.html.  */


/*  JYEAR  --  Convert  Julian  date  to  year,  month, day, which are
               returned as an Array.  */

function jyear(td) {
    var z, f, a, alpha, b, c, d, e, mm;

    td += 0.5;
    z = Math.floor(td);
    f = td - z;

    if (z < 2299161.0) {
        a = z;
    } else {
        alpha = Math.floor((z - 1867216.25) / 36524.25);
        a = z + 1 + alpha - Math.floor(alpha / 4);
    }

    b = a + 1524;
    c = Math.floor((b - 122.1) / 365.25);
    d = Math.floor(365.25 * c);
    e = Math.floor((b - d) / 30.6001);
    mm = Math.floor((e < 14) ? (e - 1) : (e - 13));

    return new Array(
                     Math.floor((mm > 2) ? (c - 4716) : (c - 4715)),
                     mm,
                     Math.floor(b - d - Math.floor(30.6001 * e) + f)
                    );
}

/*  JHMS  --  Convert Julian time to hour, minutes, and seconds,
              returned as a three-element array.  */

function jhms(j) {
    var ij;

    j += 0.5;                 /* Astronomical to civil */
    ij = (j - Math.floor(j)) * 86400.0;
    return new Array(
                     Math.floor(ij / 3600),
                     Math.floor((ij / 60) % 60),
                     Math.floor(ij % 60));
}

/*  DTR  --  Degrees to radians.  */

function dtr(d)
{
    return (d * Math.PI) / 180.0;
}

/*  FIXANGLE  --  Range reduce angle in degrees.  */

function fixangle(a)
{
        return a - 360.0 * (Math.floor((a) / 360.0));
}

/*  SUMSER  --  Sum the series of periodic terms.  */

function sumser(trig, D, M, F, T, argtab, coeff, tfix, tfixc)
{
    var i, j = 0, n = 0, sum = 0, arg, coef;

    D = dtr(fixangle(D));
    M = dtr(fixangle(M));
    F = dtr(fixangle(F));

    for (i = 0; coeff[i] != 0.0; i++) {
        arg = (D * argtab[j]) + (M * argtab[j + 1]) + (F * argtab[j + 2]);
        j += 3;
        coef = coeff[i];
        if (i == tfix[n]) {
            coef += T * tfixc[n++];
        }
        sum += coef * trig(arg);
    }

    return sum;
}

/*  We define the perigee and apogee period term arrays statically to
    avoid re-constructing them on every invocation of moonpa.  */

    var periarg = new Array(
    /*  D,  M,  F   */
        2,  0,  0,
        4,  0,  0,
        6,  0,  0,
        8,  0,  0,
        2, -1,  0,
        0,  1,  0,
       10,  0,  0,
        4, -1,  0,
        6, -1,  0,
       12,  0,  0,
        1,  0,  0,
        8, -1,  0,
       14,  0,  0,
        0,  0,  2,
        3,  0,  0,
       10, -1,  0,
       16,  0,  0,
       12, -1,  0,
        5,  0,  0,
        2,  0,  2,
       18,  0,  0,
       14, -1,  0,
        7,  0,  0,
        2,  1,  0,
       20,  0,  0,
        1,  1,  0,
       16, -1,  0,
        4,  1,  0,
        9,  0,  0,
        4,  0,  2,

        2, -2,  0,
        4, -2,  0,
        6, -2,  0,
       22,  0,  0,
       18, -1,  0,
        6,  1,  0,
       11,  0,  0,
        8,  1,  0,
        4,  0, -2,
        6,  0,  2,
        3,  1,  0,
        5,  1,  0,
       13,  0,  0,
       20, -1,  0,
        3,  2,  0,
        4, -2,  2,
        1,  2,  0,
       22, -1,  0,
        0,  0,  4,
        6,  0, -2,
        2,  1, -2,
        0,  2,  0,
        0, -1,  2,
        2,  0,  4,
        0, -2,  2,
        2,  2, -2,
       24,  0,  0,
        4,  0, -4,
        2,  2,  0,
        1, -1,  0 
    );

    var pericoeff = new Array(
        -1.6769,
         0.4589,
        -0.1856,
         0.0883,
        -0.0773,
         0.0502,
        -0.0460,
         0.0422,
        -0.0256,
         0.0253,
         0.0237,
         0.0162,
        -0.0145,
         0.0129,
        -0.0112,
        -0.0104,
         0.0086,
         0.0069,
         0.0066,
        -0.0053,
        -0.0052,
        -0.0046,
        -0.0041,
         0.0040,
         0.0032,
        -0.0032,
         0.0031,
        -0.0029,
         0.0027,
         0.0027,

        -0.0027,
         0.0024,
        -0.0021,
        -0.0021,
        -0.0021,
         0.0019,
        -0.0018,
        -0.0014,
        -0.0014,
        -0.0014,
         0.0014,
        -0.0014,
         0.0013,
         0.0013,
         0.0011,
        -0.0011,
        -0.0010,
        -0.0009,
        -0.0008,
         0.0008,
         0.0008,    
         0.0007,
         0.0007,
         0.0007,
        -0.0006,
        -0.0006,
         0.0006,
         0.0005,
         0.0005,
        -0.0004,

        0
    );

    var peritft = new Array(
        4,
        5, 
        7,
        -1
    );

    var peritfc = new Array(
         0.00019,
        -0.00013,
        -0.00011
    );

    var apoarg = new Array(
    /*  D,  M,  F   */
        2,  0,  0,
        4,  0,  0,
        0,  1,  0,
        2, -1,  0,
        0,  0,  2,
        1,  0,  0,
        6,  0,  0,
        4, -1,  0,
        2,  0,  2,
        1,  1,  0,
        8,  0,  0,
        6, -1,  0,
        2,  0, -2,
        2, -2,  0,
        3,  0,  0,
        4,  0,  2,

        8, -1,  0,
        4, -2,  0,
       10,  0,  0,
        3,  1,  0,
        0,  2,  0,
        2,  1,  0,
        2,  2,  0,
        6,  0,  2,
        6, -2,  0,
       10, -1,  0,
        5,  0,  0,
        4,  0, -2,
        0,  1,  2,
       12,  0,  0,
        2, -1,  2,
        1, -1,  0
    );

    var apocoeff = new Array(
         0.4392,
         0.0684,
         0.0456,
         0.0426,
         0.0212,
        -0.0189,
         0.0144,
         0.0113,
         0.0047,
         0.0036,
         0.0035,
         0.0034,
        -0.0034,
         0.0022,
        -0.0017,
         0.0013,

         0.0011,
         0.0010,
         0.0009,
         0.0007,
         0.0006,
         0.0005,
         0.0005,
         0.0004,
         0.0004,
         0.0004,
        -0.0004,
        -0.0004,
         0.0003,
         0.0003,
         0.0003,
        -0.0003,

        0
    );

    var apotft = new Array(
        2,
        3, 
        -1
    );

    var apotfc = new Array(
        -0.00011,
        -0.00011
    );

    var periparg = new Array(
    /*  D,  M,  F   */
        0,  0,  0,
        2,  0,  0,
        4,  0,  0,
        2, -1,  0,
        6,  0,  0,
        1,  0,  0,
        8,  0,  0,
        0,  1,  0,
        0,  0,  2,
        4, -1,  0,
        2,  0, -2,
       10,  0,  0,
        6, -1,  0,
        3,  0,  0,
        2,  1,  0,
        1,  1,  0,
       12,  0,  0,
        8, -1,  0,
        2,  0,  2,
        2, -2,  0,
        5,  0,  0,
       14,  0,  0,

       10, -1,  0,
        4,  1,  0,
       12, -1,  0,
        4, -2,  0,
        7,  0,  0,
        4,  0,  2,
       16,  0,  0,
        3,  1,  0,
        1, -1,  0,
        6,  1,  0,
        0,  2,  0,
       14, -1,  0,
        2,  2,  0,
        6, -2,  0,
        2, -1, -2,
        9,  0,  0,
       18,  0,  0,
        6,  0,  2,
        0, -1,  2,
       16, -1,  0,
        4,  0, -2,
        8,  1,  0,
       11,  0,  0,
        5,  1,  0,
       20,  0,  0
    );

    var peripcoeff = new Array(
      3629.215,
        63.224,
        -6.990,
         2.834,
         1.927,
        -1.263,
        -0.702,
         0.696,
        -0.690,
        -0.629,
        -0.392,
         0.297,
         0.260,
         0.201,
        -0.161,
         0.157,
        -0.138,
        -0.127,
         0.104,
         0.104,
        -0.079,
         0.068,

         0.067,
         0.054,
        -0.038,
        -0.038,
         0.037,
        -0.037,
        -0.035,
        -0.030,
         0.029,
        -0.025,
         0.023,
         0.023,
        -0.023,
         0.022,
        -0.021,
        -0.020,
         0.019,
         0.017,
         0.014,
        -0.014,
         0.013,
         0.012,
         0.011,
         0.010,
        -0.010,

        0
    );

    var periptft = new Array(
        3,
        7,
        9,
        -1
    );

    var periptfc = new Array(
        -0.0071,
        -0.0017,
         0.0016
    );

    var apoparg = new Array(
    /*  D,  M,  F   */
        0,  0,  0,
        2,  0,  0,
        1,  0,  0,
        0,  0,  2,
        0,  1,  0,
        4,  0,  0,
        2, -1,  0,
        1,  1,  0,
        4, -1,  0,
        6,  0,  0,
        2,  1,  0,
        2,  0,  2,
        2,  0, -2,
        2, -2,  0,
        2,  2,  0,
        0,  2,  0,
        6, -1,  0,
        8,  0,  0
    );

    var apopcoeff = new Array(
      3245.251,
        -9.147,
        -0.841,
         0.697,
        -0.656,
         0.355,
         0.159,
         0.127,
         0.065,

         0.052,
         0.043,
         0.031,
        -0.023,
         0.022,
         0.019,
        -0.016,
         0.014,
         0.010,

        0
    );

    var apoptft = new Array(
        4,
        -1
    );

    var apoptfc = new Array(
         0.0016,
         -1
    );


/*  MOONPA  --  Calculate perigee or apogee from index number.  */

function moonpa(k)
{
    var t, t2, t3, t4, JDE, D, M, F, par, apogee,
        EarthRad = 6378.14;


    t = k - Math.floor(k);
    if (t > 0.499 && t < 0.501) {
        apogee = true;
    } else if (t > 0.999 || t < 0.001) {
        apogee = false;
    } else {
        abort();
    }

    t = k / 1325.55;
    t4 = t * (t3 = t * (t2 = t * t));

    /* Mean time of perigee or apogee */
    JDE = 2451534.6698 + 27.55454989 * k
                       - 0.0006691 * t2
                       - 0.000001098 * t3
                       + 0.0000000052 * t4;

    /* Mean elongation of the Moon */
    D = 171.9179 + 335.9106046 * k
                 - 0.0100383 * t2
                 - 0.00001156 * t3
                 + 0.000000055 * t4;

    /* Mean anomaly of the Sun */
    M = 347.3477 + 27.1577721 * k
                 - 0.0008130 * t2
                 - 0.0000010 * t3;

    /* Moon's argument of latitude */
    F = 316.6109 + 364.5287911 * k
                 - 0.0125053 * t2
                 - 0.0000148 * t3;

    JDE += sumser(Math.sin, D, M, F, t,
            apogee ? apoarg : periarg, apogee ? apocoeff : pericoeff,
            apogee ? apotft : peritft, apogee ? apotfc : peritfc);
    par = sumser(Math.cos, D, M, F, t,
            apogee ? apoparg : periparg, apogee ? apopcoeff : peripcoeff,
            apogee ? apoptft : periptft, apogee ? apoptfc : periptfc);

    par = dtr(par / 3600.0);
    return new Array(JDE, par, EarthRad / Math.sin(par));
}

/*  PAD  --  Pad a string to a given length with a given fill character.  */

function pad(str, howlong, padwith) {
    var s = str.toString();

    while (s.length < howlong) {
        s = padwith + s;
    }
    return s;
}

/*  EDATE  --  Edit date and time to application specific format.  */

var Months = new Array( "Jan", "Feb", "Mar", "Apr", "May", "Jun",
                        "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
                      );

function edate(j) {
    var date, time;

    j += (30.0 / (24 * 60 * 60));     // Round to nearest minute
    date = jyear(j);
    time = jhms(j);

    return Months[date[1] - 1] + " " + pad(date[2], 2, " ") + " " +  
           pad(time[0], 2, " ") + ":" + pad(time[1], 2, "0");
}

/*  TRUEPHASE  --  Given a K value used to determine the mean phase of
                   the new moon, and a phase selector (0.0, 0.25, 0.5,
                   0.75), obtain the true, corrected phase time.  */

function dsin(x) {
    return Math.sin(dtr(x));
}

function dcos(x) {
    return Math.cos(dtr(x));
}

function truephase(k, phase)
{
    var t, t2, t3, pt, m, mprime, f,
        SynMonth = 29.53058868;     /* Synodic month (mean time from new to next new Moon) */

    k += phase;                     /* Add phase to new moon time */
    t = k / 1236.85;                /* Time in Julian centuries from
                                       1900 January 0.5 */
    t2 = t * t;                     /* Square for frequent use */
    t3 = t2 * t;                    /* Cube for frequent use */
    pt = 2415020.75933              /* Mean time of phase */
         + SynMonth * k
         + 0.0001178 * t2
         - 0.000000155 * t3
         + 0.00033 * dsin(166.56 + 132.87 * t - 0.009173 * t2);

    m = 359.2242                    /* Sun's mean anomaly */
        + 29.10535608 * k
        - 0.0000333 * t2
        - 0.00000347 * t3;
    mprime = 306.0253               /* Moon's mean anomaly */
        + 385.81691806 * k
        + 0.0107306 * t2
        + 0.00001236 * t3;
    f = 21.2964                     /* Moon's argument of latitude */
        + 390.67050646 * k
        - 0.0016528 * t2
        - 0.00000239 * t3;
    if ((phase < 0.01) || (Math.abs(phase - 0.5) < 0.01)) {

       /* Corrections for New and Full Moon */

       pt +=     (0.1734 - 0.000393 * t) * dsin(m)
                + 0.0021 * dsin(2 * m)
                - 0.4068 * dsin(mprime)
                + 0.0161 * dsin(2 * mprime)
                - 0.0004 * dsin(3 * mprime)
                + 0.0104 * dsin(2 * f)
                - 0.0051 * dsin(m + mprime)
                - 0.0074 * dsin(m - mprime)
                + 0.0004 * dsin(2 * f + m)
                - 0.0004 * dsin(2 * f - m)
                - 0.0006 * dsin(2 * f + mprime)
                + 0.0010 * dsin(2 * f - mprime)
                + 0.0005 * dsin(m + 2 * mprime);
    } else if ((abs(phase - 0.25) < 0.01 || (abs(phase - 0.75) < 0.01))) {
       pt +=     (0.1721 - 0.0004 * t) * dsin(m)
                + 0.0021 * dsin(2 * m)
                - 0.6280 * dsin(mprime)
                + 0.0089 * dsin(2 * mprime)
                - 0.0004 * dsin(3 * mprime)
                + 0.0079 * dsin(2 * f)
                - 0.0119 * dsin(m + mprime)
                - 0.0047 * dsin(m - mprime)
                + 0.0003 * dsin(2 * f + m)
                - 0.0004 * dsin(2 * f - m)
                - 0.0006 * dsin(2 * f + mprime)
                + 0.0021 * dsin(2 * f - mprime)
                + 0.0003 * dsin(m + 2 * mprime)
                + 0.0004 * dsin(m - 2 * mprime)
                - 0.0003 * dsin(2 * m + mprime);
       if (phase < 0.5)
          /* First quarter correction */
          pt += 0.0028 - 0.0004 * dcos(m) + 0.0003 * dcos(mprime);
       else
          /* Last quarter correction */
          pt += -0.0028 + 0.0004 * dcos(m) - 0.0003 * dcos(mprime);
    }
    return pt;
}

/*  NEARPHASE  --  Find closest new or full Moon to an apogee or
                   perigee passage and edit the time difference.  */

function nearphase(jd, phaset) {
    var i, closest, dt = Number.MAX_VALUE, rs;

    for (i = 0; i < phaset.length; i++) {
        if (Math.abs(jd - Math.abs(phaset[i])) < dt) {
            dt = Math.abs(jd - Math.abs(phaset[i]));
            closest = i;
        }
    }
    rs = (phaset[closest] < 0) ? "N" : "F";
    rs += (jd > Math.abs(phaset[closest])) ? "+" : "-";
    if (dt >= 1) {
        rs += Math.floor(dt) + "d";
        dt -= Math.floor(dt);
    } else {
        rs += "  ";
    }
    dt = Math.floor((dt * 86400) / 3600);
    if (dt < 10) {
        rs += " ";
    }
    rs += dt + "h";
    return rs;
}

//  GEN  --  Update the tables when an action button is pressed

function gen() {
    var v, sk, kr, l, perigee, s, Itemlen = 36,
        dat, evt, m = 0, epad, pchar, phnear,
        pmin = Number.MAX_VALUE, pminx = 0,
        pmax = Number.MIN_VALUE, pmaxx = 0,
        yrange, centile, TOLERANCE = 0.01, k1, mtime, minx, phaset,
        Pitemlen = 25;

    window.status = "Calculating..";
    document.calc.results.value = "";
    year = document.calc.year.value;
    v = "              Perigee           " +
        "                 Apogee\n" +
        "---------------------------------" +
        "   ---------------------------------\n";
    s = "";

    sk = Math.floor((year - 1999.97) * 13.2555);
    dat = new Array();
    evt = new Array();
    phaset = new Array();

    //  Tabulate perigees and apogees for the year

    for (l = 0; true; l++) {
        kr = moonpa(sk);
        date = jyear(kr[0]);
        perigee = (sk - Math.floor(sk)) < .25;

        if (date[0] == year) {
            if (kr[2] < pmin) {
                pmin = kr[2];
                pminx = m;
            } else if (kr[2] > pmax) {
                pmax = kr[2];
                pmaxx = m;
            }
            dat[m] = sk;
            evt[m++] = kr;
        }
        if (date[0] > year) {
            break;
        }
        sk += 0.5;
    }
    yrange = pmax - pmin;

    //  Tabulate new and full moons surrounding the year

    k1 = Math.floor((year - 1900) * 12.3685) - 4;
    minx = 0;
    for (l = 0; true; l++) {
        mtime = truephase(k1, (l & 1) * 0.5);
        date = jyear(mtime);
        if (date[0] >= year) {
            minx++;
        }
        phaset[minx] = mtime * ((l & 1) == 0 ? -1 : 1);
        if (date[0] > year) {
            minx++;
            break;
        }
        k1 += l & 1;
    }

    //  Generate perigee and apogee table

    for (l = 0; l < m; l++) {
        sk = dat[l];
        kr = evt[l];

        date = jyear(kr[0]);
        time = jhms(kr[0]);
        perigee = (sk - Math.floor(sk)) < .25;
        if (!perigee && s.length == 0) {
            s = pad("", Itemlen, " ");
        }
        phnear = nearphase(kr[0], phaset);
        pchar = phnear.charAt(0) == "F" ? "+" : "-";
        if (l == pminx || l == pmaxx) {
            epad = pchar + pchar;
        } else {
            centile = (kr[2] - pmin) / yrange;
            if (centile <= TOLERANCE || centile >= (1 - TOLERANCE)) {
                epad = pchar + " ";
            } else {
                epad = "  ";
            }
        }
        s += edate(kr[0]) + " " + Math.round(kr[2]) + " km " + epad + " " +
             phnear;
        if (s.length < Itemlen) { 
            while (s.length < Itemlen) {
                s += " ";
            }
        } else {
            v += s + "\n";
            s = "";
        }
    }
    if (s.length > 0) {
        v += s + "\n";
    }
    document.calc.results.value = v;

    //  Generate Moon phase table

    v = "           New                      Full\n";
    s = "";
    for (l = 0; l < minx; l++) {
        mp = phaset[l];
        if (mp < 0) {
            mp = - mp;
        } else {
            if (s.length == 0) {
                s = pad(s, Pitemlen, " ");
            }
        }
        s += "   " + jyear(mp)[0] + " " + edate(mp);
        if (s.length < Pitemlen) { 
            while (s.length < Pitemlen) {
                s += " ";
            }
        } else {
            v += s + "\n";
            s = "";
        }
    }
    if (s.length > 0) {
        v += s + "\n";
    }
    document.calc.phases.value = v;

    window.status = "Done.";
}

