// results.js: Javascript code to control the display and appearance of search results records.


// SORTING A TABLE: http://www.brainjar.com/dhtml/tablesort/
// Open source alternative to Google Maps: http://www.openlayers.org/

// Faster alternative to Google Markers:
// http://econym.googlepages.com/elabel.htm
// http://www.gmaptools.com/bpmarkerlight.php


var tooltipText = new Object();
tooltipText.downloadtxt = "Download search results as a tab-delimited text file.  The text file can be opened in Excel and used like a spreadsheet, or the data can be imported into another database.  Column names are included as the first row in the text file.  Beware, however, that Excel may incorrectly convert any values that look like a date into a date, including some collector numbers.";
tooltipText.downloadxml = "Download search results as an XML file matching the Darwin Core v2 1.2 specification. XML files contain descriptive tags that can allow for easier data exchange between web servers or other software.";
tooltipText.downloadkml = "Download <u>georeferenced</u> search results as a KML file for viewing in Google Earth.";
tooltipText.downloadrtf = "Download search results formatted as an RTF document. RTF is an open standard for creating text documents with richer content than plain text, and should be readable by any word processing software such as Microsoft Word or Open Office.";


// Cache object to hold the actual record data as it is requested:
var cache = new Object();

// Summary variables:
cache.summaryDataLoaded = false;
cache.totalFoundCount = 0;
cache.pageLimit = 100;
cache.institutionCodes = new Array();
cache.institutionCounts = new Array();
cache.queryCount = 0;
cache.querytext = new Array();
cache.queryArgs = "";
cache.queryLimit = "Limit=100";
cache.querySort = "Sort1=ScientificName&SortOrder1=ASC";
cache.querysql = "";

// Map data:
cache.mapDataLoaded = false;
cache.georeferencedCount = 0;
cache.clusterCount = 0;
cache.minLat = 0;
cache.maxLat = 0;
cache.minLon = 0;
cache.maxLon = 0;
cache.minZoom = 0;
cache.maxZoom = 20;

// List data arrays:
cache.listDataLoaded = false;
cache.selectedPage = 0;
cache.selectedRecord = -1;
cache.markedListEntry = -1;
cache.index = new Array();
cache.ID = new Array();
cache.instCode = new Array();
cache.catalog = new Array();
cache.family = new Array();
cache.genus = new Array();
cache.spEpithet = new Array();
cache.infraSpRank = new Array();
cache.infraSpEpithet = new Array();
cache.author = new Array();
cache.identQualifier = new Array();
cache.identBy = new Array();
cache.identDate = new Array();
cache.country = new Array();
cache.stateprovince= new Array();
cache.county = new Array();
cache.locality = new Array();
cache.minElev = new Array();
cache.maxElev = new Array();
cache.lat = new Array();
cache.long = new Array();
cache.coordUncertainty = new Array();
cache.year = new Array();
cache.month = new Array();
cache.day = new Array();
cache.collectors = new Array();
cache.collNumber = new Array();
cache.notes = new Array();
cache.typeStatus = new Array();
cache.otherCatalog = new Array();

cache.clearCache = function()
{
  this.loaded = false;
  this.totalFoundCount = 0;
  this.pageLimit = 100;
  this.institutionCodes = new Array();
  this.institutionCounts = new Array();
  this.queryCount = 0;
  this.querytext = new Array();
  this.queryArgs = "";
  this.queryLimit = "Limit=100";
  this.querySort = "Sort1=ScientificName&SortOrder1=ASC";
  this.querysql = "";
  this.selectedPage = 0;
  this.selectedRecord = -1;

  this.mapDataLoaded = false;
  this.georeferencedCount = 0;
  this.clusterCount = 0;
  this.minLat = 0;
  this.maxLat = 0;
  this.minLon = 0;
  this.maxLon = 0;
  this.minZoom = 0;
  this.maxZoom = 20;

  this.listDataLoaded = false;
  this.index = new Array();
  this.ID = new Array();
  this.instCode = new Array();
  this.catalog = new Array();
  this.family = new Array();
  this.genus = new Array();
  this.spEpithet = new Array();
  this.infraSpRank = new Array();
  this.infraSpEpithet = new Array();
  this.author = new Array();
  this.identQualifier = new Array();
  this.identBy = new Array();
  this.identDate = new Array();
  this.country = new Array();
  this.stateprovince = new Array();
  this.county = new Array();
  this.locality = new Array();
  this.minElev = new Array();
  this.maxElev = new Array();
  this.lat = new Array();
  this.long = new Array();
  this.coordUncertainty = new Array();
  this.year = new Array();
  this.month = new Array();
  this.day = new Array();
  this.collectors = new Array();
  this.collNumber = new Array();
  this.notes = new Array();
  this.typeStatus = new Array();
  this.otherCatalog = new Array();
};

