// System specific definitions
var strSystemTitle = "San Bernardino County Flood Warning System";

// Page reload timers
var nReloadMinutes = 5;
var nReloadSeconds = 0;

// Header image definitions
var strHeaderImageSrc = "images/sanberncounty.jpg";
var strHeaderImageSrc2 = "images/floodcontrol.jpg";
var strHeaderImageTitle = "San Bernardino County Flood Warning System";
var strHeaderImageLink = "http://www.sbcounty.gov/dpw/floodcontrol/default.asp";
var strHeaderTitle = "San Bernardino County<br/>Flood Warning System";
var strHeaderHome = "FWS Home";
var strHeaderSiteIndex = "FWS Site index";
var strHeaderTopOfPage = "Top of page";
var strHeaderBottomOfPage = "Bottom of page";

// Switches to display data-types in footer
var bDisplayAll = false;
var bDisplayRainfall = true;
var bDisplayWaterLevel = true;
var bDisplayBatteryVoltage = true;
var bDisplayWeatherStations = true;
var bDisplayWaterQuality = false;
var bDisplayScores = false;
var bDisplaySitePages = false;

// System defintions
var strRoot = "";
var strXMLpathname = "xml";
var strXSLfolder = "xsl/";
var strMAPfolder = "maps/";
var strSITEPAGEfolder = "sitepages/";
var strImagefolder = "images/";
var strIndex = "index.html";
var bDebug = false;

// Alarms
// Set bDisplayAlarms to true if you want the web pages to display alarms
// bIgnoreAlarms is an internal variable set to true when alarm acknowledgement is canceled.
// strAcknowledgeLink is the URL of the alarm acknowledgement action
var bDisplayAlarms = false;
var bIgnoreAlarms = false;
var strAcknowledgeLink = "cgi-bin/acknowledge-alarms";

// XML data file basename
var strXMLbasename = "sitedata";

// XML sites document and root node names;
var strXMLsitesName = "sites.xml";
var strXMLsitesNode = "/sites";

// XML alarms document and root node names;
var strXMLalarmsName = "alarms.xml";
var strXMLalarmsNode = "/alarms";

// XML reports document and root node names;
var strXMLreportsName = "reports.xml";
var strXMLreportsNode = "/reports";

// XML maps document and root node names
var strXMLmapsName = "maps.xml";
var strXMLmapsNode = "/maps";

// XML sensor data document and root node names
var strXMLshowdataName = "showdata.xml";
var strXMLshowdataNode = "/showdata";

// XML site pages document and root node names
var strXMLsitepagesName = "sitepages.xml";
var strXMLsitepagesNode = "/sitepages";

// XML intervals document and root node names
var strXMLintervalsName = "intervals.xml";
var strXMLintervalsNode = "/datatypeintervals/intervals";

// XML showsite document and root node names;
var strXMLshowsiteName = "showsite.xml";
var strXMLshowsiteNode = "/showsite";

// Tooltip parameters
var strToolTipClass = "";
var nToolTipWidth = 0;
var nToolTipXoff = 0;
var nToolTipYoff = 0;
var nToolTipFontWidth = 7;
var nToolTipFontHeight = 16;
var nToolTipBorder = 2;
var strToolTipLines = "";
var strToolTipStatus = "";
var objToolTipMissing = null;

// Make a list of data types by reading XML document
function makeDataTypeList(objXMLdatatypes,list,strSelectValue)
{
  // Make a list of datatypes
  if ( objXMLdatatypes != null )
  {
    // Get a list of datatypes with display enabled
    var strDatatypeQuery = "//datatypes/datatype[@display='yes']";
    var objDatatypeNodeList = objXMLdatatypes.selectNodes(strDatatypeQuery);
    if ( objDatatypeNodeList != null && objDatatypeNodeList.length > 0 )
    {
      // For each datatype, add datatype type and label to list
      var nIndex;
      var objDatatypeLabel;
      var strDatatypeName;
      var strDatatypeLabel;
      var objDatatypeNode = objDatatypeNodeList.nextNode;
      while ( objDatatypeNode != null )
      {
        // Get datatype type and label
        strDatatypeName = objDatatypeNode.getAttribute("type");
        objDatatypeLabel = objDatatypeNode.selectSingleNode("label");
        if ( strDatatypeName != null && objDatatypeLabel != null )
        {
          // Add datatype type and label to list
          strDatatypeLabel = objDatatypeLabel.text;
          nIndex = list.length;
          list.options[nIndex] = new Option(strDatatypeLabel, strDatatypeName);

          // Select datatype in list if type matches select value
          if ( strDatatypeName == strSelectValue )
            list.options[nIndex].selected = true
        }

        // Get next datatype in list
        objDatatypeNode = objDatatypeNodeList.nextNode;
      }
    }
  }
}

