/* Copyright 2009 FitnessKeeper, Inc.  All Rights Reserved. */

/**
 * Initializes the google map object.
 */
function initMap(elemId, distanceUnits)
{
   if (!GBrowserIsCompatible())
   {
      alert("Your browser must be compatible with Google Maps to use the RunKeeper mapping features.");
      return null;
   }
   
   var rkMap = {};
   
   rkMap.tripPolylines = [];
   rkMap.points 	   = [];
   rkMap.defaultLoc    = new GLatLng(42.375, -71.106);
   rkMap.gmap = new GMap2(document.getElementById(elemId));
   rkMap.gmap.enableContinuousZoom();
   //rkMap.gmap.enableScrollWheelZoom();
   rkMap.gmap.setCenter(rkMap.defaultLoc, 2);
   
   // Create our managers for the distance, pause, and resume point markers.
   rkMap.pauseMarkerManager = new MarkerManager(rkMap.gmap);
   rkMap.resumeMarkerManager = new MarkerManager(rkMap.gmap);
   rkMap.distanceMarkerManager = new MarkerManager(rkMap.gmap);
   
   // Set up the Start Icon
   var startIcon = new GIcon();
   startIcon.image = staticRoot + "/kronos/images/map/rk_start.png";
   startIcon.shadow = "";
   startIcon.iconSize = new GSize(11,29);
   startIcon.iconAnchor = new GPoint(6, 29);
   var startMarkerOptions = { icon:startIcon, clickable:false };
   rkMap.startMarker = new GMarker(rkMap.defaultLoc, startMarkerOptions);
   rkMap.gmap.addOverlay(rkMap.startMarker);
   rkMap.startMarker.hide();
      
   // Set up the End Icon, this is the for last point == EndPoint
   var endIcon = new GIcon();
   endIcon.image = staticRoot + "/kronos/images/map/rk_end.png";
   endIcon.shadow = "";
   endIcon.iconSize = new GSize(11,29);
   endIcon.iconAnchor = new GPoint(6, 29);
   var endMarkerOptions = { icon:endIcon, clickable:false };
   rkMap.endMarker = new GMarker(rkMap.defaultLoc, endMarkerOptions);
   rkMap.gmap.addOverlay(rkMap.endMarker);
   rkMap.endMarker.hide();
   
   // Set up the Last Icon, this is the for last point != EndPoint
   var lastIcon = new GIcon();
   lastIcon.image = staticRoot + "/kronos/images/map/liveRunner.png";
   lastIcon.iconSize = new GSize(49,50);
   lastIcon.iconAnchor = new GPoint(15, 50);
   var lastMarkerOptions = { icon:lastIcon, clickable:false, zIndexProcess:function(){return 5000;} };
   rkMap.lastMarker = new GMarker(rkMap.defaultLoc, lastMarkerOptions);
   rkMap.gmap.addOverlay(rkMap.lastMarker);
   rkMap.lastMarker.hide();
   
   // Set up the Current Location Icon
   var currLocIcon = new GIcon();
   currLocIcon.image = staticRoot + "/kronos/images/map/curr_loc.png";
   currLocIcon.shadow = "";
   currLocIcon.iconSize = new GSize(52,52);
   currLocIcon.iconAnchor = new GPoint(26, 26);
   var currLocMarkerOptions = { icon:currLocIcon, clickable:false };
   rkMap.currLocMarker = new GMarker(rkMap.defaultLoc, currLocMarkerOptions);
   rkMap.gmap.addOverlay(rkMap.currLocMarker);
   rkMap.currLocMarker.hide();
   
   // Set up the Pause Icon
   var pauseMarkerIcon = new GIcon();
   pauseMarkerIcon.image = staticRoot + "/kronos/images/map/rk_pause.png";
   pauseMarkerIcon.shadow = "";
   pauseMarkerIcon.iconSize = new GSize(11,29);
   pauseMarkerIcon.iconAnchor = new GPoint(6, 29);               
   rkMap.pauseMarkerOptions = { icon:pauseMarkerIcon, clickable:false };
   
   // Set up the Resume Icon
   var resumeMarkerIcon = new GIcon();
   resumeMarkerIcon.image = staticRoot + "/kronos/images/map/rk_resume.png";
   resumeMarkerIcon.shadow = "";
   resumeMarkerIcon.iconSize = new GSize(11,29);
   resumeMarkerIcon.iconAnchor = new GPoint(6, 29);               
   rkMap.resumeMarkerOptions = { icon:resumeMarkerIcon, clickable:false };

   // Set the distance units
   rkMap.distanceUnits = distanceUnits;
   
   // Set the conversion factor for meters to miles or kilometers
   rkMap.distanceConverter = kmPerMeter;
   rkMap.speedConverter    = kmphPerMps;
   rkMap.speedUnits        = "km/h";
   
   if (rkMap.distanceUnits == "mi")
   {
      rkMap.distanceConverter = milesPerMeter;
      rkMap.speedConverter    = mphPerMps;
      rkMap.speedUnits        = "mph";
   }
   
   return rkMap;
}