cache.cachePage = function(recordCount)
{
  // Copy the currently defined page of results to a permanent array that will persist for the life of the current search:
  for(i = 0; i < recordCount; i++)
  {
    var r = cache.selectedPage*cache.pageLimit + i;
    this.index[r] = listData.index[i];
    this.ID[r] = listData.ID[i];
    this.instCode[r] = listData.instCode[i];
    this.catalog[r] = listData.catalog[i];
    this.family[r] = listData.family[i];
    this.genus[r] = listData.genus[i];
    this.spEpithet[r] = listData.spEpithet[i];
    this.infraSpRank[r] = listData.infraSpRank[i];
    this.infraSpEpithet[r] = listData.infraSpEpithet[i];
    this.author[r] = listData.author[i];
    this.identQualifier[r] = listData.identQualifier[i];
    this.identBy[r] = listData.identBy[i];
    this.identDate[r] = listData.identDate[i];
    this.country[r] = listData.country[i];
    this.stateprovince[r] = listData.stateprovince[i];
    this.county[r] = listData.county[i];
    this.locality[r] = listData.locality[i];
    this.minElev[r] = listData.minElev[i];
    this.maxElev[r] = listData.maxElev[i];
    this.lat[r] = listData.lat[i];
    this.long[r] = listData.long[i];
    this.coordUncertainty[r] = listData.coordUncertainty[i];
    this.year[r] = listData.year[i];
    this.month[r] = listData.month[i];
    this.day[r] = listData.day[i];
    this.collectors[r] = listData.collectors[i];
    this.collNumber[r] = listData.collNumber[i];
    this.notes[r] = listData.notes[i];
    this.typeStatus[r] = listData.typeStatus[i];
    this.otherCatalog[r] = listData.otherCatalog[i];
  }
};


// Object to display the search results and respond to user interaction with the actual data:
var results = new Object();
results.detailViewStyle = "fields";  // fields, label
results.fadeOverlayCount = 0;

results.clearDisplay = function()
{
  var panes = ["summarytext", "dataview1", "dataview2", "dataview3"];
  for(var i = 0; i < 4; i++)
  {
    var htmltag = document.getElementById("summarytext");
    //window.alert(i + ", " + panes[i] + ": " + document.getElementById("summarytext"));
    if(htmltag)
    {
      //if("innerHTML" in htmltag) 
      htmltag.innerHTML = "";
      //if(!is_null(htmltag.innerHTML)) htmltag.innerHTML = "";
      //else if("firstChild" in htmltag && "data" in htmltag.firstChild) htmltag.firstChild.data = "";
    }
  }
  this.updatePageSelector();
  var recordSelector = document.getElementById("recordselector");
  if(recordSelector) recordSelector.value = 1;
};

results.removeFadeOverlay = function()
{
  this.fadeOverlayCount--;
  if(this.fadeOverlayCount <= 0)
  {
    resultspane.showResultsPanes();
    var grayoutDiv = document.getElementById("grayout");
    grayoutDiv.style.visibility = "hidden";
  }
};

results.updateSummary = function()
{
  if(cache.totalFoundCount < 1)
  {
    var htmltag = document.getElementById("summarytext");
    if(htmltag)
    {
      //if("innerHTML" in htmltag) 
      htmltag.innerHTML = "<b>Search results not available.</b>";
      //if(!is_null(htmltag.innerHTML)) htmltag.innerHTML = "<b>Search results not available.</b>";
      //else if("firstChild" in htmltag && "data" in htmltag.firstChild) htmltag.firstChild.data = "<b>Search results not available.</b>";
    }
    return;
  }

  var summaryText = "<div style=\"padding-bottom: 2px;\"><b>Search results: " + cache.totalFoundCount + " matching records.</b></div>";
  for (var i = 0; i < cache.queryCount; i++)
  {
    if(cache.queryCount == 1) summaryText += "<b>Search query:</b> ";
    else summaryText += "<b>Search query " + (i+1) + ":</b> ";
    summaryText += cache.querytext[i] + ". ";
  }
  summaryText += "<br/>Data provided by ";
  for(var i = 0; i < cache.institutionCodes.length; i++)
  {
    summaryText += "<a href=\"providermetadata.php?code=" + cache.institutionCodes[i] + "\" target=\"_blank\">" + cache.institutionCodes[i] + " </a> (" + cache.institutionCounts[i];
    if(cache.institutionCounts[i] == 1) summaryText += " record)";
    else summaryText += " records)";
    if(i < cache.institutionCodes.length-1) summaryText += ", ";
  }
  var htmltag = document.getElementById("summarytext");
  if(htmltag)
  {
    //if("innerHTML" in htmltag) 
    htmltag.innerHTML = summaryText + ".";
    //if(!is_null(htmltag.innerHTML)) htmltag.innerHTML = summaryText + ".";
    //else if("firstChild" in htmltag && "data" in htmltag.firstChild) htmltag.firstChild.data = summaryText + ".";
  }
};

results.getPage = function(pageNum, action)
{
  // Triggers for this function:
  // Select a marker on the map that corresponds to a record not already loaded.
  // Use the drop-down list or arrows in the list view to select another page.
  // Use the arrows in the detail view to move to a record that is not already loaded.

  // action = page: load a page of records and display the list view.
  // action = detail: load a page of records and display the detail view.
  // action = none: load a page of records then do nothing.

  if(cache.ID[pageNum * cache.pageLimit] > 0)
  {
    // Already have the page loaded, so just trigger the action:
    this.showPage(action);
    return;
  }
  if(cache.totalFoundCount < 1) return;  // No search results.

  if(pageNum <= 0) pageNum = 0;  // zero-based page number; catch undefined pageNum with the <= operator
  if(pageNum >= Math.ceil(cache.totalFoundCount / cache.pageLimit)) pageNum = Math.ceil(cache.totalFoundCount / cache.pageLimit) - 1;
  if(action == "") action = "none";

  // Position grayout screen over data pane if data pane is visible
  if(!resultspane.dataCollapsed)
  {
    var grayoutDiv = document.getElementById("grayout");
    grayoutDiv.style.top = resultspane.dataTop + "px";
    grayoutDiv.style.left = resultspane.dataLeft + "px";
    grayoutDiv.style.width = resultspane.dataWidth + "px";
    grayoutDiv.style.height = resultspane.dataHeight + "px";
    grayoutDiv.style.visibility = "visible";
  }

  var head = document.getElementsByTagName("head")[0];
  var oldScript = document.getElementById("listdata");
  if(oldScript) head.removeChild(oldScript);

  var timestamp = new Date();
  var newScript = document.createElement('script');
  newScript.id = "listdata";
  newScript.type = "text/javascript";
  newScript.src = "listdata.php?QueryCount=" + cache.queryCount + "&" + cache.queryArgs + "&Start=" + (pageNum * cache.pageLimit) + "&Limit=" + cache.pageLimit + "&" + cache.querySort + "&action=" + action + "&time=" + timestamp.getTime();
  head.appendChild(newScript);

  // Once loaded, listdata.php will call results.showPage(action) to finish the page loading and display process
};

