var me = this;
var map = null;
var mapControl = null;
var mapTypeControl = null;
var youAreHereMarker = null;
var markers = new Array();
var selectedMarkerID = null;
var selectedMarker = null;
var selectedMarkerOriginal = null;
var DEFAULT_MAP_ZOOM_LEVEL = 4;
var MAX_MAP_ZOOM_LEVEL = 17;
var MIN_MAP_ZOOM_LEVEL = 4;

function adjustSearchResultStyles()
{
   // set the appropriate height for the search results panel      
   var resultsHeader = document.getElementById('VetResultsHeaderRow');
   var resultsHeaderTop = resultsHeader.offsetTop;
   var resultsContainerCell = document.getElementById('VetResultsContainerCell');
   var resultsContainer = document.getElementById('ResultsContainer');
   var outerTable = document.getElementById('OuterTable');
      /* temporarily set the size of the search results container to 0 to
         prevent it from interfering with the outerTable resize operation */
         resultsContainer.style.height = 1;
      outerTable.style.height = document.body.clientHeight - 1;
      resultsContainer.style.height = resultsContainerCell.clientHeight;
}

function GetMap() {   
   // adjust the search results UI to fit allocated space in browser
   adjustSearchResultStyles();
   window.onresize = adjustSearchResultStyles;
   // load the map
   map = new GMap2(document.getElementById("vetHospitalMap"));
   //map.setMapType(new GMapType(G_NORMAL_MAP));
   //map.enableContinuousZoom();
   //map.addControl(new GLargeMapControl(), new GControlPosition(G_ANCHOR_TOP_LEFT, new GSize(10, 10)));
   mapControl = new PetsBestMapControl();
   map.addControl(mapControl);
   //writeToDebug(mapControl);
   mapControl.makeVisible();
   // center the map on Boise to start
   map.setCenter(new GLatLng(40, -98), DEFAULT_MAP_ZOOM_LEVEL);
   mapControl.setSliderPosition(DEFAULT_MAP_ZOOM_LEVEL);
   // map move (e.g. dragged by user) handler
//   GEvent.addListener(map, "move", function() { 
//      //alert(selectedMarkerID);
//      //alert('selectedMapID.isNull = ' + (selectedMarkerID != null));
//      if (selectedMarkerID != null) {
//         /* if the last click on the map was on a marker, move the Hospital Detail pane
//            to the marker's new location */
//         PinClick(selectedMarkerID, false);   
//         }
//      else {                  
//         // hide the detail pane
//         moveElement('vetHospitalDetail', 10, 10);
//         }
//      });
   // map click handler
   GEvent.addListener(map, "click", function(marker, point) {
      if (marker == null) {
         // reset the selectedMarkerID value
         selectedMarkerID = null;
         //alert('map clicked');
         }
      else {
         selectedMarkerID = marker.PBMarkerIndex;
         }
      });
      
   }
   
function searchByAddress() {
   try {
      /* attempt to Geocode the supplied address and send the search results to another method
         that will query the Vet Locator data based on the success of the Geocode operation */        
      var geocoder = new GClientGeocoder();
      geocoder.getLocations(document.getElementById('uiTxtAddress').value, me.centerMapThenSearch);
      }
   catch (ex) {
      document.getElementById('searchResults').innerText = (show_props(ex, 'ex'));
      }
   }

/* Web Service call that queries for Vet Hospitals located within a specific Latitude / 
   Longitude quadrant */  
function centerMapThenSearch(returnVal) {
   //writeToDebug(returnVal.Placemark.getItem(0));
   if ((returnVal.Placemark != null) && (returnVal.Placemark.getItem(0) != null)) {
      // our search returned exactly one Placemark so center the map on its Lat/Lon
      var searchMatch = returnVal.Placemark.getItem(0);
      placeLatLon = searchMatch.Point.coordinates;
      // set the new map zoom level based on the accuracy of the address search
      var newZoomLevel = DEFAULT_MAP_ZOOM_LEVEL;
      var addressAccuracy = searchMatch.AddressDetails.Accuracy;
      if (addressAccuracy >= 5) {
         newZoomLevel = 13;
         }
      else if (addressAccuracy >= 3) {
         newZoomLevel = 12;
         }        
      else {
         youAreHereMarker = null;
         }   
      // set the new map center
      newCenter = new GLatLng(placeLatLon[1], placeLatLon[0]);
      map.setCenter(newCenter, newZoomLevel);
      map.savePosition();
      // update the map control slider's position
      mapControl.setSliderPosition(newZoomLevel);
      // create/update the "You are Here" marker.
      youAreHereMarker = new GMarker(newCenter, customIconYouAreHere());
      // execute the search
      searchWithinCurrentMap();
      }
   else {
      // search yielded no results, so clear out the map and search results panels
      clearMap();
      populateSearchResults();
      }
   
   }
   
