/*
	Gear calculator scripting.
	Developed using jQuery 1.2.6.
*/
/*
	Features to add:
		Label Google Chart with Meters Development and/or Gear Inches.
		Use timer to reduce quantity of Google Chart requests (see form checker in Hile Blog)
		Click directly on row/column labels to remove that row/column
		'freeze' a table to start a new one
		Select and highlight target Ratios
		Provide predefined cassettes
		Save calculated ratio sets for comparison and recall
*/

// initiate form actions
function gearCalc() {
	if (location.href.indexOf('#')>-1) {
		thispage = location.href.substring(0,location.href.indexOf('#'));
		if (location.href.indexOf('t=')>-1) {
			tl = fno('t=');
			wl = fno('w=');
			$('#tire_sizes').val(tl);
			$('#rim_sizes').val(wl);
			wheelSize = Number(tl) + Number(wl);
		} else {
			wl = fno('w=');
			wheelSize = Number(wheelSize);
		}
		$('#wheel').val(wheelSize);
		var cl = fno('c=');
		var rl = fno('r=');
		crankLength = fno('a=');
		cogList = cl.split(',');
		ringList = rl.split(',');
		gearListMaker('cog',cogList);
		gearListMaker('ring',ringList);
		changeRatioGrid();
	} else {
		thispage = location.href;
	}
	$('#wheel,#ring,#cog,#hub,#tratio,#rratio').focus(function(){
		$(this).css('background','#ddddff');
	});
	$('#ring,#cog,#hub,#tratio,#rratio').blur(function(){
		$(this).css('background','none');
	});
	$('#wheel').blur(function() {
		$(this).css('background','none');
		var x = vN($(this).val());
		if (x) { wheelSize = x; changeRatioGrid(); } else { $(this).value = wheelSize; }
	});
	$('#wheel,#ring,#cog,#hub,#tratio,#rratio').keydown(function(event){ // neutralize return key (for now)
		if (event.keyCode == 13) { $(this).blur();$(this).focus();return false; }
	});
	$('#rim_sizes,#tire_sizes').change(function () {
		if ($('#rim_sizes').val()==0 || $('#tire_sizes').val()==0) { return; }
		var x = $('#rim_sizes').val();
		var y = $('#tire_sizes').val();
		wheelSize = Number(y) + Number(x);
		$('#wheel').val(wheelSize);
		changeRatioGrid();
	});
	$('#tratio').blur(function() { ratioTarget = $(this).val(); changeRatioGrid(); });
	$('#rratio').blur(function() { ratioRange = $(this).val(); changeRatioGrid(); });
	$('.hide.gears,.show.medev,.gi').css('display','block');
	$('.gain,.gr').css('display','none');
	if (crankLength!=0) { $('.show.gain').css('display','block'); }
	$('#crank').change(function() {
		if ($('#crank').val()==0) { $('.gain,.gr').css('display','none');return; }
		crankLength = $('#crank').val();
		$('.hide.gain').css('display','block');
		changeRatioGrid();
	});
	$('#ring').blur(function(){ ringList = gearLister('ring',ringList); });
	$('#cog').blur(function(){ cogList = gearLister('cog',cogList); });
	$('#hub').blur(function(){ hubList = gearLister('hub',cogList); });
	if (!document.getElementById('th_0')) { $('#showtypes').css('display','none'); }
	$('.show.gears,.hide.medev,.md').css('display','none');
	$('.show,.hide').mouseup(function() {
		if ($(this).hasClass('gears')) { var cs = '.gi'; var ct = '.gears'; } else if ($(this).hasClass('medev')) { var cs = '.md'; var ct = '.medev'; } else if ($(this).hasClass('gain')) { var cs = '.gr'; var ct = '.gain'; }
		if ($(this).hasClass('show')) { $(cs+',.hide'+ct).css('display','block'); } else { $(cs).css('display','none'); $('.show'+ct).css('display','block'); }
		$(this).css('display','none');
	});
}
// extracts values from the URL
function fno(x) {
	var z = location.href.indexOf(x);
	if (location.href.indexOf('&',z)>-1) {
		var y = location.href.substring(z+2,location.href.indexOf('&',z));
	} else {
		var y = location.href.substr(z+2);
	}
	return y;
}
// handles the ring and cog lists.
function gearLister(ln,ll) {
	var x = vN($('#'+ln).val());
	if (x) {
		// see if the new value is already in the list
		for (var a=0;a<ll.length;a++) { if (x==ll[a]) { $('#'+ln).val('');return ll; } }
		ll.push(x);
		ll.sort(sortNum);
		if (ln=='cog') { ll.reverse(); }
		changeRatioGrid();
		gearListMaker(ln,ll);
	}
	// clear the input field
	$('#'+ln).val('');
	return ll;
}
// update the displayed gears, add hidden form field
function gearListMaker(ln,ll) {
	var y = '<div id="'+ln+'_bin">';
	for(var a=0;a<ll.length;a++) { y += '<span class="'+ln+'_list" id="'+ln+'_'+ll[a]+'">'+ll[a]+'</span>'; }
	y += "</div>";
	$('#'+ln+'_bin').replaceWith(y);
	// add click-to-remove actions to the displayed gears
	$('.'+ln+'_list').mouseup(function(){
		for(var a=0;a<ll.length;a++) {
			if(ll[a]==$(this).text()){ll.splice(a,1);break;}
		}
		$(this).remove();
		changeRatioGrid();
	});
	return;
}
// updates the grid based on input changes.
function changeRatioGrid() {
	// this function only fires on one form field change at a time.
	if (!wheelSize) { if ($('#wheel').val()) { wheelSize = $('#wheel').val(); } }
	if (!wheelSize || ringList.length==0 || cogList.length==0) {
		$('#gearing tbody,#bookmark').empty();
		$('#showtypes').css('display','none');
		return;
	}
	chartMade = 0;
	$('#showtypes').css('display','block');
	var ws = wheelSize;
	// if user entered wheel as metric, convert to inch.
	wheelMetric = true;
	if (wheelMetric) { var ws = mm2i(wheelSize); }
	var nr = false;
	var yy = '<tr id="tr_cog"><th id="th_0">' + wheelSize + ' mm wheel</th>';
	for (var c=0;c<cogList.length;c++) {
		yy += '<th id="cog_'+cogList[c]+'" class="cog_'+cogList[c]+'">'+cogList[c]+'</th>';
	}
	yy += '</tr>';
	var ratList = new Array();
	var gData = new Array();
	var maxim = new Array();
	var rlmin = new Array(); // array of lowest values
	var rlmax = new Array(); // array of highest values
	var memin = new Array(); // ditto, meters development
	var memax = new Array(); // ditto
	var memaxim = new Array();
	var grmin = new Array(); // ditto, gain ratios
	var grmax = new Array(); // ditto
	var grmaxim = new Array();
	if (crankLength) { var grbase = wheelSize / 2 / crankLength; } else { var grbase = 0; }
	var hc = '0123456789abcdef';
	if (ratioTarget && ratioRange) {
		ratioTarget = parseFloat(ratioTarget);
		ratioRange = parseFloat(ratioRange);
		var rti = ratioTarget * ratioRange / 100;
		rti = rti.toFixed(2);
		rti = parseFloat(rti);
		var rtmin = ratioTarget - rti - 0.1;
		var rtmax = ratioTarget + rti + 0.1;
		var rtstep = rti / 15;
		rtstep = rtstep.toFixed(2);
		rtstep = parseFloat(rtstep);
	} else {
		var rtmin = rtmax = 0;
	}
	testqueue = new Array();
	for (var r=0;r<ringList.length;r++) {
		rlmin[r] = 0;
		rlmax[r] = 0;
		var wsr = ws * ringList[r];
		yy += '<tr id="tr_'+ringList[r]+'"><th id="ring_'+ringList[r]+'" class="ring_'+ringList[r]+'">'+ringList[r]+'</th>';
		ratList = new Array();
		for (var c=0;c<cogList.length;c++) {
			var rat = wsr / cogList[c];
			var medev = rat * 3.1416; // Meters Development
			medev = i2m(medev);
			medev = medev.toFixed(2);
			rat = parseFloat(rat);
			rat = rat.toFixed(1);
			if (grbase) {
				var gr = grbase * ringList[r] / cogList[c];
				gr = gr.toFixed(2);
				grtc = '<span class="gr">'+gr+'</span>';
			} else {
				grtc = '';
				gr = 0;
			}
			if (Number(rlmin[r]) > Number(rat) || rlmin[r]==0) { rlmin[r] = rat; memin[r] = medev; grmin[r] = gr; }
			if (Number(rlmax[r]) < Number(rat)) { rlmax[r] = rat; memax[r] = medev; grmax[r] = gr; }
			ratList.push(rat); // collect data for Google Chart
			maxim.push(rat);
			memaxim.push(medev);
			grmaxim.push(gr);
			if (rat > rtmin && rat < rtmax) {
				if (rat==ratioTarget) {
					var rtv = '0';
				} else if (rat<ratioTarget) {
					var rtrsa = ratioTarget - rat;
					var a = Math.round(rtrsa / rtstep);
					var rtv = hc.charAt(a);
				} else if (rat>ratioTarget) {
					var rtrsa = rat - ratioTarget;
					var a = Math.round(rtrsa / rtstep);
					var rtv = hc.charAt(a);
				}
				rtc = ' style="background-color:#f'+rtv+''+rtv+';" ';
			} else {
				rtc = '';
			}
			yy += '<td class="ring_'+ringList[r]+' cog_'+cogList[c]+'" id="gear_'+ringList[r]+'_'+cogList[c]+'"'+rtc+'><span class="gi">'+rat+'</span><span class="md">'+medev+'</span>'+grtc+'</td>';
		}
		yy += '</tr>';
		gData.push(ratList.join(','));
	}
	// define minima and maxima for graphing. pad each after this run to give the chart some air.
	maxim.sort(sortNum);
	memaxim.sort(sortNum);
	mn = new Array(maxim[0],maxim[maxim.length-1]);
	memn = new Array(memaxim[0],memaxim[memaxim.length-1]);
	mm = [ Number(mn[0]) - 5 , Number(mn[1]) + 5 ];
	memn = [ Number(memn[0]) - 5 , Number(memn[1]) + 5 ];
	colors = new Array('000099','009900','990000','007777','770077','777700');
	if (colors.length>ringList.length) { colors.splice(ringList.length,colors.length-ringList.length); }
	// figure out the field of overlap
	chmr = [];
	chxp = [];
	chxl = [];
	for (a=0;a<rlmin.length;a++) { // rlmin and rlmax should be perfectly parallel arrays
		var rfmin = (rlmin[a] - mm[0]) / ( mm[1] - mm[0] );
		rfmin = rfmin.toFixed(2);
		for (e=0;e<rlmax.length;e++) {
			var rfmax = (rlmax[e] - mm[0]) / ( mm[1] - mm[0] );
			rfmax = rfmax.toFixed(2);
			if ((Number(rlmin[a]) < Number(rlmax[e])) && a>e) {
				chmr[chmr.length] = 'r,' + colors[e] + '25,0,' + rfmin + ',' + rfmax;
			}
			if (a==0) {
				var rfmaxi = Number(rfmax) + 0.01
				rfmaxi = rfmaxi.toFixed(2);
				chmr[chmr.length] = 'r,00000020,0,' + rfmax + ',' + rfmaxi;
				chxp[chxp.length] = rfmax * 100;
				chxl[chxl.length] = rlmax[e];
			}
		}
		var rfmini = Number(rfmin) + 0.01;
		rfmini = rfmini.toFixed(2);
		chmr[chmr.length] = 'r,00000020,0,' + rfmin + ',' + rfmini;
		chxp[chxp.length] = rfmin * 100;
		chxl[chxl.length] = rlmin[a];
	}
	chxp.sort(sortNum);
	chxl.sort(sortNum);
	chg = 100 / (cogList.length - 1) ;
	chg = chg.toFixed(1);
	chm = new Array();
	for (a=0;a<colors.length;a++) {
		chm[a] = 's,' + colors[a] + ',' + a + ',-1,5';
	}
//	chdl = ringList.slice(0); // the labels are upside down. Correct, but not in the same order as the lines. Meh.
	chart = 'http://chart.apis.google.com/chart?chs=350x300&cht=lc&chf=bg,s,ffffff00&chd=t:' + gData.join('|') + '|-1&chds=' + mm.join(',') + '&chdl=' + ringList.join('|') + '&chco=' + colors.join(',') + '&chxt=x,y&chxl=0:|' + cogList.join('|') + '|1:|' + chxl.join('|') + '&chxp=1,' + chxp.join(',') + '&chm=' + chm.join('|') + '|' + chmr.join('|') + '&chg=' + chg + ',0,1,1';
	$('#gearing tbody').empty().prepend(yy);
	var shgi = $('.show.gears').css('display');
	var shmd = $('.show.medev').css('display');
	var shgr = $('.show.medev').css('display');
	if (shgi=='none') { $('.gi').css('display','block'); } else { $('.gi').css('display','none'); }
	if (shmd=='none') { $('.md').css('display','block'); } else { $('.md').css('display','none'); }
	if (shgr=='none') { $('.gr').css('display','block'); } else { $('.gr').css('display','none'); }
	urlGen();
	qq = '<a href="' + url + '" title="URI for this current gear combination" id="combo_bookmark">Right click this link to bookmark this gear combination.</a>';
//	pp = '<a href="' + chart + '" title="Chart of this table" id="chart">Click to see chart.</a>';
	pp = '<img src="' + chart + '" alt="Chart of this table" id="chartimg" width="350" height="300" />';
	if ($('#bookmark #combo_bookmark').attr('href')) {
		$('#bookmark #combo_bookmark').attr('href',url);
//		$('#bookmark #chart').attr('href',chart);
		w = setInterval(function() { if (chartMade==0) { $('#bookmark #chartimg').attr('src',chart);chartMade=1; } },500); // only request the chart after a 0.5 sec pause.
	} else {
		$('#bookmark').append('<p>'+qq+'</p>');
		$('#bookmark').append(pp);
	}
	return;
}
function urlGen() {
//	if (($('#wheel').val() && !$('#rim_sizes').val()) || ($('wheel').val() && !$('#tire_sizes').val())) { // user typed into the 'wheel' field
	if ($('#wheel').val() && ($('#rim_sizes').val()==0) || $('#tire_sizes').val()==0) { // user typed into the 'wheel' field
		var ww = $('#wheel').val();
	} else {
		var ww = $('#rim_sizes').val() + '&t=' + $('#tire_sizes').val();
	}
	url = thispage + '#r=' + ringList.join(',') + '&c=' + cogList.join(',') + '&w=' + ww;
	if (crankLength!=0) { url += '&a=' + crankLength; }
}
// a variety of utility functions
function i2m(x) { var y = x * .0254; return y; } // converts inches to meters
function mm2i(x) { var y = x * .03937; return y; } // converts millimeters to inches
function sortNum(x,y) { return x-y; }
function vN(x) { if(x==x/1) { return x; } return false; } // validate input as a number.
// returns Internet Explorer version number
function isIE() { /*@cc_on if(@_jscript_version<5.6){return 5;}if(@_jscript_version<5.7){return 6;}if(@_jscript_version<5.8){return 7;}if(@_jscript_version<5.9){return 8;} @*/ return 0; }
function launchAll() {
	if (!document.getElementById) { return; }
	if (isIE() && isIE()<=6) { }
	// define global variables
	ratioTarget = 0;
	ratioRange = 0;
	crankLength = 0;
	wheelSize = 0;
	ringList = new Array();
	cogList = new Array();
	hubList = new Array(1.00);
	gearCalc();
}
window.onload = launchAll;

/* EOF */