// Make a list of map names by reading XML document
function makeMapNameList(objXMLmaps,list,strSelectValue)
{
  // Make a list of map names
  if ( objXMLmaps != null )
  {
    // Get a list of map names with display enabled
    var strMapQuery = "/maps/map[@display='yes']";
    var objMapNodeList = objXMLmaps.selectNodes(strMapQuery);
    if ( objMapNodeList != null && objMapNodeList.length > 0 )
    {
      // For each map, add map name and label to list
      var nIndex;
      var objMapName;
      var objMapLabel;
      var strMapName;
      var strMapLabel;
      var objMapNode = objMapNodeList.nextNode;
      while ( objMapNode != null )
      {
        // Get map name and label
        objMapName = objMapNode.selectSingleNode("name");
        objMapLabel = objMapNode.selectSingleNode("label");
        if ( objMapName != null && objMapLabel != null )
        {
          // Add map name and label to list
          strMapName = objMapName.text;
          strMapLabel = objMapLabel.text;
          nIndex = list.length;
          list.options[nIndex] = new Option(strMapLabel, strMapName);

          // Select map in list if name matches select value
          if ( strMapName == strSelectValue )
            list.options[nIndex].selected = true
        }

        // Get next map in list
        objMapNode = objMapNodeList.nextNode;
      }
    }
  }
}

// Make a list of site page names by reading XML document
function makeSitePageNameList(objXMLsitepages,list,strSelectValue)
{
  // Make a list of site names
  if ( objXMLsitepages != null )
  {
    // Get a list of site names with display enabled
    var strSitePageQuery = "/sitepages/sitepage[@display='yes']";
    var objSitePageNodeList = objXMLsitepages.selectNodes(strSitePageQuery);
    if ( objSitePageNodeList != null && objSitePageNodeList.length > 0 )
    {
      // For each site, add site name and label to list
      var nIndex;
      var objSitePageName;
      var objSitePageLabel;
      var strSitePageName;
      var strSitePageLabel;
      var objSitePageNode = objSitePageNodeList.nextNode;
      while ( objSitePageNode != null )
      {
        // Get site name and label
        objSitePageName = objSitePageNode.selectSingleNode("name");
        objSitePageLabel = objSitePageNode.selectSingleNode("label");
        if ( objSitePageName != null && objSitePageLabel != null )
        {
          // Add site name and label to list
          strSitePageName = objSitePageName.text;
          strSitePageLabel = objSitePageLabel.text;
          nIndex = list.length;
          list.options[nIndex] = new Option(strSitePageLabel, strSitePageName);

          // Select site in list if name matches select value
          if ( strSitePageName == strSelectValue )
            list.options[nIndex].selected = true
        }

        // Get next site in list
        objSitePageNode = objSitePageNodeList.nextNode;
      }
    }
  }
}

// Select the month passed as (0 - 11) in month selection list
function makeMonthList(list,nSelectMonth)
{
  if ( nSelectMonth >= 0 && nSelectMonth < list.options.length )
    list.options[nSelectMonth].selected = true
}

// Rebuild a day selection list for the number of days in month
// Select the day passed as (1 - 31)
function makeDayList(list,nDaysInMonth,nSelectDay)
{
  // Append days to list until number of days in month matched
  while ( list.options.length < nDaysInMonth )
  {
    nIndex = list.options.length;
    nDay = nIndex + 1;
    strDay = nDay
    if ( nDay < 10 )
      strDay = "0" + strDay
    list.options[nIndex] = new Option(nDay, strDay)
  }

  // Truncate days in list to number of days in month
  if ( list.options.length >= nDaysInMonth )
    list.options.length = nDaysInMonth;

  // Select day number passed
  if ( nSelectDay > 0 && nSelectDay <= list.options.length )
    list.options[nSelectDay-1].selected = true
}

// Return the number of days in month
// Take into account leap years
function returnDaysPerMonth(nMonth, nYear)
{
  // Month number starts with January = 0
  switch(nMonth)
  {
    case 0:
    case 2:
    case 4:
    case 6:
    case 7:
    case 9:
    case 11:
      return 31
      break
    case 3:
    case 5:
    case 8:
    case 10:
      return 30
      break
    case 1:
      if ( nYear % 4 == 0 )
        return 29
      else
        return 28
      break
  }  
  return 31
}

// Select the year passed if in list
function makeYearList(list,nSelectYear)
{
  for ( nIndex = 0; nIndex < list.options.length; nIndex++ )
  {
    if ( nSelectYear == list.options[nIndex].value)
      list.options[nIndex].selected = true;
    else
      list.options[nIndex].selected = false;
  }
}

