/*
 * Various date-related services.  Includes astrological info such as
 * sun sign.  This file depends on loadXMLDoc(), normally found in
 * fetch_xml.js.
 */

var year;
var month;
var day = 0;
var hour;
var min;
var wday;         // Day of week, 0=Sun .. 6=Sat
var dd_p = 0;     // Previous day value
var hh_p = -1;    // Previous hour value
var hh_diff = 24; // Hours diff between server and here
var sc_p = -1;    // Previous solar crossing
var newsBucket;   // The HTML element holding a news feed
var newsObject;   // The XML object used to fetch news
var timeObject;   // The XML object used to coordinate the time
var asyncTimeOut = 5000;
var RelDir;
var sunSign;
var sunDegree;
var sunMinute;
var sunSignImg;
var moonPhase;
var moonDegree;
var moonSignImg;
var moonMinute;
var moonPhaseImg;

var Days = new Array('Sun','Mon','Tue','Wed','Thu','Fri','Sat');
var Mons = new Array('Jan','Feb','Mar','Apr','May','Jun'
                    ,'Jul','Aug','Sep','Oct','Nov','Dec');
var Sign = new Array('Aries','Taurus','Gemini','Cancer','Leo','Virgo','Libra',
                     'Scorpio','Sagittarius','Capricorn','Aquarius','Pisces');
var MoonPhase = new Array('New',        'Waxing crescent'
                         ,'1st quarter','Waxing gibbous'
                         ,'Full',       'Waning gibbous'
                         ,'3rd quarter','Waning crescent');

// For political reasons, the Sirius calendar will not show events whose
// description contains the text 'Temple Sophia'.
var TSoph = /Temple[\s]+Sophia/i;

/*
 * Attend to setting time-based fields on the page.  This script
 * reschedules itself to run once per minute.
 */
function setCurrentTime(relDir)
{
  RelDir = relDir;
  // Synchronize with our data source initially, and remember its settings.
  if (hh_diff > 23) {
    hh_diff = 0;    // Assume resync will fail, so we must use local time.
    var xo = GetXmlHttpObject();
    if (xo) {
      timeObject = xo;
      xo.onreadystatechange = formatCurrentTime;
      xo.open("GET", relDir + '/now?p=array', true);
      xo.send(null);
    }
  }

  // Now set the local time
  var d = new Date();
  year  = d.getFullYear();
  month = d.getMonth() + 1;
  day   = d.getDate();
  hour  = d.getHours();
  min   = d.getMinutes();
  wday  = d.getDay();

  // Now adjust local time to server time
  if ((hour += hh_diff) >= 24) {
    hour -= 24;
    if (++day > daysInMonth(year, month)) {
      if (++month > 12) {
        month -= 12;
        year++;
      }
    }
    if (++wday > 6) wday -= 7;
  }
  if (hour < 0) {
    hour += 24;
    if (--day < 1) {
      if (--month < 1) {
        month += 12;
        year--;
      }
      day = daysInMonth(year, month);
    }
    if (--wday < 0) wday += 7;
  }

  // If there is a "current time" element, set it
  var o = document.getElementById('current-time');
  if (o != null)
  {
    var h = hour;
    var a = (h >= 12) ? 'PM' : 'AM';
    var m = (min < 10) ? '0' : '';
    if (h > 12) h -= 12;
    if (h == 0) h = 12;
    o.innerHTML = Days[wday] + ', '
                + day + '&nbsp;' + Mons[month-1] + '&nbsp;' + year
                + ', ' + h + ':' + m + min + '&nbsp;' + a;
//  loadXMLDoc('now?p=date_time', 'date_time', 'current-time');
  }

  // Some tasks occur once per hour
  if (hh_p != hour) {
    hh_p = hour;
    newsBucket = document.getElementById('news-bucket');
    if (newsBucket) {
      update_news_feed(relDir);
    }
    getEphemerisData();
  }

  // If wanted and it has changed, set the sun sign
  resetSunSign();

  // When the day changes, reset moon phase.
  if (dd_p != day) {
    dd_p = day;
    resetMoonPhase();
  }

  // If wanted, set the moon sign
  var mpi = getMoonSignInfo();
  if (mpi) moonPhase.innerHTML = mpi;

  // On our way out, reschedule to run again next minute.
  var interval = 1000 * (60 - d.getSeconds());
  var cmd = 'setCurrentTime("' + relDir + '")';
  setTimeout(cmd, interval);
  return;
}

function resetSunSign() {
  var o = sunSign ? sunSign : document.getElementById('sun-sign');
  if (o) {
    sunSign = o;
    if (sunDegree && sunSignImg) {
        o.innerHTML = sunDegree + '&deg;&nbsp;' + sunSignImg;
    }
    else {
      var i = solarCrossing();
      if (i != sc_p) {
        sc_p = i;
        o.innerHTML =
             '<img src="'+RelDir+'/Images/Sun/' + Sign[i]
           + '.png" alt="' + Sign[i] + '" title="' + Sign[i] + '" />';
      }
    }
  }
}