function searchWithinCurrentMap() {
   /* get the LatLon values of the corners of the newly centered map and use those values as
      query filter criteria */
   var mapBounds = map.getBounds();
   var ne = mapBounds.getNorthEast();
   var sw = mapBounds.getSouthWest();
//   try {
      VetLocator.VetHospitalsService.GetVetHospitalsByCoords(ne.y, sw.x, sw.y, ne.x, onGetVetHospitalsComplete_GMAPI);
//      }
//   catch (ex) {
//      document.getElementById('debug').innerText = show_props(ex, "ex");
//      }
   //window.status = 'ne:' + ne.y + ',' + ne.x + '; sw:' + sw.y + ',' + sw.x + ';';
   }
   
/* Web Service Callback (used for all Vet Locator Web Service calls).
   This "delegate" method takes the list of Vet Hospitals returned by
   the web service and applies them as a datasource for both map pushpins
   and the templated search results list. */
function onGetVetHospitalsComplete_GMAPI(vetHospitals) {
//   if (vetHospitals.length == 0) {
//      alert('Your search returned 0 results.\r\nPlease refine your search and try again.');
//      }
   createPushpins(vetHospitals);
   populateSearchResults(vetHospitals);
   }

// Populates a templated list of search results using the provided array of VetHospital objects.
function populateSearchResults(vetHospitals) {
   /* add a custom "navigateURL" property to each vetHospital object for 
      databinding purposes only */
   if (vetHospitals != null) {   
      for (var i=0; i<vetHospitals.length; i++) {
         vetHospitals[i].navigateURL = "javascript:PinClick(" + i + ", true);";
         vetHospitals[i].PBMarkerIndex = "searchResult" + i.toString();
         // set search result item CSS class
         if (vetHospitals[i].IsProvider) {
            vetHospitals[i].ResultItemCssClass = "OfficialCon";
            }
         else {
            vetHospitals[i].ResultItemCssClass = "searchResultsItemDefault";
            }
         }
      // bind the data
      //writeToDebug($('searchResults').control);
      }
   $('searchResults').control.set_data(vetHospitals);
}

// Creates pushpins for the Virtual Earth map based on an array of VetHospital objects.
function createPushpins(vetHospitals) {
   // remove any existing items from the global pins collection and map before generating new ones
   clearMap();
   // if it exists, add the "You are Here" marker back to the page...
   if (youAreHereMarker != null) map.addOverlay(youAreHereMarker);
   // create the new markers and add them to the map
   for (var i=0; i<vetHospitals.length; i++) {
      vet = vetHospitals[i];
      var point = new GLatLng(vet.Latitude, vet.Longitude);
      /* it is CRITICAL that you understand WHY this next line is constructed the
            way it is.  If we instantiate the "marker" variable in THIS "createPushpins" 
            method, it will behave as a GLOBAL variable of sorts (Strange, I know, but
            that is how JS works) and any time a marker "click" event is captured it will
            always reference the same marker variable, which will of course still be set
            to the last marker you added to the map.
         Having a separate method instantiate a "marker" variable and return a marker
            object resolves this issue as it obviously creates a new variable with each
            call.         
         */
      map.addOverlay(createMarker(point, vet, i));
      }
   return;
   }

function clearMap() {
   // clear out any map markers
   markers = null;
   markers = new Array(); 
   map.clearOverlays();
   /* clear out any existing data in the Vet Detail panel by setting the current 
      Vet Hospital ID to 0 */
   loadVetHospitalDetail(0);      
   }

function createMarker(point, vet, markerIndex) {
   var icon = customIconNotProvider(); 
   if (vet.IsProvider) {
      icon = customIconProvider();
      }
   var marker = new GMarker(point, icon);
   marker.PBVId = vet.ID;
   marker.PBMarkerIndex = markerIndex;
   marker.IsProvider = vet.IsProvider;
   GEvent.addListener(marker, "click", function() { PinClick(markerIndex, true) });
   markers[markerIndex] = marker;
   return marker;
   }

