/*
 * 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 is_dst;
var min;
var wday;         // Day of week, 0=Sun .. 6=Sat
var tzoff;        // Time zone offset to GMT in minutes
var dd_p = 0;     // Previous day value
var hh_p = -1;    // Previous hour value
var hh_diff = 93; // Hours diff between server and here
var sc_p = -1;    // Previous solar crossing
var pct_full_p=-1;// Previous lunar percent-full value
var massObject;   // The XML object used to fetch mass teams
var massBucket;   // The HTML element holding a mass team list
var massDinner;   // The HTML element holding a mass dinner menu
var massDtlDate;  // The HTML element holding a mass team date
var massPopup;    // The HTML element holding mass team popups
var newsBucket;   // The HTML element holding a news feed
var newsObject;   // The XML object used to fetch news
var quoteObject;  // The XML object used to fetch a quotation
var quoteBucket;  // The HTML element holding a quotation
var reshObject;   // The XML object used to fetch Resh times
var timeObject;   // The XML object used to sync time to West Coast
var reshTimes;    // The HTML element holding Resh times
var timeHtml;     // The HTML element holding date/time
var moonPhase;    // The HTML element holding lunar data
var sunSign;      // The HTML element holding solar data
var RelDir;       // Relative directory from current, for links
var asyncTimeOut = 5000; // Allow 5 seconds for requests to complete

var windowList = new Array();

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');

/*
 * Attend to setting time-based fields on the page.  This function
 * reschedules itself to run once per minute.
 */
function setCurrentTime(relDir)
{
  // Make relative directory globally accessible
  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 = syncTime;
      xo._timeout = setTimeout(function() { timeObject.abort(); }, asyncTimeOut);
      xo.open("GET", relDir + '/now?p=array', true);
      xo.send(null);
    }

    // Here is also a good spot to look up HTML entities.
    moonPhase  = document.getElementById('moon-phase');
    massBucket = document.getElementById('mass-officers');
    massPopup  = document.getElementById('mass-officer-pix');
    massDtlDate= document.getElementById('mass-details-date');
    massDinner = document.getElementById('dinner-details');
    newsBucket = document.getElementById('news-bucket');
    quoteBucket= document.getElementById('random-quote');
    reshTimes  = document.getElementById('resh-list');
    sunSign    = document.getElementById('sun-sign');
    timeHtml   = document.getElementById('current-time');
  }

  // 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();
  tzoff = d.getTimezoneOffset();

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

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

  // If wanted and it has changed, set the sun sign
  if (sunSign) {
    var i = solarCrossing();
    if (i != sc_p) {
      sc_p = i;
      sunSign.innerHTML =
           '<img src="'+relDir+'/Images/Sun/' + Sign[i]
         + '.png" alt="' + Sign[i] + '" title="' + Sign[i] + '" />';
    }
  }

  // Do some setup when the day changes
  if (dd_p != day) {
    dd_p = day;
    is_dst = ((month>=11) || ((month<4) || ((month==4) && (day<5)))) ? 0 : 1;

    // Obtain Resh times.
    if (reshTimes) { getReshTimes(); }
  }

  // Some tasks occur once per hour
  if (hh_p != hour) {
	// alert(d + '=' + hh_diff);
    hh_p = hour;
    if (newsBucket) { update_news_feed(); }

    // Reset moon phase.
    if (moonPhase) {
      var age = getMoonAge();
      var phase = getMoonPhase(age);
      // Moon is 100% lit at 50% of its age. Beyond 50% it begins
      // darkening until it returns to 0% lit.
      var pct = Math.floor((age / 29.53) * 200 + 0.5);
      if (pct > 100) pct = 100 - (pct - 100);
      if (pct != pct_full_p) {
        pct_full_p = pct;
        age = Math.floor(age);
        moonPhase.innerHTML =
           '<img src="'+relDir+'/Images/Moon/moon-phase-' + age
         + '.png" alt="moon-phase-' + age + '" title="' + MoonPhase[phase]
         + ' (' + pct + '% lit)" />';
      }
    }

    // Refresh the quotation
    if (quoteBucket) { getQuote(); }

    // Obtain the mass team
    if (massBucket) { getMassTeam(); }
  }

  // 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 syncTime()
{
  xo = timeObject;
  if (xo && (xo.readyState == 4)) {
    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--;
    }
  }
  return;
}