results.showPage = function(action)
{
  if(action == "detail") this.updateDetailView(cache.selectedRecord, false);
  if(action == "list") this.updateListView(cache.selectedPage);

  document.getElementById("grayout").style.visibility = "hidden";
};

results.updatePageSelector = function()
{
  var i = 0;
  var pageSelector = document.getElementById("pageselector");
  if(pageSelector)
  {
    for(i = 0; i < pageSelector.options.length; i++) pageSelector.options[i] = null;
  
    if(cache.totalFoundCount > 0)
    {
      var pageCount = Math.ceil(cache.totalFoundCount / cache.pageLimit);
      pageSelector.options.length = pageCount;
      var optionText = "";
      for(i = 0; i < pageCount; i++)
      {
        if(i == pageCount-1) optionText = (i*cache.pageLimit+1) + "-" + cache.totalFoundCount;
        else optionText = (i*cache.pageLimit+1) + "-" + ((i+1)*cache.pageLimit);
        pageSelector[i] = new Option(optionText, i);
      }
      document.getElementById("pageselector").selectedIndex = 0;
    }
  }
};

results.resetPageSelector = function()
{
  document.getElementById("pageselector").selectedIndex = 0;
};

results.updateListView = function(pageNum)
{
  if(pageNum <= 0) pageNum = 0;
  if(pageNum > Math.ceil(cache.totalFoundCount / cache.pageLimit)-1) pageNum = Math.ceil(cache.totalFoundCount / cache.pageLimit)-1;

  // This page is already displayed, so do nothing:
  //if(pageNum = cache.selectedPage) return;

  cache.selectedPage = pageNum;
  document.getElementById("pageselector").selectedIndex = cache.selectedPage;

  // Need to retrieve another page of records before updating the list view:
  if(typeof cache.ID[cache.selectedPage * cache.pageLimit] === "undefined")
  {
    this.getPage(cache.selectedPage, "list");
    return;
  }

  var start = cache.selectedPage*cache.pageLimit;
  var end = start + cache.pageLimit;
  if(end > cache.totalFoundCount) end = cache.totalFoundCount;
  var listHTML = "";
  for(var i = start; i < end; i++)
  {
    listHTML += "<div id=\"record" + i + "\" class=\"";
    if(i % 2 == 0) listHTML += "collapsed1\">";
    else listHTML += "collapsed2\">";
    listHTML += this.getListEntry(i, "collapsed");
    listHTML += "</div>";
  }

  var htmltag = document.getElementById("dataview1");
  if(htmltag)
  {
    //if("innerHTML" in htmltag) 
    htmltag.innerHTML = listHTML;
    //if(!is_null(htmltag.innerHTML)) htmltag.innerHTML = listHTML;
    //else if("firstChild" in htmltag && "data" in htmltag.firstChild) htmltag.firstChild.data = listHTML;
  }
};