/**
 * Refreshes the given rkMap object.
 * NOTE: Prior to calling, a global variable appRoot needs to be defined to specify
 *       the base url-path for map marker icon images.
 */
function updateMap(rkMap, showRunner)
{
   if (rkMap == null) return;

   var gpoints                 = []; // All google-map points (lat/lon) of the current trip
   var pauseMarkerPoints       = []; // All pause markers (lat/lon) of the current trip
   var resumeMarkerPoints      = []; // All resume markers (lat/lon) of the current trip
   var totalDistance           = 0;  // The summed distance of the trip (in meters)
   var totalDistanceUnits      = 0;  // The summed distance of the trip (in miles or km)
   var totalDistanceWholeUnits = 0;  // The rounded down distance (in miles or km)
  
   // Hide the markers.
   rkMap.startMarker.hide();
   rkMap.endMarker.hide();
   rkMap.lastMarker.hide();
  
   // Clean out the marker-managers.
   rkMap.distanceMarkerManager.clearMarkers();
   rkMap.pauseMarkerManager.clearMarkers();
   rkMap.resumeMarkerManager.clearMarkers();
  
   // Clean out the old trip polylines
   for (var i = 0; i < rkMap.tripPolylines.length; i++)
   {
      rkMap.tripPolylines[i].disableEditing();
      rkMap.gmap.removeOverlay(rkMap.tripPolylines[i]);
   }
   
   // Clean out old route polyline
   if (rkMap.routePolyline)
   {
      rkMap.gmap.removeOverlay(rkMap.routePolyline);
   }
   
   // Create the new route polyline if there are points
   if (rkMap.routePoints)
   {
      var routeLatLngs = [];
    
      for (var i = 0; i < rkMap.routePoints.length; i++)
      {
         p = rkMap.routePoints[i];
         routeLatLngs.push(new GLatLng(p.latitude, p.longitude));
      }
       
      rkMap.routePolyline = new GPolyline(routeLatLngs, "#0000FF", 4);   
      rkMap.gmap.addOverlay(rkMap.routePolyline);            
   }
   
   // Start a new set of polylines for the new trip point-set
   rkMap.tripPolylines = [];
  
   // We create and use a hashmap keyed by distance-traveled and mapping to points
   rkMap.pointsByDistanceTraveled = {};

   // Loop through all the new trip points
   for (var i = 0; i < rkMap.points.length; i++)
   {
      // Add to the total distance traveled
      totalDistance += rkMap.points[i].deltaDistance;
  
      // Add the gpoint to the points array and the rk point to the hashmap
      // and increase the bounding rectangle as necessary.
      gpoints[gpoints.length] = new GLatLng(rkMap.points[i].latitude, rkMap.points[i].longitude);
      var key = ("d" + (totalDistance * rkMap.distanceConverter)).replace(".", "p");
      rkMap.pointsByDistanceTraveled[key] = rkMap.points[i];

      // Save off the previous total distance (in the current units) for the purposes
      // of establishing the ratio necessary to interpolate the distance marker prior
      // to saving off the latest total distance in the current units
      var totalDistanceOldUnits = totalDistanceUnits;
      totalDistanceUnits = totalDistance * rkMap.distanceConverter;

      // If we have crossed over a new distance marker...
      // Note: parseInt() is equivalent to a floor() function for rounding down
      if ((i > 0) && (parseInt(totalDistanceUnits) > totalDistanceWholeUnits))
      {
         // Save off the number of distance markers that will have
         // to be interpolated. It's possible that there is a delta
         // distance which results in the need of multiple distance
         // markers being interpolated at a single stroke. Admittedly
         // this would make the entire trip rather useless, but we
         // should still handle that situation with the distance markers.
         var numMarkers = parseInt(parseInt(totalDistanceUnits) - totalDistanceWholeUnits);
   
         // Do the actual interpolation of all the distance markers
         // Note: Iterating from 1 on purpose here as we want to set
         // the markerNumber to the next whole number.
         for (var m = 1; m <= numMarkers; m++)
         {
            // Create the appropriate icon
            var markerNumber = totalDistanceWholeUnits + m;
            var distanceMarkerIcon = new GIcon(G_DEFAULT_ICON);
            distanceMarkerIcon.image = staticRoot + "/kronos/images/map/marker" + markerNumber + "_" + rkMap.distanceUnits + ".png";
            distanceMarkerIcon.shadow = "";
            distanceMarkerIcon.iconSize = new GSize(19, 39);
            distanceMarkerIcon.iconAnchor = new GPoint(10, 39);
            var distanceMarkerOptions = { icon: distanceMarkerIcon, clickable:false };
   
            // Determine the lat/lon of the distance markers by interpolating between
            // the lat/lon of the previous point and the lat/lon of the current point.
            var ratio = (markerNumber - totalDistanceOldUnits) / (totalDistanceUnits - totalDistanceOldUnits);
            var oldLat = rkMap.points[i-1].latitude;
            var curLat = rkMap.points[i].latitude;
            var oldLon = rkMap.points[i-1].longitude;
            var curLon = rkMap.points[i].longitude;
            var markerLat = oldLat + ratio * (curLat - oldLat);
            var markerLon = oldLon + ratio * (curLon - oldLon);
     
            // Create the marker point and ...
            var markerPoint = new GLatLng(markerLat, markerLon);
   
            // ... add it to the marker manager, noting that mile markers divisible by/ 10
            // (aka 10, 20, 30 etc...) should be displayed at a further out zoom level.
            var zoomLevel = 13
            if (parseInt(markerNumber/10) == markerNumber/10)
            {
               zoomLevel = 9;
            }
            rkMap.distanceMarkerManager.addMarker(new GMarker(markerPoint, distanceMarkerOptions), zoomLevel);
         }
     
         // reset to the latest distance marker for the next go-around
         totalDistanceWholeUnits = parseInt(totalDistanceUnits);
      }
           
      // Draw the appropriate marker for the point
      if (rkMap.points[i].type == "StartPoint")
      {
         rkMap.startMarker.setLatLng(gpoints[gpoints.length - 1]);
         rkMap.startMarker.show();
      }
      else if (rkMap.points[i].type == "ResumePoint")
      {
         resumeMarkerPoints.push(new GMarker(gpoints[gpoints.length - 1], rkMap.resumeMarkerOptions));
      }
      else if (rkMap.points[i].type == "PausePoint")
      {
         pauseMarkerPoints.push(new GMarker(gpoints[gpoints.length - 1], rkMap.pauseMarkerOptions)); 
      }
      else if (rkMap.points[i].type == "EndPoint")
      {
         rkMap.endMarker.setLatLng(gpoints[gpoints.length-1]);
         rkMap.endMarker.show();
      }
      else if (i == (rkMap.points.length-1))
      {
         if(showRunner)
         {
            rkMap.lastMarker.setLatLng(gpoints[gpoints.length-1]);
            rkMap.lastMarker.show();
         }
         else
         {
            rkMap.endMarker.setLatLng(gpoints[gpoints.length-1]);
            rkMap.endMarker.show();
         }
      }

      // Handle a segment end-point
      if ((rkMap.points[i].type == "PausePoint") || 
          (rkMap.points[i].type == "EndPoint")   ||
          (i == (rkMap.points.length-1)))
      {
         // Create the GPolyline for this segment
         rkMap.tripPolylines[rkMap.tripPolylines.length] = new GPolyline(gpoints, "#FF0000", 4);
     
         // Clear out the gpoints[] array since we'll be starting a new segment if there are more points
         gpoints = [];
      }
   }
     
   // Draw the polyline segment overlays for the trip
   for (var i = 0; i < rkMap.tripPolylines.length; i++)
   {
      rkMap.gmap.addOverlay(rkMap.tripPolylines[i]);
   }

   // Refresh all the markers
   rkMap.pauseMarkerManager.addMarkers(pauseMarkerPoints, 13);
   rkMap.resumeMarkerManager.addMarkers(resumeMarkerPoints, 13);
   rkMap.pauseMarkerManager.refresh();
   rkMap.resumeMarkerManager.refresh();
   rkMap.distanceMarkerManager.refresh();
}

