// Javascript functions common to all Locator applications
// Note that any HTML elements selected by getElementById must
// have the same ID in all future Locator applications. If
// This is not possible, affected code must be moved to the
// application-specific javascript.

var map;
var displayQty = 10; // number of results to display at one time
var startIndex = 0; // index of the first location currently displayed
var endIndex = displayQty - 1; // index of the last location currently displayed
var locations = null; // The results of the query
var markers = new Array(); // List of markers currently on the map
var showAll = false; // If true, show all locations on a single map (no paging)
var basicStateSearch = false; // State searches within the basic search need to be treated a bit differently.
var baseHref = "";

// Store the query parameters of the current search for opening up the print
// window.
var currentSearchParams;

// Variables to be set by the print page for values that are normally grabbed
// off the page, but need to be set by the GET parameters.
var addressInput;
var addressRadiusMiles;

// If we're loading the route page and the start and end addresses are
// pre-populated, do a search immediately.
function routeSearchOnload() {
   var start_address = document.getElementById("start_address");
   var end_address = document.getElementById("end_address");
   if(start_address && start_address.value && end_address && end_address.value) {
      commonPreSubmit();
   }
}

// Loads a default map on application startup
function load() {
   var base = document.getElementsByTagName('base')[0];
   if(base) {
      baseHref = base.href;
   }

   if (GBrowserIsCompatible()) {
      map = new GMap2(document.getElementById("map"));
      map.setCenter(new GLatLng(37, -97), 4);
      map.addControl(new GSmallMapControl());
      map.addControl(new GMapTypeControl());

      routeSearchOnload();
   }
}

// Callback for the getStations ajax call
function handleSearchResults() {
   if (http_request.readyState == 4) {
      if (http_request.status == 200) {
//         alert(http_request.responseText);
//         return;
         
         // Hide the loading panel
         document.getElementById('loading').style.visibility="hidden";
         
         // If the server returns an empty string, exit 
         if (http_request.responseText == "") return;
         
         // if the response text starts with the string ERROR, display the error
         if (http_request.responseText.search("ERROR") == 0) {
            alert(http_request.responseText.substr("ERROR".length));
            return;
         }
         
         // Decode the response from JSON into a Javascript array.
         locations = eval('(' + http_request.responseText + ')');
         
         // Set the start and end indices
         startIndex = 0;
         endIndex = Math.min((displayQty - 1), (locations.length - 1))
         
         // Display the first set of results on the map
         displayCurrentLocations();

         // If this is a route result, save the driving directions in a div
         // that will print
         if (document.getElementById('printable-driving-directions') != null) {
            tempHTML = document.getElementById('result_list').innerHTML;
            document.getElementById('printable-driving-directions').innerHTML = tempHTML;
         }
         // Automatically jump to the map
         window.scroll(0, document.getElementById('map').offsetTop);
      }
   }
}