// Select the nearest time prior to hour and minute passed
function makeTimeList(list,nSelectHour,nSelectMinute)
{
  for ( nIndex = 0; nIndex < list.options.length; nIndex++ )
  {
    time_array = list.options[nIndex].value.split(".");
    nHour = time_array[0];
    nMinute = time_array[1];
    if ( nSelectHour == nHour && nMinute <= nSelectMinute )
      list.options[nIndex].selected = true;
    else
      list.options[nIndex].selected = false;
  }
}

// Make date and time select lists
function makeDateTimeLists(strCookiePrefix,listMonth,listDay,listYear,listTime,
strYear,strMonth,strDay,strHour,strMinute)
{
  // Get the current date
  var time_now = new Date();

  // Get the last month requested from cookie
  // Use the month passed as function argument
  // Use current month if not defined
  // Month saved as cookie is from 1 - 12
  // Month return from current time is 0 - 11
  var selectMonth = get_cookie(strCookiePrefix + "Month");
  if ( strMonth != null && strMonth != "" ) selectMonth = strMonth;
  if (selectMonth) selectMonth -= 1
  else selectMonth = time_now.getMonth();

  // Make month list, select month from 0 - 11
  makeMonthList(listMonth,selectMonth);

  // Get the last year requested from cookie
  // Use the year passed as function argument
  // Use current year if not defined
  var selectYear = get_cookie(strCookiePrefix + "Year");
  if ( strYear != null && strYear != "" ) selectYear = strYear;
  if (!selectYear) selectYear = time_now.getFullYear();

  // Get the number of days per month selected
  nDays = returnDaysPerMonth(selectMonth,selectYear);

  // Get the last day requested from cookie
  // Use the day passed as function argument
  // Use current day if not defined
  var selectDay = get_cookie(strCookiePrefix + "Day");
  if ( strDay != null && strDay != "" ) selectDay = strDay;
  if (!selectDay) selectDay = time_now.getDate(); 

  // Make day of month list and select day
  makeDayList(listDay,nDays,selectDay);

  // Make year list and select year
  makeYearList(listYear,selectYear);

  // Get the last time requested from cookie
  // Use the hour and minute passed in function time argument
  // Use current time if not defined
  var selectHour = get_cookie(strCookiePrefix + "Hour");
  if ( strHour != null && strHour != "" ) selectHour = strHour;
  if (!selectHour) selectHour = time_now.getHours();
  var selectMinute = get_cookie(strCookiePrefix + "Minute");
  if ( strMinute != null && strMinute != "" ) selectMinute = strMinute;
  if (!selectHour || !selectMinute )
  {
    // Starting time defaults to beginning of day
    if(strCookiePrefix == "Start")
      selectHour = selectMinute = 0;
    else
    {
      selectHour = time_now.getHours();
      selectMinute = time_now.getMinutes();
    }
  }

  // Make year list and select year
  makeTimeList(listTime,selectHour,selectMinute);
  return true
}

// Update date and time lists for new month
function updateDateTimeList(objMonthList,objDayList,objYearList)
{
  // Get new month selected
  var nIndexMonth = objMonthList.selectedIndex;

  // Get year and compute number of days per month
  var nIndexYear = objYearList.selectedIndex;
  var nYear = objYearList.options[nIndexYear].value;
  var nDays = returnDaysPerMonth(nIndexMonth,nYear);

  // If number of days per month changed, remake day list
  if ( objDayList.length != nDays )
  {
    var nIndexDay = objDayList.selectedIndex;
    var nDay = objDayList.options[nIndexDay].value;
    if(nDay > nDays)
      nDay = nDays;
    makeDayList(objDayList,nDays,nDay);
  }
}

// Build a time period selection list
// Select the time period passed
function makeTimePeriodList(objXMLintervals,list,strIntervalType,strSelectValue)
{
  // Make a list of interval names
  if ( objXMLintervals != null )
  {
    // Get a list of interval names with display enabled
    var strIntervalQuery = "/datatypeintervals/intervals[@type='";
    strIntervalQuery += strIntervalType;
    strIntervalQuery += "']/interval[@display='yes']";
    var objIntervalNodeList = objXMLintervals.selectNodes(strIntervalQuery);
    if ( objIntervalNodeList != null && objIntervalNodeList.length > 0 )
    {
      // For each interval, add interval length and label to list
      var nIndex;
      var strIntervalLength;
      var strIntervalLabel;
      var objIntervalNode = objIntervalNodeList.nextNode;
      while ( objIntervalNode != null )
      {
        // Get interval length and label
        strIntervalLength = objIntervalNode.getAttribute("length");
        strIntervalLabel = objIntervalNode.getAttribute("length");
        if ( strIntervalLength != null && strIntervalLabel != null )
        {
          // Get interval value and remove spaces
//          strIntervalLength = strIntervalLength.replace(" ","");

          // Add interval length and label to list
          nIndex = list.length;
          list.options[nIndex] = new Option(strIntervalLabel, strIntervalLength);

          // Select interval in list if length matches select value
          if ( strIntervalLength == strSelectValue )
            list.options[nIndex].selected = true
        }

        // Get next interval in list
        objIntervalNode = objIntervalNodeList.nextNode;
      }
    }
  }
}

