// - - - - - - - - - - - - - - - - - - - - -
//
// Title : gMap Workout Tracker
// Author : Chris Campbell, Ryan Campbell
// URL : http://particletree.com
//
// Description : This is an implementation of the 
// Google Maps API using the Garmin Forerunner 201.
//
// Created : July 5, 2005
// Modified : July 22, 2005
//
// - - - - - - - - - - - - - - - - - - - - -

// SOME GLOBAL VARIABLES //
 
 // for the map
 var map;
 var markers;
 var marker;
 var timeOut = 1;  // length to wait till next point is plotted
 var i = 0;
 
 // for display
 var loadingMessage;
 var distanceMessage;
 var speedMessage;
 var timeMessage;
 var bRowColor = false;
 var checkPointCount = 1;
 
 // for distance calculations
 var distance = 0;
 var checkPoint; 
 var checkPointDistance = 0;
 
 // for time calculations
 var currentTime = 0;
 var lastTime = 0;
 var totalTime = 0;
 
 // for speed calculations
 var averageSpeed;

// CREATE MAP & DISPLAY CONTROLS AND STARTING LOCATION //
function onLoad() {

	// kill if safari
	var detect = navigator.userAgent.toLowerCase();
	if((detect.indexOf("safari") + 1)) {
        alert("This currently crashes Safari.  We apologize, and are working on a fix.")
    }
	else {
    loadingMessage = document.getElementById('progress');
	distanceMessage = document.getElementById('distanceMessage');
	speedMessage = document.getElementById("speedMessage");
	timeMessage = document.getElementById('timeMessage');
    map = new GMap(document.getElementById("map"));
	control = new GSmallMapControl();
		
	map.addControl(control);
	map.addControl(new GMapTypeControl());
	//map.centerAndZoom(new GPoint(-82.45126, 27.94351), 4);
			
	loadInfo();
	}
}

function initButtons(){
document.getElementById("showStats").onclick=function(){
	document.getElementById("stats").style.display = "block";
	document.getElementById("showStats").style.display = "none";
	document.getElementById("hideStats").style.display = "block";
}
document.getElementById("hideStats").onclick=function(){
	document.getElementById("stats").style.display = "none";
	document.getElementById("showStats").style.display = "block";
	document.getElementById("hideStats").style.display = "none";
}
}

function initCheckpoint(){
ctrl = document.getElementById("cpDistance");
ctrl.onchange=function(){document.getElementById("checkpoints").submit();}
checkPoint = ctrl.options[ctrl.selectedIndex].value;
}

// LOAD THE XML FILE AND PLOT THE FIRST POINT //
function loadInfo(){			 
	var request = GXmlHttp.create();
		
	request.open("GET", "workoutTracker.xml", true);
	request.onreadystatechange = function() {
	if (request.readyState == 4) {
		var xmlDoc = request.responseXML;
		markers = xmlDoc.documentElement.getElementsByTagName("Trackpoint");
		plotPoint()
	}
  }
  request.send(null);
}