// Display the current set of locations on the map and in the result list
function displayCurrentLocations() {
   
   // If we want to show all results on a single map rather than by pages
   if (showAll) {
      endIndex = locations.length - 1;
   }

   // Populate the map and the results list
   for (i=startIndex; i <= endIndex; i++) {
      addMarker(locations[i], i);
      var resultListContent = getResultListContent(locations[i], i);
      
      // If this is a route, we'll show the driving directions instead of the search results
      if (document.getElementById('result_list') && document.getElementById("form_type_route") && !document.getElementById("form_type_route").checked) {
         if (typeof resultListContent == "string") {
            document.getElementById('result_list').innerHTML += resultListContent;
         } else {
            document.getElementById('result_list').appendChild(resultListContent);
         }
      }
   }

   // If this is an address search and we're not showing all of the results,
   // draw the center point of the address and a circle representing the
   // radius.
   if (!showAll && ((document.getElementById("form_type_address") && document.getElementById("form_type_address").checked) || (addressInput && addressRadiusMiles)) && !basicStateSearch) {
      // Our address and mile variables might be preset if they're provided by
      // the GET parameters on the print page. Otherwise, they'll come from
      // current document's form values.
      if(document.getElementById("address_input")) {
         addressInput = document.getElementById("address_input").value;
      }

      if(document.getElementById("mile")) {
         addressRadiusMiles = document.getElementById("mile").value;
      }
      
      // We have to geocode the address again, because before it was done on the server
      geocoder = new GClientGeocoder();
      geocoder.getLatLng(addressInput, function(point) {
         map.addOverlay(getCircle(
            point, addressRadiusMiles, "#000000", 2, 1, "#99cfee", 0));
         
         // Zoom the map to the centerpoint and radius
         map.setCenter(point, getZoomLevel(addressRadiusMiles));
         
         // Add a marker at the user's address
         map.addOverlay(new GMarker(point));
      });
   } else {
      // Otherwise zoom to the extent of the markers on the map
      zoomToMarkers();
   }

   // Get the height of the container for later calculations. We need to do
   // this before we hide or show the page links and info divs, to keep IE
   // happy.
   if(document.getElementById("list_container")) {
      var listContainerHeight = document.getElementById("list_container").offsetHeight;
   }

   if(document.getElementById('page_links')) {
      // Add the page number links if the results need to be paged. But if
      // we're showing all of the results, hide the page links area.
      pageLinks = "";
      if (!showAll) {
         pageLinks = getPageLinks();
         document.getElementById('page_links').innerHTML = pageLinks;
         document.getElementById('page_links').style.display = "";
      }

      if(showAll || !pageLinks) {
         document.getElementById('page_links').innerHTML == "";
         document.getElementById('page_links').style.display = "none";
      }
   }

   if(document.getElementById("print_all")) {
      // Hide the print all link if we're already showing everything.
      if(showAll || locations.length <= displayQty) {
         document.getElementById("print_all").style.display = "none";
      } else {
         document.getElementById("print_all").style.display = "";
      }
   }

   if(document.getElementById('showing_results')) {
      // Fill in the area telling what records we're showing, unless this is a
      // route search, in which case we're not viewing any records.
      if(document.getElementById("form_type_route") && document.getElementById("form_type_route").checked) { 
         document.getElementById("showing_results").style.display = "none";
      } else {
         document.getElementById("showing_results").style.display = "";
         document.getElementById('showing_results').innerHTML = getShowingResults();
      }
   }

   if(document.getElementById("list_container")) {
      // Because our page links and showing areas might be hidden, we might
      // need to recalculate the fixed height of our scrolling results area, so
      // it can take up the entire vertical space.
      var showingResultsHeight = document.getElementById("showing_results").offsetHeight;
      var pageLinksHeight = document.getElementById("page_links").offsetHeight;

      var scrollerHeight = listContainerHeight - showingResultsHeight - pageLinksHeight;
      document.getElementById('result_scroller').style.height = scrollerHeight + "px";
   }

   // Build the results table
   populateResultTable();
}

// Set up some variables relating to GIcon so that we don't have to
// recreate them every time we create a new icon.
var iconSize = new GSize(20, 34);
var iconAnchor = new GPoint(0, 34);
var infoWindowAnchor = new GPoint(9,2);
var indexMap = new Array("A", "B", "C", "D", "E", "F", "G", "H", "I", "J");
//var iconURL = "http://www.google.com/mapfiles/marker";
var iconURL = "/afdc/locator/images/markers/marker";
var iconExtension = ".png";


// Get an icon using google's lettered icons
// Input parameter can be a letter (the returned Icon is uses the provided letter (limited to A-J))
// or a number, in which case 0->A, 1->B, etc.
// Returns a GIcon object
function getLetteredIcon(index) {
   var URL = getIconImageUrl(index);
   
   // We have a URL to the icon, now create and return a GIcon object
   var icon = new GIcon();
   icon.image = URL;
   icon.iconSize = iconSize;
   icon.iconAnchor = iconAnchor;
   icon.infoWindowAnchor = infoWindowAnchor;
   return icon;
}

// Return the URL to an icon image
// If index is omitted, the default google map icon is returned.
// Returns a String
function getIconImageUrl(index, extension) {
   url = null;
   // If the caller provided a file extension, use it
   finalExtension = iconExtension;
   if (!(extension === undefined)) {
      finalExtension = extension;
   }
   if (index == null) {
      url = baseHref + iconURL + finalExtension;
   } else {
      if (typeof index == "string") {
         // Make sure the requested value is in range.
         if (indexMap.indexOf(index.toUpperCase()) == -1) {
            throw "Error retrieving lettered icon (getLetteredIcon): Index out of range";
         } else {
            url = baseHref + iconURL + index.toUpperCase() + finalExtension;
         }
      } else if (typeof index == "number") {
         // Scale the index to fall within the range of displayQty, i.e.
         // an index of 12 with a display quantity of 10 means the index should be 2.
         iconIndex = index - Math.floor(index / displayQty) * displayQty; // This is a MOD operation
         if (iconIndex < 0 || iconIndex > 9) {
            throw "Error retrieving lettered icon (getLetteredIcon): Index out of range";
         } else {
            url = baseHref + iconURL + indexMap[iconIndex] + finalExtension;
         }
      }
   }
   return url;
}