// Clear the status line
function clearStatus()
{
  status = "";
}

// Set the status line
function setStatus(strStatus)
{
  status = strStatus;
}

// Create a DOM document object
function newDOMDocument(strPathName,bVerbose)
{
  var objDocument = new ActiveXObject("Msxml2.DOMDocument");
  return objDocument;
}

// Create a DOM document object and load from file path name.
// Argument: strPathName - Document file path name
// Returns: objDocument - DOM Document object or (null) if error
//<object id="Document" width="0" height="0"
//  classid="clsid:f5078f32-c551-11d3-89b9-0000f81fe221">
//  <param name="async" value="false">
//  <param name="validateOnParse" value="false">
//</object>
function loadDOMDocument(strPathName,bVerbose)
{
  // Show activity on status line
  var strStatus = "Load XML document: " + strPathName;
  if ( bVerbose == true )
    setStatus(strStatus);

  // Create a new DOM document
  var objDocument = newDOMDocument();
  if ( objDocument == null )
  {
    // Show document creation error on status line
    if ( bVerbose == true )
      setStatus(strStatus + " - Unable to create a document");
    return null;
  }
  objDocument.async = false;

  // Load the DOM document from the file path name
  if ( objDocument.load(strPathName) == false )
  {
    // Show load error on status line
    if ( bVerbose == true )
      setStatus(strStatus + " - " + objDocument.parseError.reason);

    // Delete document and return failure
    delete objDocument;
    return null;
  }

  // Return DOM document object on success
  if ( bVerbose == true )
    setStatus(strStatus + " - success");
  return objDocument;
}

// Append another DOM document to the DOM document passed
// Arguments: objDocument - DOM document to append to
//            strPathName - DOM document path name
//            strNodeName - Node in new DOM document to copy
// Returns: true on success, false on error
function appendDOMDocument(objDocument,strPathName,strNodeName,bVerbose)
{
  // Load the DOM document into a new document object
  var newDocument = loadDOMDocument(strPathName,bVerbose);
  if ( newDocument == null )
    return false;

  // Get the node named in the new DOM document
  var objNodeList = newDocument.selectNodes(strNodeName);

  // Append nodes from list
  appendDocumentNodeList(objDocument.documentElement,objNodeList);

  // Delete DOM document and return success
  delete newDocument;
  return true;
}

// Append all child nodes from a node list to an document node
// Nodes are first cloned then appended.
// Arguments: objDocumentNode - Document node to append to
//            objDocumentNodeList - Document node list to append
function appendDocumentNodeList(objDocumentNode,objDocumentNodeList)
{
  if ( !objDocumentNode ) return false;
  if ( !objDocumentNodeList ) return false;

  var objNode = objDocumentNodeList.nextNode;
  while ( objNode != null )
  {
    objDocumentNode.appendChild(objNode.cloneNode(true));
    objNode = objDocumentNodeList.nextNode;
  }

  // Return success
  return true;
}

// Merge site data into sites XML document
function mergeSiteData(objXMLsites,objXMLsitedata)
{
  if ( objXMLsites == null || objXMLsitedata == null )
    return;

  // Copy the site data XML document to the working document
  objXMLwork = newDOMDocument();
  if ( objXMLwork == null ) return;
  objXMLsitedata.save(objXMLwork);

  var objSiteSensorList = objXMLsites.selectNodes("/*/site/sensor");
  var objDataSensorList = objXMLwork.selectNodes("/*/site/sensor");
  if ( objSiteSensorList != null && objSiteSensorList.length > 0 &&
       objDataSensorList != null && objDataSensorList.length > 0 )
  {
    var objSiteSensorNode;
    while ( (objSiteSensorNode = objSiteSensorList.nextNode) != null )
    {
      var strSiteSensorID = objSiteSensorNode.getAttribute("id");
      var strSiteSensorName = objSiteSensorNode.getAttribute("name");

      // Two pass search for matching sensor in site data
      // Pass 1 starts after last sensor
      // Pass 2 restart from beginning
      var objDataSensorNode;
      for ( var nPass = 1; nPass <= 2; nPass++ )
      {
        // Restart search from beginning on pass 2
        if( nPass == 2)
          objDataSensorList.reset();

        // Search for matching sensor id and name
        while ( (objDataSensorNode = objDataSensorList.nextNode) != null )
        {
          var strDataSensorID = objDataSensorNode.getAttribute("id");
          var strDataSensorName = objDataSensorNode.getAttribute("name");
          if ( strSiteSensorID == strDataSensorID &&
               strSiteSensorName == strDataSensorName)
          {
            // Append site data to site sensor node
            var objDataList = objDataSensorNode.selectNodes("data");
            if ( objDataList != null )
              appendDocumentNodeList(objSiteSensorNode,objDataList);

            // Set pass counter to quit two pass loop and break while loop
            nPass  = 2;
            break;
          }
        }
      }
    }
  }
  delete objXMLwork;
}