function plotPoint(){
	if (i < markers.length ) {			
		var Lat = markers[i].getElementsByTagName("Latitude");
		var Lng = markers[i].getElementsByTagName("Longitude");
			
		Lat = Lat[0].firstChild.nodeValue;
		Lng = Lng[0].firstChild.nodeValue;
		var point = new GPoint(Lng, Lat);
		
		marker = createMarker(point,i,markers.length);
			
		if (i < markers.length  && i != 0){
			
			var Lat1 = markers[i-1].getElementsByTagName("Latitude");
			var Lng1 = markers[i-1].getElementsByTagName("Longitude");
				
			Lat1 = Lat1[0].firstChild.nodeValue;
			Lng1 = Lng1[0].firstChild.nodeValue;
					
			var point1 = new GPoint(Lng1, Lat1);

			var points=[point, point1];
			
			//RECENTER MAP EVERY TEN POINTS
			if (i%10==0){map.recenterOrPanToLatLng(point, 2000);}
							
			if (i < markers.length/2){
				map.addOverlay(new GPolyline(points, "#0000ff",3,1));
			}
			else{
				map.addOverlay(new GPolyline(points, "#ff0000",3,1));
			}
			calculateDistance(Lng, Lat, Lng1, Lat1)
		}
		
	    loadingPercentage(i);
		distanceMessage.innerHTML=Math.round(distance*100)/100 + " miles";
		
		//Thanks to Walter Blume for helping out with this conditional statement
		if (i < markers.length - 1){
            window.setTimeout(plotPoint,timeOut);
            calculateTime(i);
            averageSpeed = Math.round((distance / (totalTime/3600))*100)/100;
            speedMessage.innerHTML = averageSpeed + " mph ";
        }
        else {
            calculateTime(i);
            averageSpeed = Math.round((distance / (totalTime/3600))*100)/100;
            speedMessage.innerHTML = averageSpeed + " mph ";
            createStat("End", distance, timeMessage.innerHTML.replace(" secs", ""), speedMessage.innerHTML.replace(" mph", ""));
        }

		i++;
	}
}

// GET THE APPROPRIATE MARKER FOR START, FINISH, CHECKPOINT, AND LINE
function createMarker(point, i, markerLength) {

	var icon = new GIcon();
	icon.shadow = "images/mm_20_shadow.png";
	icon.iconSize = new GSize(12, 20);
	icon.shadowSize = new GSize(22, 20);
	icon.iconAnchor = new GPoint(6, 18);    
	icon.infoWindowAnchor = new GPoint(9, 5);
						
	if (i == 0 ){
		map.centerAndZoom(point, 4);
		icon.image = "images/mm_20_green.png";
		var marker = new GMarker(point,icon);
		map.addOverlay(marker);
		GEvent.addListener(marker, "click", function() {
		marker.openInfoWindowHtml("Start");});
		createStat("Start", 0, 0, 0);
		return marker;
	} else if(i == markers.length -1){
		icon.image = "images/mm_20_red.png";
		var marker = new GMarker(point,icon);
		map.addOverlay(marker);
		GEvent.addListener(marker, "click", function() {
		marker.openInfoWindowHtml("Finish");});
		map.recenterOrPanToLatLng(point, 2000);
		return marker;
	}		

	if (checkPointDistance - checkPoint >= 0 && checkPoint != 0){
		icon.image = "images/mm_20_yellow.png";
		var distance1 = checkPointDistance;
		var marker = new GMarker(point,icon);
		map.addOverlay(marker);
		/*GEvent.addListener(marker, "click", function() {
		marker.openInfoWindowHtml(distance1);});*/
		createStat("Checkpoint " + checkPointCount, distance, totalTime, averageSpeed);
		checkPointDistance = 0;
		checkPointCount += 1;
	}
	else{
		var marker = new GMarker(point, icon);
	}
		
	return marker;
}

function createStat(pointName, distance, time, speed){
	var tbody;
	if(pointName != "End") {
		tbody = document.getElementById("statsTable").getElementsByTagName("tbody")[0];
	}
	else {
		tbody = document.getElementById("statsTable").getElementsByTagName("tfoot")[0];
	}

	row = document.createElement("tr");
	if(bRowColor == true && pointName != "End") {
		row.className = "alt";
		bRowColor = false;
	}
	else {
		bRowColor = true;
	}
	pName = document.createElement("td");
	pName.innerHTML = pointName;
	dName = document.createElement("td");
	dName.innerHTML = Math.round(distance*100)/100 +' miles';
	tName = document.createElement("td");
	tName.innerHTML = convertSeconds(time);
	sName = document.createElement("td");
	sName.innerHTML = speed +' mph';
	row.appendChild(pName);
	row.appendChild(dName);
	row.appendChild(tName);
	row.appendChild(sName);
	tbody.appendChild(row);
}