/*
 * Calculate the number of days in a given month.  This function does not
 * deal correctly with February in years outside the range 2000 - 2099,
 * as it cheats by knowing that year 2000 was a leap year where century
 * years are normally not.
 */
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   = parseInt(getNodeValue(elem, 'minutes'), 10);
  hour  = parseInt(getNodeValue(elem, 'hours'), 10);
  day   = parseInt(getNodeValue(elem, 'mday'), 10);
  month = parseInt(getNodeValue(elem, 'month'), 10);
  year  = parseInt(getNodeValue(elem, 'year'), 10);
  wday  = parseInt(getNodeValue(elem, 'wday'), 10);
}

/*
 * Return the moon's age, [0 .. 29.53], for the current (year,month,day).
 */
function getMoonAge() {
  // Calculate the Julian date at 12h UT
  var yy = year - Math.floor( (12 - month) / 10 );
  var mm = month + 9;
  if (mm >= 12) mm -= 12;

  var k1 = Math.floor( 365.25 * (yy + 4712) );
  var k2 = Math.floor( 30.6 * mm + 0.5 );
  var k3 = Math.floor( Math.floor( (yy/100) + 49 ) * 0.75 ) - 38;

  var jd = k1 + k2 + day + 59;   // for dates in Julian calendar
  if (jd > 2299160) jd -= k3;    // for Gregorian calendar

  // Advance the Julian day by the [fractional] number of days between
  // our time and midnight GMT.  Constant '36' below is a hack; it was
  // '12', but we seem to be 1.5 days behind the above calculation.
  var h = ((hour - 36) * 60) + min;
  h = (tzoff + h) / (24 * 60);
  jd += h;

  // Calculate the moon's age in days
  var age = normalize( (jd - 2451550.1) / 29.530588853 ) * 29.53;

  return age;
}

/*
 * Given an input age, [0 .. 29.53], determine which of a set of
 * 8 phases the moon is in, with 0=new and 4=full.
 */
function getMoonPhase(age) {
  if (age <  1.84566) return 0;
  if (age <  5.53699) return 1;
  if (age <  9.22831) return 2;
  if (age < 12.91963) return 3;
  if (age < 16.61096) return 4;
  if (age < 20.30228) return 5;
  if (age < 23.99361) return 6;
  if (age < 27.68493) return 7;
  return 0;
}

/*
 * Remove the integer portion of an input value.  Normalize the
 * remainder to a range [0 <= value < 1.0], by adding 1 if the
 * input is < 0.
 */
function normalize(v) {
  v -= Math.floor(v);
  return (v < 0) ? (v + 1) : v;
}

/*
 * Make an async call to fetch sunrise/sunset data.  When it completes,
 * fill in Resh times via formatReshTimes().
 */
function getReshTimes()
{
  var xo = GetXmlHttpObject();
  if (xo) {
    reshObject = xo;
    xo.onreadystatechange = formatReshTimes;
    xo._timeout = setTimeout(function() { reshObject.abort(); }, asyncTimeOut);
    xo.open("GET"
           ,RelDir+'/Apps/get_sunrise_data/'+year+'/'+month+'/'+day+'/'+is_dst+'/oakland%20ca%20usa'
           ,true);
    xo.send(null);
  }
}

function formatReshTimes()
{
  xo = reshObject;
  if (xo.readyState != 4) return;
  reshObject = null;
  if (xo.status != 200) return;
  var r = xo.responseText.split(',');
  if (r[0] == 200) {
    var x = new RegExp('"(.*?)"');
    var t1 = r[3].match(x)[1];  // today sunrise
    var t2 = r[4].match(x)[1];  // today sunset
    var t3 = r[5].match(x)[1];  // tomorrow sunrise
    var today = r[2] + ' ' + Mons[r[1]-1];
    reshTimes.innerHTML = '<p><a href="' + RelDir
      + '/en/Tools/reshTimes">Resh times</a> for ' + today + ' at our temple:</p>'
      + formatTime(t1, '<div class="t">Sunrise:</div>')
      + timeDiff(t1, t2, '<div class="t">Midday:</div>')
      + formatTime(t2, '<div class="t">Sunset:</div>')
      + timeDiff(t2, t3, '<div class="t">Midnight:</div>')
      + formatTime(t3, '<div class="t">Sunrise:</div>');
  }
  else {
    alert('Resh time fetch failed with status '+r[0]+'.');
  }
}