// Display DOM document loading or parsing errors in an alert dialogue box
function showDOMDocumentError(objDocument) 
{
  var strError = new String;
  strError = ""
  strError = "DOM Document parsing error!\n"
           + "File URL: " + objDocument.parseError.url + "\n"
           + "Line No.: " + objDocument.parseError.line + "\n"
           + "Character: " + objDocument.parseError.linepos + "\n"
           + "File Position: " + objDocument.parseError.filepos + "\n"
           + "Source Text: " + objDocument.parseError.srcText + "\n"
           + "Error Code: " + objDocument.parseError.errorCode + "\n"
           + "Description: " + objDocument.parseError.reason
  alert(strError);
}

// Return the XML file path name
function getXMLPathName(strXMLFileName)
{
  return strRoot + strXMLpathname + "/" + strXMLFileName;
}

// Return the XML data file path name
function getCurrentXMLDataFileName()
{
  return getXMLPathName(strXMLbasename + ".xml");
}

// Return the XML data file name
function getHistoricXMLDataFileName(strYear, strMonth, strDay, strHour, strMinute, strSecond)
{
  var strXMLFileName = strYear + "/" + strMonth + "/" + strDay;
  var strFileTime = strHour + "-" + strMinute + "-" + strSecond;
  var strFileDate = strYear + "-" + strMonth + "-" + strDay;
  var strFileDateTime = strFileDate + "-" + strFileTime;
  strXMLFileName += "/" + strXMLbasename + "-" + strFileDateTime + ".xml";
  return getXMLPathName(strXMLFileName);
}

// Return the XML data file name
function getHistoricXMLSensorDataFileName(strSensorID, nYear, nMonth)
{
  var strFileDate = nYear + "-";
  if ( nMonth < 10 ) strFileDate += "0";
  strFileDate += nMonth;
  var strXMLFileName = nYear + "/";
  strXMLFileName += strSensorID + "-" + strFileDate + ".xml";
  return getXMLPathName(strXMLFileName);
}

// Check for alarms and display
function checkAlarms()
{
  // Return if alarm display disabled
  if ( bDisplayAlarms == false ) return;

  // Load the XML data document
  var objXMLAlarms = loadDOMDocument(getXMLPathName(strXMLalarmsName),false);

  // Check for an alarm
  if ( objXMLAlarms != null )
  {
    var strAlarmQuery = "/alarms/alarm[active='yes']/*";
    var objAlarmNodeList = objXMLAlarms.selectNodes(strAlarmQuery);
    if ( objAlarmNodeList != null && objAlarmNodeList.length > 0 )
    {
      var objAlarmNode = objAlarmNodeList.nextNode;
      var strAlarm = "Flood Alert System alarm!";
      while ( objAlarmNode != null )
      {
		if ( objAlarmNode.nodeName == "name" )
	        strAlarm += "\n " + objAlarmNode.text;
		if ( objAlarmNode.nodeName == "type" )
	        strAlarm += " " + objAlarmNode.text;
		if ( objAlarmNode.nodeName == "cause" )
	        strAlarm += " " + objAlarmNode.text;
		if ( objAlarmNode.nodeName == "value" )
	        strAlarm += " " + objAlarmNode.text;
        objAlarmNode = objAlarmNodeList.nextNode;
      }
      strAlarm += "\nClick OK to acknowledge alarm";
      if ( bIgnoreAlarms == false )
      {
        if ( confirm(strAlarm) == true )
          location.href = strRoot + strAcknowledgeLink;
        else
          bIgnoreAlarms = true;
      }
    }
    delete objXMLAlarms;
  }
}

// Save cookie
function set_cookie(cookie_name, cookie_value, cookie_expire, cookie_path, cookie_domain, cookie_secure) {

    // Begin the cookie parameter string
    var cookie_string = cookie_name + "=" + cookie_value
    
    // Add the expiration date, if it was specified
    if (cookie_expire) {
        var expire_date = new Date()
        var ms_from_now = cookie_expire * 24 * 60 * 60 * 1000
        expire_date.setTime(expire_date.getTime() + ms_from_now)
        var expire_string = expire_date.toGMTString()
        cookie_string += "; expires=" + expire_string
    }
    
    // Add the path, if it was specified
    if (cookie_path) {
        cookie_string += "; path=" + cookie_path
    }

    // Add the domain, if it was specified
    if (cookie_domain) {
        cookie_string += "; domain=" + cookie_domain
    }
    
    // Add the secure boolean, if it's true
    if (cookie_secure) {
        cookie_string += "; true"
    }
    
    // Set the cookie
    document.cookie = cookie_string
}

