/*
	This code module is a modified version of the
	Moonrise, Moonset solver by Stephen R. Schmitt
	who wrote:

	"You may use or modify this source code in any way you find useful, provided 
	that you agree that the author has no warranty, obligations or liability.
	You must determine the suitability of this source code for your use."
	
	Copyright © 2004, Stephen R. Schmitt
*/

var PI = Math.PI;
var DR = PI/180;
var K1 = 15*DR*1.0027379;

var Moonrise = false;
var Moonset  = false;

var Rise_time = [0, 0, 0];
var Set_time  = [0, 0, 0];
var Rise_az = 0.0;
var Set_az  = 0.0;

var Sky = [0.0, 0.0, 0.0];
var RAn = [0.0, 0.0, 0.0];
var Dec = [0.0, 0.0, 0.0];
var VHz = [0.0, 0.0, 0.0];

function toHours12(time)	// convert time from 24-hour to 12-hour format
{
	var hours = Number(time.substring(0,2));
	var ampm = 'AM';
	if (hours >  11) { hours -= 12; ampm = 'PM'; }
	if (hours === 0) { hours = 12; }
	hours = String(hours); if (hours.length == 1) { hours = '0' + hours; }
	return hours + time.substring(2) + ampm;
}

// calculate moonrise and moonset times
function moonRiseSet( lat, lon, Now, zone, h12 )
{
    var i, j, k, ph;
    
    // var zone = Math.round(Now.getTimezoneOffset()/60);	// time zone offset in hours
    
    var jd = julian_day(Now) - 2451545;           // Julian day relative to Jan 1.5, 2000
    
    // if ((sgn(zone) == sgn(lon))&&(zone !== 0)) { alert("WARNING: time zone and longitude are incompatible!"); }

    var mp = [];                     // create a 3x3 array
    for (i = 0; i < 3; i += 1)
    {
        mp[i] = [];
        for (j = 0; j < 3; j += 1) { mp[i][j] = 0.0; }
    }

    lon = lon/360;
    var tz = zone/24;
    var t0 = lst(lon, jd, tz);                 // local sidereal time

    jd = jd + tz;                              // get moon position at start of day

    for (k = 0; k < 3; k += 1)
    {
        moon(jd);
        mp[k][0] = Sky[0];
        mp[k][1] = Sky[1];
        mp[k][2] = Sky[2];
        jd = jd + 0.5;      
    }   

    if (mp[1][0] <= mp[0][0]) { mp[1][0] = mp[1][0] + 2*PI; }

    if (mp[2][0] <= mp[1][0]) { mp[2][0] = mp[2][0] + 2*PI; }

    RAn[0] = mp[0][0];
    Dec[0] = mp[0][1];

    Moonrise = false;                          // initialize
    Moonset  = false;
    
    for (k = 0; k < 24; k += 1)                   // check each hour of this day
    {
        ph = (k + 1)/24;
        
        RAn[2] = interpolate(mp[0][0], mp[1][0], mp[2][0], ph);
        Dec[2] = interpolate(mp[0][1], mp[1][1], mp[2][1], ph);
        
        VHz[2] = test_moon(k, zone, t0, lat, mp[1][2]);

        RAn[0] = RAn[2];                       // advance to next hour
        Dec[0] = Dec[2];
        VHz[0] = VHz[2];
    }

    // display results

    var calc_moonrise_value   = zintstr(Rise_time[0], 2) + ":" + zintstr(Rise_time[1], 2) + ":" + zintstr(Rise_time[2], 2);	// + ", az = " + frealstr(Rise_az, 5, 1) + "¬∞ÔøΩ";
    var calc_moonrise_value24 = calc_moonrise_value;
    if (h12) { calc_moonrise_value = toHours12(calc_moonrise_value); }

    var calc_moonset_value   = zintstr( Set_time[0], 2) + ":" + zintstr( Set_time[1], 2) + ":" + zintstr( Set_time[2], 2);	// + ", az = " + frealstr(Set_az, 5, 1) + "¬∞ÔøΩ";
    var calc_moonset_value24 = calc_moonset_value;
    if (h12) { calc_moonset_value = toHours12(calc_moonset_value); }

    if ((!Moonrise)&&(!Moonset))               // neither moonrise nor moonset
    {
        if (VHz[2] < 0) {  return "Down all day"; } else { return "Up all day"; }
    }
    else                                       // moonrise or moonset
    {
        if (!Moonrise)     { calc_moonrise_value = "None"; }				// No moonrise this date
        else if (!Moonset) { calc_moonset_value  = "None";  }				// No moonset this date
    }
    
    if (calc_moonrise_value24 < calc_moonset_value24) { return 'R ' + calc_moonrise_value + '/S ' + calc_moonset_value; }
	else { return 'S ' + calc_moonset_value + '/R ' + calc_moonrise_value; }
}

