/**
 * The Active Google Map Route
 * This easy-to-use-tool lets you quickly create routes on google map with the click of your mouse.
 * Change Log:
 * 
 * July/15/2008
 * 		1. encapsulate the whole appplication into javascript class.
 * 		2. prototype based.
 *
 * @version 2.0
 * @requires prototype.js v 1.6.0.1 <http://www.prototypejs.org/>
 * @author Eric Yao <Eric.Yao@activenetwork.com>
 *-------------------------------------------------------------------------------------------------------------------*/
/**
 * This is the instance of the google route
 */
var routeInstance = null;
/**
 * Unit_Miles
 */
var UNITS_MILES = {
	label : "miles",
	f : function(distance) {
		return distance / 1609.344;
	},
	r : function(distance) {
		return distance * 1609.344;
	}
};
/**
 * Unit_Kms
 */
var	UNITS_KMS = {
	label: "kms",
	f : function(distance) {
		return distance / 1000;
	},
	r : function(distance) {
		return distance * 1000;
	}
};

var GoogleRoute = Object.extend(Class.create());
GoogleRoute.prototype = {
//
// Properties
//
	/**
	 * address search zoom Accuracy
	 * var {Array}
	 */
	SEARCH_ACCURACIES : [2, 4, 9, 11, 13, 14, 15, 16, 17],

	/**
	 * ID for GoogleRoute
	 * @var {Number}
	 */
	id : null,

	/**
	 * Google map instance
	 * @var {GMap2}
	 */	
	map : null,

	/**
	 * route command, default to be "new"
	 * "new" stands for making a new route
	 * "edit" stands for editing an old route
	 * "readonly" stands for viewing a route and no permission to edit it
	 */
	command : "new",

	/**
	 * container id, default to be 'map'
	 * @var {String}
	 */
	containerId : "map",

	/**
	 * the element which implement this css will display distance information for users
	 */
	showDistance : "span.showDistance",

	/**
	 * duration input hour id for dipslay duration#hour value for user
	 */
	durationHourId : "durationH",

	/**
	 * duration input minute id for dipslay duration#minute value for user
	 */
	durationMinuteId : "durationM",

	/**
	 * duration input second id for dipslay duration#second value for user
	 */
	durationSecondId : "durationS",

	/**
	 * pace input id for display pace value for user
	 */
	paceId : "pace",

	/**
	 * pace value
	 */
	paceValue : "",

	/**
	 * has pace flag
	 */
	hasPace : false,

	/**
	 * google map center.  San Diego = new GLatLng(37.4419, -122.1419) zoom:13
	 * @var {GLatLng}
	 */
	center : new GLatLng(42.16, -100.72),

	/**
	 * google map zoom level, default to be 4
	 * @var {Number}
	 */
	zoom : 4,

	/**
	 * google map type, default to be G_NORMAL_MAP
	 * @var {GMapType}
	 */	 
	mapType : G_NORMAL_MAP,

	/**
	 * To store the route points
	 * @var {Array[GoogleRoutePoint]}
	 */
	points : [],

	/**
	 * To store the route distance, default to be 0.0
	 * @var {Number}
	 */
	totalDistance : 0.0,

	/**
	 * To store the distance so far every time when add a new point to the route.
	 * @var {Array}
	 */
	distanceSofar : [],

	/**
	 * To store the integral distance number
	 * @var {Number}
	 */
	integralNumber : 0,

	/**
	 * To store the integral distnace markers
	 * @var {Array}
	 */
	integralMarkers : [],

	/**
	 * whether to show the integral marker, default to be true
	 * @var {boolean}
	 */
	showIntegralMarker : true,

	/**
	 * whether to show the waypoint marker, default to be true
	 * @var {boolean}
	 */
	showWaypointMarker : true,

	/**
	 * unit handler, default to miles
	 */
	unitHandler : UNITS_MILES,

	/**
	 * To store the points polyline overlay
	 * @var {Polyline}
	 */
	polylineOverlay : null,

	/**
	 * To store the waypoint type, default to be start, that means the start point of the route
	 * 'point' stands for normal point, 'water' stands for wather point etc.
	 * var {String}
	 */
	waypointType : "start",

	/**
	 * To store the temp floating waypoint marker when hover over the map.
	 * var {GMarker}
	 */
	waypointMarker : null,

	/**
	 * To store the nearest normal point when hover over the map
	 * var {GoogleRoutePoint}
	 */
	nearestPoint : null,
	
	/**
	 * To store the nearest normal marker when hover over the map
	 * var {GMarker}
	 */
	nearestMarker : null,
	
	/**
	 * To store the staus of editing the note for the nearest point
	 * var {boolean}
	 */
	showingNearestNote : false,

	/**
	 * To store the status of always pan to the center when making route
	 */
	alwayPanToCenter : true,
//
// Methods
//

	/**
	 * @param {Object} options
	 * @constructor
	 */
	initialize : function(options) {
		Object.extend(this, options || {});

		// give reference to RouteInstance
	    routeInstance = this;

		this.map = new GMap2($(this.containerId), {draggableCursor:'crosshair', draggingCursor:'move'});
	    this.map.addControl(new GLargeMapControl());
		this.map.addControl(new GMapTypeControl());
		new GKeyboardHandler(this.map);
		this.map.enableScrollWheelZoom();
		this.map.enableContinuousZoom();
	    this.map.setCenter(this.center, this.zoom, this.mapType);

		if (this.command == "new") {
			this._addEventListeners();	    
	    	this.map.addControl(new RouteMenuControl());
		} else if (this.command == "edit") {
			var firstP = this.points.first();
			if(firstP.type == 'point' || firstP.type == 'null') {
				firstP.type = 'start';
			}
			var lastP = this.points.last();
			if(lastP.type == 'point' || lastP.type == 'null') {
				lastP.type = 'end';
			}
			this.points.each(function(p){
				if (p.type != 'point' && p.type != 'null') {
					p.marker = routeInstance._getWaypointMarker(p.point, p.type);
					routeInstance.map.addOverlay(p.marker);
					routeInstance._addWaypointMarkerEvents(p);
				}
			});
			this.waypointType = 'point';

			this.showIntegralMarker = false;
			this.showDistanceMarkers(true);
			this.map.addControl(new RouteMenuControl());
			this._addEventListeners();
			this._drawRoute();

			if (this.paceValue != null && !this.paceValue.blank()) {
				this.hasPace = true;
			}

			this._updateDisplay();
		} else if (this.command == "readonly") {
			// we will not show the elevation until find a new tool for displaying elevations
			//this.map.addControl(new RouteElevationControl());
			var firstP = this.points.first();
			if(firstP.type == 'point' || firstP.type == 'null') {
				firstP.type = 'start';
			}
			var lastP = this.points.last();
			if(lastP.type == 'point' || lastP.type == 'null') {
				lastP.type = 'end';
			}
			this.points.each(function(p){
				if (p.type != 'point' && p.type != 'null') {
					p.marker = routeInstance._getWaypointMarker(p.point, p.type);
					routeInstance.map.addOverlay(p.marker);
					routeInstance._addWaypointMarkerReadonlyEvents(p);
				}
			});
			this.polylineOverlay = new GPolyline(this.points, '#0000FF', 5, 0.5, {clickable: false});
			this.map.addOverlay(this.polylineOverlay);
			this.waypointType = "point";
			this.waypointMarker = null;
			this.showIntegralMarker = false;
			this.showDistanceMarkers(true);
			this._updateDisplay();

			GEvent.addListener(this.map, "mousemove", function(point) {			
			if (point) {
				if (routeInstance.showWaypointMarker) {
					if (!routeInstance.showingNearestNote) {
						routeInstance._blinkNearestMarker(point);
					}					
				}
			}
		});
		}
	},

	/**
	 * add event listeners to google map
	 * @param {GoogleRoute}
	 */
	_addEventListeners : function() {
		GEvent.addListener(this.map, "click", function(overlay, point) {
	      	if(point) {	      		
	      		if ($('addRouteStep2') != null && $('addRouteStep2').style.display == 'none') {
					Effect.BlindDown('addRouteStep2', {duration: 1});
				}
	      		var distanceTooLarge = false;
	      		if (routeInstance.points.length > 0) {
	      			thisdistance = routeInstance.points.last().point.distanceFrom(point);
	      			if (thisdistance > 20000.0) {distanceTooLarge = true;}
	      		}
	      		if (distanceTooLarge) {
	      			alert("Distance from last point is too large!");
	      		} else {
	      			//if always pan to center, then pan to the center every time.
	      			if (routeInstance.alwayPanToCenter) {
	      				routeInstance.map.panTo(point);
	      			}
	      			if (routeInstance.points.length > 0 && routeInstance.points.last().type == 'end') {
	      				routeInstance.map.removeOverlay(routeInstance.points.last().marker);
	      				routeInstance.points.last().marker = null;
	      				routeInstance.points.last().type = 'point';
	      			}
	      			routeInstance.points.push(new GoogleRoutePoint(point, {type : routeInstance.waypointType, marker : routeInstance._getWaypointMarker(point, routeInstance.waypointType)}));

		      		// Re-calculate the distance so far
		      		if(routeInstance.points.length > 1) {
		      			routeInstance._addDistanceMarkers(routeInstance.totalDistance, routeInstance.points[routeInstance.points.length - 2], routeInstance.points.last());
		      			      			
						var lastleg = routeInstance.points.last().point.distanceFrom(routeInstance.points[routeInstance.points.length - 2].point);
						routeInstance.totalDistance += lastleg;

						routeInstance._updateDisplay();
		      		}
		      		var nowDistance = routeInstance.totalDistance;
					routeInstance.distanceSofar.push(nowDistance);
					
					// re-draw the map
					routeInstance._drawRoute();
	      		}	      					
			}
		});

		GEvent.addListener(this.map, "mousemove", function(point) {			
			if (point) {
				if (routeInstance.waypointType != "point" && routeInstance.waypointType != "end") {
					if (routeInstance.waypointMarker != null) {
						routeInstance.map.removeOverlay(routeInstance.waypointMarker);
					}
					routeInstance.waypointMarker = routeInstance._getFloatingWaypointMarker(point, routeInstance.waypointType);
					routeInstance.map.addOverlay(routeInstance.waypointMarker);
				} else if (routeInstance.points.length != 0 && routeInstance.showWaypointMarker) {
					if (!routeInstance.showingNearestNote) {
						routeInstance._blinkNearestMarker(point);
					}					
				}
			}
		});
	},

	/**
	 * draw the route
	 */
	_drawRoute : function() {
		if (this.polylineOverlay != null) {
	      	this.map.removeOverlay(this.polylineOverlay);
	    }

	    this.polylineOverlay = new GPolyline(this.points, '#0000FF', 5, 0.5, {clickable: false});
		this.map.addOverlay(this.polylineOverlay);

		this._addWaypoint();

		this.waypointType = "end";
		this.waypointMarker = null;
		this._setWaypointMarkersOff();
	},

	/**
	 * set the waypoint markers off.
	 */
	_setWaypointMarkersOff : function() {
		//$('pointIMG').src = '/ActiveTrainer/lib/routes/images/tool_point_off.png';
		$('waterIMG').src = '/ActiveTrainer/lib/routes/images/tool_water_off.png';
		$('bathroomIMG').src = '/ActiveTrainer/lib/routes/images/tool_bathroom_off.png';
		$('medicalIMG').src = '/ActiveTrainer/lib/routes/images/tool_medical_off.png';
	},

	/**
	 * blink the nearest normal point marker when hover over the map.
	 */
	_blinkNearestMarker : function(point) {
		var nearest = null;
		var nearDis = null;
		this.points.each(function(p){
			if (p.marker == null && (p.type == 'point' || p.type == 'null')) {
				var tempDis = point.distanceFrom(p.point);
				if ((nearest == null && nearDis == null) || tempDis < nearDis) {
					nearDis = tempDis;
					nearest = p;
				} 
			}						
		});
		if (nearest != null && (this.nearestPoint == null || this.nearestPoint.x != nearest.x || this.nearestPoint.y != nearest.y)) {
			if (this.nearestMarker != null) {
				this.map.removeOverlay(this.nearestMarker);
				this.nearestMarker = null;
			}
			this.nearestPoint = nearest;
			this.nearestMarker = this._getBlinkMarker(this.nearestPoint);
			if (this.command == 'readonly') {
				this._addNearestMarkerReadonlyEvents();
			} else {
				this._addNearestMarkerEvents();
			}
		}
	},

	/**
	 * add nearest marker normal events
	 */
	_addNearestMarkerEvents : function() {
		GEvent.addListener(this.nearestMarker, 'click', function(){
			var div = new Element("div");
			div.innerHTML = '<span class="font11pxBold">Enter Notes:</span><br/>'
						+ '<textarea id="waypointMarkerNote"'
						+ '" name="note" cols="10" rows="5" style="width: 200px; height: 100px;" class="font11px">'
						+ routeInstance.nearestPoint.note + '</textarea><br/>';
		
			var cancelButton = new Element("input");
			cancelButton.setAttribute("type", "button");
			cancelButton.setAttribute("value", "Cancel");
			cancelButton.addClassName("buttonCancel");
			GEvent.addDomListener(cancelButton, "click", function() {
				routeInstance.showingNearestNote = false;
			  	routeInstance.nearestMarker.closeInfoWindow();
			});
			div.appendChild(cancelButton);
					
			var saveButton = new Element("input");
			saveButton.setAttribute("type", "button");
			saveButton.setAttribute("value", "Save");
			Element.setStyle(saveButton, {'marginLeft' 	: '4px'});
			saveButton.addClassName("buttonAction");
			GEvent.addDomListener(saveButton, "click", function() {
			   	routeInstance.nearestPoint.note = $('waypointMarkerNote').value;
			   	routeInstance.showingNearestNote = false;
			   	routeInstance.nearestMarker.closeInfoWindow();		   	
			});
			div.appendChild(saveButton);
					
		    routeInstance.nearestMarker.openInfoWindowHtml(div);
		    routeInstance.showingNearestNote = true;
		});

		GEvent.addListener(this.nearestMarker, 'infowindowclose', function(){
		  	routeInstance.showingNearestNote = false;
		});

		this.map.addOverlay(this.nearestMarker);
	},

	/**
	 * add nearest marker readonly events
	 */
	_addNearestMarkerReadonlyEvents : function() {
		GEvent.addListener(this.nearestMarker, 'click', function(){
			var div = new Element("div");
			div.innerHTML = '<div style="width: 200px; height: 100px; vertical-align:top; overflow: auto; "><span class="font11pxBold">Notes:</span><br/>'
						+ routeInstance.nearestPoint.note + '</div><br/>';

			var closeButton = new Element("input");
			closeButton.setAttribute("type", "button");
			closeButton.setAttribute("value", "Close");
			closeButton.addClassName("buttonCancel");
			GEvent.addDomListener(closeButton, "click", function() {
			  	routeInstance.nearestMarker.closeInfoWindow();
			  	routeInstance.showingNearestNote = false;
			});
			div.appendChild(closeButton);

           	routeInstance.nearestMarker.openInfoWindowHtml(div);
           	routeInstance.showingNearestNote = true;
	    });

		GEvent.addListener(this.nearestMarker, 'infowindowclose', function(){
			routeInstance.showingNearestNote = false;
		});

		this.map.addOverlay(this.nearestMarker);
	},

	/**
	 * add a waypoint marker
	 */
	_addWaypoint : function() {
		if (this.waypointMarker != null) {
			this.map.removeOverlay(this.waypointMarker);
		}

		var grpoint = this.points.last();
		var marker = grpoint.marker;
		if (marker != null) {
			this._addWaypointMarkerEvents(grpoint);
		}

		if (this.showWaypointMarker) {
			this.map.addOverlay(marker);
		}
	},

	/**
	 * add waypoint marker's events
	 */
	_addWaypointMarkerEvents : function(grpoint) {
		GEvent.addListener(grpoint.marker, 'click', function(){
			var div = new Element("div");
			div.innerHTML = '<span class="font11pxBold">Enter Notes:</span><br/>'
						+ '<textarea id="waypointMarkerNote"'
						+ '" name="note" cols="10" rows="5" style="width: 200px; height: 100px;" class="font11px">'
						+ grpoint.note + '</textarea><br/>';

			var cancelButton = new Element("input");
			cancelButton.setAttribute("type", "button");
			cancelButton.setAttribute("value", "Cancel");
			cancelButton.addClassName("buttonCancel");
			GEvent.addDomListener(cancelButton, "click", function() {
			  	grpoint.marker.closeInfoWindow();
			});
			div.appendChild(cancelButton);

			var saveButton = new Element("input");
			saveButton.setAttribute("type", "button");
			saveButton.setAttribute("value", "Save");
			Element.setStyle(saveButton, {'marginLeft' 	: '4px'});
			saveButton.addClassName("buttonAction");
			GEvent.addDomListener(saveButton, "click", function() {
			   	grpoint.note = $('waypointMarkerNote').value;
			   	grpoint.marker.closeInfoWindow();		   	
			});
			div.appendChild(saveButton);
			
           	grpoint.marker.openInfoWindowHtml(div);
	    });
	},

	/**
	 * add waypoint readonly events
	 */
	_addWaypointMarkerReadonlyEvents : function(grpoint) {
		GEvent.addListener(grpoint.marker, 'click', function(){
			var div = new Element("div");
			div.innerHTML = '<div style="width: 200px; height: 100px; vertical-align:top; overflow: auto; "><span class="font11pxBold">Notes:</span><br/>'
						+ grpoint.note + '</div><br/>';

			var closeButton = new Element("input");
			closeButton.setAttribute("type", "button");
			closeButton.setAttribute("value", "Close");
			closeButton.addClassName("buttonCancel");
			GEvent.addDomListener(closeButton, "click", function() {
			  	grpoint.marker.closeInfoWindow();
			});
			div.appendChild(closeButton);
			
           	grpoint.marker.openInfoWindowHtml(div);
	    });
	},

	/**
	 * update all the integral distance markers of the route
	 */
	_updateDistanceMarkers : function() {
		// remove the original integral distance markers
		routeInstance.integralMarkers.each(function(im) {
			routeInstance.map.removeOverlay(im);
		});
		this.integralMarkers.clear();
		this.integralNumber = 0;
		
		// add new integral distance markers
		if (this.showIntegralMarker) {
			var len = this.points.length;
			if (len > 1) {
				for(var i = 1; i < len; i++) {
					this._addDistanceMarkers(this.distanceSofar[i-1], this.points[i-1], this.points[i]);
				}
			}
		}
	},

	/**
	 * add the integral point markers between the given 2 points
	 * @param legDistance distance by last time
	 * @param point1 the previous point
	 * @param point2 new added point
	 */
	_addDistanceMarkers : function(legDistance, point1, point2) {		
		//only show integral point
		if (this.showIntegralMarker) {
			var dis = this.unitHandler.f(point1.point.distanceFrom(point2.point));
			legDistance = this.unitHandler.f(legDistance);
			var d = Math.ceil(legDistance) - legDistance;
			if (d == 0) {d+=1;}			
			//
			while (d < (dis + legDistance) && (legDistance + d) < (dis + legDistance)) {
				var cx = (point2.x - point1.x) / dis * d;
				var cy = (point2.y - point1.y) / dis * d;
				var newpoint = new GPoint(point1.x + cx, point1.y + cy);
				this.integralNumber++;
				this.integralMarkers.push(this._getIntegralMarker(newpoint));
				this.map.addOverlay(this.integralMarkers.last());

				d+=1;
			}
		}
	},

	/**
	 * get integral point marker
	 */
	_getIntegralMarker : function(integralPoint) {
		var icon = new GIcon();
	    icon.shadow = "http://www.google.com/intl/en_ALL/mapfiles/shadow50.png";
	    icon.shadowSize = new GSize(21, 29);
	    icon.iconAnchor = new GPoint(10, 30);
	    icon.infoWindowAnchor = new GPoint(9, 5);
	    icon.infoShadowAnchor = new GPoint(9, 5);
	    //icon.image = "/ActiveTrainer/lib/routes/integral/mk" + (this.integralNumber > 100 ? "" : this.integralNumber) + ".png";
	    icon.image = "/ActiveTrainer/iconMarker.do?num=" + this.integralNumber + "&img=mk";
	    icon.iconSize = new GSize(20, 34);
	    var marker = new GMarker(integralPoint, icon);
	   	return marker;
	},

	/**
	 * get waypoint marker including start, end, water, washroom, medical so far.
	 */
	_getWaypointMarker : function(waypoint, type) {
		var marker = null;
		if (type != "point") {
			var icon = new GIcon();
		    icon.shadow = "http://www.google.com/intl/en_ALL/mapfiles/shadow50.png";
		    icon.shadowSize = new GSize(21, 21);
		    icon.iconAnchor = new GPoint(27, 21);
		    icon.infoWindowAnchor = new GPoint(9, 5);
		    icon.infoShadowAnchor = new GPoint(9, 5);
		    icon.image = "/ActiveTrainer/lib/routes/images/" + type + ".png";
		    icon.iconSize = new GSize(27, 21);
		   	marker = new GMarker(waypoint, icon);
		}
		return marker;
	},

	/**
	 * get the floating waypoint marker when hove over the map
	 */
	_getFloatingWaypointMarker : function(waypoint, type) {
		var marker = null;
		if (type != "point") {
			var icon = new GIcon();
		    icon.shadow = "http://www.google.com/intl/en_ALL/mapfiles/shadow50.png";
		    icon.shadowSize = new GSize(21, 21);
		    icon.iconAnchor = new GPoint(29, 25);
		    icon.infoWindowAnchor = new GPoint(9, 5);
		    icon.infoShadowAnchor = new GPoint(9, 5);
		    icon.image = "/ActiveTrainer/lib/routes/images/" + type + ".png";
		    icon.iconSize = new GSize(27, 21);
		   	marker = new GMarker(waypoint, icon);
		}
		return marker;
	},

	/**
	 * get blink marker, this is the marker for every normal point stop which have no specail waypoint type
	 */
	_getBlinkMarker : function(p) {
		var icon = new GIcon();
	    icon.shadow = "http://www.google.com/intl/en_ALL/mapfiles/shadow50.png";
	    icon.shadowSize = new GSize(6, 6);
	    icon.iconAnchor = new GPoint(4, 4);
	    icon.infoWindowAnchor = new GPoint(4, 4);
	    icon.infoShadowAnchor = new GPoint(4, 4);
	    icon.image = "/ActiveTrainer/lib/routes/images/point.png";
	    icon.iconSize = new GSize(8, 8);
	    var marker = new GMarker(p.point, icon);
	   	return marker;
	},

	/**
	 * update the display for the user when route's changed, such as distance/pace
	 */
	_updateDisplay : function() {
		var displayDistance = this.unitHandler.f(this.totalDistance);
		var unitDisplay = this.unitHandler.label == "miles" ? "miles" : "kms";
		$$(this.showDistance).each( function(c) {
			c.innerHTML = displayDistance.toFixed(3) + "&nbsp;" + unitDisplay;
		});
		if (this.hasPace){
			this.calculatePace();
		}
	},	

	/**
	 * set marker type
	 */
	setMarkerType : function(marktype) {
		this.waypointType = marktype;
	},

	/**
	 * clear the whole route
	 */
	clearRoute : function() {
		if (this.points.length > 0){
			if (confirm('Are you sure you want to clear all points on this route?\n\nClick OK to clear all points and stop mapping.\nOtherwise click Cancel.')){
				this.map.clearOverlays();
				this.points.clear();
				this.distanceSofar.clear();
				this.totalDistance = 0.0;
				this.paceValue = "";
				this.polylineOverlay = null;
				this.integralNumber = 0;
				this.integralMarkers.clear();
				this.waypointMarker = null;
				this.waypointType = "start";
				this.showingNearestNote = false;
				this.nearestMarker = null;
				this.nearestPoint = null;
				// Refresh the distance field
				this._updateDisplay();
			}
		}
	},

	/**
	 * undo function
	 */
	undo : function() {
		if (this.points.length == 2){
			var lastPoint = this.points.pop();
			if (lastPoint.marker != null) {
				this.map.removeOverlay(lastPoint.marker);
			}
			if (this.showingNearestNote) {
				this.nearestMarker.closeInfoWindow();
				this.map.removeOverlay(this.nearestMarker);
				this.nearestMarker = null;
				this.showingNearestNote = false;
			}
			this.distanceSofar.pop();
			this.totalDistance = 0.0;
			this.integralNumber = 0;
			this.integralMarkers.clear();
			this.map.clearOverlays();
			// Add the start markers
			if (this.showWaypointMarker && this.points.first().marker != null) {
				this.map.addOverlay(this.points.first().marker);
			}
			this.paceValue = "";
			this.showingNearestNote = false;
			// udpate display
			this._updateDisplay();
		} else if (this.points.length < 2){
			//do nothing
		} else{
			// Re-calculate the distance so far
			var lastleg = this.points.last().point.distanceFrom(this.points[this.points.length - 2].point);
			
			this.totalDistance -= lastleg;
			var lastPoint = this.points.pop();
			if (lastPoint.marker != null) {
				this.map.removeOverlay(lastPoint.marker);
			}
			if (this.points.last().marker == null) {
				this.points.last().marker = this._getWaypointMarker(this.points.last().point, 'end');
				this.points.last().type = "end";
			}
			if (this.showingNearestNote || this.nearestMarker != null) {
				this.nearestMarker.closeInfoWindow();
				this.map.removeOverlay(this.nearestMarker);
				this.nearestMarker = null;
				this.nearestPoint = null;
				this.showingNearestNote = false;
			}
			this.distanceSofar.pop();
			while (this.integralNumber > this.unitHandler.f(this.totalDistance)) {
				this.map.removeOverlay(this.integralMarkers.last());
				this.integralNumber -= 1;
				this.integralMarkers.pop();
			}
			this._drawRoute();
			// re-draw the route
			this._updateDisplay();
		}
	},

	/**
	 * make the route a loop
	 */
	makeALoop : function() {
		if(this.points.length > 2){
			var lastPoint = this.points.last();
			if (lastPoint.marker != null && lastPoint.type == 'end') {
				this.map.removeOverlay(lastPoint.marker);
				lastPoint.marker = null;
				lastPoint.type = 'point';
			}
			var firstPoint = this.points.first();
			if(lastPoint.x != firstPoint.x || lastPoint.y != firstPoint.y){
				this._addDistanceMarkers(this.totalDistance, lastPoint, firstPoint);		
				this.points.push(new GoogleRoutePoint(firstPoint.point, {type : 'end', marker : this._getWaypointMarker(firstPoint.point, 'end')}));
				this._drawRoute();

				// Re-calculate the distance so far  			
				var lastleg = lastPoint.point.distanceFrom(firstPoint.point);
				this.totalDistance += lastleg;
	      		var nowDistance = this.totalDistance;
				this.distanceSofar.push(nowDistance);

				this._updateDisplay();
			}
		} else {
			alert('Error: There must be at least 2 route segments to complete a loop route!');
		}
	},

	/**
	 * make the route out&back
	 */
	makeOutbackRoute : function() {
		if (this.points.length > 1){
			var segLeg = this.points.length;
			var lastPoint = this.points.last();
			if (lastPoint.marker != null && lastPoint.type == 'end') {
				this.map.removeOverlay(lastPoint.marker);
				lastPoint.marker = null;
				lastPoint.type = 'point';
			}
			for (var i = segLeg-1; i > 0; i--){
				var tempSeg = this.points[i-1];
				this._addDistanceMarkers(this.totalDistance, this.points[i], tempSeg);
				this.points.push(new GoogleRoutePoint(tempSeg.point, {type : 'point', marker : null}));
				this.totalDistance += tempSeg.point.distanceFrom(this.points[i].point);
				var tempDis = this.totalDistance;
				this.distanceSofar.push(tempDis);				
			}
			if (this.points.last().marker == null) {
				this.points.last().marker = this._getWaypointMarker(this.points.last().point, 'end');
				this.points.last().type = "end";
			}
			// re-draw the route
			this._drawRoute();
			this._updateDisplay();
		} else {
			alert('Error: There must be at least one route segment to complete an out & back route!');
		}
	},

	/**
	 * toggle distance unit
	 */
	toggleUnits : function(arg) {
		// change unit handler first.
		if (arg == "Kms"){
			this.unitHandler = UNITS_KMS;
		} else {
			// otherwise revert to the unit_miles.
			this.unitHandler = UNITS_MILES;
		}
		
		// update integral distance markers
		this._updateDistanceMarkers();

		// update display
		this._updateDisplay();
	},

	/**
	 * show distance markers or not
	 */
	showDistanceMarkers : function(showit) {
		if (showit) {
			if (!this.showIntegralMarker) {
				this.showIntegralMarker = true;
				this._updateDistanceMarkers();
			}			
		} else {
			this.showIntegralMarker = false;
			// remove the original integral distance markers
			routeInstance.integralMarkers.each(function(im) {
				routeInstance.map.removeOverlay(im);
			});			
			this.integralMarkers.clear();
			this.integralNumber = 0;
		}
	},

	/**
	 * show waypoint markers or not
	 */
	showWaypointMarkers : function(showit) {
		if (showit) {
			if (!this.showWaypointMarker) {
				this.showWaypointMarker = true;
				// add the original waypoint markers
				this.points.each(function(p){
					if (p.marker != null) {
						routeInstance.map.addOverlay(p.marker);
					}				
				});				
			}
		} else {
			this.showWaypointMarker = false;
			// remove the original waypoint markers
			this.points.each(function(p){
				if (p.marker != null) {
					routeInstance.map.removeOverlay(p.marker);
				}				
			});
		}
	},

	/**
	 * google map pan to a new place with the given address
	 */
	findAddr : function(addr) {		
		if(addr == null || addr.length == 0) { alert("Please Enter An Address!"); }
		else {
			if (addr == 'EX: San Diego, CA'){
				addr = 'San Diego, CA';
			}
			var geo = new GClientGeocoder();
			geo.getLocations(addr, function(response) {
				if(!response || response.Status.code != 200) {
					alert("Sorry, google can't find the address: " + addr + ". be more specific.");
				} else {
					var place = response.Placemark[0];
					var zoomLevel = 13;
					var zoomL = place.AddressDetails.Accuracy;
					if (zoomL){
						zoomLevel = routeInstance.SEARCH_ACCURACIES[parseInt(zoomL)];
					}
					routeInstance._moveMapTo(new GLatLng(place.Point.coordinates[1], place.Point.coordinates[0]), zoomLevel);				
				}
			});
		}
	},

	/**
	 * move map to a new center
	 */
	_moveMapTo : function (center, zoom) {
		routeInstance.map.setCenter(center, zoom);
		window.setTimeout(function() {
			routeInstance.map.panTo(center);
		}, 100);
	},

	/**
	 * calculate the pace value
	 */
	calculatePace : function() {
		var hoursElement =  $(this.durationHourId);
		var minsElement = $(this.durationMinuteId);
		var secsElement = $(this.durationSecondId);
		
		if (!this._validateDuration()){
			alert('Invalid Data in Duration Fields!');
		}
		
		if (this.totalDistance != 0.0){
			var newHours = $(this.durationHourId).value;
			var newMins = $(this.durationMinuteId).value;
			var newSecs = $(this.durationSecondId).value;
			var totalSecs = parseInt((newHours == '' ? 0 : newHours)) * 3600 + parseInt((newMins == '' ? 0 : newMins)) * 60 + parseInt((newSecs == '' ? 0 : newSecs));
			if (totalSecs > 0){
				this.paceValue = this._calculatePerPace(totalSecs);
				$(this.paceId).value = this.paceValue;

				if (hoursElement.value == ''){
					hoursElement.value = '00';
				}
				if (minsElement.value == ''){
					minsElement.value = '00';
				}
				if (secsElement.value == ''){
					secsElement.value = '00';
				}
				this.hasPace = true;
			} else {
				hoursElement.value = '';
				minsElement.value = '';
				secsElement.value = '';
				$(this.paceId).value = '';
				this.hasPace = false;
			}
		} else {
			$(this.paceId).value = '';
		}
	},

	/**
	 * validate the pace values
	 */
	_validateDuration : function() {
		var validPattern = /^[0-5]?[0-9]$/;
		var validPatternHour = /^[0-9]?[0-9]$/;
		var validatedHour = true;
		var validatedMins = true;
		var validatedSecs = true;
		var hoursValue = $(this.durationHourId).value;
		var minsValue = $(this.durationMinuteId).value;
		var secsValue = $(this.durationSecondId).value;
		
		if (hoursValue != '' || minsValue != '' || secsValue != ''){
			if (hoursValue != '' && !validPatternHour.exec(hoursValue)){
				validatedHour = false;
				$(this.durationHourId).value = '00';
			}
			if (minsValue != '' && !validPattern.exec(minsValue)){
				validatedMins = false;
				$(this.durationMinuteId).value = '00';
			}
			if (secsValue != '' && !validPattern.exec(secsValue)){
				validatedSecs = false;
				$(this.durationSecondId).value = '00';
			}
		}
		
		return (validatedHour && validatedMins && validatedSecs);
	},

	/**
	 * calculate pace value
	 */
	_calculatePerPace : function(totalSecs) {	 	
	   	return this._converSecondToTime(totalSecs / this.unitHandler.f(this.totalDistance));	 		
	},

	/**
	 * convert pace second to ##:##:## format
	 */
	_converSecondToTime : function(second) {
		if(second!=null) {
			var hour;
			var h = parseInt(second/3600);
			var m = parseInt((second%3600)/60);
			var s = parseInt((second%3600)%60);
			h = h<10?("0"+h):h;
			m = m<10?("0"+m):m;
			s = s<10?("0"+s):s;
			return h+":"+m+":"+s;
		}
	},

	/**
	 * do anything before save such as resize etc
	 */
	_beforeSave : function() {
		// re-center first
		var bounds = this.polylineOverlay.getBounds();
		this.map.setCenter(bounds.getCenter(), this.map.getBoundsZoomLevel(bounds));
		
		var geo = new GClientGeocoder();
		geo.getLocations(routeInstance.points.first().point, function(addresses) {
			if(addresses.Status.code != 200) {
				routeInstance.finalSave();
			} else {
			    var placemark = addresses.Placemark[0];
			    var countryCode = getPlacemarkProperty(placemark, "CountryNameCode");
				if (countryCode != null) {
					$('countryCode').value = countryCode;
				}
				var countryName = getPlacemarkProperty(placemark, "CountryName");
				if (countryName != null) {
					$('countryName').value = countryName;
				}
				var areaName = getPlacemarkProperty(placemark, "AdministrativeAreaName");
				if (areaName != null) {
					$('administrativeAreaName').value = areaName;
				}
				var subAreaName = getPlacemarkProperty(placemark, "SubAdministrativeAreaName");
				if (subAreaName != null) {
					$('subAdministrativeAreaName').value = subAreaName;
				}
				var localityName = getPlacemarkProperty(placemark, "LocalityName");
				if (localityName != null) {
					$('localityName').value = localityName;
				}
				var thoroughfare = getPlacemarkProperty(placemark, "ThoroughfareName");
				if (thoroughfare != null) {
					$('thoroughfare').value = thoroughfare;
				}
				var postalCode = getPlacemarkProperty(placemark, "PostalCodeNumber");
				if (postalCode != null) {
					$('postalCode').value = postalCode;
				}
				routeInstance.finalSave();
			  }
		});
	},

	/**
	 * save this route to the server.
	 */
	saveRoute : function() {
		if (this.points.length <= 1){
			alert('Please Finish the Route First!');
		} else {
			$('saveImage').src = '/ActiveTrainer/images/Saving-BTN.gif';
			
			this._beforeSave();
		}
	},
	
	/**
	 * final save this route
	 */
	finalSave : function() {
		if (this.id != null) {
			$('currentId').value = this.id;
		}

		var lats='', lons='', diss='', markers='', notes='';
		var pointsNum = this.points.length;
		for (var i = 0; i < pointsNum; i++) {
			var p = this.points[i];
			var separator = '|';
			// we don't want to add a separator in the end
			if ((i+1) == pointsNum) {separator = '';}
			lats += p.x + separator;
			lons += p.y + separator;
			markers += p.type + separator;
			notes += escape(p.note.empty() ? " " : p.note) + separator;
			diss += this.unitHandler.f(this.distanceSofar[i]) + separator;
		}			
		$('routeDistanceNows').value = diss;
		$('routeLatitudes').value = lats;
		$('routeLongitudes').value = lons;
		$('markers').value = markers;
		$('notes').value = notes;
		$('routeCenterLat').value = this.map.getCenter().x;
		$('routeCenterLon').value = this.map.getCenter().y;

		$('distance').value = this.unitHandler.f(this.totalDistance).toFixed(3);
		$('routeMapType').value = this.map.getCurrentMapType().getName();
		$('routeZoom').value = this.map.getZoom();
		// submit the form.
		document.forms[0].submit();
	}
};