/* The code that runs when a pushpin is clicked.  The primary purpose is to move/position
   the VetHospitalDetail panel right next to the pushpin that was just clicked and also
   populate that panel (div) with detailed information about the selected Vet Hospital
   (Hours of Operation, Services Provided, image(s) of the facility, etc.) */
function PinClick(markerIndex, queryDB) {
   try {
      highlightSelectedSearchResult(markerIndex);
   
      // reset the previous selected marker
      if (selectedMarker != null) {
         map.removeOverlay(selectedMarker);
         map.addOverlay(selectedMarkerOriginal);
         }
      // replace the marker that was clicked with a "fancy" selected icon   
      var marker = markers[markerIndex];
      selectedMarkerOriginal = marker;
      map.removeOverlay(marker);
      selectedMarker = new GMarker(marker.getPoint(), customIconCurrentlySelected(marker.IsProvider));
      //writeToDebug(marker.getPoint(), "s");
      map.addOverlay(selectedMarker);      
      
      if (queryDB != false) {         
         loadVetHospitalDetail(marker.PBVId);
         }
      //var point = ; //marker.MapXY;
      var mapSize = map.getSize();
      var centerXY = map.fromLatLngToDivPixel(map.getCenter());
      var markerXY = map.fromLatLngToDivPixel(marker.getPoint());
      // center the clicked marker
      map.panTo(marker.getPoint());
      // move the Hospital Detail pane next to the marker that was clicked...
      //var hospitalDetailPane = document.getElementById('vetHospitalDetail');
      /* map and the div that contains the map have different XYs (map starts out the same as the
         container but as you drag it around you will see that the center of the map is actually
         relative to the point on the map that corresponded with 0,0 of the container, in this 
         case a DIV) */
      //alert(show_props(centerXY, 'thePoint'));
      /////hospitalDetailPane.style.left = (mapSize.width/2 + (markerXY.x - centerXY.x)) + 30;
      /////hospitalDetailPane.style.top = (mapSize.height/2 + (markerXY.y - centerXY.y)) - 15;
      /* unless queryDB == false (would occur when detail has already been displayed and
         the user has simply dragged the map to a new location, for example), call the 
         function that updates the Hospital Detail pane with the data for the selected 
         Vet Hospital */
      }
   catch (ex) {
      writeToDebug(show_props(ex, "theException"));
      }
   }

function moveElement(id, left, top) {
   element = document.getElementById(id);
   element.style.left = left;
   element.style.top = top;
   }      

// custom icons
function customIconProvider() {
   var icon = new GIcon();
   icon.image = "App_Themes/Main/Images/PushPin_Provider.png";
   icon.shadow = "App_Themes/Main/Images/Shadow.png";
   icon.iconSize = new GSize(35, 45);
   icon.shadowSize = new GSize(35, 45);
   icon.iconAnchor = new GPoint(11, 39);
   icon.infoWindowAnchor = new GPoint(22, 5);
   icon.infoShadowAnchor = new GPoint(0,0);
   return icon;
   }

function customIconNotProvider() {
   var icon = new GIcon();
   icon.image = "App_Themes/Main/Images/PushPin.png";
   icon.shadow = "App_Themes/Main/Images/Shadow.png";
   icon.iconSize = new GSize(35, 45);
   icon.shadowSize = new GSize(35, 45);
   icon.iconAnchor = new GPoint(11, 39);
   icon.infoWindowAnchor = new GPoint(22, 5);
   icon.infoShadowAnchor = new GPoint(0,0);
   return icon;
   }
   
function customIconCurrentlySelected(isProvider) {
   var icon = new GIcon();
   var imageUrl = (isProvider ? "App_Themes/Main/Images/OfficialActive.gif":"App_Themes/Main/Images/Active.gif");
   icon.image = imageUrl;
   icon.shadow = "App_Themes/Main/Images/Shadow.png";
   icon.iconSize = new GSize(35, 45);
   icon.shadowSize = new GSize(35, 45);
   icon.iconAnchor = new GPoint(11, 39);
   icon.infoWindowAnchor = new GPoint(22, 5);
   icon.infoShadowAnchor = new GPoint(0,0);
   return icon;
   }

function customIconYouAreHere() {
   var icon = new GIcon();
   icon.image = "App_Themes/Main/Images/YouHerePin.png";
   icon.iconSize = new GSize(60, 60);
   icon.shadowSize = new GSize(60, 60);
   icon.iconAnchor = new GPoint(11, 39);
   icon.infoWindowAnchor = new GPoint(22, 5);
   icon.infoShadowAnchor = new GPoint(0,0);
   return icon;
   }
   
