/* Copyright 2010 FitnessKeeper, Inc.  All Rights Reserved. */

//utility functions
function rad(x)
{
   return x * Math.PI / 180;
}

function rawDistHaversine(lat1, lng1, lat2, lng2)
{
   var R = 6371; // earth's mean radius in km
   var dLat = rad(lat2 - lat1);
   var dLong = rad(lng2 - lng1);

   var a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.cos(rad(lat1)) * Math.cos(rad(lat2))
         * Math.sin(dLong / 2) * Math.sin(dLong / 2);
   var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
   var d = R * c;

   return d.toFixed(3);
}

function distHaversine(p1, p2)
{
   return rawDistHaversine(p1.lat(), p1.lng(), p2.lat(), p2.lng());  
}

function lengthSquared(v1,v2)
{
   return Math.pow(v1[0] - v2[0], 2) + Math.pow(v1[1] - v2[1], 2);
}

function distance(v1,v2)
{
	return Math.sqrt(lengthSquared(v1,v2));
}

function add(v1, v2)
{
	return [v1[0] + v2[0], v1[1] + v2[1]];	
}

function scale(v1, s) 
{
	return [v1[0] * s, v1[1]*s];
}

function subtract(v1, v2)
{
	return [v1[0] - v2[0], v1[1] - v2[1]];
}

function dotProduct(v1, v2)
{
	return v1[0] * v2[0] + v1[1] * v2[1]; 
}

function distanceFromPointToLineSegment(v, w, p)
{	
	if(v == null || w == null) 
		return Number.MAX_VALUE;
	
	//convert into handy vectors
	var v = [v.lat(), v.lng()];
	var w = [w.lat(), w.lng()];
	var p = [p.lat(), p.lng()];
		
   // Return minimum distance between line segment vw and point p
   var l2 = lengthSquared(v, w);  // i.e. |w-v|^2 -  avoid a sqrt
   if (l2 == 0.0) 
	   return distance(p, v);   // v == w case

   // Consider the line extending the segment, parameterized as v + t (w - v).
   // We find projection of point p onto the line. 
   // It falls where t = [(p-v) . (w-v)] / |w-v|^2
   var t = dotProduct(subtract(p, v), subtract(w, v)) / l2;
   
   if (t < 0) { 
	   return distance(p, v);       // Beyond the 'v' end of the segment
   }
   else if (t > 1.0) {
	   return distance(p, w);  // Beyond the 'w' end of the segment
   }

   var projection = add(v,scale(subtract(w, v), t));  // Projection falls on the segment
   return distance(p, projection); 
}

//end utility functions

var renderFactory = {
      
   createActivityPolyOptions : function()
   {
      return {      
         strokeColor : '#FF0000',
         strokeOpacity : .5,
         strokeWeight : 4
      };
   },   
   
   createRoutePolyOptions : function()
   {
      return {      
         strokeColor : '#0000FF',
         strokeOpacity : .5,
         strokeWeight : 4
      };
   },

   verticeIcon : new google.maps.MarkerImage(staticRoot + "/kronos/images/map/vertice-up.png", 
                  new google.maps.Size(16, 15),
                  new google.maps.Point(0, 0),
                  new google.maps.Point(8, 8)),
   
   startIcon : new google.maps.MarkerImage(staticRoot + "/kronos/images/map/rk_start.png", 
                  new google.maps.Size(11, 29),
                  new google.maps.Point(0, 0),
                  new google.maps.Point(6, 29)),

   endIcon : new google.maps.MarkerImage(staticRoot + "/kronos/images/map/rk_end.png", 
                  new google.maps.Size(11, 29),
                  new google.maps.Point(0, 0),
                  new google.maps.Point(6, 29)),   
   
   pauseIcon : new google.maps.MarkerImage(staticRoot + "/kronos/images/map/rk_pause.png", 
           		  new google.maps.Size(11, 29),
           		  new google.maps.Point(0, 0),
           		  new google.maps.Point(6, 29)),

   resumeIcon : new google.maps.MarkerImage(staticRoot + "/kronos/images/map/rk_resume.png", 
		   		  new google.maps.Size(11, 29),
		   		  new google.maps.Point(0, 0),
		   		  new google.maps.Point(6, 29)),
		   		  
   currentLocationIcon : new google.maps.MarkerImage(staticRoot + "/kronos/images/map/curr_loc.png", 
	   		  new google.maps.Size(52, 52),
	   		  new google.maps.Point(0, 0),
	   		  new google.maps.Point(26, 26))
};