/**
 * GoogleRoutePoint encapsulate the GLatLng and other things like elevation, note etc.
 * We can add more thing into this class if we need more features for every point in the route.
 */
var GoogleRoutePoint = Object.extend(Class.create());
GoogleRoutePoint.prototype = {
	point : null,			//GLatLng instance
	x : null,				//GLatLng.x
	y : null,				//GLatLng.y
	id : null,				//id? i am still thinking if we need this.
	elevation : null,		//elevation for this GLatLng
	type : 'point',			//point type, can be 'water', 'aid' etc.
	marker : null,			//waypoint marker for this point
	note  : '',				//note
	/**
	 * @constructor
	 */
	initialize : function(point, options) {
		this.point = point;
		this.x = point.x;
		this.y = point.y;
		Object.extend(this, options || {});
	},
	setPoint : function(p) {
		this.point = p;
		this.x = p.x;
		this.y = p.y;
	},
	setNote : function(n) {
		this.note = n;
	}
}
/**
 * Google Route Control Menu Panel
 */
function RouteMenuControl(){}
RouteMenuControl.prototype = new GControl();
RouteMenuControl.prototype.initialize = function(map) {
	var linkStyle = {
		'font-family' 		: 'Arial, Helvetica, sans-serif',
		'font-size' 		: '11px',
		'color' 			: '#000000',
		'textDecoration'	: 'underline',
		'cursor'			: 'normal',
		'font-weight'		: 'bold'
	};
	var linkHoverStyle = {
		'cursor'			: 'pointer',
		'color'				: 'blue'
	};
	var container = new Element("div");
	Element.setStyle(container, {
		'id'		: 'Route_Menu_Control',
		'width'		: '380px',
		'height'	: '50px',
		'background': '#ffffff',
		'border'	: '1px solid #000000',
		'position'	: 'absolute'
	});

	// show distance section
	var distanceValue = new Element("span");
	distanceValue.setAttribute("id", "distance");
	distanceValue.addClassName("showDistance");
	distanceValue.addClassName("font11px");
	distanceValue.innerHTML = "0.0";
	Element.setStyle(distanceValue, {
		'paddingTop': '6px',
		'paddingRight': '5px',
		'float' 		: 'right'
	});
	container.appendChild(distanceValue);

	var distanceTitle = new Element("span");
	distanceTitle.innerHTML = "Distance&nbsp;";
	Element.setStyle(distanceTitle, {
		'paddingTop': '6px',
		'float' 	: 'right'
	});
	container.appendChild(distanceTitle);

	//row 1 div
	var div1 = new Element("div");
	Element.setStyle(div1, {
		'padding' 	: '5px 10px'
	});

	// out & back menu
	var outBackLink = new Element("a");
	outBackLink.innerHTML = "Out & Back";
	this.setLinkMenuStyles(outBackLink, linkStyle, linkHoverStyle);	  
    GEvent.addDomListener(outBackLink, "click", function() {
    	routeInstance.makeOutbackRoute();
    });
	div1.appendChild(outBackLink);
	this.addMenuSeparator(div1);

	// loop menu
	var loopLink = new Element("a");
	loopLink.innerHTML = "Close Loop";
	this.setLinkMenuStyles(loopLink, linkStyle, linkHoverStyle);	  
    GEvent.addDomListener(loopLink, "click", function() {
    	routeInstance.makeALoop();
    });
	div1.appendChild(loopLink);
	this.addMenuSeparator(div1);

	// clear route menu
	var clearLink = new Element("a");
	clearLink.innerHTML = "Clear All";
	this.setLinkMenuStyles(clearLink, linkStyle, linkHoverStyle);	  
    GEvent.addDomListener(clearLink, "click", function() {
    	routeInstance.clearRoute();
    });
	div1.appendChild(clearLink);
	this.addMenuSeparator(div1);

	// undo menu
	var undoLink = new Element("a");
	undoLink.innerHTML = "Undo";
	this.setLinkMenuStyles(undoLink, linkStyle, linkHoverStyle);	  
    GEvent.addDomListener(undoLink, "click", function() {
    	routeInstance.undo();
    });
	div1.appendChild(undoLink);

	container.appendChild(div1);

	//// row 2 div
	var div2 = new Element("div");
	Element.setStyle(div2, {
		'padding' 	: '0px 10px 0px 10px'
	});
	var subContainerStyle = {
		'verticalAlign' 	: 'middle',
		'padding'			: '1px',
		'width'				: '22px',
		'height'			: '22px',
		'float'				: 'left'
	};
	var imageStyle = {
		'border'		: 'none'
	};
	var imageHoverStyle = {
		'border'		: '1px solid #ffcc33'
	};

	var div21 = new Element("div");
	Element.setStyle(div21, {
		'verticalAlign' 	: 'middle',
		'paddingTop'		: '5px',
		'paddingRight'		: '5px',
		'float'				: 'left'
	});
	div21.addClassName("font11pxBold");
	div21.innerHTML = "Waypoint Markers";
	div2.appendChild(div21);

//	var div22 = new Element("div");
//	Element.setStyle(div22, subContainerStyle);
//	div22.innerHTML = "<img id='pointIMG' src='lib/routes/images/tool_point_off.png' title='No Marker' border='0'/>";
//	this.setLinkMenuStyles(div22, imageStyle, imageHoverStyle);
//	GEvent.addDomListener(div22, "click", function() {
//		routeInstance._setWaypointMarkersOff();
//		$('pointIMG').src = 'lib/routes/images/tool_point_on.png';
//    	routeInstance.setMarkerType("point");
//    });
//	div2.appendChild(div22);

	var div23 = new Element("div");
	Element.setStyle(div23, subContainerStyle);
	div23.innerHTML = "<img id='waterIMG' src='/ActiveTrainer/lib/routes/images/tool_water_off.png' title='Water' border='0'/>";
	this.setLinkMenuStyles(div23, imageStyle, imageHoverStyle);
	GEvent.addDomListener(div23, "click", function() {
    	routeInstance._setWaypointMarkersOff();
    	$('waterIMG').src = '/ActiveTrainer/lib/routes/images/tool_water_on.png';
    	routeInstance.setMarkerType("water");
    });
	div2.appendChild(div23);

	var div24 = new Element("div");
	Element.setStyle(div24, subContainerStyle);
	div24.innerHTML = "<img id='bathroomIMG' src='/ActiveTrainer/lib/routes/images/tool_bathroom_off.png' title='Bathroom' border='0'/>";
	this.setLinkMenuStyles(div24, imageStyle, imageHoverStyle);
	GEvent.addDomListener(div24, "click", function() {
    	routeInstance._setWaypointMarkersOff();
    	$('bathroomIMG').src = '/ActiveTrainer/lib/routes/images/tool_bathroom_on.png';
    	routeInstance.setMarkerType("bathroom");
    });
	div2.appendChild(div24);

	var div25 = new Element("div");
	Element.setStyle(div25, subContainerStyle);
	div25.innerHTML = "<img id='medicalIMG' src='/ActiveTrainer/lib/routes/images/tool_medical_off.png' title='Aid' border='0'/>";
	this.setLinkMenuStyles(div25, imageStyle, imageHoverStyle);
	GEvent.addDomListener(div25, "click", function() {
		routeInstance._setWaypointMarkersOff();
    	$('medicalIMG').src = '/ActiveTrainer/lib/routes/images/tool_medical_on.png';
    	routeInstance.setMarkerType("medical");
    });
	div2.appendChild(div25);	

	container.appendChild(div2);

	map.getContainer().appendChild(container);
	return container;
}
// get position
RouteMenuControl.prototype.getDefaultPosition = function() {
	return new GControlPosition(G_ANCHOR_TOP_LEFT, new GSize(75, 10));
}
// set link menu style
RouteMenuControl.prototype.setLinkMenuStyles = function(linkMenu, linkStyle, linkHoverStyle) {
	linkMenu.addClassName("font11pxBold");
	linkMenu.setStyle(linkStyle);
    GEvent.addDomListener(linkMenu, "mouseover", function() {
    	linkMenu.setStyle(linkHoverStyle);
    });
    GEvent.addDomListener(linkMenu, "mouseout", function() {
    	linkMenu.setStyle(linkStyle);
    });
}
// add menu separator
RouteMenuControl.prototype.addMenuSeparator = function(menuContainer) {
	var separator = new Element("span");
	separator.addClassName("font11pxBold");
	separator.innerHTML = "&nbsp;|&nbsp;";
	menuContainer.appendChild(separator);
}
/**
 * Google Route elevation Menu Panel
 * To be implemented later in future
 */