function getMoonSignInfo() {
  return (moonPhase && moonPhaseImg && moonDegree && moonSignImg)
    ? moonPhaseImg + ' ' + moonDegree + '&deg;&nbsp;' + moonSignImg
    : '';
}

function resetMoonPhase() {
  var o = moonPhase ? moonPhase : document.getElementById('moon-phase');
  if (o) {
    moonPhase = o;
    var phase = getMoonPhase();
    moonPhaseImg =
       '<img src="'+RelDir+'/Images/Moon/moon-phase-' + phase
     + '.png" alt="' + phase + '" title="' + MoonPhase[phase] + '" />';
    var txt = getMoonSignInfo();
    if (!txt)
      txt = moonPhaseImg;
    o.innerHTML = txt;
  }
  return;
}

function formatCurrentTime() {
  var xo = timeObject;
  if (xo.readyState != 4) return;
  timeObject = null;
  if (xo.status == 200)
  {
    // Get time/date info from the server.
    setTimeInfo(xo);

    // Get local time, calculate the difference in hours.
    // If minutes disagree by too much, we fetched the time
    // from the server, then the hour changed, then we fetched
    // our local time.  Adjust for that discrepancy.
    var d = new Date();
    var hr = d.getHours();
    hh_diff = hour - hr;
    if ((min - d.getMinutes()) > 40) hh_diff--;
  }
}

/*
 * Calculate the number of days in a given month.  This function does not deal
 * correctly with years outside the range 2000 - 2099.
 */
function daysInMonth(yy, mm) {
  if (mm == 2) return ((yy & 3) ? 28 : 29);
  var m = (mm & 1);
  return (mm < 8) ? (30 + m) : (30 + (m ^ 1));
}

function setTimeInfo(xo) {
  var r    = xo.responseXML.documentElement;
  var elem = r.getElementsByTagName('data')[0];
  dd_p  = day;
  min   = getNodeValue(elem, 'minutes') * 1; // "*1" converts string to number
  hour  = getNodeValue(elem, 'hours') * 1;
  day   = getNodeValue(elem, 'mday') * 1;
  month = getNodeValue(elem, 'month') * 1;
  year  = getNodeValue(elem, 'year') * 1;
  wday  = getNodeValue(elem, 'wday') * 1;
}

// Return a moon phase one of 8 phases [0=new, 4=full]
function getMoonPhase() {
  var yy = year;
  var mm = month;
  if (mm < 3) { mm += 12; yy--; }
  var jd;
  jd = (  Math.floor(365.25 * yy)
        + Math.floor(30.6 * (mm + 1))
        + day
        - 694039.09 )
      / 29.53;
  jd -= Math.floor(jd);
  return (Math.floor((jd * 8) + 0.5) & 7);
}

/*
 * This array of solar crossings represents data from NASA for calendar
 * year 2000, adjusted to be in Pacific time rather than UT.  See below
 * for more information.
 */
var EvTypeInfo = new Array(
  //         dd, hh,mm,ss, ylen,          yadj
  new Array( 19, 23,36, 0, 365.24237404,  0.00000010338), // Aries 2000
  new Array( 19, 11,35,22, 365.24212470,  0.00000007109),
  new Array( 20, 10,45,49, 365.24187537,  0.00000003879),
  new Array( 20, 18,45,15, 365.24162603,  0.00000000650),
  new Array( 22,  5,37,28, 365.24175658, -0.00000007283),
  new Array( 22, 12,39,44, 365.24188712, -0.00000015217),
  new Array( 22, 10,16, 0, 365.24201767, -0.00000023150),
  new Array( 22, 19,33,35, 365.24225861, -0.00000019582),
  new Array( 21, 16, 5,12, 365.24249955, -0.00000016014),
  new Array( 21,  5,23,48, 365.24274049, -0.00000012446),
  new Array( 19, 16,14,53, 365.24261713, -0.00000004851),
  new Array( 18,  6,22,29, 365.24249376,  0.00000002743));

