// cycling data display.
var cycling = {
    data : { rideXML : "rides.xml",
             year : "",
             base : "http://www.pink-triangle.org/scott/biking/",
	     id_count : 0,
	     curKml : null,
	     toMpH : 60*60*0.000621371192,
	     toMiles : 0.000621371192,
	     xmlDataFile : '',
	     rideSpec : null,
	     speedData : null,
	     speedGaugeOptions : { },
	     cadenceData : null,
	     cadenceGaugeOptions : { },
	     elevationData : null,
	     elevationOptions : { },
	     hrData : null,
	     hrOptions : { },
	     slider : null,
	     curTable : null,
	     bikeIcon : null,
	     interactive : { datetime : null,
			     latitude : 0,
			     longitude : 0,
			     elevation : 0,
			     ridetime : 0,
			     distance : 0,
			     speed : 0,
			     cadence : 0,
			     hr : 0,
			     maximum : 0,
			     marker : null,
	},
    }, 
    // data and functions that are on the page.
    pageElements : { map : null,
		     speedGauge : null,
		     cadenceGauge : null,
		     htGauge : null,
		     elevation : null,
		     timeElement : null,
		     promptElement : null,
    },
    // utility functions
    util : { },
    dataCallback : function(spec, file) {
    },
};

// ------------------------ UTIL -------------------------
// Make an id for elements being displayed
cycling.util.genID = function() {
    return "gen"+cycling.data.id_count++;
};

cycling.util.parseTime = function(timeString) {
    var d = new Date();
    var i = timeString.indexOf('T');
    var date = timeString.substr(0, i);
    timeString = timeString.substr(i+1);
    i = date.indexOf('-');
    d.setUTCFullYear(date.substr(0,i));
    date = date.substr(i+1);
    i = date.indexOf('-');
    d.setUTCMonth(date.substr(0,i)-1);
    d.setUTCDate(date.substr(i+1)-1);
    i = timeString.indexOf(':');
    d.setUTCHours(timeString.substr(0,i));
    timeString = timeString.substr(i+1);
    i = timeString.indexOf(':');
    d.setUTCMinutes(timeString.substr(0,i));
    timeString = timeString.substr(i+1);
    i = timeString.indexOf('Z');
    d.setUTCSeconds(timeString.substr(0,i));
    return d;
};

cycling.util.dateString = function(utc) {
    var date = new Date();
    date.setTime(utc*1000);
    var month = date.getMonth()+1;
    var day = date.getDate()+1;
    var year = date.getFullYear();
    return month+"/"+day+"/"+year;
};

cycling.util.timeString = function(utc) {
    var date = new Date();
    var m = "AM";
    date.setTime(utc*1000);
    var hour = date.getHours();
    if (hour > 11) m = "PM";
    if (hour > 12) hour = hour-12;
    if (hour == 0) hour = 12;
    var minutes = date.getMinutes()*1;
    if (minutes < 10) {
	minutes = '0'+minutes;
    }
    return hour+":"+minutes+m;
};

cycling.util.formatTime = function(text) {
    text = new Number(text/60).toFixed()*1;
    if (text >= 60) {
	var h = new Number(text/60).toFixed();
	var m = text-h*60;
	if (m < 0) {
	    h=h-1;
	    m=60+m;
	}
	text = h+':';
	if (m == 0) {
	    text = text+'00';
	} else if (m < 10) {
	    text+='0';
	    text+=m;
	} else text = text+m;
    } else {
	var m = text*1;
	text = '0:';
	if (m == 0) {
	    text = text+'00';
	} else if (m < 10) {
	    text+='0';
	    text+=m;
	} else text = text+m;
    }
    return text;
};

cycling.util.scaleAndTrim = function(text, scale) {
    return new Number(text*scale*10).toFixed()/10;
};

cycling.util.parseRideTime = function(timeString) {
    var i = timeString.indexOf(':');
    var h = timeString.substr(0, i);
    var m = timeString.substr(i+1);
    return Number(60*h+m*1).toFixed();
};

cycling.util.transformToHtml = function(myUrl, myDom, myElement) {
    var request = google.maps.XmlHttp.create();
    request.open("GET", myUrl, true);
    request.onreadystatechange = function() {
	if (request.readyState == 4) {
	    var xslDoc = request.responseXML;
	    if (typeof myDom.transformNode!="undefined") {
		myElement.innerHTML=myDom.transformNode(xslDoc);
	    } else {
		var c = new XSLTProcessor();
		c.importStylesheet(xslDoc);
		var d=c.transformToFragment(myDom, window.document);
		myElement.innerHTML="";
		myElement.appendChild(d);
	    }
	}
    }
    request.send(null);
};

//--------------------------- DATA ---------------------------------
cycling.data.initialize = function(maximum) {
    cycling.pageElements.setMarker(0);
    cycling.pageElements.setGauges(0);
    this.slider.setMaximum(maximum);
    this.slider.setMinimum(0);
    this.slider.setValue(0);
    this.slider.onchange = function() {
	var row = cycling.data.slider.getValue();
	cycling.pageElements.setMarker(row);
	cycling.pageElements.setGauges(row);
    };
};