results.getListEntry = function(i, viewState)
{
  // i = record to display (starting with record 0)
  // viewState = "collapsed", "expanded", or "detail"

  var entryHTML = "";

  if(cache.family[i]) entryHTML += "<div style=\"float: right; margin: 0px 3px 0px 6px;\"><b>" + cache.family[i] + "</b></div>";
  if(viewState == "detail") entryHTML += "<br/>";

  if(viewState == "expanded") entryHTML += "<img src=\"../images/minus-button-on.gif\" width=\"11px\" height=\"11px\" style=\"margin-right: 5px;\" onmouseover=\"this.style.cursor = 'pointer';\" onclick=\"results.toggleListEntry(" + i + ",'collapsed');\">";
  else if(viewState == "collapsed") entryHTML += "<img src=\"../images/plus-button-on.gif\" width=\"11px\" height=\"11px\" style=\"margin-right: 5px;\" onmouseover=\"this.style.cursor = 'pointer';\" onclick=\"results.toggleListEntry(" + i + ",'expanded');\">";

  entryHTML += "<span id=\"record" + i + "mapicon\"></span>";

  if(i == cache.SelectedRecord && cache.lat[i] != null) entryHTML += "<img src=\"../images/google-icons/green-square-14.png\" width=\"14px\" height=\"14px\" style=\"margin-right: 5px;\">";

  //if(viewState == "detail") entryHTML += "<span style=\"margin-left: 16px;\">&nbsp;</span>";
  entryHTML += "<b>" + (cache.index[i]+1) + ".</b> ";

  entryHTML += "<b><i><u>" + cache.genus[i] + " " + cache.spEpithet[i] + "</u></i></b> ";
  if(cache.infraSpEpithet[i]) entryHTML += "<b>" + cache.infraSpRank[i]  + " <i><u>" + cache.infraSpEpithet[i] + "</u></i></b> ";
  if(cache.author[i]) entryHTML += cache.author[i];
  if(cache.identQualifier[i]) entryHTML += " " + cache.identQualifier[i];
  entryHTML += "<br/>";
  if(viewState != "collapsed")
  {
    if(cache.identBy[i] || cache.identDate[i]) entryHTML += "<span style=\"margin-left: 18px; font-size: 0.88em;\">Identified";
    if(cache.identBy[i]) entryHTML += " by " + cache.identBy[i];
    if(cache.identDate[i]) entryHTML += " on " + cache.identDate[i];
    if(cache.identBy[i] || cache.identDate[i]) entryHTML += "</span><br/><br/>";
    else entryHTML += "<br/>";
  }
  if(cache.country[i]) entryHTML += cache.country[i];
  if(cache.stateprovince[i]) entryHTML += ", " + cache.stateprovince[i];
  if(cache.stateprovince[i] && cache.county[i]) entryHTML += ", ";
  if(cache.county[i]) entryHTML += cache.county[i] + " County:";
  else entryHTML += ":";
  if(viewState == "collapsed") entryHTML += " ";
  else entryHTML += "<br/>";
  if(cache.locality[i]) entryHTML += cache.locality[i] + "<br/>";
  else entryHTML += "[no locality defined]<br/>";
  if(viewState != "collapsed")
  {
    if(typeof cache.minElev[i] == "number")
    {
      entryHTML += "Elev. " + cache.minElev[i];
      if(typeof cache.maxElev[i] == "number" && cache.maxElev[i] != cache.minElev[i]) entryHTML += "-" + cache.maxElev[i] + " ft.<br/>";
      else entryHTML += " ft.<br/>";
    }
    if(cache.lat[i] > 0) entryHTML += cache.lat[i] + "° N, ";
    else if(cache.lat[i] < 0) entryHTML += Math.abs(cache.lat[i]) + "° S, ";
    if(cache.long[i] > 0) entryHTML += cache.long[i] + "° E<br/>";
    else if(cache.long[i] < 0) entryHTML += Math.abs(cache.long[i]) + "° W<br/>";
    if(viewState != "collapsed") entryHTML += "<br/>";
    if(cache.notes[i]) entryHTML += cache.notes[i] + "<br/><br/>";
  }

  if(viewState == "collapsed" || viewState == "expanded")
  {
    entryHTML += "<div style=\"float: right;\">";
    if(cache.mapDataLoaded && cache.lat[i] != null) entryHTML += "<img src=\"../images/map-icon-14.gif\" width=\"14px\" height=\"14px\" style=\"margin-left: 5px;\" onmouseover=\"this.style.cursor = 'pointer';\" onclick=\"results.markListEntry(" + i + "); var c = googleMap.findSpecimenCluster(" + i + "); if(c >= 0) googleMap.selectMarker(c, true); results.updateDetailView(" + i + ", false);\" alt=\"Show position on map\">";
    entryHTML += "<img src=\"../images/detail-icon-14.gif\" width=\"11px\" height=\"14px\" style=\"margin-left: 5px;\" onmouseover=\"this.style.cursor = 'pointer';\" onclick=\"results.markListEntry(" + i + "); results.updateDetailView(" + i + ", true); resultspane.toggleData(false); resultspane.switchDataTab(3);\" alt=\"Show record details\">";
    entryHTML += "</div>";
  }

  if(cache.collectors[i]) entryHTML += "<b>" + cache.collectors[i] + " ";
  if(cache.collNumber[i]) entryHTML += cache.collNumber[i] + ",";
  entryHTML += "</b><span style=\"margin-left: 12px;\">";
  if(cache.month[i] > 0) entryHTML += cache.month[i] + "/";
  if(cache.day[i] > 0) entryHTML += cache.day[i] + "/";
  if(cache.year[i] > 0) entryHTML += cache.year[i] + ".";
  else entryHTML += "(no date).";
  entryHTML += "</span>";

  if(viewState == "detail") entryHTML += "<br/><div style=\"text-align: right;\"><b>" + cache.instCode[i] + "-" + cache.catalog[i] + "</b></div>";
  else entryHTML += "<span style=\"margin-left: 12px;\"><b>" + cache.instCode[i] + "-" + cache.catalog[i] + "</b></span>";
  if(viewState != "collapsed" && cache.otherCatalog[i]) entryHTML += "<div style=\"text-align: right;\"><br/>(other catalog #'s: " + cache.otherCatalog[i] + ")</div>";

  return entryHTML;
};

results.toggleListEntry = function(i, viewState)
{
  var listEntry = document.getElementById("record" + i);
  if(viewState == "expanded") listEntry.className = "expanded";
  else if(i % 2 == 0) listEntry.className = "collapsed1";
  else listEntry.className = "collapsed2";
  if(listEntry)
  {
    //if("innerHTML" in listEntry) 
    listEntry.innerHTML = this.getListEntry(i, viewState);
    //if(!is_null(htmltag.listEntry)) listEntry.innerHTML = this.getListEntry(i, viewState);
    //else if("firstChild" in listEntry && "data" in listEntry.firstChild) listEntry.firstChild.data = this.getListEntry(i, viewState);
  }
};