// Local Sidereal Time for zone
function lst( lon, jd, z )
{
    var s = 24110.5 + 8640184.812999999*jd/36525 + 86636.6*z + 86400*lon;
    s = s/86400;
    s = s - Math.floor(s);
    return s*360*DR;
}

// 3-point interpolation
function interpolate( f0, f1, f2, p )
{
    var a = f1 - f0;
    var b = f2 - f1 - a;
    var f = f0 + p*(2*a + b*(2*p - 1));

    return f;
}

// test an hour for an event
function test_moon( k, zone, t0, lat, plx )
{
    var ha = [0.0, 0.0, 0.0];
    var a, b, c, d, e, s, z;
    var hr, min, sec, time, res;
    var az, hz, nz, dz;

    if (RAn[2] < RAn[0]) { RAn[2] = RAn[2] + 2*PI; }
    
    ha[0] = t0 - RAn[0] + k*K1;
    ha[2] = t0 - RAn[2] + k*K1 + K1;
    
    ha[1]  = (ha[2] + ha[0])/2;                // hour angle at half hour
    Dec[1] = (Dec[2] + Dec[0])/2;              // declination at half hour

    s = Math.sin(DR*lat);
    c = Math.cos(DR*lat);

    // refraction + sun semidiameter at horizon + parallax correction
    z = Math.cos(DR*(90.567 - 41.685/plx));

    if (k <= 0)                                // first call of function
        { VHz[0] = s*Math.sin(Dec[0]) + c*Math.cos(Dec[0])*Math.cos(ha[0]) - z; }

    VHz[2] = s*Math.sin(Dec[2]) + c*Math.cos(Dec[2])*Math.cos(ha[2]) - z;
    
    if (sgn(VHz[0]) == sgn(VHz[2])) { return VHz[2]; }	// no event this hour
    
    VHz[1] = s*Math.sin(Dec[1]) + c*Math.cos(Dec[1])*Math.cos(ha[1]) - z;

    a = 2*VHz[2] - 4*VHz[1] + 2*VHz[0];
    b = 4*VHz[1] - 3*VHz[0] - VHz[2];
    d = b*b - 4*a*VHz[0];

    if (d < 0) { return VHz[2]; }              // no event this hour
    
    d = Math.sqrt(d);
    e = (-b + d)/(2*a);

    if (( e > 1 )||( e < 0 )) { e = (-b - d)/(2*a); }

    time = k + e + 1/7200;                      // time of an event in hours plus round up of half second
    hr   = Math.floor(time);
    res  = (time - hr)*60;
    min  = Math.floor(res);
    res  = (res - min)*60;
    sec = Math.floor(res);

    hz = ha[0] + e*(ha[2] - ha[0]);            // azimuth of the moon at the event
    nz = -Math.cos(Dec[1])*Math.sin(hz);
    dz = c*Math.sin(Dec[1]) - s*Math.cos(Dec[1])*Math.cos(hz);
    az = Math.atan2(nz, dz)/DR;
    if (az < 0) { az = az + 360; }
    
    if ((VHz[0] < 0)&&(VHz[2] > 0))
    {
        Rise_time[0] = hr;
        Rise_time[1] = min;
        Rise_time[2] = sec;
        Rise_az = az;
        Moonrise = true;
    }
    
    if ((VHz[0] > 0)&&(VHz[2] < 0))
    {
        Set_time[0] = hr;
        Set_time[1] = min;
        Set_time[2] = sec;
        Set_az = az;
        Moonset = true;
    }

    return VHz[2];
}