cycling.data.clear = function() {
    if (cycling.pageElements.map) cycling.pageElements.map.clearOverlays();
    this.curKml = null;
    this.rideSpec = null;
    this.curTable = null;
    this.interactive.datetime = null;
    this.interactive.latitude = 0;
    this.interactive.longitude = 0;
    this.interactive.elevation = 0;
    this.interactive.ridetime = 0;
    this.interactive.distance = 0;
    this.interactive.speed = 0;
    this.interactive.cadence = 0;
    this.interactive.hr = 0;
    this.interactive.maximum = 0;
    this.interactive.marker = null;
    cycling.pageElements.clear();
};

// find a ride.  TODO getAttribute?
cycling.data.getRide = function(viewName) {
    var rides = this.cycleDom.documentElement.getElementsByTagName("ride");
    for(var i=0;i<rides.length;++i){
	for(var j=0;j<rides[i].attributes.length;++j){
	    if(rides[i].attributes[j].nodeName=="name"){
		if(rides[i].attributes[j].nodeValue==viewName)
		    return rides[i];
	    }
	}
    }
    return null;
};

cycling.data.readXmlFile = function(file) {
    var request = google.maps.XmlHttp.create();
    request.open("GET", file, true);

    request.onreadystatechange = function() {
	if (request.readyState == 4) {
	    cycling.data.interactive.initialize(request.responseXML);
	}
    }
    request.send(null);
}

cycling.data.initializeDom = function() {
    var request = google.maps.XmlHttp.create();
    request.open("GET",
                 cycling.data.rideXML,
                 true);
    request.onreadystatechange = function() {
	if (request.readyState == 4) {
	    cycling.data.cycleDom = request.responseXML;
	    cycling.pageElements.initializeControls(document.getElementById("controls"));
	    cycling.data.initializeWorksheet();
	}
    }
    request.send(null);
    var baseIcon = new GIcon();
    baseIcon.iconSize=new GSize(32,32);
    baseIcon.shadowSize=new GSize(56,32);
    baseIcon.iconAnchor=new GPoint(16,32);
    baseIcon.infoWindowAnchor=new GPoint(16,0);
    cycling.data.bikeIcon =
	new google.maps.Icon(baseIcon,
			     "http://maps.google.com/mapfiles/kml/shapes/cycling.png");
};

cycling.data.setRideDataFeed = function(ride, feed) {
    var rides =
	cycling.data.cycleDom.documentElement.getElementsByTagName("ride");
    var index = feed.indexOf('basic');
    if (index != -1) {
	feed = feed.substr(0, index) + 'values';
    }
    for(var i=0;i<rides.length;++i){
	var dataElement = rides[i].getElementsByTagName('data');
	if (dataElement && dataElement.length == 1 &&
	    dataElement[0].getAttribute('feed') == ride) {
	    dataElement[0].setAttribute('feed', feed);
	    return;
	}
    }
    GLog.write('not found ' + ride, 'black');
    return;
};

cycling.data.translateWorksheet = function(json) {
    for (var i=0; i < json.feed.entry.length; ++i) {
    	var entry = json.feed.entry[i];
	var name = entry.title.$t;
	var links = entry.link;
	for (var j=0; j < links.length; ++j) {
	    if (links[j]['rel'].indexOf('#cellsfeed') != -1) {
		this.setRideDataFeed(name, links[j]['href']);
	    }
	}
    }
};

cycling.data.initializeWorksheet = function() {
    var spreadsheet = this.cycleDom.documentElement.getAttribute('database');
    cycling.pageElements.removeOldJSONScriptNodes();
    var script = document.createElement('script');
    script.setAttribute('src',
			'http://spreadsheets.google.com/feeds/worksheets/' +
			spreadsheet + '/public/basic' +
			'?alt=json-in-script&callback=cycling.data.translateWorksheet');
    script.setAttribute('id', 'jsonScript');
    script.setAttribute('type', 'text/javascript');
    document.documentElement.firstChild.appendChild(script);
};

//--------------------------- DATA.INTERACTIVE --------------------------
cycling.data.interactive.initialize = function(myDom) {
    this.transformToDataTable(myDom);
    cycling.pageElements.elevation.draw(cycling.data.elevationData,
					cycling.data.elevationOptions);
    this.marker = null;
    cycling.data.initialize(this.maximum);
    cycling.pageElements.promptElement.innerHTML = '<p>Use the slider to move to points in time in this ride.  The time is the total time including rest stops.</p>';
};

cycling.data.interactive.transformToDataTable = function(myDom) {
    var table = cycling.data.curTable = new Array(9);
    table[this.datetime = 0] = new Array();
    table[this.latitude = 1] = new Array();
    table[this.longitude = 2] = new Array();
    table[this.elevation = 3] = new Array();
    table[this.ridetime = 4] = new Array();
    table[this.distance = 5] = new Array();
    table[this.speed = 6] = new Array();
    table[this.cadence = 7] = new Array();
    table[this.hr = 8] = new Array();
    var eltable =
    cycling.data.elevationData = new google.visualization.DataTable();
    this.compressToMinuteData(eltable.addColumn('string', 'Time', 'time'),
			      eltable.addColumn('number', 'Elevation', 'el'),
			      myDom.getElementsByTagName('Trackpoint'));
};