results.markListEntry = function(i)
{
  // actions that trigger this function:
  // clicking the "map" icon for a record in list view.
  // changing the record in detail view and highlight that record on the map.

  // Unmark the previously marked record in list view, but only if that record is still present in the list view:
  if(cache.selectedRecord >= 0 && cache.selectedRecord < cache.totalFoundCount && Math.floor(cache.selectedRecord/cache.pageLimit) == cache.selectedPage)
  {
    var listentrymapicon = document.getElementById("record" + cache.selectedRecord + "mapicon");
    if(listentrymapicon)
    {
      //if("innerHTML" in listentrymapicon) 
      listentrymapicon.innerHTML = "";
      //if(!is_null(listentrymapicon.innerHTML)) listentrymapicon.innerHTML = "";
      //else if("firstChild" in listentrymapicon && "data" in listentrymapicon.firstChild) listentrymapicon.firstChild.data = "";
    }
  }

  // Mark the record in list view identified by i, but only if that record is present in the list view:
  if(i >= 0 && i < cache.totalFoundCount && Math.floor(i/cache.pageLimit) == cache.selectedPage)
  {
    var mapIcon = "<img src=\"../images/google-icons/green-square-14.png\" width=\"14px\" height=\"14px\" style=\"margin-right: 5px;\">";
    var listentrymapicon = document.getElementById("record" + i + "mapicon");
    if(listentrymapicon)
    {
      //if("innerHTML" in listentrymapicon) 
      listentrymapicon.innerHTML = mapIcon;
      //if(!is_null(listentrymapicon.innerHTML)) listentrymapicon.innerHTML = mapIcon;
      //else if("firstChild" in listentrymapicon && "data" in listentrymapicon.firstChild) listentrymapicon.firstChild.data = mapIcon;
    }
  }
};

results.updateGridView = function(pageNum)
{
  // TODO
};

results.updateDetailView = function(i, showOnMap)
{
  if(i >= cache.totalFoundCount) return;
  if(i < 0) i = 0;
  cache.selectedRecord = i;

  // Need to retrieve another page of records to display this record:
  // Once page is retrieved this function will be called again.
  if(typeof cache.ID[i] === "undefined")
  {
    cache.selectedRecord = i;
    this.getPage(Math.floor(i/cache.pageLimit), "detail");
    return;
  }

  if(showOnMap && cache.lat[i] != null)
  {
    var c = googleMap.findSpecimenCluster(i);
    this.markListEntry(i);  // Mark the corresponding list entry, if present in list view
    if(c >= 0) googleMap.selectMarker(c, true);  // Mark the record on the map
  }

  document.getElementById("recordselector").value = i + 1;

  var entryHTML = "<div class=\"detailrow2\" style=\"height: 22px; padding: 2px;\">";
  if(cache.selectedRecord >= 0 && cache.lat[i] != null)
  {
    entryHTML += "<div style=\"width: 14px; height: 14px; float: left;\"><img src=\"../images/google-icons/green-square-14.png\" width=\"14px\" height=\"14px\" border=\"0\" style=\"padding-top: 6px; padding-left: 5px;\" onmouseover=\"this.style.cursor = 'pointer';\" onclick=\"results.markListEntry(" + i + "); var c = googleMap.findSpecimenCluster(" + i + "); if(c >= 0) googleMap.selectMarker(c, true); googleMap.showMarkers();\" alt=\"Show position on map\"></div> ";
    entryHTML += "<div style=\"margin-left: 24px; margin-top: 5px;\"><b>Record " + (cache.index[i]+1) + " of " + cache.totalFoundCount + ":</b></div>";
  }
  else
  {
    entryHTML += "<div style=\"margin-left: 6px; margin-top: 5px;\"><b>Record " + (cache.index[i]+1) + " of " + cache.totalFoundCount + ":</b> <span style=\"color: #AA0000;\">(not displayed on map)</span></div>";
  }

  entryHTML += "</div>";

  if(this.detailViewStyle == "fields")
  {
    var divRow1 = "<div class=\"detailrow1\"><div class=\"detaillabel\">";
    var divRow2 = "<div class=\"detailrow2\"><div class=\"detaillabel\">";
    var divText = "</div><div class=\"detailtext\">";
    var divEnd = "</div></div>";

    entryHTML += divRow1 + "Provider" + divText;
    if(cache.instCode[i] != "") entryHTML += cache.instCode[i] + divEnd;
    else entryHTML += "&nbsp;" + divEnd;

    entryHTML += divRow1 + "Catalog #" + divText;
    if(cache.catalog[i] != "") entryHTML += cache.catalog[i] + divEnd;
    else entryHTML += "&nbsp;" + divEnd;

    entryHTML += divRow1 + "Other Cat. #'s" + divText;
    if(cache.otherCatalog[i] != "") entryHTML += cache.otherCatalog[i] + divEnd;
    else entryHTML += "&nbsp;" + divEnd;

    entryHTML += divRow2 + "Family" + divText;
    if(cache.family[i] != "") entryHTML += cache.family[i] + divEnd;
    else entryHTML += "&nbsp;" + divEnd;

    entryHTML += divRow2 + "Scientific Name" + divText;
    if(cache.genus[i] != "")
    {
      entryHTML += "<b><i><u>" + cache.genus[i] + " " + cache.spEpithet[i] + "</u></i></b> ";
      if(cache.infraSpEpithet[i]) entryHTML += "<b>" + cache.infraSpRank[i]  + " <i><u>" + cache.infraSpEpithet[i] + "</u></i></b> ";
      if(cache.author[i]) entryHTML += cache.author[i];
      if(cache.identQualifier[i]) entryHTML += " " + cache.identQualifier[i];
    }
    else entryHTML += "&nbsp;";
    entryHTML += divEnd;

    entryHTML += divRow2 + "Identified By" + divText;
    if(cache.identBy[i] != "") entryHTML += cache.identBy[i] + divEnd;
    else entryHTML += "&nbsp;" + divEnd;

    entryHTML += divRow2 + "Identified On" + divText;
    if(cache.identDate[i] != "") entryHTML += cache.identDate[i] + divEnd;
    else entryHTML += "&nbsp;" + divEnd;

    entryHTML += divRow1 + "Collectors" + divText;
    if(cache.collectors[i] != "") entryHTML += cache.collectors[i] + divEnd;
    else entryHTML += "&nbsp;" + divEnd;

    entryHTML += divRow1 + "Collector #" + divText;
    if(cache.collNumber[i] != "") entryHTML += cache.collNumber[i] + divEnd;
    else entryHTML += "&nbsp;" + divEnd;

    entryHTML += divRow1 + "Collection Date" + divText;
    if(cache.month[i] > 0) entryHTML += cache.month[i] + "/";
    if(cache.day[i] > 0) entryHTML += cache.day[i] + "/";
    if(cache.year[i] > 0) entryHTML += cache.year[i];
    if(cache.day[i] < 1 && cache.month[i] < 1 && cache.year[i] < 1) entryHTML += "(no date)";
    entryHTML += "&nbsp;" + divEnd;

    entryHTML += divRow2 + "Country" + divText;
    if(cache.country[i] != "") entryHTML += cache.country[i] + divEnd;
    else entryHTML += "&nbsp;" + divEnd;

    entryHTML += divRow2 + "State" + divText;
    if(cache.stateprovince[i] != "") entryHTML += cache.stateprovince[i] + divEnd;
    else entryHTML += "&nbsp;" + divEnd;

    entryHTML += divRow2 + "County" + divText;
    if(cache.county[i] != "") entryHTML += cache.county[i] + divEnd;
    else entryHTML += "&nbsp;" + divEnd;

    entryHTML += divRow2 + "Locality" + divText;
    if(cache.locality[i] != "") entryHTML += cache.locality[i] + divEnd;
    else entryHTML += "&nbsp;" + divEnd;

    entryHTML += divRow2 + "Elevation" + divText;
    if(typeof cache.minElev[i] == "number")
    {
      entryHTML += cache.minElev[i];
      if(typeof cache.maxElev[i] == "number" && cache.maxElev[i] != cache.minElev[i]) entryHTML += "-" + cache.maxElev[i] + " ft.";
      else entryHTML += " ft.";
    }
    entryHTML += "&nbsp;" + divEnd;

    entryHTML += divRow2 + "Lat/Lon" + divText;
    if(cache.lat[i] > 0) entryHTML += cache.lat[i] + "° N, ";
    else if(cache.lat[i] < 0) entryHTML += Math.abs(cache.lat[i]) + "° S, ";
    if(cache.long[i] > 0) entryHTML += cache.long[i] + "° E";
    else if(cache.long[i] < 0) entryHTML += Math.abs(cache.long[i]) + "° W";
    entryHTML += "&nbsp;" + divEnd;

    entryHTML += divRow2 + "Lat/Lon Error" + divText;
    if(cache.coordUncertainty[i] != "") entryHTML += cache.coordUncertainty[i] + divEnd;
    else entryHTML += "&nbsp;" + divEnd;

    entryHTML += divRow1 + "Notes" + divText;
    if(cache.notes[i] != "") entryHTML += cache.notes[i] + divEnd;
    else entryHTML += "&nbsp;" + divEnd;

    entryHTML += divRow1 + "Type Status" + divText;
    if(cache.typeStatus[i] != "") entryHTML += cache.typeStatus[i] + divEnd;
    else entryHTML += "&nbsp;" + divEnd;
  }
  else if(this.detailViewStyle == "label")
  {
    entryHTML += "<div class=\"text\" style=\"padding: 5px; background-color: #F0F2E9; border-bottom: 1px solid #AAAAAA;\">" + this.getListEntry(i, "detail") + "</div>";
  }

  var dataview3 = document.getElementById("dataview3");
  if(dataview3)
  {
    //if("innerHTML" in dataview3) 
    dataview3.innerHTML=entryHTML;
    //if(!is_null(dataview3.innerHTML)) dataview3.innerHTML=entryHTML;
    //else if("firstChild" in dataview3 && "data" in dataview3.firstChild) dataview3.firstChild.data=entryHTML;
  }
};