/**
 * Returns the URL of the icon for a given location at a certain index. By
 * default, this just provides the normal icon, but this can be overriden by
 * other applications if the icon needs to vary depending on the location's
 * attributes (think different markers for different fuel types).
 */
function getLocationIconUrl(location, index) {
   return getIconImageUrl(index);
}

// Return the html for the page results page links (<< 1 2 3 4 >>)
function getPageLinks() {
   var snippet = "";
   var numPages = 1;
   if (Math.floor(locations.length / displayQty) < (locations.length / displayQty)) {
      numPages = Math.floor(locations.length / displayQty) + 1;   
   } else {
      numPages = Math.floor(locations.length / displayQty);   
   }
   if (numPages == 1) // If there's only one page, we don't need any page navigation
      return "";
   var currentPage = Math.floor(startIndex / displayQty) + 1;
   if (currentPage > 1) {
      snippet += "<a href='javascript:previousPage();'>\<\<</a> ";
   }

   // Sliding pagination.
   var numDisplayPages = 9;
   var startPage = currentPage - (Math.ceil(numDisplayPages / 2)) + 1;
   if(startPage < 1) {
      startPage = 1;
   }

   var endPage = startPage + numDisplayPages - 1;
   if(endPage > numPages) {
      startPage -= (endPage - numPages);
      endPage = numPages;
   }

   if(startPage < 1) {
      startPage = 1;
   }


   for (i = startPage; i <= endPage; i++) {
      if (i == currentPage) {
         snippet += i + " ";
      } else {
         snippet += "<a href='javascript:gotoPage(" + i + ");'>" + i + "</a> ";
      }
   }
   if (currentPage < numPages) {
      snippet += "<a href='javascript:nextPage();'>\>\></a>";
   }
   return snippet;
}

// return the html for "Results 1 - x of y"
function getShowingResults() {
   if (locations.length == 0) {
      return "No Results Found";
   }
   var snippet = "Results ";
   snippet += (startIndex + 1) + " to " + (endIndex + 1);
   snippet += " of " + locations.length;
   snippet += "<br>";
   return snippet;
}

// Go to the next page for multi-page results
function nextPage() {
   
   // Clear overlays and the results area before adding new points
   document.getElementById('result_list').innerHTML = "";
   map.clearOverlays();
   
   startIndex += displayQty;
   endIndex += displayQty;
   if (endIndex >= locations.length)
      endIndex = locations.length - 1;
   
   //alert("NextPage: start: " + startIndex + "; end " + endIndex);
   displayCurrentLocations();
}

// Go to the previous page for multi-page results
function previousPage() {
   
   // Clear overlays and the results area before adding new points
   document.getElementById('result_list').innerHTML = "";
   map.clearOverlays();
   
   startIndex -= displayQty;
   endIndex = startIndex + displayQty - 1;
   
   // These should never happen, but...
   if (startIndex < 0)
      startIndex = 0;
   if (endIndex >= locations.length)
      endIndex = locations.length - 1;
   
   //alert("PrevPage: start: " + startIndex + "; end " + endIndex);
   displayCurrentLocations();
}

// Go to the specified page for multi-page results
function gotoPage(page) {
   
   // Clear overlays and the results area before adding new points
   document.getElementById('result_list').innerHTML = "";
   map.clearOverlays();
   
   startIndex = (page - 1)  * displayQty;
   endIndex = startIndex + displayQty - 1;
   if (endIndex >= locations.length)
      endIndex = locations.length - 1;
   
   displayCurrentLocations();
}

// Zoom the map to show all current markers
// NEWLOCATOR: For this routine to work with other locator types, the latitude
// and longitude must be stored in the database in the fields 'latitude' and 'longitude'
// The mapObj parameter allows the caller to pass in a specific map to re-zoom.
// If that parameter is undefined, use the global map. Added to support printable view.
function zoomToMarkers(mapObj) {
   if (locations.length == 0) return;
   
   if (mapObj == undefined) {
      mapObj = map;
   }
   // First determine the min and max lat and lon of the displayed points
   var minLat = 1000;
   var maxLat = -1000;
   var minLon = 1000;
   var maxLon = -1000;
   for (i=startIndex; i <= endIndex; i++) {
      if (locations[i]['LATITUDE'] < minLat) minLat = locations[i]['LATITUDE'];
      if (locations[i]['LATITUDE'] > maxLat) maxLat = locations[i]['LATITUDE'];
      if (locations[i]['LONGITUDE'] < minLon) minLon = locations[i]['LONGITUDE'];
      if (locations[i]['LONGITUDE'] > maxLon) maxLon = locations[i]['LONGITUDE'];
   }

   var centerLat = +minLat + ((maxLat - minLat) / 2);
   var centerLon = +minLon + ((maxLon - minLon) / 2);

   var distance = calcDistance(minLat, minLon, maxLat, maxLon);
   
   // Use the distance between the bounding box corners to guess at the zoom level
   // Center and zoom the map
   mapObj.setCenter(new GLatLng(centerLat, centerLon), getZoomLevel(distance));
}