cycling.data.interactive.compressToMinuteData = function(eldatetime, elel,
							 tracks) {
    var table = cycling.data.curTable;
    var eltable = cycling.data.elevationData;
    var local = { totaltime : 0,
		  meters : 0,
		  last : 0,
		  lastDist : 0,
		  j : 0,
		  track : null,
		  dt : null,
		  curDistance : 0,
		  t : 0,
		  longdeg : null,
		  latdeg : null,
		  alt : null,
		  datetime : eldatetime,
		  elevation : elel,
		  curMinute : { time : 0,
				distance : 0,
				hr : 0,
				cadence : 0,
	},
    };
    this.maximum = 0;
    for(var i = 0; i < tracks.length; ++i) {
	local.track = tracks[i];
	var timeElement = local.track.getElementsByTagName('Time');
	if (!timeElement || timeElement.length == 0) continue;
	timeElement = timeElement[0];
	var distanceElement = local.track.getElementsByTagName('DistanceMeters');
	if (!distanceElement || distanceElement.length == 0) continue;
	distanceElement = distanceElement[0];
	local.dt = cycling.util.parseTime(timeElement.childNodes[0].nodeValue);
	local.t = new Number(local.dt.getTime() / 1000).toFixed()*1;
	local.curDistance = distanceElement.childNodes[0].nodeValue*1;
	var longdeg = local.track.getElementsByTagName('LongitudeDegrees');
	var hasLong = false;
	if (longdeg) {
	    local.longdeg = longdeg[0];
	    hasLong = true;
	}
	this.addTrackToMinute(local, table, eltable, hasLong);
    }
    if (local.curMinute.time != 0) {
	this.addRowToTables(local, table, eltable);
    }
};
    
cycling.data.interactive.addTrackToMinute = function(local, table, eltable,
						     hasLong) {
    if (local.curDistance < 1 || (local.lastDist - local.curDistance) == 0 ||
	local.last == 0 || !hasLong) {
	local.last = local.t;
	local.lastDist = local.curDistance;
	if (local.curMinute.time != 0) {
	    var element = local.track.getElementsByTagName('LatitudeDegrees');
	    if (element) local.latdeg = element[0];
	    element = local.track.getElementsByTagName('AltitudeMeters');
	    if (element) local.alt = element[0];
	    this.addRowToTables(local, table, eltable);
	    local.curMinute.time = 0;
	    local.curMinute.distance = 0;
	    local.curMinute.hr = 0;
	    local.curMinute.cadence = 0;
	}
    } else {
	var delta = local.t - local.last;	    
	var hrElement = local.track.getElementsByTagName('HeartRateBpm');
	var cadenceElement = local.track.getElementsByTagName('Cadence');
	var distance = local.curDistance-local.lastDist;
	var element = local.track.getElementsByTagName('LatitudeDegrees');
	if (element) local.latdeg = element[0];
	element = local.track.getElementsByTagName('AltitudeMeters');
	if (element) local.alt = element[0];
	local.last = local.t;
	local.totaltime += delta;
	local.meters += local.curDistance-local.lastDist;
	local.lastDist = local.curDistance;
	if (local.curMinute.time + delta >= 60) {
	    var newDelta = local.curMinute.time + delta - 60;
	    var restDistance = 0;
	    var hr = 0;
	    var cadence = 0;
	    if (newDelta != 0) {
		restDistance = new Number(distance*newDelta/delta).toFixed()*1;
		distance -= restDistance;
	    }
	    local.curMinute.time = 60;
	    local.curMinute.distance += distance;
	    delta -= newDelta;
	    if (hrElement && (hrElement.length == 1)) {
		var v = hrElement[0].getElementsByTagName('Value')[0];
		var x = v.childNodes[0].nodeValue*1;
		local.curMinute.hr += x*delta/60;
		hr = x*newDelta/60;
	    }
	    local.curMinute.hr = new Number(local.curMinute.hr).toFixed()*1;
	    if (cadenceElement && (cadenceElement.length == 1)) {
		var v = cadenceElement[0].childNodes[0].nodeValue*1;
		local.curMinute.cadence += v*delta/60;
		cadence = v*newDelta/60;
	    }
	    local.curMinute.cadence =
		new Number(local.curMinute.cadence).toFixed()*1;
	    this.addRowToTables(local, table, eltable);
	    local.curMinute.time = newDelta;
	    local.curMinute.distance = restDistance;
	    local.curMinute.hr = hr;
	    local.curMinute.cadence = cadence;
	} else {
	    local.curMinute.time += delta;
	    local.curMinute.distance += distance;
	    if (hrElement && (hrElement.length == 1)) {
		var v = hrElement[0].getElementsByTagName('Value')[0];
		var x = v.childNodes[0].nodeValue*1;
		local.curMinute.hr += x*delta/60;
	    }
	    local.curMinute.hr = new Number(local.curMinute.hr).toFixed()*1;
	    if (cadenceElement && (cadenceElement.length == 1)) {
		var v = cadenceElement[0].childNodes[0].nodeValue*1;
		local.curMinute.cadence += v*delta/60;
	    }
	}
    }
};

cycling.data.interactive.addRowToTables = function(local, table, eltable) {
    if (!local.longdeg) return;
    ++this.maximum;
    table[this.hr].push(local.curMinute.hr);
    table[this.cadence].push(local.curMinute.cadence);
    table[this.datetime].push(local.dt);
    table[this.longitude].push(local.longdeg.childNodes[0].nodeValue*1);
    table[this.latitude].push(local.latdeg.childNodes[0].nodeValue*1);
    table[this.elevation].push(local.alt.childNodes[0].nodeValue*1);
    table[this.ridetime].push(local.totaltime);
    var speed = local.curMinute.distance/local.curMinute.time*
                cycling.data.toMpH;
    table[this.speed].push(speed);
    table[this.distance].push(local.meters);
    var row = eltable.addRow();
    if ((local.j % 30) == 0) {
	eltable.setCell(row, local.datetime,
			cycling.util.timeString(local.dt.getTime()/1000));
    } else {
	eltable.setCell(row, local.datetime, '');
    }
    eltable.setCell(row, local.elevation,
		    local.alt.childNodes[0].nodeValue*1);
    ++local.j;
};