function RouteElevationControl(){}
RouteElevationControl.prototype = new GControl();
RouteElevationControl.prototype.initialize = function(map) {
	var container = new Element("div");
	Element.setStyle(container, {
		'id'		: 'Route_Elevation_Control',
		'paddingLeft': '5px',
		'paddingTop': '2px',
		'width'		: '70px',
		'height'	: '18px',
		'background': '#ffffff',
		'position'	: 'absolute'
	});
	container.innerHTML = "<span class='font11pxBoldLink'>&#9660;Elevation</span>";
	GEvent.addDomListener(container, "mouseover", function() {
    	container.setStyle({cursor : 'pointer', textDecoration : 'underline'});
    });
    GEvent.addDomListener(container, "mouseout", function() {
    	container.setStyle({textDecoration : 'none'});
    });
    GEvent.addDomListener(container, "click", function() {
    	if ($('elevationDiv').style.display == 'none') {
    		container.innerHTML = "<span class='font11pxBoldLink'>&#9650;Elevation</span>";
    		Effect.BlindDown('elevationDiv', {duration: 0.5});
    	} else {
    		container.innerHTML = "<span class='font11pxBoldLink'>&#9660;Elevation</span>";
    		Effect.BlindUp('elevationDiv', {duration: 0.5});
    	}
    });

	map.getContainer().appendChild(container);
	return container;
}
RouteElevationControl.prototype.getDefaultPosition = function() {
	return new GControlPosition(G_ANCHOR_BOTTOM_RIGHT, new GSize(10, 0));
}