// Return an appropriate zoom level
function getZoomLevel(distance) {
   var zoomLevel = 4;
   if (distance < 4) zoomLevel = 12;
   else if (distance < 7) zoomLevel = 11;
   else if (distance < 15) zoomLevel = 10;
   else if (distance < 30) zoomLevel = 9;
   else if (distance < 60) zoomLevel = 8;
   else if (distance < 125) zoomLevel = 7;
   else if (distance < 250) zoomLevel = 6;
   else if (distance < 500) zoomLevel = 5;
   else if (distance < 1000) zoomLevel = 4;

   return zoomLevel;   
}

// Calculate the distance between two lat/lon points
function calcDistance(lat1, lon1, lat2, lon2) {
   var R = 3963.1 // radius of the earth in statute miles
   var dLat = toRad((lat2-lat1));
   var dLon = toRad((lon2-lon1)); 
   var a = Math.sin(dLat/2) * Math.sin(dLat/2) +
           Math.cos(toRad(lat1)) * Math.cos(toRad(lat2)) * 
           Math.sin(dLon/2) * Math.sin(dLon/2); 
   var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)); 
   var d = R * c;
   return d;
}

// Convert degrees to radians
function toRad(val) {  // convert degrees to radians
  return val * Math.PI / 180;
}

// Function to show/hide a set of divs based on radio button selections.
// Div ids must be the same as the radio button value for this to work.
// Caller must pass in the desired height of the div to be made visible
function showForm(mySelect, divHeight) {
   // Get all of the radio buttons in this group
   buttonGroup = document.getElementsByName(mySelect.name); 
   
   // Hide all of the divs associated with this button group
   for (var i=0; i < buttonGroup.length; i++) {
      if ((thisDiv = document.getElementById(buttonGroup[i].value)) != null) {
         thisDiv.style.visibility = "hidden";
         thisDiv.style.height = "0px";
         
      }
   }
   // Now show the one that was clicked
   if (mySelect.checked) {
      if ((div = document.getElementById(mySelect.value)) != null) {
         div.style.visibility = "visible";
//         div.style.height = divHeight + "px";
         div.style.height = "auto";
      }
   }
}