// LOADING BAR //
function loadingPercentage(currentPoint){
	var percentage = Math.round((currentPoint/(markers.length - 1)) * 100);
	loadingMessage.style.width = percentage +"%"; 
}

// Function based on Vincenty formula 
// Website referenced: http://www.movable-type.co.uk/scripts/LatLongVincenty.html
// Thanks Chris Veness for this!
//Also a big thank you to Steve Conniff for taking the time to intruoduce to this more accurate method of calculating distance

function calculateDistance(point1y, point1x, point2y, point2x) {  // Vincenty formula

  traveled = LatLong.distVincenty(new LatLong(point2x, point2y), new LatLong(point1x, point1y));
  traveled = traveled * 0.000621371192;  // Convert to miles from meters

  distance = distance + traveled;
  checkPointDistance = checkPointDistance + traveled;
}



/*
 * LatLong constructor:
 *
 *   arguments are in degrees, either numeric or formatted as per LatLong.degToRad
 *   returned lat -pi/2 ... +pi/2, E = +ve
 *   returned lon -pi ... +pi, N = +ve
 */
function LatLong(degLat, degLong) {
  if (typeof degLat == 'number' && typeof degLong == 'number') {  // numerics
    this.lat = degLat * Math.PI / 180;
    this.lon = degLong * Math.PI / 180;
  } else if (!isNaN(Number(degLat)) && !isNaN(Number(degLong))) { // numerics-as-strings
    this.lat = degLat * Math.PI / 180;
    this.lon = degLong * Math.PI / 180;
  } else {                                                        // deg-min-sec with dir'n
    this.lat = LatLong.degToRad(degLat);
    this.lon = LatLong.degToRad(degLong);
  }
}

/*
 * Calculate geodesic distance (in m) between two points specified by latitude/longitude.
 *
 * from: Vincenty inverse formula - T Vincenty, "Direct and Inverse Solutions of Geodesics on the 
 *       Ellipsoid with application of nested equations", Survey Review, vol XXII no 176, 1975
 *       http://www.ngs.noaa.gov/PUBS_LIB/inverse.pdf
 */
LatLong.distVincenty = function(p1, p2) {
  var a = 6378137, b = 6356752.3142,  f = 1/298.257223563;
  var L = p2.lon - p1.lon;
  var U1 = Math.atan((1-f) * Math.tan(p1.lat));
  var U2 = Math.atan((1-f) * Math.tan(p2.lat));
  var sinU1 = Math.sin(U1), cosU1 = Math.cos(U1);
  var sinU2 = Math.sin(U2), cosU2 = Math.cos(U2);
  var lambda = L, lambdaP = 2*Math.PI;
  var iterLimit = 20;
  while (Math.abs(lambda-lambdaP) > 1e-12 && --iterLimit>0) {
    var sinLambda = Math.sin(lambda), cosLambda = Math.cos(lambda);
    var sinSigma = Math.sqrt((cosU2*sinLambda) * (cosU2*sinLambda) + 
      (cosU1*sinU2-sinU1*cosU2*cosLambda) * (cosU1*sinU2-sinU1*cosU2*cosLambda));
    if (sinSigma==0) return 0;  // co-incident points
    var cosSigma = sinU1*sinU2 + cosU1*cosU2*cosLambda;
    var sigma = Math.atan2(sinSigma, cosSigma);
    var alpha = Math.asin(cosU1 * cosU2 * sinLambda / sinSigma);
    var cosSqAlpha = Math.cos(alpha) * Math.cos(alpha);
    var cos2SigmaM = cosSigma - 2*sinU1*sinU2/cosSqAlpha;
    var C = f/16*cosSqAlpha*(4+f*(4-3*cosSqAlpha));
    lambdaP = lambda;
    lambda = L + (1-C) * f * Math.sin(alpha) *
      (sigma + C*sinSigma*(cos2SigmaM+C*cosSigma*(-1+2*cos2SigmaM*cos2SigmaM)));
  }
  if (iterLimit==0) return NaN  // formula failed to converge
  var uSq = cosSqAlpha * (a*a - b*b) / (b*b);
  var A = 1 + uSq/16384*(4096+uSq*(-768+uSq*(320-175*uSq)));
  var B = uSq/1024 * (256+uSq*(-128+uSq*(74-47*uSq)));
  deltaSigma = B*sinSigma*(cos2SigmaM+B/4*(cosSigma*(-1+2*cos2SigmaM*cos2SigmaM)-
    B/6*cos2SigmaM*(-3+4*sinSigma*sinSigma)*(-3+4*cos2SigmaM*cos2SigmaM)));
  s = b*A*(sigma-deltaSigma);
  s = s.toFixed(3); // round to 1mm precision
  return s;
}