//--------------------------- pageElements --------------------------
cycling.pageElements.clear = function() {
    this.promptElement.innerHTML = '';
};

cycling.pageElements.removeOldJSONScriptNodes = function() {
    var jsonScript = document.getElementById('jsonScript');
    if (jsonScript) {
	jsonScript.parentNode.removeChild(jsonScript);
    }
};

cycling.pageElements.removeOldResults = function() {
    var div = document.getElementById('data');
    if (div.firstChild) {
	div.removeChild(div.firstChild);
    }
};

// Toggle table rows that display laps.
cycling.pageElements.toggleLaps = function(id) {
    var body = document.getElementById(id);
    if (body) {
	if (body.style.visibility == 'collapse')
	    body.style.visibility = 'visible';
	else
	    body.style.visibility = 'collapse';
    }
};
    
cycling.pageElements.setMarker = function(row) {
    var lat = cycling.data.curTable[cycling.data.interactive.latitude][row];
    var lng = cycling.data.curTable[cycling.data.interactive.longitude][row];
    var latlong = new google.maps.LatLng(lat, lng);
    if (cycling.data.interactive.marker) {
	cycling.data.interactive.marker.setLatLng(latlong);
    } else {
	cycling.data.interactive.marker = new google.maps.Marker(latlong,
								 cycling.data.bikeIcon);
	cycling.pageElements.map.addOverlay(cycling.data.interactive.marker);
    }
};

cycling.pageElements.setGauges = function(row) {
    var speed =	cycling.data.curTable[cycling.data.interactive.speed][row];
    var cadence = cycling.data.curTable[cycling.data.interactive.cadence][row];
    var hr = cycling.data.curTable[cycling.data.interactive.hr][row];
    var startTime = cycling.data.curTable[cycling.data.interactive.datetime][0];
    var now = cycling.data.curTable[cycling.data.interactive.datetime][row];
    var text  = Number(speed*10).toFixed();
    text = (text.substr(0,text.length-1)+'.'+text.substr(text.length-1))*1;
    cycling.data.speedData.setCell(0, 0, text);
    this.speedGauge.draw(cycling.data.speedData,
			 cycling.data.speedGaugeOptions);
    text  = Number(cadence*10).toFixed();
    text = (text.substr(0,text.length-1)+'.'+text.substr(text.length-1))*1;
    cycling.data.cadenceData.setCell(0, 0, text);
    this.cadenceGauge.draw(cycling.data.cadenceData,
			   cycling.data.cadenceGaugeOptions);
    if (hr) {
	text  = Number(hr*10).toFixed();
    } else {
	text = '0';
    }
    text = (text.substr(0,text.length-1)+'.'+text.substr(text.length-1))*1;
    cycling.data.hrData.setCell(0, 0, text);
    this.hrGauge.draw(cycling.data.hrData, cycling.data.hrGaugeOptions);
    var rideTime = new Date();
    rideTime.setTime(now.getTime()-startTime.getTime());
    var h = rideTime.getUTCHours();
    var m = rideTime.getUTCMinutes();
    if (h == 0) {
	h = '00';
    } else if (h < 10) {
	h = '0' + h;
    }
    if (m == 0) {
	m = '00';
    } else if (m < 10) {
	m = '0' + m;
    }
    cycling.pageElements.timeElement.innerHTML = h +':'+ m;
};

cycling.pageElements.addRideHeaders = function(table, indexes, showButton) {
    var headers = ['Date', 'Started', 'Completed', 
		   'Speed/', 'Max', 'Ride Time', 'Distance',
		   'N/A', 'Heart Rate/', 'Max',
		   'Cadence/', 'Max', 'Calories', 'Laps', 'N/a'];

    var tbody = document.createElement('thead');
    var tr = document.createElement('tr');
    var td = null;
    if (showButton) {
	td = document.createElement('th');
	td.appendChild(document.createTextNode('Show'));
	tr.appendChild(td);
    }
    for (var j=0; j < indexes.length; ++j) {
	var k = indexes[j];
	td = document.createElement('th');
	td.appendChild(document.createTextNode(headers[k]));
	tr.appendChild(td);
    }
    tbody.appendChild(tr);
    table.appendChild(tbody);
};

cycling.pageElements.createFormDefault = function(form) {
    var text = document.createTextNode('Show default map and averages');
    var input = document.createElement('input');
    input.setAttribute('type', 'radio');
    input.setAttribute('name', 'RideButton');
    input.setAttribute('checked', 'true');
    input.onclick = function(){cycling.pageElements.disableInteractiveView()};
    form.appendChild(input);
    form.appendChild(text);
};

cycling.pageElements.singleRideEntries = function(json) {
    this.cellEntries(json, false);
};

cycling.pageElements.multiRideEntries = function(json) {
    this.cellEntries(json, true);
};