// Common pre-submit function - executes code prior to submitting the form, then calls
// the application specific pre-submit
function commonPreSubmit() {
   // Perform application specific processing prior to form submission
   appPreSubmit();
   
   // Perform client side input validation
   if (!validateInput()) {
      return;
   } 
   
   // If this is a state-based search, set a flag to cause all of the
   // results to be shown on a single map rather than paging them. 
   var state_sel = document.getElementById('state_select');
   var start_addr = document.getElementById('start_address');
   var end_addr = document.getElementById('end_address');
   var addr_input = document.getElementById('address_input');
   
   // The third check in the if statement below determines whether
   // the user typed only a state into the address box. In that case, we treat
   // the query like a state query. sjaxGetRequest makes a synchronous
   // call to the server
   if (document.getElementById("form_type_state").checked ||
       document.getElementById("form_type_route").checked) {
      showAll = true;
   } else {
      showAll = false;

      if(sjaxGetRequest("includes/isState.php?value=" + addr_input.value) != "") {
         basicStateSearch = true;
      }
   }
   // For the advanced search, make sure that only values in the currently visible
   // search mode are filled out. For example, if the user selects a state, then
   // switches to address and submits the form, we want to clear the state selection
   if (document.getElementById('form_type_address').checked) {
      if (state_sel != undefined)
         state_sel.value = "";
      if (start_addr != undefined)
         start_addr.value = "";
      if (end_addr != undefined)
         end_addr.value = "";
   }
   
   if (document.getElementById('form_type_state').checked) {
      if (addr_input != undefined) addr_input.value = "";
      if (start_addr != undefined) start_addr.value = "";
      if (end_addr != undefined) end_addr.value = "";
   }
   
   if (document.getElementById('form_type_route').checked) {
      if (addr_input != undefined) addr_input.value = "";
      if (state_sel != undefined) state_sel.value = "";
   }
   
   // Clear previous search results
   document.getElementById('result_list').innerHTML = "";
   map.clearOverlays();
   
   // Show the modal loading dialog
   document.getElementById('loading').style.visibility="visible";
   
   // If this is a route based search,
   // Get the route from google here, plot it on the map,
   // then create a json string containing the route waypoints and send it
   // to getStations.php. We can still handle the results with the common
   // handleResults.
   if (document.getElementById("form_type_route").checked) { 
      directions = new GDirections(map, document.getElementById('result_list'));
      startLocation = document.getElementById('start_address').value;
      endLocation = document.getElementById('end_address').value;
   
      directions.load("from: " + startLocation + " to: " + endLocation);
   
      GEvent.addListener(directions, "load", function() {
         // Listener invoked once directions results are available.
         // Now we can make our ajax call.
         
         // First get a list of vertices from the directions object
         // Google returns too many vertices for the SDO_GEOMETRY function. (vertexScaling)
         polyline = directions.getPolyline();
         vertexArray = new Array();
         vertexNumber = 0;
         vertexScaling = Math.round(polyline.getVertexCount() / 50);
         for (i = 0; i < polyline.getVertexCount(); i++) {
            point = new Object();
            point.lat= polyline.getVertex(i).lat();
            point.lon= polyline.getVertex(i).lng();
            if (i % vertexScaling == 0) { 
               vertexArray[vertexNumber++] = point;
            }
         }
         // Send the vertices to the server to get the station list
         currentSearchParams = "vertices=" + JSON.stringify(vertexArray)
                         + "&" + encodePostParams(document.getElementById('locatorform'));
         ajaxPostRequest('/afdc/locator/getStations.php', currentSearchParams, handleSearchResults);
      
      });
   
      // Add an error event listener to the GDirections object
      GEvent.addListener(directions, "error", function() {
         alert("Unable to locate one or both of the addresses provided (Geocode Status: "
               + directions.getStatus().code + ")");
         document.getElementById('loading').style.visibility="hidden";
      });
   } else {
      // Else send a normal (non-route) ajax request
      currentSearchParams = encodePostParams(document.getElementById('locatorform'));
      ajaxPostRequest('/afdc/locator/getStations.php', currentSearchParams, handleSearchResults);   
   }
   
}

/**
 * Populate the results table for the printable view 
 */
function populateResultTable() {
   // Slice away the currently displayed locations so we can render their
   // printable version.
   var printLocations = locations.slice(startIndex, endIndex + 1);

   // If we can use letters to associate the locations with their marker on the
   // map, then their lettered, distance order is fine. But if we have more to
   // display, we need to sort the results in alphabetical order.
   if(printLocations.length > displayQty) {
      printLocations.sort(locationSort);
   }

   htmlString = "<p></p><table border=\"0\" cellpadding=\"0\" cellspacing=\"0\">";
   numCols = 3;

   // For every table row.
   for(var i = 0; i < Math.ceil(printLocations.length / numCols); i++) {
      htmlString += "<tr>";

      // For every column cell inside this row.
      for(var col = 0; col < numCols; col++) {
         htmlString += "<td width=\"33%\">";

         // Figure out the liniar index, based on our row and column position.
         var index = i * numCols + col;

         // See if we have a location for this cell.
         var location = printLocations[index];
         if(location) {
            var iconUrl;
            if(printLocations.length <= displayQty) {
               iconUrl = getLocationIconUrl(location, index)
            } else {
               iconUrl = getLocationIconUrl(location)
            }

            htmlString += '<div class="result_icon"><img src="' + iconUrl + '"></div>';
            htmlString += '<div class="result_details">' + getPrintableResults(location, index).innerHTML + '</div>';
         }
         htmlString += "</td>";
      }
      htmlString += "</tr>";
   }
   htmlString += "</table>";
   
   document.getElementById('print_results').innerHTML = htmlString;
}

/**
 * A sort function to sort locations. This default implementation sorts by state, then
 * city, then name. Override this function in <apptype>.js for different behavior
 * @param a {Location} first location to compare
 * @param b {Location} second location to compare
 * @returns number -1, 0, or 1
 */
function locationSort(a, b) {
   if (a['STATE'] != b['STATE']) {
      if (a['STATE'] < b['STATE']) {
         return -1;
      } else if (a['STATE'] > b['STATE']) {
         return 1;
      }
   }

   if (a['CITY'] != b['CITY']) {
      if (a['CITY'] < b['CITY']) {
         return -1;
      } else if (a['CITY'] > b['CITY']) {
         return 1;
      }
   }
   
   if (a['SITE_NAME'] != b['SITE_NAME']) {
      if (a['SITE_NAME'] < b['SITE_NAME']) {
         return -1;
      } else if (a['SITE_NAME'] > b['SITE_NAME']) {
         return 1;
      }
   }
   
   return 0;
}