//from constants_js
var KM_PER_MILE = (1/milesPerMeter)/1000;

/*
 * Segment = significantPoints * renderArcs * polyLine * postSegmentDelay 
 */

var mapController = {
   model : {            
         initialized : false,
         
         //UI options
         useMiles : true,         
         initialPoints : [],    
         initialRoutePoints : null,
         editable : false,
         fixToRoads : false,
         gpsMode : false,         
                     
         //core model           
         activitySegments : [],         
                  
         //renderCache
         map : null,                  
         distanceMarkers : [],   
         routePolyline : null,

         mouseOverEditVertice : null,
         mouseOverEditVerticeTimeout : null,
         
         currentLocationVertice : null,

         //drag modeling
         segmentUnderDragIndex : null,
         segmentUnderDrag : null, 
         segmentSignificantPointUnderDragIndex : null,
         
         
         //callbacks
         distanceUpdated : $.noop,
         mapEdited : [],
         mapReset : [],
         
         //cache data
         distance : 0,
                 
         //undo
         undoStack : []
   },
   
   respondToResize : function() {	   
	   google.maps.event.trigger(mapController.model.map, 'resize');
   },
         
   directionsService : new google.maps.DirectionsService(),
   
   renderRoutePoints : function(routePoints)
   {  
	  if(mapController.model.routePolyline) {
		  mapController.model.routePolyline.setMap(null);
	  }
	  
	  mapController.model.initialRoutePoints = routePoints;
	   
	  if(mapController.model.initialRoutePoints.length > 0)      
	  {
		 var points = [];
         var bounds = new google.maps.LatLngBounds();     
          
         for(var i = 0; i < mapController.model.initialRoutePoints.length; i++) {
            var point = mapController.model.initialRoutePoints[i];                         
            var latLng = new google.maps.LatLng(point.latitude, point.longitude);         
            bounds.extend(latLng);
            points.push(latLng);
         }
         
         mapController.model.routePolyline = new google.maps.Polyline(renderFactory.createRoutePolyOptions());
         mapController.model.routePolyline.setMap(mapController.model.map);
         mapController.model.routePolyline.setPath(points);          
         
         mapController.model.map.fitBounds(bounds);
      }	  	  
   },
   
   hasActivityData : function() {
	   var hasData = false;
	   $.each(mapController.model.activitySegments, function(segmentIndex, segment){			 
			 $.each(segment.getSignificantPoints(), function(significantPointIndex, significantPoint){
				 //we have a sign point? Call it a map!
				 hasData = true;
			 });
		  });
	   
	   return hasData;
   },
   
   clear : function() {	   
	  //first detach all of our old segments and the drop 'em
	  $.each(mapController.model.activitySegments, function(segmentIndex, segment){
		 segment.detach();			
		 $.each(segment.getSignificantPoints(), function(significantPointIndex, significantPoint){
				if(significantPoint.editVertice) {
					significantPoint.editVertice.setMap(null);
					significantPoint.editVertice = null;
				}
		 });
	  });
	  
	  mapController.model.activitySegments = [];
	  mapController.model.undoStack = []; //until reset is undoable, drop undo
   },
   
   renderActivityPoints : function(activityPoints, triggerMapEdited)
   {
	  //just in case we had previous data, clear it:
	  mapController.clear();
	   
	  if(!triggerMapEdited) {
	   mapController.model.initialPoints = activityPoints;
	  }
	  
	  var significantPoints = [];
      var renderOnlyPointArc = [];
      var currentRenderArc = [];
      var currentDeltaPause = null;
      
      function closeSegment() {
    	  var segment = new Segment(mapController.model.map, 
				  renderFactory, 
				  significantPoints, 
				  renderOnlyPointArc, 
				  currentDeltaPause);

    	  segment.setMouseMoveHandler(mapController.mouseMoveOverPolyline);

    	  mapController.addSegment(segment);            	

    	  //reset temp vars as we continue
    	  significantPoints = [];
    	  renderOnlyPointArc = [];           
    	  currentDeltaPause = null;
      };
      
      if(activityPoints.length > 0)
      {
         var bounds = new google.maps.LatLngBounds();     
          
         for(var i = 0; i < activityPoints.length; i++) {
            var point = activityPoints[i];             
            
            if(currentDeltaPause == null && point.deltaPause) {            	
            	currentDeltaPause = point.deltaPause;
            }
            
            var latLng = new google.maps.LatLng(point.latitude, point.longitude);         
            latLng.deltaTime = point.deltaTime;  
            
            bounds.extend(latLng);
            
            //if its a trip point and we ARENT gps mode, its 'insignificant'
            if(point.type == "TripPoint" && !mapController.model.gpsMode) {
            	currentRenderArc.push(latLng);
            }
            else {
            	renderOnlyPointArc.push(currentRenderArc);
            	currentRenderArc = [];
            	significantPoints.push(latLng);
            }
            
            //do we need to end the current segment?
            if(point.type == "PausePoint" || point.type == "EndPoint") {
            	closeSegment();
            }                                                             
         }
         
         //if we've made it this far, but the segment is still open
         //close it. This is likely due to missing end point.
         if(significantPoints.length > 0) {
        	 closeSegment();
         }
         
         mapController.model.map.fitBounds(bounds);
      }
      else {
    	  var segment = new Segment(mapController.model.map, renderFactory, [], [], 0);
    	  segment.setMouseMoveHandler(mapController.mouseMoveOverPolyline);
    	  mapController.addSegment(segment);
      }
      
      mapController.regenerateDistanceMarkers();
      
      if(triggerMapEdited) {
    	  mapController.triggerMapEdited();
      }
   },
  
   initialize : function(params, mapOptions) {
	  if(mapController.model.initialized == true) {
         return;
      }
	  
      if(params.distanceUnits === "km") {
          mapController.model.useMiles = false;
       }
                 
      mapOptions.draggableCursor = 'crosshair';
      
      mapOptions.scrollwheel = false;
      
      mapController.model.map = new google.maps.Map($("#" + params.map_div_id).get(0), mapOptions);

      if(params.editable) {
         mapController.model.editable = params.editable;
      }
      
      if(params.fixToRoads) {
         mapController.model.fixToRoads = params.fixToRoads;
      }
                     
      if(params.gpsMode) {
         mapController.model.gpsMode = params.gpsMode;
      }
      
      if(params.routePoints) {
         mapController.renderRoutePoints(params.routePoints);
      }
      
      if(params.points) {  
         mapController.renderActivityPoints(params.points);                                
      } else {
    	 mapController.renderActivityPoints([]);
      }
                              
      // Add a listener for the click event
      google.maps.event.addListener(mapController.model.map, 'click', mapController.addFixedPoint);   

      //setup a mouse over edit vertice (invisible)        
      mapController.model.mouseOverEditVertice =  new google.maps.Marker( {           
            map : mapController.model.editable ? mapController.model.map : null,              
            icon : renderFactory.verticeIcon,
            draggable : true,
            visible : false
       });     

       mapController.model.mouseOverEditVertice.isMouseOverEditVertice = true;
       mapController.addEventHandlerstoEditVertice(mapController.model.mouseOverEditVertice, null);
              
       mapController.model.initialized = true;
   },

   setFixToRoad : function(snap) {
      mapController.model.fixToRoads = snap;
   },
   
   reset : function() { 
	  mapController.clear();	   
      mapController.renderActivityPoints(mapController.model.initialPoints);      
      
      for(var i = 0, n = mapController.model.mapReset.length; i < n; i++) {
 		   mapController.model.mapReset[i](); //let callback know
        }
   },
   
   undo : function() {
      var undoFunction = mapController.model.undoStack.pop();
      
      if(undoFunction != null) {
         undoFunction();     
         
         //force
         mapController.regenerateDistanceMarkers();
         
         for(var i = 0, n = mapController.model.mapEdited.length; i < n; i++) {
  		   mapController.model.mapEdited[i](); //let callback know
         }
                  
         //assume distance updated by undo
         if(mapController.model.distanceUpdated) {        	 
            mapController.model.distanceUpdated(mapController.model.distance);         
         }         
      }
   },
   
   //private really
   triggerMapEdited : function() {	   
       for(var i = 0, n = mapController.model.mapEdited.length; i < n; i++) {
  		   mapController.model.mapEdited[i](); //let callback know
       }
   },
   
   //private really
   pushUndoEdit : function(f) {	   
	   mapController.model.undoStack.push(f);
	   mapController.triggerMapEdited();
   },
   
   addFixedPoint : function(event) {
      if(!mapController.model.editable) {
         return
      }
      
      var point = event.latLng;
      var lastSegmentIndex = mapController.model.activitySegments.length - 1;      
      var lastSegment = mapController.model.activitySegments[lastSegmentIndex];          
      lastSegment.appendSignificantPoint(point, mapController.model.fixToRoads, function(modifiedPoint, modifiedSignificantPointIndex){
    	  var previousLastSignificantPointIndex = modifiedSignificantPointIndex - 1;
    	  
    	  mapController.createOrUpdateEditVertice(lastSegmentIndex, lastSegment, modifiedSignificantPointIndex, modifiedPoint); //to create a new vertice
      	  
      	  if(previousLastSignificantPointIndex > 0) //update last guys vertice to correct his graphic icon
      		  mapController.createOrUpdateEditVertice(lastSegmentIndex, lastSegment, previousLastSignificantPointIndex, lastSegment.getSignificantPoint(previousLastSignificantPointIndex)); 
          
          //setup undo
          mapController.pushUndoEdit(function(){
        	  var lastPoint = lastSegment.removeLastSignificantPoint();
        	  lastPoint.editVertice.setMap(null);
        	      	      	  
        	  if(previousLastSignificantPointIndex > 0) //update last guys vertice to correct his graphic icon
          		  mapController.createOrUpdateEditVertice(lastSegmentIndex, lastSegment, previousLastSignificantPointIndex, lastSegment.getSignificantPoint(previousLastSignificantPointIndex));
          });  
      });  
      
      mapController.regenerateDistanceMarkers();
   },
 
   addEventHandlerstoEditVertice : function(editVertice) {     
     
	  // Add right click handler for removing signficant points
      google.maps.event.clearListeners(editVertice, 'rightclick');
      google.maps.event.addListener(editVertice, 'rightclick', function(){         
    	 if(editVertice.isMouseOverEditVertice) { return; } //ignore
    	 
    	 var targetSegment = editVertice.segment;
    	 var targetSegmentIndex = editVertice.segmentIndex;
    	 var targetSignificantPointIndex = editVertice.significantPointIndex;
    	 
    	 var sigPointCount = targetSegment.getSignificantPoints().length;
    	 
    	 //for undo closure
    	 var renderArc = targetSegment.getRenderOnlyPointArc(targetSignificantPointIndex);
    	 var postRenderArc = null;
    	 
    	 if(targetSignificantPointIndex < sigPointCount - 1) {
    		 postRenderArc = targetSegment.getRenderOnlyPointArc(targetSignificantPointIndex + 1);
    		 //zero out the render arc leading up to the next point
    		 targetSegment.setRenderOnlyPointArc(targetSignificantPointIndex + 1, []);    		 
    	 }
    	     	 
    	 targetSegment.removeSignificantPointAtIndex(targetSignificantPointIndex, function(removedPoint){
    		             		 
    		 removedPoint.editVertice.setMap(null);
    		 
    		 if(targetSignificantPointIndex == sigPointCount - 1) { //we were the last, update the new last    			 
         		  mapController.createOrUpdateEditVertice(targetSegmentIndex, targetSegment, targetSignificantPointIndex - 1, targetSegment.getSignificantPoint(targetSignificantPointIndex - 1));
    		 } 
    		 else if(targetSignificantPointIndex == 0) { //we were the first, update the new first
    			 mapController.createOrUpdateEditVertice(targetSegmentIndex, targetSegment, 0, targetSegment.getSignificantPoint(0));
    		 }
         	
	   		 //update our edit vertice mappings.... basically we need to re-jigger the edit vertices locations
	   		 //for vertices past this one since their numerals just decremented
	   		 var points = targetSegment.getSignificantPoints();	   		 
	   		 for(var i = targetSignificantPointIndex; i < points.length; i++) {  	   			 
	   			 points[i].editVertice.significantPointIndex = i;
	   		 }
	   		         		     		 
             //register the undo
             mapController.pushUndoEdit(function(){            	 
            	 targetSegment.insertSignficantPointAtIndex(targetSignificantPointIndex, removedPoint, function(insertedPoint){
                	 
            		 targetSegment.setRenderOnlyPointArc(targetSignificantPointIndex, renderArc);
                	 
            		 if(postRenderArc != null) 
            			 targetSegment.setRenderOnlyPointArc(targetSignificantPointIndex + 1, postRenderArc);            		 
            		 
                	 removedPoint.editVertice.setMap(mapController.model.map); 
                	 
                	 if(targetSignificantPointIndex == sigPointCount - 1) { //we are the new last, update the previous last
                		  mapController.createOrUpdateEditVertice(targetSegmentIndex, targetSegment, targetSignificantPointIndex - 1, targetSegment.getSignificantPoint(targetSignificantPointIndex - 1));
                	 } 
                	 else if(targetSignificantPointIndex == 0) { //we are the new first, update the previous first
                		 mapController.createOrUpdateEditVertice(targetSegmentIndex, targetSegment, 1, targetSegment.getSignificantPoint(1));
                	 }
                	 
                	 //update our edit vertice mappings.... basically we need to re-jigger the edit vertices locations
        	   		 //for vertices past this one since their numerals should be incremented
        	   		 var points = targetSegment.getSignificantPoints();
        	   		 for(var i = targetSignificantPointIndex; i < points.length; i++) {    			  
        	   			  points[i].editVertice.significantPointIndex = i;
        	   		 }
            	 });
             });
    	 });
    	 
    	 mapController.regenerateDistanceMarkers();
      });
	   
      // Add dragging event listeners.
      google.maps.event.clearListeners(editVertice, 'dragstart');
      google.maps.event.addListener(editVertice, 'dragstart', function() {     
    	  
    	  var position = editVertice.getPosition();
    	  
    	  var targetSegment = null;
    	  var targetSegmentIndex = null;
    	  var targetSignificantPointIndex = null;    	     	      	  
    	      	    	  
          if(editVertice.isMouseOverEditVertice) {
        	  //make sure this dynamic vertex doesn't go away on us
              clearTimeout(mapController.model.mouseOverEditVerticeTimeout);        

              var closestData = null;
              
              //find the best segment to modify, and the best point therein.
    		  $.each(mapController.model.activitySegments, function(segmentIndex, segment){
    			 var candidateData = segment.getPointClosestDataTo(position);        	 
    			 if(closestData == null || candidateData.closestDistance < closestData.closestDistance) {
    				 closestData = candidateData;				 
    				 targetSegmentIndex = segmentIndex;
    			 }        	
    		  });    	
    		      		      		  
    		  targetSignificantPointIndex = closestData.closestSignificantPointIndex;     		  
    		  targetSegment = mapController.model.activitySegments[targetSegmentIndex];    		
    		  
    		  //capture for undo closure before we insert a point    		  
    		  var renderArc = targetSegment.getRenderOnlyPointArc(targetSignificantPointIndex);
    		      		  
    		  //create the new point, but don't create a specific vertice for it until 
    		  //we are done dragging the mouseOver editVertice
    		  targetSegment.insertSignficantPointAtIndex(targetSignificantPointIndex, position, function(targetSignificantPoint){});
    		      		  
    		  //update our edit vertice mappings.... basically we need to increment all edit vertices locations
    		  //in for vertices past this one. Ignore current one however, it doesnt exist till dragend.
    		  var points = targetSegment.getSignificantPoints();
    		  for(var i = targetSignificantPointIndex + 1; i < points.length; i++) {    			  
    			  points[i].editVertice.significantPointIndex = i;
    		  }
    		     
    		  //setup undo
        	  mapController.pushUndoEdit(function(){        		  
        		  //decrement all edit vertice mappings, remove this edit vertice
        		  targetSegment.removeSignificantPointAtIndex(targetSignificantPointIndex, function(removedPoint){
        			  removedPoint.editVertice.setMap(null);
        			  //restore previous renderArcs manually (not a fan, a bit abstraction breaking, but simplest way
        			  //w/o introducting undoStock and "beginEditGroup" "endEditGroup" concepts to segment
        			  targetSegment.setRenderOnlyPointArc(targetSignificantPointIndex, renderArc);
        		  });
        		  
        		  var points = targetSegment.getSignificantPoints();
        		  for(var i = targetSignificantPointIndex; i < points.length; i++) {        			  
        			  points[i].editVertice.significantPointIndex = i;
        		  }        		 
        	  });
          } 
          else {
        	  //we implicitly know the best segment to modify by way of editVertice        	  
        	  targetSegmentIndex = editVertice.segmentIndex;
        	  targetSignificantPointIndex = editVertice.significantPointIndex;        	  
        	  targetSegment = mapController.model.activitySegments[targetSegmentIndex]; 
        	  
        	  //capture for undo closure
        	  var initialLatLng = targetSegment.getSignificantPoint(targetSignificantPointIndex);        	  
        	  var renderArc = targetSegment.getRenderOnlyPointArc(targetSignificantPointIndex);
        	  var preRenderArc = null;
        	  if(targetSignificantPointIndex > 0)
        		  preRenderArc= targetSegment.getRenderOnlyPointArc(targetSignificantPointIndex - 1);
        	  
        	  //setup undo
        	  mapController.pushUndoEdit(function(){        		          		  
        		  targetSegment.changePositionOfSignificantPoint(targetSignificantPointIndex, initialLatLng, false, function(){
        			  //restore previous renderArcs manually (not a fan, a bit abstraction breaking, but simplest way
        			  //w/o introducting undoStock and "beginEditGroup" "endEditGroup" concepts to segment
        			  targetSegment.setRenderOnlyPointArc(targetSignificantPointIndex, renderArc);
        			  if(preRenderArc != null)
        				  targetSegment.setRenderOnlyPointArc(targetSignificantPointIndex - 1, preRenderArc);
        		  });
        		  
        		  initialLatLng.editVertice.setPosition(initialLatLng);
        	  });
          }
                    
          mapController.model.segmentUnderDrag = targetSegment;
          mapController.model.segmentUnderDragIndex = targetSegmentIndex
          mapController.model.segmentSignificantPointUnderDragIndex = targetSignificantPointIndex;
          
		  //clear out distance markers during drag rendering
		  mapController.clearDistanceMarkers();       		                                           
      });

      google.maps.event.clearListeners(editVertice, 'drag');
      google.maps.event.addListener(editVertice, 'drag', function() {        
    	 var latLng = editVertice.getPosition();
    	 var previousPoint = mapController.model.segmentUnderDrag.getSignificantPoint(mapController.model.segmentSignificantPointUnderDragIndex);
    	 mapController.model.segmentUnderDrag.changePositionOfSignificantPoint(mapController.model.segmentSignificantPointUnderDragIndex, 
    			 editVertice.getPosition(), mapController.model.fixToRoads, function(modifiedPoint, modifiedSignificantPointIndex){
    		 		//propagate type info & editVertice	if it has one (dragging mouse over edit vertice is special case where it does not)  
    		 		modifiedPoint.type = previousPoint.type;
    		 		
    		 		if(previousPoint.editVertice) {
    		 			modifiedPoint.editVertice = previousPoint.editVertice;
    		 			modifiedPoint.editVertice.significantPoint = modifiedPoint;
    		 		}
    	 });
      });

      google.maps.event.clearListeners(editVertice, 'dragend');
      google.maps.event.addListener(editVertice, 'dragend', function() {           	 
    	  //restore mouse over edit timeout which we disabled at start of drag
          mapController.model.mouseOverEditVerticeTimeout = setTimeout("mapController.hideMouseOverEditVertice()", 1000);
          
          //if this was a dynamically created point, we need to create a new editVertice for it.
          if(editVertice.isMouseOverEditVertice) {
        	mapController.createOrUpdateEditVertice(mapController.model.segmentUnderDragIndex, 
        											mapController.model.segmentUnderDrag, 
        											mapController.model.segmentSignificantPointUnderDragIndex, 
        											editVertice.getPosition()); //bugbug assuming position object === last saved during drag callback  
          }    
          
          mapController.regenerateDistanceMarkers();
      });
   },
 
   //private function, really
   mouseMoveOverPolyline : function(event) {                
       //update its position                     
       mapController.model.mouseOverEditVertice.setPosition(event.latLng);         
       //set a timeout to make it invisible
       if(mapController.model.mouseOverEditVerticeTimeout != null){
          clearTimeout(mapController.model.mouseOverEditVerticeTimeout);            
       }

       //Set it to auto-hide
       mapController.model.mouseOverEditVerticeTimeout = setTimeout("mapController.hideMouseOverEditVertice()", 1000);

       //make it visible
       if(mapController.model.mouseOverEditVertice.getVisible() != true) {
          mapController.model.mouseOverEditVertice.setVisible(true);            
       }
   },

   //private function, really
   hideMouseOverEditVertice : function(){
      mapController.model.mouseOverEditVertice.setVisible(false);
   },

   clearDistanceMarkers : function(){
      $.each(mapController.model.distanceMarkers, function(index, distanceMarker) {
         if(index > 0 && distanceMarker != null) {
            distanceMarker.setMap(null);
            mapController.model.distanceMarkers[index] = null;
         }
      });      
   },
   
   addSegment : function(segment) {	   
	   var numberSegments = mapController.model.activitySegments.push(segment);
	   var endSegmentIndex = numberSegments - 1;
	   
	   if(numberSegments > 1) {
		   //it can no longer be an "end" marker, update it
		   var previousEndSegmentIndex = numberSegments - 2;
		   var previousEndSegment = mapController.model.activitySegments[previousEndSegmentIndex];	
		   var previousEndSegmentSignificantPoints = previousEndSegment.getSignificantPoints();
		   var previousEndSignificantPointIndex = previousEndSegmentSignificantPoints.length - 1;
		   var previousEndSignificantPoint = previousEndSegmentSignificantPoints[previousEndSignificantPointIndex];
		   mapController.createOrUpdateEditVertice(previousEndSegmentIndex, previousEndSegment, previousEndSignificantPointIndex, previousEndSignificantPoint);
	   }
	   
	   $.each(segment.getSignificantPoints(), function(significantPointIndex, significantPoint){
		   mapController.createOrUpdateEditVertice(endSegmentIndex, segment, significantPointIndex, significantPoint);
	   });	   
   },
   
   createOrUpdateEditVertice: function(segmentIndex, segment, significantPointIndex, significantPoint) {
	   var lastSegmentIndex = mapController.model.activitySegments.length - 1;
	   var lastSignificantPointIndex = segment.getSignificantPoints().length - 1;
	   
	   var icon = null;
	   
	   if(segmentIndex == 0 && significantPointIndex == 0) {
			icon = renderFactory.startIcon;
		}
		else if(segmentIndex == lastSegmentIndex && significantPointIndex == lastSignificantPointIndex) {
			icon = renderFactory.endIcon;
		}
		else if(significantPointIndex == 0) {	    			
			icon = renderFactory.resumeIcon;
		}
		else if(significantPointIndex == lastSignificantPointIndex) {
			icon = renderFactory.pauseIcon;
		} 
		else {
			icon = renderFactory.verticeIcon;
		}
	   
		var editVertice = new google.maps.Marker({
            position : significantPoint,
            map : mapController.model.map,
            icon : icon,
            draggable : true
        });  
		
		editVertice.segment = segment;
		editVertice.segmentIndex = segmentIndex;
		editVertice.significantPoint = significantPoint;		
		editVertice.significantPointIndex = significantPointIndex;
		
		mapController.addEventHandlerstoEditVertice(editVertice);		
		
		if(significantPoint.editVertice) {
			//detach the old one
			significantPoint.editVertice.setMap(null);
		}
						
		significantPoint.editVertice = editVertice;				
   },
    
   regenerateDistanceMarkers : function() {           
     
	  var currentMarker = 0;
      var currentDistance = 0.0;
            
      $.each(mapController.model.activitySegments, function(segmentIndex, segment){
    	  
    	  //reset lastpoint between segments so we dont count that difference
    	  var lastPoint = null;
    	  
          $.each(segment.getPoints(), function(pointIndex, point) {
              if (lastPoint != null) {
                 var distanceAtLastPoint = currentDistance;
                 var distanceDiff = parseFloat(distHaversine(lastPoint, point));
                 if (mapController.model.useMiles) distanceDiff /= KM_PER_MILE;
                 currentDistance += distanceDiff;
           
                 //if our new total distance is at least 1 unit greater than when we last dropped a marker            
                 if (currentDistance >= currentMarker + 1) {
                    //for each integral unit above the last marker, we are, attempt to drop a marker
                    for ( var i = currentMarker + 1; i < currentDistance; i++) {
                       var ratio = (i - distanceAtLastPoint) / distanceDiff;
                       var dLat = (point.lat() - lastPoint.lat()) * ratio;
                       var dLng = (point.lng() - lastPoint.lng()) * ratio;         
                       var latLng = new google.maps.LatLng(lastPoint.lat() + dLat, lastPoint.lng() + dLng);
                       
                       if (mapController.model.distanceMarkers[i] == null) {                     
                          var icon = null;
                                               
                          if(mapController.model.useMiles)                     
                             icon = staticRoot + "/kronos/images/map/marker" + i + "_mi.png";
                          else
                             icon = staticRoot + "/kronos/images/map/marker" + i + "_km.png";                     
                          
                          mapController.model.distanceMarkers[i] = new google.maps.Marker( {
                             position : latLng,
                             map : mapController.model.map,
                             title : (mapController.model.useMiles ? "Mile " : "Kilometer ") + i,
                             icon : icon,
                             draggable : false,
                             clickable : false
                          });                                         
                       }
                       else { //just update the coord
                          mapController.model.distanceMarkers[i].setPosition(latLng);
                       }
              
                       currentMarker++;
                    }
                 }
              }
           
              lastPoint = point;
           });  
      });
            
      //clear out markers we didn't get to 
      for(var i = currentMarker + 1; i < mapController.model.distanceMarkers.length; i++) {
         var marker = mapController.model.distanceMarkers[i];
         
         if(marker != null) {
            marker.setMap(null);
            mapController.model.distanceMarkers[i] = null;
         }
      }
      
      mapController.model.distance = currentDistance;
      if(mapController.model.distanceUpdated)
         mapController.model.distanceUpdated(currentDistance);
   },
   
   onDistanceUpdated : function(f) {
      mapController.model.distanceUpdated = f;
   },
   
   onMapEdited : function(f) {
	 mapController.model.mapEdited.push(f);  
   },
   
   onMapReset : function(f) {
	 mapController.model.mapReset.push(f);
   },
   
   serializePoints : function()
   {         
      var result = "";      
         
      $.each(mapController.model.activitySegments, function(segmentIndex, segment){
    	  result += segment.serialize();
      });
      
      return result;     
   },
   
   getTotalDistance : function() {
      return mapController.model.distance;
   },
   
   getMapThumbnailEncoding : function() {	 	  
	  var tripPoints = [];
	  $.each(mapController.model.activitySegments, function(segmentIndex, segment){
		  tripPoints = tripPoints.concat(segment.getPoints());    	  
      });
	  
	  var maxPoints = 80; //don't over-run URL
	  
	  var filterLevel = 1;	  
	  if(tripPoints.length > maxPoints) {
		  filterLevel = Math.round(tripPoints.length / maxPoints);		  
	  }
	             
      var tripDataBuff = [];            
      for(var i=0,n=tripPoints.length; i < n; i += filterLevel) //only get every filterlevel point.            
      {         
		var point = tripPoints[i]; 
		tripDataBuff.push(point.lat().toFixed(5));
		tripDataBuff.push(",");
		tripDataBuff.push(point.lng().toFixed(5));
		
		if (i < tripPoints.length - 1)
		{
		   tripDataBuff.push("|");
		}         
      }

      return tripDataBuff.join("");
   },
   
   displayCurrentLocation : function(lat, lng) {
	   var position = new google.maps.LatLng(lat, lng);
	   if(!mapController.model.currentLocationVertice) {		   
		   mapController.model.currentLocationVertice = new google.maps.Marker( {
               position : position,
               map : mapController.model.map,               
               icon : renderFactory.currentLocationIcon,
               draggable : false,
               clickable : false
            });                                    
	   } 
	   else {
		   mapController.model.currentLocationVertice.setPosition(position);
	   }
   },
   
   removeCurrentLocation : function() {
	   if(mapController.model.currentLocationVertice) {
		   mapController.model.currentLocationVertice.setMap(null);
	   }
   }
};