/*****
This is an approximate time of equinoxes or solstices in days measured from
the beginning of the input year.  This function's base year is the epoch,
2000.  Each major solar crossing has its own year length, given thusly:

	vernal equinox:  365.24237404 +  0.00000010338*y
	summer solstice: 365.24162603 +  0.00000000650*y
	autumn equinox:  365.24201767 + -0.00000023150*y
	winter solstice: 365.24274049 + -0.00000012446*y

...where "y" is the count of years since the epoch.

Here are times for the vernal equinox given by various sources:

  2000: 19 March, 07:36 GMT per NASA Pub 1349, Oct 1994.
  2003: 20 March, 17:00 PST per Jim Maynard's Pocket Astrologer.
  2004: 19 March, 22:49 PST per Jim Maynard's Celestial Guide.

My math using the NASA data yields this:

  2000: 19 March, 23:36
  2001: 20 March, 05:25
  2002: 20 March, 11:14
  2003: 20 March, 17:03
  2004: 19 March, 22:52
  2005: 20 March, 04:41
  2006: 20 March, 10:30
  2007: 20 March, 16:19
  2008: 19 March, 22:08
  2009: 20 March, 03:57

Rather than drive myself crazy trying to discover the year lengths of the
interstitial months, I have simply interpolated the year lengths between
major events.  This is not wrong enough to make any real difference.
*****/

/*
 * Determine which sign the sun is in based on the current time.
 */
function solarCrossing() {
  // We want year as an offset from the base year, in a form where 1 March
  // is the first day of the year.  (Months number 0=Mar .. 11=Feb)
  var yr = year;
  var mn = month;
  if ((mn -= 3) < 0) {
    yr--;
    mn += 12;
  }
  var yct = yr - 2000;

  // EvTypeInfo stores data 0-based, with March = 0
  var evinfo = EvTypeInfo[mn];
  var d0   = evinfo[0];
  var h0   = evinfo[1];
  var m0   = evinfo[2];
  var s0   = evinfo[3];
  var ylen = evinfo[4];
  var yK   = evinfo[5];

  // Calculate equinoctial days in yct years.
  if (yct >= 0) {
    var yadj = 0;
    for (var i = yct; i; i--) {
      yadj += yK;
      d0   += (ylen + yadj);
    }
  }
  else {
    var yadj = 0;
    for (var i = yct; i; i++) {
      yadj += yK;
      d0   -= (ylen + yadj);
    }
  }

  var d1 = Math.floor(d0);    // strip days
  d0 = (d0 - d1) * 24;        // convert day fraction to hours

  var h1 = Math.floor(d0);    // strip hours
  d0 = (d0 - h1) * 60;        // convert hour fraction to minutes

  var m1 = Math.floor(d0);
  var s1 = (d0 - m1) * 60;    // convert minutes fraction to seconds

  while (s1 < 0) { m1--; s1 += 60; }
  while (m1 < 0) { h1--; m1 += 60; }
  while (h1 < 0) { d1--; h1 += 24; }
  h1 += h0;
  m1 += m0;
  s1 += s0;
  while (s1 >= 60) { m1++; s1 -= 60; }
  while (m1 >= 60) { h1++; m1 -= 60; }
  while (h1 >= 24) { d1++; h1 -= 24; }

  // Now we remove $yct years from the new day number,
  // which should leave us with $d1 = day-of-month.
  d1 -= Math.floor(365.25 * yct);

  // Now we want to know which of 2 possible signs the sun is in.
  // It enters the current sign at our calculation, so:
  // if (day,hour,min) < our calculation, it's in Sign[mn - 1]
  // else it's in Sign[m]
  return ((day < d1)
       || ((day == d1)
        && ((hour < h1)
         || ((hour == h1) && (min < m1)))))
    ? ((mn == 0) ? 11 : (mn - 1))
    : mn;
}

/*
 * Calculate day-of-week [0..6] for an input date
 */
function zDay(yy,mm,dd)
{
  var cc;

  // Convert the month/year into their Zeller form
  if (mm < 3) { mm += 12; yy--; }
  mm -= 2;
  cc = Math.floor(yy / 100);
  yy -= (cc * 100);

  // This is the Zeller day, 0 = Sunday .. 6 = Saturday
  cc = (Math.floor((2.6 * mm) - 0.2)
          + dd
          + yy
          + Math.floor(yy/4)
          + Math.floor(cc/4)
          - (2 * cc))
      % 7;
  return (cc < 0) ? (cc + 7) : cc;
}

/*
 * Receive a date formatted as YYYY-MM-DD
 * Return "Day, dd Month, yyyy"
 */
function formatDate(date)
{
  var d = date.split(/-/);
  var mm = d[1]*1;
  var dow = zDay(d[0]*1, mm, d[2]*1);
  var m = Mons[mm - 1];
  return Days[dow] + ', ' + d[2] + ' ' + m + ' ' + d[0];
}

/*
 * Receive a time formatted as HH:MM:SS
 * Return "HH:MM {AM,PM,noon}"
 */
function formatTime(time)
{
  var t = time.split(/:/);
  var h = t[0] * 1;
  var m = t[1];
  var am = 'AM';
  if (h >= 12) { am = (h == 12) ? 'noon' : 'PM'; h -= 12; }
  if (h == 0)  { h = 12; if (am == 'AM') am = 'midnight'; }
  return h + ':' + m + ' ' + am;
}