// PARSE TIME FROM XML AND THEN FIND THE DIFFERENCE BETWEEN LAST MEASUREMENT WITH RUNNING TOTAL //
function calculateTime(i)
{
	currentTime = markers[i].getElementsByTagName("Time");
	currentTime = currentTime[0].firstChild.nodeValue;
	if (i > 0 ){
		lastTime = markers[i-1].getElementsByTagName("Time");
		lastTime = lastTime[0].firstChild.nodeValue;
	
		//begin year/month/day
		var largeDate = currentTime.split("T");
		var date = largeDate[0];
		date = date.split("-");
		var beforeYear = date[0];
		var beforeDay = date[2];
		//beforeDay = beforeDay.replace("0","");
		var beforeMonth = date[1];
		//beforeMonth = beforeMonth.replace("0","");
	
		//end year/month/day
		var largeDate1 = lastTime.split("T");
		var date1 = largeDate1[0];
		date1 = date1.split("-");
		var afterYear = date1[0];
		var afterDay = date1[2];
		//afterDay = afterDay.replace("0","");
		var afterMonth = date1[1];
		//afterMonth = afterMonth.replace("0","");
	
		//begin hour/min/seconds
		var after = largeDate[1].replace("T","");
		after = largeDate[1].replace("Z","");	
	
		afterArray = after.split(":");
		var hour = afterArray[0];
		var minute = afterArray[1];
		var second = afterArray[2];
	
		//end hour/min/seconds
		var after1 = largeDate1[1].replace("T","");
		after1 = largeDate1[1].replace("Z","");	
	
		afterArray1 = after1.split(":");
		var hour1 = afterArray1[0];
		var minute1 = afterArray1[1];
		var second1 = afterArray1[2];
	
		var before =new Date(beforeYear, beforeMonth, beforeDay, hour, minute, second);
		var after =new Date(afterYear, afterMonth, afterDay, hour1, minute1, second1);
		var seconds = 1000;
		var secondsBetween = Math.ceil((before.getTime()-after.getTime())/(seconds));
		totalTime = totalTime + secondsBetween;
		timeMessage.innerHTML=convertSeconds(totalTime); 
	}
}

function convertSeconds(seconds) {
	 //find hours
	 if(seconds > 3600) {
		hours = Math.round(((seconds / 3600)*10)/10) - Math.round((((seconds % 3600)*10)/3600)/10);
		seconds = seconds - (hours * 3600);
		hours += " hrs ";
	 }
	 else {
		hours = "";
	 }
	 //find minutes
	 if(seconds > 60) {
		minutes = Math.round(((seconds / 60)*10)/10) - Math.round((((seconds % 60)*10)/60)/10);
		seconds = seconds - (minutes * 60);
		minutes += " mins ";
	 }
	 else {
		minutes = "";
	 }
	
	return hours + minutes + seconds + " secs ";
}

window.onload = function(){
initCheckpoint();
initButtons();
onLoad();
}