cycling.pageElements.cellEntries = function(json, showButton) {
    // GLog.write('cell entries', 'black');
    var form = document.createElement('form');
    var table = document.createElement('table');
    var tbody = document.createElement('tbody');
    var row = 0;
    var lap = 13;
    var curLap = 14;
    var speed = 3;
    var mspeed = 4;
    var cadence = 10;
    var mcadence = 11;
    var hr = 8;
    var rideTime = 5;
    var totalTime = 0;
    var ctotalTime = 0;
    var hrtotalTime = 0;
    var avgSpeed = 0;
    var maxSpeed = 0;
    var avgHr = 0;
    var avgCadence = 0;
    var maxCadence = 0;
    var indexes = [0, 13, 1, 2, 5, 6, 3, 4, 10, 11, 8, 9, 12];
    var subindexes = [-1, 14, 1, 2, 5, 6, 3, 4, 10, 11, 8, 9, 12];
    var fileIndex = '15';
    var fileName = '';
    var tr = null;
    var td = null;
    var ind = null;
    var data = null;
    var visible = true;
    var idName = null;
    table.setAttribute('id', 'output');
    table.style.fontSize="83%";
    cycling.pageElements.removeOldResults();
    if (showButton) this.createFormDefault(form);
    this.addRideHeaders(table, indexes, showButton);
    for (var i=0; i < json.feed.entry.length; i++) {
	var entry = json.feed.entry[i];
	var text = '';
	if (entry.gs$cell.col == '1') {
	    if (row > 1) {
		if (data[curLap] != '0') {
		    ind = subindexes;
		} else {
		    ind = indexes;
		}
		if (data[curLap] == 0) {
		    var rt = cycling.util.parseRideTime(data[rideTime]);
		    avgSpeed += data[speed]*rt;
		    if (maxSpeed < data[mspeed]*1) maxSpeed = data[mspeed]*1;
		    if (data[cadence] != '0') {
			avgCadence += data[cadence]*rt;
			if (maxCadence < data[mcadence]*1)
			    maxCadence = data[mcadence]*1;
			ctotalTime += rt*1;
		    }
		    if (data[hr] != '0') {
			avgHr += data[hr]*rt;
			hrtotalTime += rt*1;
		    }
		    totalTime += rt*1;
		}
		if (showButton && data[curLap] == '0') {
		    td = document.createElement('td');
		    td.style.textAlign='center';
		    var input = document.createElement('input');
		    input.setAttribute('type', 'radio');
		    input.setAttribute('name', 'RideButton');
		    input.setAttribute('value', data[15]);
		    input.onclick = function(){
			cycling.pageElements.enableInteractiveView(this.getAttribute('value'));
		    };
		    td.appendChild(input);
		    tr.appendChild(td);
		}
		for (var j=0; j < ind.length; ++j) {
		    var k = ind[j];
		    td = document.createElement('td');
		    td.style.textAlign = 'center';
		    if (k == lap && data[lap] != '1') {
			td.style.cursor = 'pointer';
			idName = cycling.util.genID();
			var a = document.createElement("a");
			a.setAttribute("onclick",
				       "cycling.pageElements.toggleLaps('"+
				       idName+"')");
			a.innerHTML = data[k]+' &darr;';
			td.appendChild(a);
		    } else if (k != -1) {
			td.appendChild(document.createTextNode(data[k]));
		    }
		    tr.appendChild(td);
		}
		if (visible && data[lap] != '1') {
		    visible = false;
		    tbody.appendChild(tr);
		    table.appendChild(tbody);
		    tbody = document.createElement('tbody');
		    tbody.setAttribute("id", idName);
		    tbody.style.visibility = "collapse";
		} else if (!visible && data[curLap] == '0') {
		    visible = true;
		    table.appendChild(tbody);
		    tbody = document.createElement('tbody');
		    tbody.appendChild(tr);
		    if (row != 2 && data[lap] != '1') {
			table.appendChild(tbody);
			tbody = document.createElement('tbody');
			tbody.setAttribute("id", idName);
			tbody.style.visibility = "collapse";
			visible = false;
		    }
		} else
		    tbody.appendChild(tr);
	    }
	    ++row;
	    data = new Array();
	    tr = document.createElement('tr');
	}
	if (row > 1) {
	    text = entry.content.$t;
	    if (entry.gs$cell.col == '1') {
		data.push(cycling.util.dateString(text));
		data.push(cycling.util.timeString(text));
	    } else if (entry.gs$cell.col == '2') {
		data.push(cycling.util.timeString(text));
	    } else {
		if (entry.gs$cell.col == '5') {
		    text = cycling.util.formatTime(text);
		} else if (entry.gs$cell.col == '3' ||
			   entry.gs$cell.col == '4') {
		    text = cycling.util.scaleAndTrim(text,cycling.data.toMpH);
		} else if (entry.gs$cell.col == '6') {
		    text = cycling.util.scaleAndTrim(text,cycling.data.toMiles);
		} else if (entry.gs$cell.col == fileIndex) {
		    fileName = text;
		} else {
		    text = new Number(text).toFixed();
		}
		data.push(text);
	    }
	}
    }
    if (data != null) {
	if (data[curLap] == '0') {
	    var rt = cycling.util.parseRideTime(data[rideTime]);
	    avgSpeed += data[speed]*rt;
	    if (maxSpeed < data[mspeed]*1) maxSpeed = data[mspeed]*1;
	    if (data[cadence] != '0') {
		avgCadence += data[cadence]*rt;
		if (maxCadence < data[mcadence]*1)
		    maxCadence = data[mcadence]*1;
		ctotalTime += rt*1;
	    }
	    if (data[hr] != '0') {
		avgHr += data[cadence]*rt;
		hrtotalTime += rt*1;
	    }
	    totalTime += rt*1;
	}

	if (data[curLap] != '0') {
	    ind = subindexes;
	} else {
	    ind = indexes;
	}
	if (showButton && data[curLap] == '0') {
	    td = document.createElement('td');
	    var input = document.createElement('input');
	    input.setAttribute('type', 'radio');
	    input.setAttribute('name', 'RideButton');
	    input.setAttribute('value', data[15]);
	    input.onclick = function(){
		cycling.pageElements.enableInteractiveView(this.getAttribute('value'));
	    };
	    td.style.textAlign='center';
	    td.appendChild(input);
	    tr.appendChild(td);
	}
	for (var j=0; j < ind.length; ++j) {
	    var k = ind[j];
	    td = document.createElement('td');
	    td.style.textAlign = 'center';
	    if (k == lap && data[lap] != '1') {
		td.style.cursor = 'pointer';
		idName = cycling.util.genID();
		var a = document.createElement("a");
		a.setAttribute("onclick",
			       "cycling.pageElements.toggleLaps('"+idName+"')");
		a.appendChild(document.createTextNode(data[k]));
		td.appendChild(a);
	    } else if (k != -1) {
		td.style.textAlign = 'center';
		td.appendChild(document.createTextNode(data[k]));
	    }
	    tr.appendChild(td);
	}
    }
    if (!visible && data[curLap] == '0') {
	table.appendChild(tbody);
	tbody = document.createElement('tbody');
	tbody.appendChild(tr);
    } else  {
	tbody.appendChild(tr);
    }
    table.appendChild(tbody);
    form.appendChild(table);
    document.getElementById('data').appendChild(form);
    var avgcad = 0;
    var avghr = 0;
    if (ctotalTime > 0) {
	avgcad = avgCadence / ctotalTime;
    }
    if (hrtotalTime > 0) {
	avghr = avgHr / hrtotalTime;
    }
    cycling.data.rideSpec = {'avgSpeed' : avgSpeed /totalTime,
			     'maxSpeed' : maxSpeed,
			     'avgCadence' : avgcad,
			     'maxCadence' : maxCadence,
			     'avgHr' : avghr,
			     'rideTime' : '00:00',
    };
    cycling.dataCallback(cycling.data.rideSpec, fileName);
}