function defaultGoogleIcon() {
   return G_DEFAULT_ICON;
   }

// search results panel interaction
function highlightSelectedSearchResult(markerIndex) {
   /* if another search result was previously selected, remove the "selected" 
      styling from that result item. */
   if (selectedMarkerOriginal != null) {
      previousSelectedItem = getSelectedResultItemDiv(selectedMarkerOriginal.PBMarkerIndex);
      if (previousSelectedItem != null) {
         previousSelectedItem.className = "";
         }
      }
   selectedItem = getSelectedResultItemDiv(markerIndex);
   if (selectedItem != null) {
      selectedItem.className = "searchResultsSelectedItem";   
      var container = document.getElementById('ResultsContainer');
      /* scroll the search results panel such that the selected result appears 
         at the top of the pane. */
      if (selectedItem.offsetTop < container.scrollTop) selectedItem.scrollIntoView(true);
      if (selectedItem.offsetTop > (container.clientHeight + container.scrollTop)) selectedItem.scrollIntoView(false);
      }
   }   

/* returns the Nth item (div) from the search results list.  This method 
   should work fine in any browser that supports the DOM. */
function getSelectedResultItemDiv(markerIndex) {
   var resultContainer = document.getElementById('vhTemplate');
   var returnObj = null;
   /* we want to return the "markerIndex"th DIV that represents a search 
      result item and ignore everything else. */
   var x = 0;
   for (var i=0; i<resultContainer.childNodes.length; i++) {
      searchResult = resultContainer.childNodes[i];
      /* IF this is a div with an id of vhItemTemplate (i.e. not a #Text 
         DOM element, etc.), it is one of the nodes we're interested in. */
      if ((searchResult.nodeType == 1) && (searchResult.id == 'vhItemTemplate')) {
         if (x == markerIndex) {
            returnObj = searchResult;
            break;
            }
         else {
            x++;
            }
         }
      }
   return returnObj;
   }   