// test an hour for an event
function test_sun( k, zone, t0, lat )
{
    var ha = [];
    var a, b, c, d, e, s, z;
    var hr, min, sec, time, res;
    var az, dz, hz, nz;
    
    ha[0] = t0 - RAn[0] + k*K1; 
    ha[2] = t0 - RAn[2] + k*K1 + K1; 

    ha[1]  = (ha[2]  + ha[0])/2;               // hour angle at half hour
    Dec[1] = (Dec[2] + Dec[0])/2 ;             // declination at half hour
    
    s = Math.sin(lat*DR);
    c = Math.cos(lat*DR);
    z = Math.cos(90.833*DR);                   // refraction + sun semidiameter at horizon

    if (k <= 0)
        { VHz[0] = s*Math.sin(Dec[0]) + c*Math.cos(Dec[0])*Math.cos(ha[0]) - z; }

    VHz[2] = s*Math.sin(Dec[2]) + c*Math.cos(Dec[2])*Math.cos(ha[2]) - z;
    
    if (sgn(VHz[0]) == sgn(VHz[2]))  { return VHz[2]; }	// no event this hour
    
    VHz[1] = s*Math.sin(Dec[1]) + c*Math.cos(Dec[1])*Math.cos(ha[1]) - z;
    
    a =  2* VHz[0] - 4*VHz[1] + 2*VHz[2]; 
    b = -3* VHz[0] + 4*VHz[1] - VHz[2];   
    d = b*b - 4*a*VHz[0];

    if (d < 0) { return VHz[2]; }              // no event this hour
    
    d = Math.sqrt(d);    
    e = (-b + d)/(2 * a);
    
    if ((e > 1)||(e < 0)) { e = (-b - d)/(2*a); }

    time = k + e + 1/7200;                      // time of an event in hours plus round up of half second
    hr   = Math.floor(time);
    res  = (time - hr)*60;
    min  = Math.floor(res);
    res  = (res - min)*60;
    sec = Math.floor(res);

    hz = ha[0] + e*(ha[2] - ha[0]);            // azimuth of the sun at the event
    nz = -Math.cos(Dec[1])*Math.sin(hz);
    dz = c*Math.sin(Dec[1]) - s*Math.cos(Dec[1])*Math.cos(hz);
    az = Math.atan2(nz, dz)/DR;
    if (az < 0) { az = az + 360; }
    
    if ((VHz[0] < 0)&&(VHz[2] > 0))
    {
        Rise_time[0] = hr;
        Rise_time[1] = min;
        Rise_time[2] = sec;
        Rise_az = az;
        Moonrise = true;
    }
    
    if ((VHz[0] > 0)&&(VHz[2] < 0))
    {
        Set_time[0] = hr;
        Set_time[1] = min;
        Set_time[2] = sec;
        Set_az = az;
        Moonset = true;
    }

    return VHz[2];
}