cycling.pageElements.initializeGauges = function(spec) {
    var m = spec.maxSpeed;
    var avg = Number(spec.avgSpeed*10).toFixed();
    avg = (avg.substr(0,avg.length-1)+'.'+avg.substr(avg.length-1))*1;
    cycling.data.speedData.setCell(0, 0, avg);
    cycling.data.speedGaugeOptions.max = m;
    cycling.data.speedGaugeOptions.yellowFrom = m-20;
    cycling.data.speedGaugeOptions.yellowTo = m-10;
    cycling.data.speedGaugeOptions.redFrom = m-10;
    cycling.data.speedGaugeOptions.redTo = m;
    this.speedGauge.draw(cycling.data.speedData, cycling.data.speedGaugeOptions);
    if (spec.maxCadence != 0) {
	m = spec.maxCadence;
	avg = Number(spec.avgCadence*10).toFixed()/10;
	cycling.data.cadenceData.setCell(0, 0, avg);
	cycling.data.cadenceGaugeOptions.max = m;
	cycling.data.cadenceGaugeOptions.yellowFrom = m-20;
	cycling.data.cadenceGaugeOptions.yellowTo = m-10;
	cycling.data.cadenceGaugeOptions.redFrom = m-10;
	cycling.data.cadenceGaugeOptions.redTo = m;
	this.cadenceGauge.draw(cycling.data.cadenceData, cycling.data.cadenceGaugeOptions);
    } else {
	cycling.data.cadenceData.setCell(0, 0, 0);
	cycling.data.cadenceGaugeOptions.max = 200;
	cycling.data.cadenceGaugeOptions.yellowFrom = 180;
	cycling.data.cadenceGaugeOptions.yellowTo = 190;
	cycling.data.cadenceGaugeOptions.redFrom = 190;
	cycling.data.cadenceGaugeOptions.redTo = 200;
	this.cadenceGauge.draw(cycling.data.cadenceData, cycling.data.cadenceGaugeOptions);
    }
    if (spec.avgHr != 0) {
	avg = Number(spec.avgHr*10).toFixed()/10;
	cycling.data.hrData.setCell(0, 0, avg);
	this.hrGauge.draw(cycling.data.hrData, cycling.data.hrGaugeOptions);
    } else {
	cycling.data.hrData.setCell(0, 0, 0);
	this.hrGauge.draw(cycling.data.hrData, cycling.data.hrGaugeOptions);
    }
    cycling.pageElements.timeElement.innerHTML = spec.rideTime;
}

cycling.pageElements.displayCyclingData = function(type, worksheet) {
    this.removeOldJSONScriptNodes();
    this.removeOldResults();
    // Retrieve the JSON feed.
    var script = document.createElement('script');
    if (type == 'single') {
	script.setAttribute('src',
			    worksheet +
			    '?alt=json-in-script&callback=cycling.pageElements.singleRideEntries');
    } else {
	script.setAttribute('src',
			    worksheet +
			    '?alt=json-in-script&callback=cycling.pageElements.multiRideEntries');
    }
    script.setAttribute('id', 'jsonScript');
    script.setAttribute('type', 'text/javascript');
    document.documentElement.firstChild.appendChild(script);
};