function selectedIndex(marker,b)
{
  return 1000;
}

var googleMap = new Object();
googleMap.loaded = false;
googleMap.map = null;  // A reference to the actual Google Map object
googleMap.currentZoom = 3;
googleMap.markers = new Array();
googleMap.markerCount = 0;
googleMap.selectedMarker = null;
googleMap.selectedMarkerIndex = -1;


// Icon for 1 specimen:
googleMap.baseIcon1 = new GIcon();
googleMap.baseIcon1.iconSize = new GSize(11, 11);
googleMap.baseIcon1.iconAnchor = new GPoint(5, 5);
googleMap.baseIcon1.infoWindowAnchor = new GPoint(5, 2);

// Icon for 2-3 specimens:
googleMap.baseIcon2 = new GIcon();
googleMap.baseIcon2.iconSize = new GSize(12, 12);
googleMap.baseIcon2.iconAnchor = new GPoint(6, 6);
googleMap.baseIcon2.infoWindowAnchor = new GPoint(6, 2);

// Icon for >3 specimens:
googleMap.baseIcon3 = new GIcon();
//googleMap.baseIcon3.shadow = "../images/google-icons/red-square-14-shadow.png";
googleMap.baseIcon3.iconSize = new GSize(14, 14);
//googleMap.baseIcon3.shadowSize = new GSize(22, 14);
googleMap.baseIcon3.iconAnchor = new GPoint(7, 7);
googleMap.baseIcon3.infoWindowAnchor = new GPoint(7, 2);
//googleMap.baseIcon3.infoShadowAnchor = new GPoint(12,8);