// Custom map control (zoom / positioning)
function PetsBestMapControl() {
   }
   PetsBestMapControl.prototype = new GControl();
   PetsBestMapControl.prototype.initialize = function(map) {

      //var sliderBox = document.getElementById('MapZoomArea');
      var controlContainerDiv = document.getElementById('PetsBestMapControl');
      
      // apply map type change events
      this.applyMapTypeChangeEvent('RoadMapButton', G_NORMAL_MAP);
      this.applyMapTypeChangeEvent('SatelliteMapButton', G_HYBRID_MAP);
      // apply map pan events
      this.applyPanEvent('MapButtonNorth', 0, 1);
      this.applyPanEvent('MapButtonEast', -1, 0);
      this.applyPanEvent('MapButtonSouth', 0, -1);
      this.applyPanEvent('MapButtonWest', 1, 0);
      this.applyMapCenterEvent('MapButtonCenter');
      // apply map zoom events
      this.applyZoomEvent('MapZoomButtonPlus', 'in');
      this.applyZoomEvent('MapZoomButtonMinus', 'out');
      // add the map slider
      var zoomScaleContainer = document.getElementById('MapZoomArea');
      var sliderImg = document.createElement("img");
      sliderImg.id = "MapZoomSlider";
      sliderImg.src = "App_Themes/Main/Images/Slider.png";
      zoomScaleContainer.appendChild(sliderImg);
      // apply map zoom scale events
      for (var i=MAX_MAP_ZOOM_LEVEL; i>=MIN_MAP_ZOOM_LEVEL; i--) {
         zoomScaleContainer.appendChild(this.createZoomMarker(i));
         }
      // add the div that contains the control background image to the map surface
      var bgDiv = document.getElementById('PetsBestMapControlBG');
      map.getContainer().appendChild(bgDiv);      
      // add the custom control to the map surface
      map.getContainer().appendChild(controlContainerDiv);
      return controlContainerDiv;
      }
      
   PetsBestMapControl.prototype.makeVisible = function() {
      document.getElementById('PetsBestMapControl').style.visibility = "visible";
      document.getElementById('PetsBestMapControlBG').style.visibility = "visible";
      }

   PetsBestMapControl.prototype.createZoomMarker = function(newZoomLevel) {
      var zoomMarker = document.createElement("div");
      zoomMarker.id = "HashMark";
      zoomMarker.className = "HashMark";
      var hashMarkImg = document.createElement("img");
      hashMarkImg.src = "App_Themes/Main/Images/Hashmark.png";
      zoomMarker.appendChild(hashMarkImg);
      // add the click event for this zoom level
      GEvent.addDomListener(zoomMarker, "click", function() {
         mapControl.setSliderPosition(newZoomLevel);
         });
      return zoomMarker;
      }
   
   PetsBestMapControl.prototype.setSliderPosition = function(newZoomLevel) {
      if ((newZoomLevel<=MAX_MAP_ZOOM_LEVEL) && (newZoomLevel>=MIN_MAP_ZOOM_LEVEL)) {
         map.setZoom(newZoomLevel);
         // get a reference to the slider div
         var slider = document.getElementById('MapZoomSlider');
         // locate the div element that corresponds with the new zoom level
         var zoomLevelContainer = document.getElementById('MapZoomArea');
         /* set the zoom slider's position = that of the "newZoomLevel"th zoomLevel
            (we obviously want to "skip over" text nodes and any other types of child
            elements that do not represent "Hash Marks" */
         var targetZoomPosIndex = MAX_MAP_ZOOM_LEVEL - newZoomLevel;
         var hashMarkItemIndex = 0;
         var targetChildNodeIndex = 0;
         var nodes = zoomLevelContainer.childNodes;
         for (var i=0; i<nodes.length; i++)
            { 
            var nodeToCheck = nodes[i];
            if ((nodeToCheck.nodeType == 1) && (nodeToCheck.id == 'HashMark'))
               {
               if (hashMarkItemIndex == targetZoomPosIndex) {
                  targetChildNodeIndex = i;
                  break;
                  }
               else {
                  hashMarkItemIndex++;
                  }
               }
            }
         var zoomMarker = zoomLevelContainer.childNodes[targetChildNodeIndex];
         slider.style.top = zoomMarker.offsetTop;
         }
      }

   PetsBestMapControl.prototype.applyMapTypeChangeEvent = function(controlID, mapType) {
      var btn = document.getElementById(controlID);
      GEvent.addDomListener(btn, "click", function() {
         map.setCenter();
         map.setMapType(mapType);
         });
      }
      
   PetsBestMapControl.prototype.applyMapCenterEvent = function(controlID) {
      var btn = document.getElementById(controlID);
      GEvent.addDomListener(btn, "click", function() {
         map.returnToSavedPosition();
         });
      }
      
   PetsBestMapControl.prototype.applyPanEvent = function(controlID, xDir, yDir) {
      var btn = document.getElementById(controlID);
      GEvent.addDomListener(btn, "click", function() {
         map.panDirection(xDir, yDir);
         });
      //writeToDebug(btn);
      }
      
   PetsBestMapControl.prototype.applyZoomEvent = function(controlID, zoomDir) {
      var btn = document.getElementById(controlID);
      switch(zoomDir) {
         case "in":
            GEvent.addDomListener(btn, "click", function() { mapControl.setSliderPosition(map.getZoom() + 1); });
            break;
         case "out":
            GEvent.addDomListener(btn, "click", function() { mapControl.setSliderPosition(map.getZoom() - 1); });
            break;
         }
      }
      
   // By default, the control will appear in the top left corner of the
   // map with 7 pixels of padding.
   PetsBestMapControl.prototype.getDefaultPosition = function() {
      return new GControlPosition(G_ANCHOR_TOP_LEFT, new GSize(7, 7));
      }   

// Utility methods

/* Generic JavaScript utility function that iterates over all of the properties of the 
   object passed in and concatenates a name/value pair for each property into a single string */
function show_props(obj, objName) {
   var result = "";
   for (var i in obj) {
      result += objName + "." + i + " = " + obj[i] + "\n";
   }
   return result;
}
      
function showDetailItem(id) {
   var element = document.getElementById(id)
   if (element != null) {
      element.style.display = "block";
      }
   }

function hideDetailItems(ids) {
   if (ids.length > 0) {
      items = ids.split(",");
      for (var i=0; i<items.length; i++) {
         var element = document.getElementById(items[i])
         if (element != null) {
            element.style.display = "none";
            }
         }  
      }               
   }  
    
function writeToDebug(obj, objName) {
   var debugDiv = document.getElementById('debug');
   if (debugDiv != null) {
      debugDiv.innerText = show_props(obj, objName);
      }
   }   