// Read cookie
function get_cookie(name_to_get) {

    var cookie_pair
    var cookie_name
    var cookie_value
    
    // Split all the cookies into an array
    var cookie_array = document.cookie.split("; ")
    
    // Run through the cookies
    for (var counter = 0; counter < cookie_array.length; counter++) {
        // Split the cookie into a name/value pair
        cookie_pair = cookie_array[counter].split("=")
        cookie_name = cookie_pair[0]
        cookie_value = cookie_pair[1]
        
        // Compare the name with the name we want
        if (cookie_name == name_to_get) {
        
            // If this is the one, return the value
            return unescape(cookie_value)
        }
    }
    
    // If the cookie doesn't exist, return null
    return null
}

function delete_cookie(cookie_name) {
    set_cookie(cookie_name, "", -1000*60*60*24*5)
}

// Extract year, month, day, hour, minute, second from date-time string:
// YYYY-MM-DD-HH-MM-SS
function getDateTimeYear(strDateTimeArg)
{ return strDateTimeArg.slice(0,4); }
function getDateTimeMonth(strDateTimeArg)
{ return strDateTimeArg.slice(5,7); }
function getDateTimeDay(strDateTimeArg)
{ return strDateTimeArg.slice(8,10); }
function getDateTimeHour(strDateTimeArg)
{ return strDateTimeArg.slice(11,13); }
function getDateTimeMinute(strDateTimeArg)
{ return strDateTimeArg.slice(14,16); }
function getDateTimeSecond(strDateTimeArg)
{ return strDateTimeArg.slice(17,19); }

// Extract hour, minute, second from time string:
// HH-MM-SS
function getTimeHour(strTimeArg)
{ return strTimeArg.slice(0,2); }
function getTimeMinute(strTimeArg)
{ return strTimeArg.slice(3,5); }
function getTimeSecond(strTimeArg)
{ return strTimeArg.slice(6,8); }

function makeTimeArg(strYear,strMonth,strDay,strHour,strMinute,strSecond)
{ 
  if ( strSecond == "" ) strSecond = "00";
  if ( strMinute == "" ) strMinute = "00";
  if ( strHour == "" ) strHour = "00";
  if ( strDay == "" ) strDay = "01";
  if ( strMonth == "" ) strMonth = "01";
  if ( strYear == "" ) strYear = "2000";
  return strMonth+"-"+strDay+"-"+strYear+"-"+strHour+"-"+strMinute+"-"+strSecond;
}

function makeDateTimeString(objDate,strDelimiterDate,strDelimiterDateTime,strDelimiterTime)
{
  var strTimeArg = objDate.getFullYear();
  var n,strDelimiter;
  for ( i=0 ; i < 5 ; i++ )
  {
    switch (i)
    {
      case 0: n = objDate.getMonth() + 1; strDelimiter = strDelimiterDate; break;
      case 1: n = objDate.getDate(); break;
      case 2: n = objDate.getHours(); strDelimiter = strDelimiterDateTime;  break;
      case 3: n = objDate.getMinutes(); strDelimiter = strDelimiterTime;  break;
      case 4: n = objDate.getSeconds(); break;
    }
    strTimeArg += strDelimiter;
    if ( n < 10 ) strTimeArg += "0";
    strTimeArg += "" + n;
  }
  return strTimeArg;
}

function makeDateTimeArg(objDate)
{
  return makeDateTimeString(objDate,"-","-","-");
}
function makeDateTimeDisplay(objDate)
{
  return makeDateTimeString(objDate,"-"," ",":");
}
function makeDateTimeXML(objDate)
{
  return makeDateTimeString(objDate,"-","T",":") + getTimeZoneString();
}

var strTZoffset;
function getTimeZoneString()
{
  // Return timezone string if already defined
  if ( strTZoffset != null ) return strTZoffset;

  // Get the timezone offset
  var time_now = new Date();
  var strYear = time_now.getFullYear();
  var nTimezoneOffset = time_now.getTimezoneOffset();
  var nMinutes = nTimezoneOffset % 60;
  var nHours = (nTimezoneOffset - nMinutes) / 60;
  if ( nMinutes < 0 ) nMinutes = -nMinutes;
  if ( nHours < 0 ) nHours = -nHours;
  if ( nTimezoneOffset <= 0 )
    strTZoffset = "+";  
  else
    strTZoffset = "-";
  if ( nHours < 10 ) strTZoffset += "0"
  strTZoffset += "" + nHours;
  strMinuteTZoffset = "" + nMinutes;
  strTZoffset += ":";
  if ( nMinutes < 10 ) strTZoffset += "0";
  strTZoffset += "" + nMinutes;
  return strTZoffset
}