googleMap.loadMap = function()
{
  if (GBrowserIsCompatible())
  {
    this.map = new GMap2(document.getElementById("mapview"));
    GEvent.addListener(this.map, "dragend", function() {
      googleMap.showMarkers();
    });
    GEvent.addListener(this.map, "zoomend", function() {
      googleMap.showMarkers();
    });
    this.map.addMapType(G_PHYSICAL_MAP);
    var mapControl = new GHierarchicalMapTypeControl();
    mapControl.clearRelationships();
    mapControl.addRelationship(G_SATELLITE_MAP, G_HYBRID_MAP, "Labels", false);
    this.map.addControl(mapControl);
    this.map.addControl(new GLargeMapControl());
    this.map.setCenter(new GLatLng(55.0000, -140.0000), this.currentZoom);
    this.map.setMapType(G_PHYSICAL_MAP);
    this.loaded = true;
  }
};

googleMap.clearMap = function()
{
  if(!this.loaded) return;

  var htmltag = document.getElementById("mapheadertext");
  if(htmltag)
  {
    //if("innerHTML" in htmltag) 
    htmltag.innerHTML = "Specimen map";
    //if(!is_null(htmltag.innerHTML)) htmltag.innerHTML = "Specimen map";
    //else if("firstChild" in htmltag && "data" in htmltag.firstChild) htmltag.firstChild.data = "Specimen map";
  }
  
  this.clearMarkers();
};

googleMap.clearMarkers = function()
{
  if(!this.loaded) return;

  this.markers = new Array();
  this.markerCount = 0;
  this.selectedMarker = null;
  this.selectedMarkerIndex = -1;
  this.map.clearOverlays();
};

googleMap.updateMapHeader = function()
{
  var count = cache.georeferencedCount;
  if (cache.georeferencedCount < 1) count = 0;
  var summaryHTML = "<b>Specimen map: " + count + " specimens displayed.</b>";
  var htmltag = document.getElementById("mapheadertext");
  if(htmltag)
  {
    //if("innerHTML" in htmltag) 
    htmltag.innerHTML = summaryHTML;
    //if(!is_null(htmltag.innerHTML)) htmltag.innerHTML = summaryHTML;
    //else if("firstChild" in htmltag && "data" in htmltag.firstChild) htmltag.firstChild.data=summaryHTML;
  }
};

googleMap.showMarkers = function()
{
  if(!cache.mapDataLoaded || !this.loaded) return false;
  if(cache.clusterCount == 0) return false;

  //Figure out which zoom list to use:
  var newZoom = this.map.getZoom();
  var newList = newZoom;
  if(newList < cache.minZoom) newList = cache.minZoom;
  if(newList > cache.maxZoom) newList = cache.maxZoom;
  newList = newList - cache.minZoom;

  // Find the bounds of the current map:
  var bounds = this.map.getBounds();
  var southWest = bounds.getSouthWest();
  var northEast = bounds.getNorthEast();
  var scaleMult = Math.pow(2,newZoom);
  var south = southWest.lat() - (0.703125/scaleMult)*256;  // Pad by one tile on all sides (256 pixels)
  var west = southWest.lng() - (1.40625/scaleMult)*256;
  var north = northEast.lat() + (0.703125/scaleMult)*256;
  var east = northEast.lng() + (1.40625/scaleMult)*256;

  // If the zoom level changed, remove all the currently displayed markers (to be replaced by markers for new zoom level):
  if(newZoom != this.currentZoom)
  {
    this.markers = new Array();
    this.markerCount = 0;
    this.selectedMarker = null;
    this.selectedMarkerIndex = -1;
    this.map.clearOverlays();
  }

  // Add any new markers that are now visible at the current zoom level within the bounds calculated above:
  // And remove any currently displayed markers at the current zoom level that are no longer visible:
  var i = 0;
  for (c = 0; c < cache.zoomListCount[newList]; c++)
  {
    i = cache.zoomLists[newList][c]-1;
    if(!this.markers[i] && cache.clusters[i][0] > south && cache.clusters[i][0] < north)
    {
      if(west > east && (cache.clusters[i][1] > west && cache.clusters[i][1] <= 180) || (cache.clusters[i][1] >= -180 && cache.clusters[i][1] < east)) this.map.addOverlay(this.createMarker(i));
      else if(cache.clusters[i][1] > west && cache.clusters[i][1] < east) this.map.addOverlay(this.createMarker(i));
    }
    else if(this.markers[i] && (cache.clusters[i][0] < south || cache.clusters[i][0] > north) || (cache.clusters[i][1] < west && cache.clusters[i][1] > east))
    {
      this.map.removeOverlay(this.markers[i]);
      this.markers[i] = null;
      this.markerCount--;
    }
  }

  // Make sure the selected marker is shown even if the corresponding cluster was merged or split:
  // But only if the zoom level changed
  if(cache.selectedRecord >= 0 && newZoom != this.currentZoom)
  {
    c = this.findSpecimenCluster(cache.selectedRecord);
    this.selectMarker(c, false);  
  }
  this.currentZoom = newZoom;
};