// moon's position using fundamental arguments 
// (Van Flandern & Pulkkinen, 1979)
function moon( jd )
{
    var d, f, g, h, m, n, s, u, v, w;

    h = 0.606434 + 0.03660110129*jd;
    m = 0.374897 + 0.03629164709*jd;
    f = 0.259091 + 0.0367481952 *jd;
    d = 0.827362 + 0.03386319198*jd;
    n = 0.347343 - 0.00014709391*jd;
    g = 0.993126 + 0.0027377785 *jd;

    h = h - Math.floor(h);
    m = m - Math.floor(m);
    f = f - Math.floor(f);
    d = d - Math.floor(d);
    n = n - Math.floor(n);
    g = g - Math.floor(g);

    h = h*2*PI;
    m = m*2*PI;
    f = f*2*PI;
    d = d*2*PI;
    n = n*2*PI;
    g = g*2*PI;

    v = 0.39558*Math.sin(f + n);
    v = v + 0.082  *Math.sin(f);
    v = v + 0.03257*Math.sin(m - f - n);
    v = v + 0.01092*Math.sin(m + f + n);
    v = v + 0.00666*Math.sin(m - f);
    v = v - 0.00644*Math.sin(m + f - 2*d + n);
    v = v - 0.00331*Math.sin(f - 2*d + n);
    v = v - 0.00304*Math.sin(f - 2*d);
    v = v - 0.0024 *Math.sin(m - f - 2*d - n);
    v = v + 0.00226*Math.sin(m + f);
    v = v - 0.00108*Math.sin(m + f - 2*d);
    v = v - 0.00079*Math.sin(f - n);
    v = v + 0.00078*Math.sin(f + 2*d + n);
    
    u = 1 - 0.10828*Math.cos(m);
    u = u - 0.0188 *Math.cos(m - 2*d);
    u = u - 0.01479*Math.cos(2*d);
    u = u + 0.00181*Math.cos(2*m - 2*d);
    u = u - 0.00147*Math.cos(2*m);
    u = u - 0.00105*Math.cos(2*d - g);
    u = u - 0.00075*Math.cos(m - 2*d + g);
    
    w = 0.10478*Math.sin(m);
    w = w - 0.04105*Math.sin(2*f + 2*n);
    w = w - 0.0213 *Math.sin(m - 2*d);
    w = w - 0.01779*Math.sin(2*f + n);
    w = w + 0.01774*Math.sin(n);
    w = w + 0.00987*Math.sin(2*d);
    w = w - 0.00338*Math.sin(m - 2*f - 2*n);
    w = w - 0.00309*Math.sin(g);
    w = w - 0.0019 *Math.sin(2*f);
    w = w - 0.00144*Math.sin(m + n);
    w = w - 0.00144*Math.sin(m - 2*f - n);
    w = w - 0.00113*Math.sin(m + 2*f + 2*n);
    w = w - 0.00094*Math.sin(m - 2*d + g);
    w = w - 0.00092*Math.sin(2*m - 2*d);

    s = w/Math.sqrt(u - v*v);                  // compute moon's right ascension ...  
    Sky[0] = h + Math.atan(s/Math.sqrt(1 - s*s));

    s = v/Math.sqrt(u);                        // declination ...
    Sky[1] = Math.atan(s/Math.sqrt(1 - s*s));

    Sky[2] = 60.40974*Math.sqrt( u );          // and parallax
}

// determine Julian day from calendar date
// (Jean Meeus, "Astronomical Algorithms", Willmann-Bell, 1991)
function julian_day(Now)
{
    var a, b, jd;
    var gregorian;

    var month = Now.getMonth() + 1;
    var day   = Now.getDate();
    var year  = Now.getFullYear();

    gregorian = (year < 1583) ? false : true;
    
    if ((month == 1)||(month == 2))
    {
        year  = year  - 1;
        month = month + 12;
    }

    a = Math.floor(year/100);
    if (gregorian) { b = 2 - a + Math.floor(a/4); }
    else           { b = 0.0; }

    jd = Math.floor(365.25*(year + 4716)) + Math.floor(30.6001*(month + 1)) + day + b - 1524.5;
    
    return jd;
}

// returns value for sign of argument
function sgn( x )
{
    var rv;
    if (x > 0.0)      { rv =  1; }
    else if (x < 0.0) { rv = -1; }
    else              { rv =  0; }
    return rv;
}

// format a positive integer with leading zeroes
function zintstr( num, width )
{
    var str = num.toString(10);
    var len = str.length;
    var intgr = "";
    var i;

    for (i = 0; i < width - len; i += 1) { intgr += '0'; }		// append leading zeroes
    for (i = 0; i < len; i += 1) { intgr += str.charAt(i); }	// append digits
    return intgr;
}

// format an integer
function cintstr( num, width )
{
    var str = num.toString(10);
    var len = str.length;
    var intgr = "";
    var i;

    for (i = 0; i < width - len; i += 1) { intgr += ' '; }		// append leading spaces
    for (i = 0; i < len; i += 1) { intgr += str.charAt(i); }	// append digits
    return intgr;
}

// format a real number
function frealstr( num, width, fract )
{
    var str = num.toFixed(fract);
    var len = str.length;
    var real = "";
    var i;

    for (i = 0; i < width - len; i += 1) { real += ' '; }		// append leading spaces
    for (i = 0; i < len; i += 1) {  real += str.charAt(i); }	// append digits
	return real;
}