// Replace a matched substring with an XML Query
function replaceXMLString(strLine,strMatch,strReplace)
{
  // If replacement string if null or missing
  // substitute it with tool tip missing string for match string
  if ( strReplace == null || strReplace == "" )
  {
    strQuery = strMatch.slice(1,strMatch.length);
    if ( objToolTipMissing != null &&
       (objMissing = objToolTipMissing.selectSingleNode(strQuery)) != null )
      strReplace = objMissing.text;
  }

  // Replace matched string with replacement string
  strLine = strLine.replace(strMatch,strReplace);

  // Return string after replacement
  return strLine;
}

// Replace special data substrings with XML data values
function replaceXMLStrings(strLine,objXMLdata,strDateTime,strSiteID,strSensorID,strSensorName,strDataInterval,strDataTime,strDataChange)
{
  if ( strLine == null )
    return null;

  // Translate time of site data
  if ( strDateTime != null )
    strLine = strLine.replace("#timeofdata",strDateTime);

  // Translate site page name
  var strMatch = "#sitepagename";
  if ( strLine.search(strMatch) >= 0 )
  {
    var objSitePageNode;
    if ( (objSitePageNode = objXMLdata.selectSingleNode("//sitepages/sitepage/name")) != null )
      strLine = strLine.replace(strMatch,objSitePageNode.text);
  }

  // Search for matching site IDs
  // Use selectNodes instead of selectSingleNode because several site may have the same ID.
  var objSiteNodeList;
  if ( objXMLdata != null && strSiteID != null &&
      (objSiteNodeList = objXMLdata.selectNodes("/sites/site[@id='" + strSiteID + "']")) != null )
  {
    var objSiteNode;
    objSiteNode = objSiteNodeList.nextNode;
    while ( objSiteNode != null )
    {
      // Get site and sensor information from data object
      strLine = strLine.replace("#siteid",objSiteNode.getAttribute("id"));
      strLine = strLine.replace("#sitename",objSiteNode.getAttribute("name"));

      var objSensorNode;
      if ( strSensorID != null && strSensorName != null &&
        (objSensorNode = objSiteNode.selectSingleNode("sensor[@id='" + strSensorID + "' and @name='" + strSensorName + "']")) != null )
      {
        strLine = strLine.replace("#sensorid",objSensorNode.getAttribute("id"));
        strLine = strLine.replace("#sensorname",objSensorNode.getAttribute("name"));
        strLine = strLine.replace("#dataunits",objSensorNode.getAttribute("units"));

        // Get sensor data information from data object
        var objDataNode;
        if ( strDataInterval != null &&
          (objDataNode = objSensorNode.selectSingleNode("data/*[@interval = '" + strDataInterval + "']")) != null )
        {
          strLine = replaceXMLString(strLine,"#datavalue",objDataNode.text);
          strLine = strLine.replace("#datascore",objDataNode.getAttribute("score"));
          strLine = strLine.replace("#datainterval",objDataNode.getAttribute("interval"));
          strLine = strLine.replace("#datatype",objDataNode.nodeName);
        }
      }
      // Check next site with matching ID
      objSiteNode = objSiteNodeList.nextNode;
    }
  }

  // Translate time of sensor data
  if ( strDataTime != null )
    strLine = strLine.replace('#datatime',strDataTime);

  // Translate change in sensor data
  if ( strDataChange != null )
    strLine = strLine.replace('#datachange',strDataChange);

  // Return string after replacement
  return strLine;
}

// Load the tooltip parameters
function load_tooltip(objXML,strQuery)
{
  // Get tooltip parameters for sensor
  var objTooltipNode = objXML.selectSingleNode(strQuery);
  if ( objTooltipNode != null )
  {
    strToolTipClass = objTooltipNode.getAttribute("class");
    nToolTipWidth = eval(objTooltipNode.getAttribute("width"));
    nToolTipXoff = eval(objTooltipNode.getAttribute("xoff"));
    nToolTipYoff = eval(objTooltipNode.getAttribute("yoff"));
    nToolTipFontWidth = eval(objTooltipNode.getAttribute("font-width"));
    nToolTipFontHeight = eval(objTooltipNode.getAttribute("font-height"));
    nToolTipBorder = eval(objTooltipNode.getAttribute("border"));

    // Concatenate tooltip lines and delimit with newline
    strToolTipLines = "";
    var objToolTipList = objTooltipNode.selectNodes("line");
    if ( objToolTipList != null )
    {
      // Concatenate the tooltip lines into one string
      var objNode;
      while ( (objNode = objToolTipList.nextNode) != null )
        strToolTipLines += objNode.text + "\n";
    }

    // Concatenate tooltip status and delimit with space
    strToolTipStatus = "";
    var objToolTipList = objTooltipNode.selectNodes("status");
    if ( objToolTipList != null )
    {
      // Concatenate the tooltip lines into one string
      var objNode;
      while ( (objNode = objToolTipList.nextNode) != null )
        strToolTipStatus += objNode.text + " ";
    }

    // Read tooltip missing string list
    objToolTipMissing = objTooltipNode.selectSingleNode("missing");
  }
  return objTooltipNode;
}

