// Google Map Running Log
// Dan Maynes-Aminzade
// Based on code by Chris and Ryan Campbell (particletree.com)

// SOME GLOBAL VARIABLES //
 
 // for the map
 var map;
 var markers;
 var timeOut = 20;  // length to wait till next point is plotted
 var i = 0;
 var lat_data = new Array();
 var long_data = new Array();
 var alt_data = new Array();
  
 // for display
 var loadingMessage;
 var distanceMessage;
 var speedMessage;
 var timeMessage;
 var bRowColor = false;
 var checkPointCount = 1;
 var linecolor = "#0000FF";
 
 // for distance calculations
 var distance = 0;
 var checkPoint; 
 var runNumber;
 var checkPointDistance = 0;
 
 // for time calculations
 var currentTime = 0;
 var lastTime = 0;
 var totalTime = 0;
 var currentLap = 0;
 var accumLapDistances = 0;
 var accumLapDurs = 0;
 var numLaps = 1;
 var lapDistances;
 var lapDurations;
 var lapStartTimes;
 
 // for speed calculations
 var averageSpeed;
 
 // altitude graph
  var graph;            // the canvas for our graph
  var graph_ymax = 10;  // maximum altitude
  var graph_ymin = 0;   // minimum altitude
  var graph_h = 160;    // height of graph in pixels
  var graph_w = 470;    // width of graph in pixels
  var graph_dx = 1;     // x distance between plotted points
  var graph_ox = 20;    // graph origin x-coord
  var graph_oy = -30;    // graph origin y-coord
  var graph_marks = 5;  // number of horizonatal lines on graph


// CREATE MAP & DISPLAY CONTROLS AND STARTING LOCATION //
function onLoad() {
    // kill if browser is not compatible
    if(!GBrowserIsCompatible()) {
        alert("Sorry, your browser is not compatible with Google Maps.")
    }
    else {
        loadingMessage = document.getElementById('progress');
        distanceMessage = document.getElementById('distanceMessage');
        speedMessage = document.getElementById("speedMessage");
        timeMessage = document.getElementById('timeMessage');
        map = new GMap2(document.getElementById("map"));
        control = new GSmallMapControl();
        map.addControl(control);
        map.addControl(new GMapTypeControl());

        loadInfo();
    }
}

function initLinegraph() 
{
    graph = new jsGraphics("linegraph");

    // draw labeled axes
    graph.setColor("black");
    graph.setStroke(1);
    graph.drawLine(graph_ox,graph_h-graph_oy,graph_w+30,graph_h-graph_oy);
    graph.drawLine(graph_ox,graph_h-graph_oy,graph_ox,0);
    
    graph.setStroke(Stroke.DOTTED);
    for(var i = 0; i <= graph_marks; i++)
    {
        var marker_y = graph_h-graph_oy-Math.round(graph_h*i/graph_marks);
        if (i != 0) graph.drawLine(graph_ox, marker_y, graph_w+30, marker_y);
        var label = Math.round(graph_ymin+(graph_ymax-graph_ymin)*i/graph_marks);
        graph.drawString(label+"",2,marker_y-8);
    }   
    graph.drawString("Altitude (Meters)", graph_w/2-30, 0);
    graph.paint();
}

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

    pspeed = document.getElementById("playSpeed");
    pspeed.onchange=function(){document.getElementById("runparams").submit();}
    timeOut = pspeed.options[pspeed.selectedIndex].value;

    chooserun = document.getElementById("runIndex");
    runNumber = chooserun.value;

    if (runNumber == 16)
    {   // pink lines easter egg for Valentine's Day
        linecolor = "#FF96C8"  ;
    }

}