cycling.pageElements.initializeControls = function(controlElement) {
    cycling.util.transformToHtml("../controls.xsl",
				 cycling.data.cycleDom,
				 controlElement);
}

cycling.pageElements.setDescription = function(descNode) {
    cycling.util.transformToHtml("../description.xsl",
				 descNode,
				 document.getElementById("description"));
};

cycling.pageElements.setMap = function(viewNode) {
    var mapNode = viewNode.getElementsByTagName("map")[0];
    var path = mapNode.getAttribute("path");
    var geoXml = new google.maps.GeoXml(path);
    if (!this.map) {
	var mapElement = document.getElementById("map");
	this.map = new google.maps.Map2(mapElement);
    }
    google.maps.Event.addListener(geoXml, 'load', function() {
	    if (cycling.data.curKml) {
		cycling.pageElements.map.removeOverlay(cycling.data.curKml);
	    }
	    cycling.data.curKml = geoXml;
	    geoXml.gotoDefaultViewport(cycling.pageElements.map);
	    cycling.pageElements.map.addControl(new google.maps.LargeMapControl());
	    google.maps.Event.clearNode(geoXml);
	    cycling.pageElements.map.addOverlay(geoXml);
	});
};

cycling.pageElements.setData = function(viewNode) {
    var dataElement = viewNode.getElementsByTagName('data');
    var dataType = viewNode.getAttribute('type');
    if (dataElement && dataElement.length == 1) {
	var worksheet = dataElement[0].getAttribute('feed');
	var titleElement = document.getElementById('dtitle');
	var gtitleElement = document.getElementById('gtitle');
	var titleText = 'Training data';
	var gaugeText = 'Training';
	if (dataType == 'single') {
	    titleText = 'Ride data';
	    gaugeText = 'Ride';
	}
	if (titleElement.firstChild) {
	    titleElement.removeChild(titleElement.firstChild);
	}
	titleElement.appendChild(document.createTextNode(titleText));
	if (gtitleElement.firstChild) {
	    gtitleElement.removeChild(gtitleElement.firstChild);
	}
	gtitleElement.appendChild(document.createTextNode(gaugeText));
	cycling.pageElements.displayCyclingData(dataType, worksheet);
    }
};

cycling.pageElements.setGDB = function(viewNode) {
    var mapNode = viewNode.getElementsByTagName("map")[0];
    var data = mapNode.getAttribute("data");
    var text = mapNode.getAttribute("datafile");
    var gdbElement = document.getElementById("gdb");
    var children = gdbElement.childNodes;
    for (var i = children.length; i > 0; --i) {
	gdbElement.removeChild(gdbElement.childNodes[i - 1]);
    }
    if (data) {
	var a = document.createElement("a");
	a.className = "l";
	a.appendChild(document.createTextNode(text));
	a.href = data;
	gdbElement.appendChild(document.createTextNode("MapSource file: "));
	gdbElement.appendChild(a);
    }
};

cycling.pageElements.enableInteractiveView = function(file) {
    var elsec = document.getElementById('elevationSection');
    var slidesec = document.getElementById('sliderSection');
    if (cycling.data.interactive.marker) {
	cycling.pageElements.map.removeOverlay(cycling.data.interactive.marker);
	cycling.data.interactiveMarker = null;
    }
    elsec.style.visibility = 'visible';
    slidesec.style.visibility = 'visible';
    cycling.data.xmlDataFile = file;
    var rideSpec = {'avgSpeed' : 0,
		    'maxSpeed' : cycling.data.rideSpec.maxSpeed,
		    'avgCadence' : 0,
		    'maxCadence' : cycling.data.rideSpec.maxCadence,
		    'avgHr' : cycling.data.rideSpec.avgHr,
		    'rideTime' : '00:00',
    };
    cycling.pageElements.initializeGauges(cycling.data.rideSpec);
    if (cycling.pageElements.promptElement.firstChild)
	cycling.pageElements.promptElement.innerHTML = '';
    var blink = document.createElement('blink');
    blink.appendChild(document.createTextNode('Loading...'));
    cycling.pageElements.promptElement.appendChild(blink);
    cycling.pageElements.promptElement.appendChild(document.createTextNode('This script may take a few seconds.  If you see an error to that effect, please click '));
    blink = document.createElement('b');
    blink.appendChild(document.createTextNode('continue'));
    cycling.pageElements.promptElement.appendChild(blink);
    cycling.data.readXmlFile(file);
};

cycling.pageElements.disableInteractiveView = function() {
    var elsec = document.getElementById('elevationSection');
    var slidesec = document.getElementById('sliderSection');
    if (cycling.data.interactive.marker) {
	cycling.pageElements.map.removeOverlay(cycling.data.interactive.marker);
	cycling.data.interactiveMarker = null;
    }
    elsec.style.visibility = 'collapse';
    slidesec.style.visibility = 'collapse';
    cycling.pageElements.initializeGauges(cycling.data.rideSpec);
};