googleMap.createMarker = function(c)
{
  if(!cache.mapDataLoaded || !this.loaded) return false;
  if(c < 0) return;
  if(c >= cache.clusterCount) return;
  if(cache.clusters[c][0] == null) return;

  var latlng = new GLatLng(cache.clusters[c][0], cache.clusters[c][1]);

  var markerSize = "12";
  if(cache.clusters[c][2] <= 1) { markerSize = "11"; var newIcon = new GIcon(this.baseIcon1); }
  else if(cache.clusters[c][2] > 3) { markerSize = "14"; var newIcon = new GIcon(this.baseIcon3); }
  else { markerSize = "12";var newIcon = new GIcon(this.baseIcon2); }

  newIcon.image = "../images/google-icons/red-square-" + markerSize + ".png";
  markerOptions = { icon:newIcon };
  var marker = new GMarker(latlng, markerOptions);

  GEvent.addListener(marker, "click", function() {
    var infoWindowHTML = "<b>" + cache.clusters[c][3].length + " specimens:</b><br>";
    if(cache.clusters[c][3].length > 7) infoWindowHTML = infoWindowHTML + "<div style=\"height: 200px; overflow-y: scroll; padding-right: 5px;\">";
    for(var i = 0; i < cache.clusters[c][4].length; i++) infoWindowHTML = infoWindowHTML + "<a href=\"#\" onclick=\"googleMap.showMarkerInfo(" + c + ", " + cache.clusters[c][3][i] + "); return false;\">" + cache.clusters[c][4][i] + "</a><br>";
    if(cache.clusters[c][3].length > 7) infoWindowHTML = infoWindowHTML + "</div>";
    if(cache.clusters[c][3].length > 1) marker.openInfoWindowHtml(infoWindowHTML);
    else googleMap.showMarkerInfo(c, cache.clusters[c][3][0]);
  });
  GEvent.addListener(marker, "mouseover", function() {
    marker.setImage("../images/google-icons/green-square-" + markerSize + ".png");
  });
  GEvent.addListener(marker, "mouseout", function() {
    if(this.selectedMarkerIndex != c) marker.setImage("../images/google-icons/red-square-" + markerSize + ".png");
  });

  this.markers[c] = marker;
  this.markerCount++;
  return marker;
};

googleMap.selectMarker = function(c, panMap)
{
  if(!cache.mapDataLoaded || !this.loaded) return false;
  if (c < 0 || c >= cache.clusterCount) return false;

  // Remove the previously selected marker, if any:
  if(this.selectedMarker) this.removeSelectedMarker();

  if(cache.clusters[c][0] == null || cache.clusters[c][1] == null) return false;

  var latlng = new GLatLng(cache.clusters[c][0], cache.clusters[c][1]);
  if(panMap)
  {
    this.map.setCenter(latlng, null);
    this.showMarkers();
  }

  // Create a selected marker and overlay it on the marker defined by index:
  var markerSize = "12";
  if(cache.clusters[c][2] <= 1) { markerSize = "11"; var newIcon = new GIcon(this.baseIcon1); }
  else if(cache.clusters[c][2] > 3) { markerSize = "14"; var newIcon = new GIcon(this.baseIcon3); }
  else { markerSize = "12";var newIcon = new GIcon(this.baseIcon2); }
  newIcon.image = "../images/google-icons/green-square-" + markerSize + ".png"
  markerOptions = { icon:newIcon, zIndexProcess:selectedIndex };
  var newMarker = new GMarker(latlng, markerOptions);

  GEvent.addListener(newMarker, "click", function() {
    var infoWindowHTML = "<b>" + cache.clusters[c][3].length + " specimens:</b><br>";
    if(cache.clusters[c][3].length > 7) infoWindowHTML = infoWindowHTML + "<div style=\"height: 200px; overflow-y: scroll; padding-right: 5px;\">";
    for(var i = 0; i < cache.clusters[c][4].length; i++) infoWindowHTML = infoWindowHTML + "<a href=\"#\" onclick=\"googleMap.showMarkerInfo(" + c + ", " + cache.clusters[c][3][i] + "); return false;\">" + cache.clusters[c][4][i] + "</a><br>";
    if(cache.clusters[c][3].length > 7) infoWindowHTML = infoWindowHTML + "</div>";
    if(cache.clusters[c][3].length > 1) newMarker.openInfoWindowHtml(infoWindowHTML);
    else googleMap.showMarkerInfo(c, cache.clusters[c][3][0]);
  });
  this.map.addOverlay(newMarker);
  this.selectedMarker = newMarker;
  this.selectedMarkerIndex = c;
};

googleMap.removeSelectedMarker = function()
{
  if(!cache.mapDataLoaded || !this.loaded) return false;
  this.map.removeOverlay(this.selectedMarker);
  this.selectedMarker = null;
  this.selectedMarkerIndex = -1;
};

googleMap.showMarkerInfo = function(c, i)
{
  if(!cache.mapDataLoaded || !this.loaded) return false;
  if (c < 0 || c >= cache.clusterCount) return false;
  if (i < 0 || i >= cache.totalFoundCount) return false;

  this.selectMarker(c, false);
  results.markListEntry(i);
  results.updateDetailView(i, false);

  if(resultspane.dataCollapsed && resultspane.layout == 2)  // Make sure the results pane is not redrawn too large when toggled back on
  {
    resultspane.dataExpandedSize = 400;
    if(resultspane.dataExpandedSize > resultspane.width) resultspane.dataExpandedSize = Math.floor(resultspane.width / 2);
  }
  resultspane.toggleData(false);
  resultspane.switchDataTab(3);
};

googleMap.findSpecimenCluster = function(index)
{
  // This function locates the cluster/marker at the current zoom level that contains the specimen with index i:
  // If successful, returns the index of the cluster in the clusters array; or returns -1 upon failure.

  var currentZoom = this.map.getZoom();
  if(currentZoom < cache.minZoom) currentZoom = cache.minZoom;
  if(currentZoom > cache.maxZoom) currentZoom = cache.maxZoom;
  var selectedList = currentZoom - cache.minZoom;

  var i = 0;
  var s = 0;
  for (var c = 0; c < cache.zoomListCount[selectedList]; c++)
  {
    i = cache.zoomLists[selectedList][c]-1;
    var spCount = cache.clusters[i][3].length;
    for(s = 0; s < spCount; s++) if(cache.clusters[i][3][s] == index) return i;
  }
  return -1;
};