// LOAD THE XML FILE AND PLOT THE FIRST POINT //
function loadInfo(){			 
	var request = GXmlHttp.create();
		
	request.open("GET", "monzyruns.xml", true);
	request.onreadystatechange = function() {
  	if (request.readyState == 4) {
  		var xmlDoc = request.responseXML;
  		runs = xmlDoc.documentElement.getElementsByTagName("Run");
  		markers = runs[runNumber].getElementsByTagName("Trackpoint");
  		lapDistances = runs[runNumber].getElementsByTagName("Length");
	        lapDurations = runs[runNumber].getElementsByTagName("Duration");
  		lapStartTimes = runs[runNumber].getElementsByTagName("StartTime");
  		numLaps = lapDistances.length;

  		graph_dx = (graph_w/markers.length);  // determine x separation on points based on number of points

      // populate data arrays
      // find min and max altitude for line graph
      for (var i=0; i<markers.length; i++)
      {
            var Lat = markers[i].getElementsByTagName("Latitude");
            var Lng = markers[i].getElementsByTagName("Longitude");
            var Alt = markers[i].getElementsByTagName("Altitude");
            
            Lat = parseFloat(Lat[0].firstChild.nodeValue);
            Lng = parseFloat(Lng[0].firstChild.nodeValue);
            Alt = parseFloat(Alt[0].firstChild.nodeValue);
            
            lat_data.push(Lat);
            long_data.push(Lng);
            alt_data.push(Alt);
            
            if (Alt < graph_ymin) graph_ymin = Alt;
            if (Alt > graph_ymax) graph_ymax = Alt;
        }
        initLinegraph();

        // pan and zoom to start point
        var startpoint = new GLatLng(lat_data[0], long_data[0]);
	    map.setCenter(startpoint, 20);

        // set map type based on form input
        var maptypectrl = document.getElementById("maptype");
        if (maptypectrl.value == 0)  map.setMapType(G_NORMAL_MAP);
        if (maptypectrl.value == 1)  map.setMapType(G_SATELLITE_MAP);
        if (maptypectrl.value == 2)  map.setMapType(G_HYBRID_MAP);
        
        // update form when map type is changed
        GEvent.addListener(map, 'maptypechanged', function() {
        var maptype = map.getCurrentMapType();
        var maptypectrl = document.getElementById("maptype");
        if (maptype == G_NORMAL_MAP) maptypectrl.value = 0;
        if (maptype == G_SATELLITE_MAP) maptypectrl.value = 1;
        if (maptype == G_HYBRID_MAP) maptypectrl.value = 2;
        });

        plotPoint()
    	}
  }
  request.send(null);
}

function plotPoint(){
	if (i < markers.length ) 
	{
		var point = new GLatLng(lat_data[i], long_data[i]);
		createMarker(point,i,markers.length);
			
		if (i < markers.length  && i != 0){
					
			var point1 = new GLatLng(lat_data[i-1], long_data[i-1]);
			var points=[point, point1];
			
			// recenter map and update altitude graph every 20 points
			if (i%20==0){
			    map.panTo(point);
			    graph.paint();
		    }
							
      // add line segment to map
			map.addOverlay(new GPolyline(points, linecolor,5,.8));

      // add line segment to altitude graph
      var ht  = graph_h-Math.round((alt_data[i]-graph_ymin)*graph_h/(graph_ymax-graph_ymin))-graph_oy;
      var ht1 = graph_h-Math.round((alt_data[i-1]-graph_ymin)*graph_h/(graph_ymax-graph_ymin))-graph_oy;
      graph.setStroke(1);
      graph.setColor("blue");
      graph.drawLine(Math.round(graph_ox+(i-1)*graph_dx), ht1, Math.round(graph_ox+i*graph_dx), ht);

			calculateDistance(long_data[i], lat_data[i], long_data[i-1], lat_data[i-1])

		}

	    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", ""));
            graph.paint();
        }

		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 ){
        graph.setColor("#00FF00");
        var ht  = graph_h-Math.round((alt_data[i]-graph_ymin)*graph_h/(graph_ymax-graph_ymin))-graph_oy;

        graph.fillEllipse(graph_ox-4, ht-4, 9, 9);
        map.setCenter(point, 13);
        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)
  	{
        graph.setColor("red");
        var ht  = graph_h-Math.round((alt_data[i]-graph_ymin)*graph_h/(graph_ymax-graph_ymin))-graph_oy;
        graph.fillEllipse(graph_ox+Math.round(i*graph_dx)-4, ht-4, 9, 9);
        
        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.panTo(point);
        return marker;
  	}		
    if (checkPointDistance - checkPoint >= 0 && checkPoint != 0){
        // add an ellipse marker point to the graph
        graph.setColor("#FFFF00");
        var ht  = graph_h-Math.round((alt_data[i]-graph_ymin)*graph_h/(graph_ymax-graph_ymin))-graph_oy;
        graph.fillEllipse(graph_ox+Math.round(i*graph_dx)-4, ht-4, 9, 9);
        
        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(distance);});
        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;

    var currentLapDist = lapDistances[currentLap].firstChild.nodeValue;
    var currentLapDuration = lapDurations[currentLap].firstChild.nodeValue;
    currentLapDuration = currentLapDuration.replace("PT","")
    currentLapDuration = currentLapDuration.replace("S","")
    var lapDur = parseFloat(currentLapDuration);

	if (i > 0 ){
		lastTime = markers[i-1].getElementsByTagName("Time");
		lastTime = lastTime[0].firstChild.nodeValue;
		
		if (currentLap+1<numLaps)
		{   // multi-lap map; check to see if we've hit the next lap yet
		    if  ((totalTime>accumLapDurs+lapDur) || (distance >= (accumLapDistances+currentLapDist)*0.000621371192))
    		{
    		    accumLapDurs += lapDur;
    		    accumLapDistances += currentLapDist;
    		    currentLap++;
                lastTime = lapStartTimes[currentLap].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(){
    initForm();
    onLoad();
}

window.onunload = function() {
    GUnload();
}