function timeDiff(t1, t2, head) {
  t1 = t1.split(':');
  t2 = t2.split(':');
  t1[0] = parseInt(t1[0]);
  t1[1] = parseInt(t1[1]);
  t1[2] = parseInt(t1[2]);
  t2[0] = parseInt(t2[0]);
  t2[1] = parseInt(t2[1]);
  t2[2] = parseInt(t2[2]);

  // subtract (t2 - t1)
  if ((t2[2] -= t1[2]) < 0) { // Seconds must borrow from minutes
    t2[2] += 60;
    t2[1]--;
    if (t2[1] < 0) {
      t2[1] += 60;
      t2[0]--;
    }
  }
  if ((t2[1] -= t1[1]) < 0) { // Minutes must borrow from hours
    t2[1] += 60;
    t2[0]--;
  }
  if ((t2[0] -= t1[0]) < 0) { // Hours must borrow from days
    t2[0] += 24;
  }

  // t1 += (t2 / 2)
  if (t2[0] & 1) { t2[1] += 60; t2[0]--; }
  if (t2[1] & 1) { t2[2] += 60; t2[1]--; }
  if (t2[2] & 1) { t2[2]--; }

  t1[0] += t2[0] / 2;
  t1[1] += t2[1] / 2;
  t1[2] += t2[2] / 2;

  if (t1[2] > 59) { t1[1]++; t1[2] -= 60; }
  if (t1[1] > 59) { t1[0]++; t1[1] -= 60; }
  if (t1[0] > 23) { t1[0] -= 24; }

  return formatTime(t1[0]+':'+t1[1]+':'+t1[2], head);
}

/*
 * Make an async call to fetch mass team data.  When it completes,
 * fill in the mass team via formatMassTeam().
 */
function getMassTeam()
{
  if (massBucket) {
    var xo = GetXmlHttpObject();
    if (xo) {
      massObject = xo;
      xo.onreadystatechange = formatMassTeam;
      xo._timeout = setTimeout(function() { massObject.abort(); }, asyncTimeOut);
      xo.open("GET", RelDir+'/Apps/get_mass_data/', true);
      xo.send(null);
    }
  }
}

function formatMassTeam()
{
  xo = massObject;
  if (xo.readyState != 4) return;
  massObject = null;
  var r = format_response_csv(xo);
  if (r) {
    r[1] = parseInt(r[1]);
    massDtlDate.innerHTML = r[2] + ' ' + Mons[r[1] - 1] + ' ' + r[0];
    massBucket.innerHTML = '<p>' + formatMassMember(r[4], 'Priestess')
                         + formatMassMember(r[3], '<br />Priest' )
                         + formatMassMember(r[5], '<br />Deacon' ) + '</p>';
    massPopup.innerHTML = formatMassMemberPic(r[4])
                        + formatMassMemberPic(r[3])
                        + formatMassMemberPic(r[5]);
    if (massDinner) {
      massDinner.innerHTML = r[6] ? 'For dinner: ' + r[6] : '';
    }
  }
  return;
}
function formatMassMember(who, what)
{
  if (who == '') return '';
  var canon = who.replace(/\s/g, '');
  return what + ': <a name="ret_' + canon
       + '" onmouseover="show(event, \'p-' + canon
       + '\')" onmouseout="hide(\'p-' + canon + '\')">' + who + '</a>';
}
function formatMassMemberPic(who)
{
  if (who == '') return '';
  who = who.replace(/\s/g, '');
  return '<p id="p-' + who + '" class="popup"><img src="' + RelDir + '/Images/People/' + who + '.jpg" alt="' + who + '" /></p>';
}

/*
 * 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
  var z = (Math.floor((2.6 * mm) - 0.2)
          + dd
          + yy
          + Math.floor(yy/4)
          + Math.floor(cc/4)
          - (2 * cc))
      % 7;

  return (z < 0) ? z + 7 : z;
}

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

/*
 * Receive a time formatted as HH:MM:SS
 * Return "HH:MM {AM,PM,noon}"
 */