cycling.pageElements.enableInteractiveMode = function() {
    var elsec = document.getElementById('elevationSection');
    var slidesec = document.getElementById('sliderSection');
    elsec.style.visibility = 'visible';
    slidesec.style.visibility = 'visible';
    cycling.dataCallback = function(spec, file) {
        cycling.data.xmlDataFile = file;
	var rideSpec = {'avgSpeed' : 0,
			'maxSpeed' : spec.maxSpeed,
			'avgCadence' : 0,
			'maxCadence' : spec.maxCadence,
			'avgHr' : spec.avgHr,
			'rideTime' : '00:00',
	};
	cycling.pageElements.initializeGauges(rideSpec);
	cycling.data.readXmlFile(file);
    }
    var blink = document.createElement('blink');
    blink.appendChild(document.createTextNode('Loading...'));
    cycling.pageElements.promptElement.appendChild(blink);
    cycling.pageElements.promptElement.appendChild(document.createTextNode('This script may take a few seconds.  If you see an error to that effect, please click '));
    blink = document.createElement('b');
    blink.appendChild(document.createTextNode('continue'));
    cycling.pageElements.promptElement.appendChild(blink);
};

cycling.pageElements.disableInteractiveMode = function() {
    var elsec = document.getElementById('elevationSection');
    var slidesec = document.getElementById('sliderSection');
    elsec.style.visibility = 'collapse';
    slidesec.style.visibility = 'collapse';
    cycling.dataCallback = function(spec, file) {
	cycling.pageElements.initializeGauges(spec);
    }
};

cycling.pageElements.setView = function(viewName) {
    var ride = cycling.data.getRide(viewName);
    if (ride) {
	cycling.data.clear();
	if (ride.getAttribute('type') == 'single') {
	    this.enableInteractiveMode();
	    this.setDescription(ride);
	    this.setGDB(ride);
	    this.setMap(ride);
	    this.setData(ride);
	} else {
	    this.disableInteractiveMode();
	    this.setDescription(ride);
	    this.setMap(ride);
	    this.setGDB(ride);
	    this.setData(ride);
	    this.promptElement.innerHTML = 'These are rides I do often.  Click the button next to any single ride to it interactively.';
	}
    } else {
	window.alert("can't find " + viewName);
    }
};

cycling.pageElements.initializeVisualElements = function() {
    var gaugeData = new google.visualization.DataTable();
    gaugeData.addColumn('number', 'Speed');
    gaugeData.addRows(1);
    gaugeData.setCell(0, 0, 0);
    var gaugeOptions = {'min': 0, 'max': 50, 'yellowFrom': 30, 'yellowTo': 40,
			'redFrom': 40, 'redTo': 50, 'minorTicks': 5,
			'width': 150, 'height': 150};
    var gauge;
    gauge = new google.visualization.Gauge(document.getElementById('speedGage'));
    gauge.draw(gaugeData, gaugeOptions);
    cycling.pageElements.speedGauge = gauge;
    cycling.data.speedData = gaugeData;
    cycling.data.speedGaugeOptions = gaugeOptions;

    gaugeData = new google.visualization.DataTable();
    gaugeData.addColumn('number', 'Heart Rate');
    gaugeData.addRows(1);
    gaugeData.setCell(0, 0, 0);
    gaugeOptions = {'min': 0, 'max': 200, 'yellowFrom': 145, 'yellowTo': 175,
		    'redFrom': 175, 'redTo': 200, 'minorTicks': 5,
		    'width': 150, 'height': 150};
    gauge = new google.visualization.Gauge(document.getElementById('hrGage'));
    gauge.draw(gaugeData, gaugeOptions);
    cycling.pageElements.hrGauge = gauge;
    cycling.data.hrData = gaugeData;
    cycling.data.hrGaugeOptions = gaugeOptions;

    gaugeData = new google.visualization.DataTable();
    gaugeData.addColumn('number', 'Cadence');
    gaugeData.addRows(1);
    gaugeData.setCell(0, 0, 0);
    gaugeOptions = {'min': 0, 'max': 200, 'yellowFrom': 150, 'yellowTo': 170,
		    'redFrom': 170, 'redTo': 200, 'minorTicks': 5,
		    'width': 150, 'height': 150};
    gauge = new google.visualization.Gauge(document.getElementById('cadenceGage'));
    gauge.draw(gaugeData, gaugeOptions);
    cycling.pageElements.cadenceGauge = gauge;
    cycling.data.cadenceData = gaugeData;
    cycling.data.cadenceGaugeOptions = gaugeOptions;

    gauge = new google.visualization.LineChart(document.getElementById('elevation'));
    cycling.data.elevationData = new google.visualization.DataTable();
    cycling.data.elevationData.addColumn('string', 'Time');
    cycling.data.elevationData.addColumn('number', 'Elevation');
    cycling.pageElements.elevation = gauge;
    cycling.data.elevationOptions = {width: 640, height: 240,
			     legend: 'bottom',	legend: 'none',
			     lineSize: '1', pointSize: '1',
			     title: 'Elevation' };
    gauge.draw(cycling.data.elevationData, cycling.data.elevationOptions);
};

//------------------------------------------------------------------
cycling.initialize = function(year) {
    cycling.data.year = year;
    cycling.data.rideXML = cycling.data.base + year + '/' + cycling.data.rideXML;
    cycling.data.slider = new Slider(document.getElementById("slider-1"),
				     document.getElementById("slider-input-1"));
    cycling.pageElements.timeElement = document.getElementById('time');
    cycling.pageElements.promptElement = document.getElementById('prompt');
    google.load("maps", "2", {'callback': cycling.data.initializeDom});
    google.load("visualization", "1",
		{'packages':["gauge", "map", "linechart"],
			'callback': cycling.pageElements.initializeVisualElements});
}