/**
 * The print links actually need to submit a form, so all of the parameters can
 * be passed to the print page using POST. This is particularly needed with the
 * route mapping, where the vertices parameter is too long to pass through GET.
 */
function getPrintForm(link) {
   // Create a hidden form.
   var form = document.createElement("form");
   form.style.display = "none";

   link.parentNode.appendChild(form);

   // We'll send using post, and send it to a new blank window to create a
   // popup.
   form.method = "post";
   form.target = "_blank";
   form.action = link.href;

   // Create a hidden input for the application type.
   var input = document.createElement("input");
   input.type = "hidden";
   input.name = "apptype";
   input.value = apptype;
   form.appendChild(input);

   // And another input so the page knows for sure that we're in printing mode.
   input = document.createElement("input");
   input.type = "hidden";
   input.name = "print_view";
   input.value = "1";
   form.appendChild(input);

   // Since we want to print what's actually being currently displayed, we've
   // basically serialized the current search query string. We need to bust
   // that apart so we can create individiual hidden inputs.
   if(currentSearchParams) {
      params = currentSearchParams.split("&");
      for(var i = 0; i < params.length; i++) {
         var param = params[i].split("=");

         input = document.createElement("input");
         input.type = "hidden";
         input.name = param[0];
         input.value = param[1];
         form.appendChild(input);
      }
   } else {
      // If the user hasn't actually searched for anything, let the print page
      // know.
      input = document.createElement("input");
      input.type = "hidden";
      input.name = "nosearch";
      input.value = "1";
      form.appendChild(input);
   }

   return form;
}

/**
 * Open a new printable page for the results currently being viewed.
 */
function printView(link) {
   var form = getPrintForm(link);

   // Tack on a couple of paraemters to the form for the currenty shown result indexes.
   if(!showAll) {
      var input = document.createElement("input");
      input.type = "hidden";
      input.name = "start_index";
      input.value = startIndex;
      form.appendChild(input);

      input = document.createElement("input");
      input.type = "hidden";
      input.name = "end_index";
      input.value = endIndex;
      form.appendChild(input);
   }

   form.submit();
}

/**
 * Open a new printable page for all of the results returned from the current
 * search.
 */
function printAll(link) {
   var form = getPrintForm(link);
   form.submit();
}

// Return a GPolygon object that represents a circle
function getCircle(center, radius, strokeColor, strokeWeight, strokeOpacity, fillColor, fillOpacity)
{
   var points = 32;
   var d2r = Math.PI / 180;   // degrees to radians
   var r2d = 180 / Math.PI;   // radians to degrees
   var earthsradius = 3963; // 3963 is the radius of the earth in miles
   var lat = center.lat();
   var lng = center.lng();

   // find the raidus in lat/lon
   var rlat = (radius / earthsradius) * r2d;
   var rlng = rlat / Math.cos(lat * d2r);

   var extp = new Array();
   for (var i=0; i < points+1; i++) // one extra here makes sure we connect the
   {
      var theta = Math.PI * (i / (points/2));
      ex = lng + (rlng * Math.cos(theta)); // center a + radius x * cos(theta)
      ey = lat + (rlat * Math.sin(theta)); // center b + radius y * sin(theta)
      extp.push(new GPoint(ex, ey));
   }
   fillColor = fillColor||strokeColor||"#99cfee";
   strokeWeight = strokeWeight||2;
   return new GPolygon(extp,strokeColor,strokeWeight,strokeOpacity,fillColor,fillOpacity);
}

/**
 * Swap the two addresses in the route mapper
 */
function swapAddresses() {
   startAddress = document.getElementById('start_address').value;
   endAddress = document.getElementById('end_address').value;
   document.getElementById('start_address').value = endAddress;
   document.getElementById('end_address').value = startAddress;
}

// If we're showing less results than our max, we can use the lettered icons on
// the map.
function useLetteredMarkers() {
   return (endIndex - startIndex < displayQty);
}

// Add a marker to the map
// index specifies which icon to use (A, B, C, etc).
function addMarker(location, index) {
   var icon = G_DEFAULT_ICON;

   if(useLetteredMarkers()) {
      icon = getLetteredIcon(index);
   }
   
   var marker = new GMarker(new GLatLng(location['LATITUDE'], location['LONGITUDE']), icon);
   GEvent.addListener(marker, 'click',
      function() {
         marker.openInfoWindowHtml(getDescription(location), {
            maxWidth: 350
         });
      }
   );
   map.addOverlay(marker);
   markers[index] = marker;
}