/**
 * Refreshes the given rkMap object with status update markers.
 */
function overlayStatusUpdates(rkMap)
{
   if (rkMap == null) return;
   
   if (!rkMap.statusUpdateMarkerManager)
   {
      rkMap.statusUpdateMarkerManager = new MarkerManager(rkMap.gmap);
   }
   else
   {
      rkMap.statusUpdateMarkerManager.clearMarkers();
   }
   
   if (!rkMap.statusUpdatePopup)
   {
      rkMap.statusUpdatePopup = new MapTooltip("statusUpdatePopup", "statusUpdatePopup");
      rkMap.statusUpdatePopup.anchorX = "left";
      rkMap.statusUpdatePopup.anchorY = "top";
      rkMap.statusUpdatePopup.offsetX = 15;
      rkMap.gmap.addOverlay(rkMap.statusUpdatePopup);
      
      $("#statusUpdatePopup").fadeOut();
      $("#statusUpdatePopup").mouseleave(function(){
         $("#statusUpdatePopup").fadeOut();
      });
   }
   
   // Loop through all the status updates
   for (var i = 0; i < rkMap.statusUpdates.length; i++)
   {
      var statusUpdateIcon = new GIcon(G_DEFAULT_ICON);
      
      if (rkMap.statusUpdates[i].photoKey == undefined) // text only
      {
         statusUpdateIcon.image      = staticRoot + "/kronos/images/map/text.png";
         statusUpdateIcon.shadow     = "";
         statusUpdateIcon.iconSize   = new GSize(23, 33);
         statusUpdateIcon.iconAnchor = new GPoint(11, 32);
      }
      else // photo
      {
         statusUpdateIcon.image      = staticRoot + "/kronos/images/map/photos.png";
         statusUpdateIcon.shadow     = "";
         statusUpdateIcon.iconSize   = new GSize(24, 36);
         statusUpdateIcon.iconAnchor = new GPoint(12, 35);
      }
      
      var statusUpdateMarkerOptions = { icon: statusUpdateIcon };

      // Create the marker point
      var markerPoint           = new GLatLng(rkMap.statusUpdates[i].latitude, rkMap.statusUpdates[i].longitude);
      var statusUpdateMarker    = new GMarker(markerPoint, statusUpdateMarkerOptions);
      statusUpdateMarker.update = rkMap.statusUpdates[i];
      rkMap.statusUpdateMarkerManager.addMarker(statusUpdateMarker, 9);
      
      GEvent.addListener(statusUpdateMarker, "mouseover", function()
      {
         var html = "";
         
         if (this.update.photoKey != undefined)
         {
            html += "<img class='mapPhotoThumb' src='" + statusUpdatePhotoBaseUrl + "/" + this.update.photoKey + "_small.jpg' width='50' height='50' border='0' id='statusUpdatePopupPhoto' style='cursor:pointer'/>";
         }
         
         if (this.update.text != undefined)
         {
            html += "<div class='statusUpdatePopupText'>" + this.update.text + "</div>";
         }
         
         rkMap.statusUpdatePopup.setHtml(html);
         var picId = this.update.photoKey;
         
         if (this.update.photoKey != undefined)
         {
            $("#statusUpdatePopupPhoto").click(function(){$("#" + picId).click();});
            rkMap.statusUpdatePopup.offsetY = $("#" + rkMap.statusUpdatePopup.id).height();
         }
         else // text only
         {
            rkMap.statusUpdatePopup.offsetY = $("#" + rkMap.statusUpdatePopup.id).height() + 20;
         }
         
         rkMap.statusUpdatePopup.setLocation(this.getLatLng());
         $("#statusUpdatePopup").stop(true, true);
         $("#statusUpdatePopup").fadeIn();
      });
   }
   
   rkMap.statusUpdateMarkerManager.refresh();
}

/**
 * 
 */
function fitPath(rkMap)
{
   // Start with a fresh bounds which will be sized to fit the trip path
   rkMap.bounds = new GLatLngBounds();

   // Loop through all the new trip points
   for (var i = 0; i < rkMap.tripPolylines.length; i++)
   {
      for (var j = 0; j < rkMap.tripPolylines[i].getVertexCount(); j++)
      {
         rkMap.bounds.extend(rkMap.tripPolylines[i].getVertex(j));
      }
   }
   
   // Center the map for the current trip bounds
   rkMap.gmap.setCenter(rkMap.bounds.getCenter(), rkMap.gmap.getBoundsZoomLevel(rkMap.bounds));
}

/**
 * Positions the current location marker at the specified lat/lon
 */
function setCurrLoc(rkMap, lat, lon)
{
	rkMap.currLocMarker.setLatLng(new GLatLng(lat, lon));
	rkMap.currLocMarker.show();
}