function formatTime(time, head)
{
  var t = time.split(/:/);
  var h = t[0] * 1;
  var m = t[1];
  var s = t[2];
  if (!head) {
    head = '';
    s = '';
  }
  else {
    if (m.match(/^\d$/)) m = '0' + m;
    if (s.match(/^\d$/)) s = '0' + s;
    s = ':' + s;
  }
  var am = ' AM';
  if (h >= 12) { am = (h == 12 && m == 0 && s == 0) ? ' noon' : ' PM'; h -= 12; }
  if (h == 0)  { h = 12; if (m == '00' && s == '00') am = ' midnight'; }
  return '<div class="time">'+head+h+':'+m+s+am+'</div>';
}

/*
 * Initialize a request to fetch news.  The request will finish
 * asynchronously in formatNewsFeed().
 */
function update_news_feed()
{
  var xo = GetXmlHttpObject();
  if (xo) {
    newsObject = xo;
    xo.onreadystatechange = formatNewsFeed;
    xo._timeout = setTimeout(function() { newsObject.abort(); }, asyncTimeOut);
    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;

  newsObject = null;
  var news='';
  var r = xo.responseXML;
  var items = r.getElementsByTagName('item');
  var len = items.length;
  if (len > 0) {
    news = '<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 i = item.getElementsByTagName('eventInfo');
      if (i) {
        var value = i[0].childNodes[0].nodeValue;
        news = news + '<div class="item">'
               + value + '</div>';
      }
      news = news + '</div>\n';
    }
    news = news + '</div>';
  }
  newsBucket.innerHTML = news;
}

/*
 * Make an async call to fetch a quotation.  When it completes,
 * fill in the quotation via formatQuote().
 */
function getQuote()
{
  if (quoteBucket) {
    var xo = GetXmlHttpObject();
    if (xo) {
      quoteObject = xo;
      xo.onreadystatechange = formatQuote;
      xo._timeout = setTimeout(function() { quoteObject.abort(); }, asyncTimeOut);
      xo.open("GET", RelDir+'/Apps/get_quote', true);
      xo.send(null);
    }
  }
}

function formatQuote()
{
  xo = quoteObject;
  if (xo.readyState != 4) return;
  quoteObject = null;

  var q = unEscape( fetchElement('text', xo) );
  if (q) {
    q = '<q>' + q + '</q>';
    var a = fetchElement('author', xo);
    if (a) {
      q = q + '<br/><span class="author">&mdash; ' + a + '</span>';
      var i = fetchElement('is-a', xo);
      if (i) q = q + ', ' + i;
      var w = fetchElement('work', xo);
      if (w) q = q + ', <cite>' + w + '</cite>';
    }
    quoteBucket.innerHTML = q;
  }

  return;
}

/*
 * Receive a url and a window name.  If a window named 'name' does not exist,
 * Open a new popup window and add it to a list of currently open windows.
 * If it does exist, bring it to the front; and if the supplied URL is not its
 * current location, redirect it.
 *   This function returns nothing, so as not to redirect its calling window.
 */
function popWin(url, name)
{
  if (url == null) url = '';
  if (name == null) name = '';
  var opts = 'status=1,resize=1,toolbar=1,scrollbars=1,location=1,resizable=1,menubar=1';
  for (var i = 0; i < windowList.length; i++)
  {
    var w = windowList[i][2];
    if (windowList[i][0] == name)
    {
      // This is the window we're looking for.
      if (w.closed)
      {
        // Reopen a window the user closed.
        windowList[i][1] = url;
        windowList[i][2] = window.open(url, name, opts);
      }
      else {
        // The window is already open.
        if (windowList[i][1] != url) {
          // Redirect this window to the supplied location.
          w.location.href = url;
        }
        // Make sure this window is visible.
        w.focus();
      }
    }
  }
  if (i >= windowList.length) {
    // Open a new window, then store its information.
    windowList[i] = new Array(name, url, window.open(url, name, opts));
  }
}

/*
 * Locate a window by name, and return a reference to it.
 */
function getWindowByName(name)
{
  for (var i = 0; i < windowList.length; i++)
  {
    if (windowList[i][0] == name)
      return windowList[i][2];
  }
  return null;
}

/*
 * Return the current relative directory path, or '' if there is none.  If the
 * caller provides a reference path, we append that to the relative directory.
 */
function getRelDir(ref) {
  var r = (RelDir) ? RelDir : '';
  return (ref) ? r + ref : r;
}