// return the description HTML to be shown in the popup window when the user clicks on the
// marker in the google map.
// May be overridden in apptype.js
function getDescription(location) {
   var snippet = "<br>";
   var location_address = [];

   if(location["SITE_NAME"]) {
      snippet += "<strong>" + location['SITE_NAME']+"</strong><br>";
   } else if(location["STATION_NAME"]) {
      snippet += "<strong>" + location['STATION_NAME']+"</strong><br>";
   }

   if(location["FUEL_TYPE_CODE"]) {
      snippet += location['FUEL_TYPE_CODE'] + "<br>";
   }

   if(location["STREET_ADDRESS"]) {
      snippet += location['STREET_ADDRESS'] + "<br>";
      location_address.push(location["STREET_ADDRESS"]);
   }

   if(location["CITY"]) {
      snippet += location['CITY'] + " ";
      location_address.push(location["CITY"]);
   }

   if(location["STATE"]) {
      snippet += location['STATE'] + " ";
      location_address.push(location["STATE"]);
   } else if(location["ST_PRV_CODE"]) {
      snippet += location["ST_PRV_CODE"] + " ";
      location_address.push(location["ST_PRV_CODE"]);
   }
   
   if(location["ZIP"]) {
      snippet += location['ZIP'];
      location_address.push(location["ZIP"]);
   }

   snippet += "<br>";

   if(apptype == "stations" && location["BD_BLENDS"]) {
      snippet += "Biodiesel Blends: " + location['BD_BLENDS'] + "<br>";
   }

   if(location["PHONE"]) {
      snippet += "Phone: " + location['PHONE'] + "<br>";
   } else if(location["STATION_PHONE"]) {
      snippet += "Phone: " + location["STATION_PHONE"] + "<br>";
   }

   if (!showAll) {
      if(location["DISTANCE"]) {
         snippet += "Distance: " + location['DISTANCE'] + " Miles" + "<br>";
      }
   }

   if(apptype == "def") {
      if(location["STATUS"]) {
         snippet += "Status: " + location['STATUS'] + "<br>";
      }
   }

   if(apptype == "tse") {
      if(location["STATION_TYPE"]) {
         snippet += "Brand: " + location['STATION_TYPE'] + "<br>";
      }
   }

   if(location["INTERSECTION_DIRECTIONS"]) {
      snippet += "Intersection Directions: " + location['INTERSECTION_DIRECTIONS'] + "<br>";
   }

   if(location["DIRECTIONS"]) {
      snippet += "Intersection Directions: " + location['DIRECTIONS'] + "<br>";
   }

   var direction_params = "";
   if(apptype == "stations") {
      var fuel_checkboxes = document.getElementsByName("fuels[]");
      for(var i = 0; i < fuel_checkboxes.length; i++) {
         if(fuel_checkboxes[i].checked) {
            direction_params += "&" + fuel_checkboxes[i].name + "=" + encodeURIComponent(fuel_checkboxes[i].value);
         }
      }
   }

   snippet += "<a title='Get location details in a new window'" +
                  " href=\"/afdc/locator/details.php?id=" + location['ID_NUMBER'] + "&apptype=" + apptype + "\"" +
                  " onclick=\"var win = window.open(this.href, 'details', 'width=450,height=480,scrollbars=yes'); win.focus(); return false;\">Details</a>  ";

   var start_address = "";
   if(document.getElementById("address_input")) {
      start_address = document.getElementById("address_input").value;
   }

   snippet += "<a title='Get directions to/from this address'" +
                  " href='/afdc/locator/" + apptype + "/route?start=" +
                  start_address + "&end=" + location_address.join(" ") + direction_params + "'>Driving Directions</a>";
   snippet += "<br/>";

   if(location["STATION_STATUS"]) {
      snippet += "<a title='Get real-time status information for this station'" +
                  " href='" + location['STATION_STATUS'] + "' target='_blank'>Station Status</a>";
   }

   return snippet;
}