/*
 * Initialize a request to fetch news.  The request will finish
 * asynchronously in formatNewsFeed().
 */
function update_news_feed(relDir)
{
  var xo = GetXmlHttpObject();
  if (xo) {
    newsObject = xo;
    xo.onreadystatechange = formatNewsFeed;
    xo.open("GET", relDir + '/Apps/get_news_feed', true);
    xo.send(null);
  }
}

/*
 * When the state of an XML request for news reaches 'done' and
 * the request status is 'success', format news items to replace
 * the current news contents.
 */
function formatNewsFeed()
{
  var xo = newsObject;
  if (xo.readyState != 4) return;
  newsObject = null;
  if (xo.status != 200) return;

  var news='';
  var r = xo.responseXML;
  var items = r.getElementsByTagName('item');
  var len = items.length;
  if (len > 0) {
    var info = '';
    news = '<p id="news-intro">Upcoming encampment events:</p>\n<div id="news-display">\n';
    for (var ix = 0; ix < len; ix++)
    {
      news = news + '<div class="news-item">\n';
      var item = items[ix];
      var d = item.getElementsByTagName('eventDate');
      if (d && d[0]) {
        news = news + formatDate(d[0].childNodes[0].nodeValue) + ', ';
        var t = item.getElementsByTagName('eventTime');
        if (t && t[0])
          news = news + formatTime(t[0].childNodes[0].nodeValue) + ' ';
      }
      var t = item.getElementsByTagName('eventDetail');
      if (t && t[0]) {
        d = item.getElementsByTagName('eventID');
        if (d && d[0]) {
          d = ' href="' + RelDir + '/Apps/showNewsDetail/' + d[0].childNodes[0].nodeValue + '"';
        }
        else
          d = '';
        info = info + '<div id="NewsDtl' + ix + '" class="footnote">'
             + t[0].childNodes[0].nodeValue
             + '</div>\n';
        t = '<a' + d + ' name="NewsInfo' + ix + '" onmouseover="show(event,' + "'NewsDtl" + ix + "'"
          + ')" onmouseout="hide('
          + "'NewsDtl" + ix + "'" + ')"><img src="' + RelDir + '/Images/info" alt="info"/></a>';
      }
      else
        t = '';
      var i = item.getElementsByTagName('eventInfo');
      if (i) {
        var value = i[0].childNodes[0].nodeValue;
        news = news + '<div class="item">'
               + value + t + '</div>';
      }
      news = news + '</div>\n';
    }
    news = news + '</div>';
  }
  newsBucket.innerHTML = news;
}

function getEphemerisData() {
  var xo = GetXmlHttpObject();
  if (xo) { 
    ephemObject = xo;
    xo.onreadystatechange = syncEphem;
    xo._timeout = setTimeout(function() { if (ephemObject) ephemObject.abort(); }, asyncTimeOut);
    xo.open("GET", RelDir + '/Apps/ephemeris_data.cgi?p=sun+moon', true);
    xo.send(null);
  }     
  return;
}

function syncEphem()  
{       
  xo = ephemObject;
  if (xo && (xo.readyState == 4)) { 
    ephemObject = null;
    if (xo.status == 200)
    {
      var r = xo.responseText.split(/\s*\n/m);
      for (var i = 1; i < r.length; i++) {
        var x = r[i].split(/,/);
        if (x.length < 2)
          continue;
        var y = x[1].split(/"/);
        y = (y.length > 1) ? y[1] : y[0];
        y = y.split(/:/);
        var s = Math.floor(y[0] / 30);  // sign: Aries = 0, Taurus = 1, etc.
        var d = y[0] - (s * 30);        // degree within the sign, 0..29
        if (x[0] == 'SU') {
          sunSignImg = getSignImage(s);
          sunDegree  = d;
          sunMinute  = y[1];
          resetSunSign();
        }
        else if (x[0] == 'MO') {
          moonSignImg = getSignImage(s);
          moonDegree  = d;
          moonMinute  = y[1];
          var txt = getMoonSignInfo();
          var o = moonPhase ? moonPhase : document.getElementById('moon-phase');
          if (o && txt) o.innerHTML = txt;
        }
        else
          alert('Planet "' + x[0] + '" is one I don\'t know.');
      }
    }
  }
  return;
}

function getSignImage(i) {
  if (i >= 0 && i < Sign.length) {
    return '<img src="'+RelDir+'/Images/Sun/' + Sign[i]
         + '.png" alt="' + Sign[i] + '" title="' + Sign[i] + '" />';
  }

  alert('There is no Zodiac sign corresponding to "' + i + '"');
  return '';
}