// Display the tooltip
function display_tooltip(current_event, strToolTip, nWidth, nHeight)
{
  // If this is a non-DHTML browser, bail out
  if (!dhtml_ok) return;

  // Get the mouse and scroll coordinates
  var mouse_x = get_mouse_x(current_event);
  var scroll_x = get_client_scroll_left();
  var width_x = get_client_width();
  var mouse_y = get_mouse_y(current_event);
  var scroll_y = get_client_scroll_top();
  var height_y = get_client_height();

  // Compute tooltip location from mouse and scroll coordinates
  var nBorder = nToolTipBorder * 2;
  var px = mouse_x + nToolTipXoff;
  if ( nWidth > 0 && px + nWidth + nBorder >= width_x )
    px = width_x - nWidth - nBorder;
  if ( px < 0 )
    px = 0;
  px += scroll_x;
  var py = mouse_y + nToolTipYoff;
  if ( nHeight > 0 && py + nHeight + nBorder >= height_y )
  {
    py = height_y - nHeight - nBorder;
    if ( py < mouse_y && py + nHeight + nBorder > mouse_y )
      py = mouse_y - nToolTipYoff - nHeight - nBorder;
  }
  if ( py < 0 )
    py = 0;
  py += scroll_y;

  // Make HTML text to replace tooltip
  var html_text = "<div";
  if ( strToolTipClass != "" )
    html_text += " class='" + strToolTipClass + "'";
  html_text += ">" + strToolTip + "</div>"

  // Apply the cross-browser functions
  dhtml_objects["tooltip"].set_html(html_text)
  dhtml_objects["tooltip"].set_left(px)
  dhtml_objects["tooltip"].set_top(py)
  dhtml_objects["tooltip"].set_visibility("visible") 

  return true;
}

// Hide the tooltip after pointer has moved away
function hide_tooltip()
{
  // Clear status bar
  clearStatus();

  // If this is a non-DHTML browser, bail out
  if (!dhtml_ok) { return }
  
  // Set the <div> tag's visibility to "hidden"
  dhtml_objects["tooltip"].set_visibility("hidden")
}

function show_tooltip(current_event, strDateTime, strLine, strSiteID, strSensorID, strSensorName, strDataInterval, strDataTime, strDataChange)
{
  // Save popup lines to status
  strStatus = strLine;

  // Display tooltip help on screen if tooltip list defined
  if ( strLine == "#tooltip\n" )
  {
    strLine = strToolTipLines;
    strStatus = strToolTipStatus;
  }

  // Translate tooltip lines
  strLine = replaceXMLStrings(strLine,objXMLdata,strDateTime,strSiteID,strSensorID,strSensorName,strDataInterval,strDataTime,strDataChange);

  // Split tooltip into lines and get the longest line length
  var strArray = strLine.split("\n");
  var nWidth = 0;
  var nLines = 0;
  var nLineWidth = 0;
  for (var counter = 0; counter < strArray.length; counter++)
  {
    nLineWidth = strArray[counter].length;
    if ( nWidth < nLineWidth )
      nWidth = nLineWidth;
    nLines++;
  }
  if ( nLineWidth == 0 && nLines > 0 )
    nLines--;

  // Set tooltip width and height
  nWidth = nWidth * nToolTipFontWidth;
  nHeight = nLines * nToolTipFontHeight;

  // Make tooltip table
  var strToolTip = "<table border='0' cellspacing='0' cellpadding='0'"
  if ( nWidth > 0 )
    strToolTip += " width='" + nWidth + "'";
  strToolTip += ">"

  // Print lines
  for (var counter = 0; counter < strArray.length; counter++)
  {
    strToolTip += "<tr><td"
    if ( strToolTipClass != "" )
      strToolTip += " class='" + strToolTipClass + "'";
    strToolTip += '>' + strArray[counter] + "</td></tr>";
  }
  strToolTip += "</table>";

  // Display tool tip
  display_tooltip(current_event, strToolTip, nWidth, nHeight);

  // Display help on status bar
  strStatus = replaceXMLStrings(strStatus,objXMLdata,strDateTime,strSiteID,strSensorID,strSensorName,strDataInterval,strDataTime,strDataChange);
  strArray = strStatus.split("\n");
  strStatus = "";
  for (var counter = 0; counter < strArray.length; counter++)
    strStatus += strArray[counter] + " ";
  setStatus(strStatus);
  return true;
}