// return a div element for the entry in the result list for a given station
// May be overridden in apptype.js
function getResultListContent(location, index) {
   var url = "";
   if(useLetteredMarkers()) {
      url = getIconImageUrl(index, ".gif"); // otherwise get an icon based on the current index
   } else {
      url = getIconImageUrl(null, ".gif"); // get the default icon
   }
  // Define a function to re-center the map and open the popup bubble when the user
  // clicks on the location name.
   var focus = function() {
      markers[index].openInfoWindowHtml(getDescription(location), {
         maxWidth: 350
      });
      map.panTo(new GLatLng(location['LATITUDE'], location['LONGITUDE']));
      return false;
   }
   titleLink = document.createElement('a');
   titleLink.href = "#";
   titleLink.onclick = focus;
   titleLink.appendChild(document.createTextNode(location['SITE_NAME']));
   
   iconImg = document.createElement('img');
   iconImg.src = url;

   var iconDiv = document.createElement("div");
   iconDiv.className = "result_icon";
   iconDiv.appendChild(iconImg);

   var detailsDiv = document.createElement("div");
   detailsDiv.className = "result_details";
   detailsDiv.appendChild(titleLink);
   detailsDiv.appendChild(document.createElement('br'));
   if(location["STREET_ADDRESS"]) {
      detailsDiv.appendChild(document.createTextNode(location['STREET_ADDRESS']));
      detailsDiv.appendChild(document.createElement('br'));
   }

   if(location["CITY"]) {
      detailsDiv.appendChild(document.createTextNode(location['CITY'] + " "));
   }

   if(location["STATE"]) {
      detailsDiv.appendChild(document.createTextNode(location['STATE'] + " "));
   }

   if(location["ZIP"]) {
      detailsDiv.appendChild(document.createTextNode(location['ZIP']));
   }

   detailsDiv.appendChild(document.createElement('br'));

   if(location["PHONE"]) {
      detailsDiv.appendChild(document.createTextNode("Phone: " + location['PHONE']));
      detailsDiv.appendChild(document.createElement('br'));
   }

   if (!showAll) {
      detailsDiv.appendChild(document.createTextNode("Distance: " + location['DISTANCE'] + " Miles"));   
      detailsDiv.appendChild(document.createElement('br'));   
   }   

   if(apptype == "def") {
      detailsDiv.appendChild(document.createTextNode("Status: " + location['STATUS']));   
   }

   if(apptype == "tse") {
      if(location["STATION_TYPE"]) {
         detailsDiv.appendChild(document.createTextNode("Brand: " + location['STATION_TYPE']));   
      }
   }

   var resultDiv = document.createElement('div');
   resultDiv.className = "result_item";
   resultDiv.appendChild(iconDiv);
   resultDiv.appendChild(detailsDiv);

   return resultDiv;
}

/**
 * Perform client side input validation on form fields
 */
function validateInput() {
   var state_sel = (document.getElementById('state_select') == undefined) ? "" : document.getElementById('state_select').value;
   var start_addr = (document.getElementById('start_address') == undefined) ? "" : document.getElementById('start_address').value;
   var end_addr = (document.getElementById('end_address') == undefined) ? "" : document.getElementById('end_address').value;
   var addr_input = (document.getElementById('address_input') == undefined) ? "" : document.getElementById('address_input').value;
   var route_radius = (document.getElementById('route_mile') == undefined) ? "" : document.getElementById('route_mile').value;
   var addr_radius = (document.getElementById('mile') == undefined) ? "" : document.getElementById('mile').value;
   
   if (document.getElementById('form_type_address').checked) {
      if (addr_input == "") {
         alert("Please enter a valid address");
         return false;
      }
      if (addr_radius == "" || addr_radius < 0.1 || addr_radius > 200) {
         alert("Please enter a search radius between 0.1 and 200 miles");
         return false;
      }
   }
   
   if (document.getElementById('form_type_state').checked) {
      if (state_sel == "") {
         alert("Please Select a State");
         return false;
      }
   }
   
   if (document.getElementById('form_type_route').checked) {
      if (start_addr == "") {
         alert("Please enter a starting address for your route");
         return false;
      }
      if (end_addr == "") {
         alert("Please enter an ending address for your route");
         return false;
      }
      if (route_radius == "" || route_radius < 0.1 || route_radius > 50) {
         alert("Please enter a search radius between 0.1 and 50 miles");
         return false;
      }
   }
   return true;
}

function toggleOptions(link, options_id) {
   var options = document.getElementById(options_id);

   if(link.className == "options_closed") {
      link.className = "options_open";
      options.className = "";
   } else {
      link.className = "options_closed";
      options.className = "hide";
   }

   // For whatever reason, IE 6 won't move the map down the page when the
   // options div appears. And for whatever reason, resetting the map's height
   // fixes this in IE. I have no clue why this works or how I even stumbled
   // upon this solution, but it seems to work.
   if(!window.XMLHttpRequest) {
      var height = document.getElementById("map").offsetHeight;

      document.getElementById("map").style.height = "0px";

      document.getElementById("map").style.height = height + "px";
      map.checkResize();
   }
}
