///////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////        CalcResults        //////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////

//Main Function, updates web page form

function CalcResults(form) {

constCruiseRPM = 3200; //Cruising RPM, global scope
constMaxRPM = 3700; //Maximum cruising RPM, global scope
constMaxCad = 140; //Maximum cadence, global scope

var lstFRings = new Array(); //List of Front Chainring Teeth

//Get Form values...///////////////////////////////////////////////////////////////////////////////
var fltWheelSize = Number(form.RWSize.value); //Wheel Size
lstFRings[0] = Number(form.SRing.value); //Front Small Ring Teeth
lstFRings[1] = Number(form.MRing.value); //Front Middle Ring Teeth
lstFRings[2] = Number(form.BRing.value); //Front Big Ring Teeth
var intNumCogs = Number(form.RCogs.value); //Number of Rear Cogs
var intSmRCog = Number(form.SRCog.value); //Smallest Rear Cog Teeth
var intLgRCog = Number(form.LRCog.value); //Largest Rear Cog Teeth
var intDesCad = Number(form.NCad.value); //Desired Pedal Cadence
var intShift = Number(form.Shift.value); //Optimize Shifting For: 0 = Power, 1= Cruise
var intPeakPowerRPM = Number(form.MType.value); //Motor Peak Power RPM, 2500 = BMC, 2800 = Powerpack
var fltSpeed = Number(form.Speed.value); //Current Speed
var intMidFreewheel = Number(form.MFree.value); //EMD Mid Freewheel Teeth
var intMidChainring = Number(form.MCRing.value); //EMD Mid Chainring Teeth
var fltMotorRatio = Number(form.MRat.value); //EMD Motor Speed Reduction Ratio.
var boolEMDInstalled = form.EMDInst[1].checked; //1 = EMD installed, 0 = not installed
//end//////////////////////////////////////////////////////////////////////////////////////////////

//Adjust for units...
if (form.Unit.value == 'KMH') fltSpeed = fltSpeed * 0.62;

//Adjust effective wheel size if EMD installed...
if (boolEMDInstalled) fltWheelSize = fltWheelSize * intMidChainring/intMidFreewheel;

//Calculate Desired Gear Inches...
var fltDesGInch = ((fltSpeed * (5280 * 12)/60)/Math.PI)/intDesCad;

//Calculate gear data array.../////////////////////////////////////////////////////////////////////
var arGearData = new Array(); //Data for all gears
arGearData = CalcGearData(intNumCogs, intSmRCog, intLgRCog, fltWheelSize, fltSpeed, fltMotorRatio, intMidFreewheel, lstFRings);
//end//////////////////////////////////////////////////////////////////////////////////////////////

//Sort the Gear Data Array by gear inch values (low to high)...
arGearData.sort(ByFirstElement);

//Find the nearest gear inch value greater than the desired gear inch...///////////////////////////
   var intNextGE = 0; //Index of next gear inch value greater or equal to desired, or smallest available
   for (k=0; k < (intNumCogs * 3); k++) {
	intNextGE = k;
	if (arGearData[k][0] >= fltDesGInch) break;
    } // end for
//end//////////////////////////////////////////////////////////////////////////////////////////////

//Find Best Gear Combination, No EMD Installed Case.../////////////////////////////////////////////
var lstGIDiff = new Array(); //Gear Inch Difference value list
var lstBestGear = new Array(); //Best Gear Combination for conditions

if (!boolEMDInstalled) { //No EMD Installed Case
    if (intNextGE == 0) lstBestGear = arGearData[0];
    else {
	lstGIDiff[0] = Math.abs(arGearData[intNextGE][0] - fltDesGInch); //Difference vs next higher gear inch value
	lstGIDiff[1] = Math.abs(arGearData[intNextGE-1][0] - fltDesGInch);  //Difference vs next lower gear inch value
        if (lstGIDiff[0] > lstGIDiff[1]) lstBestGear = arGearData[intNextGE-1]; //Pick closest gear inch value
        else lstBestGear = arGearData[intNextGE];
    } //end else
} //end outer if
//end//////////////////////////////////////////////////////////////////////////////////////////////

//Best gear accounting for shift preference, EMD Installed Case.../////////////////////////////////
else if (fltSpeed == 0) lstBestGear = arGearData[0]; //zero speed case, pick smallest gear inch value
// Else find best cadence/motor rpm combination...
else lstBestGear = FindBest(arGearData, intShift, intPeakPowerRPM, intNumCogs, intDesCad, lstFRings);
//end//////////////////////////////////////////////////////////////////////////////////////////////

//Update form display values...////////////////////////////////////////////////////////////////////
form.FRingName.value = lstBestGear[4];
form.RCogNum.value = lstBestGear[3];
form.ActCad.value = lstBestGear[1];
form.GInch.value = lstBestGear[0];
if (boolEMDInstalled) form.MRPM.value = lstBestGear[2];
else form.MRPM.value = '';

} // end function



///////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////        CalcGearData        //////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////

//Creates array of Gearing Data

function CalcGearData(intNumCogs, intSmRCog, intLgRCog, fltWheelSize, fltSpeed, fltMotorRatio, intMidFreewheel, lstFRings) {

var intIndex = 0; //Array Index
var intCogNum = 0; //Current Rear Cog Number
var intCogTeeth = 0; //Current Rear Cog Teeth
var fltGearInches = 0; //Current Gear Inches
var fltCadence = 0;//Current Cadence
var fltMRPM = 0; //Current Motor RPM
var arGearData = new Array(); //Data for all gears

//Calculate Cog Step Ratio assuming even cog spacing...
var fltStepRatio = Math.pow((intLgRCog/intSmRCog),(1/intNumCogs));

for (i=0; i<3; i++) {
    for (j=0; j<intNumCogs; j++) {
	intCogNum = intNumCogs - j;
	switch(intCogNum) {
	    case intNumCogs: intCogTeeth = intSmRCog; break;
	    case 1: intCogTeeth = intLgRCog; break;
	     //Assume closest integer even cog spacing...
	    default: intCogTeeth = Math.round(intSmRCog * Math.pow(fltStepRatio, j)); break;
        } //end switch

	//Compute Current Gear Inches...
        fltGearInches = (lstFRings[i]/intCogTeeth) * fltWheelSize;

	//Compute Current Cadence...
	if (fltGearInches == 0) fltCadence = 0;
	else fltCadence = ((fltSpeed * 5280 * 12)/60)/(fltGearInches * Math.PI);

	//Compute Current Motor RPM...
	fltMRPM = fltMotorRatio * fltCadence * lstFRings[i]/intMidFreewheel;

	//Build Gear Data list...
	var lstGearData = new Array(); //Data for one gear pair
	lstGearData[0] = fltGearInches;
	lstGearData[1] = fltCadence;
	lstGearData[2] = fltMRPM;
	lstGearData[3] = intCogNum;

	switch(i) {
	    case 0: lstGearData[4] = 'Small'; break;
	    case 1: lstGearData[4] = 'Middle'; break;
	    case 2: lstGearData[4] = 'Big'; break;
	} //end switch

	lstGearData[5] = intCogTeeth;
	lstGearData[6] = 0; //placeholder

	//Build Gear Data Array...
	arGearData[intIndex] = lstGearData;
	intIndex = intIndex + 1;

    } //end inner for

} //end outer for

return arGearData;

} //end function



///////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////        FindBest        //////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////

//Find best fit cadence and motor RPM...

function FindBest(arGearData, intShift, intPeakPowerRPM, intNumCogs, intDesCad, lstFRings) {

var intRPMCenterPt = intPeakPowerRPM; //RPM Center Point to optimize around in power case
var boolSmallRingAllowed; //Use of small front ring allowed?
var lstBestGear = new Array(); //Best Gear Combination for conditions

//Sort Gear Data by front ring (big to small), then by RPM (high to low)

arGearData.sort(ByFRingThenRPM);

//scan through gear data and compute least square distance from optimal cadence/RPM pair for each entry...

var fltNDCad; //Normalized distance of cadence from optimum
var fltNDRPM; //Normalized distance of RPM from optimum
var fltWF = 1.5; //Weighting factor. Emphasize RPM is Power case, Cadence in Cruise case

for (i=0; i < (intNumCogs * 3); i++) {
    if (arGearData[i][1] > constMaxCad) fltNDCad = 1; //Lock out cadence values greater than Max
    else if (intShift == 1) fltNDCad = (Math.abs((arGearData[i][1] - intDesCad)/constMaxCad)) * fltWF; //Weight cadence
	 else fltNDCad = (Math.abs((arGearData[i][1] - intDesCad)/constMaxCad)); // Do not weight cadence
    if (intShift == 1) { //cruising case
	if (arGearData[i][2] > constMaxRPM) fltNDRPM = 1; //Lock out RPM values greater than Max
	else fltNDRPM = Math.abs((arGearData[i][2] - constCruiseRPM)/constMaxRPM);  //Cruising case
    } //end if
    else { //power case
	if (arGearData[i][2] > constMaxRPM) fltNDRPM = 1; //Lock out RPM values greater than Max
	else fltNDRPM = (Math.abs((arGearData[i][2] - intPeakPowerRPM)/constMaxRPM)) * fltWF;  //Power case
    } //end else
    arGearData[i][6] = Math.sqrt(Math.pow(fltNDCad,2) + Math.pow(fltNDRPM,2)); //Compute distance from optimum
} //end for

//Find the smallest distance value

var arGearDataCopy = new Array();

//exclude small rings, if possible, in cruising case...
if ((intShift == 1) && ((lstFRings[1] > 0) || (lstFRings[2] > 0))) {
    for (j=0; j < (intNumCogs * 2); j++) {
	arGearDataCopy[j] = arGearData[j];
    }
    arGearDataCopy.sort(ByLastElement);
    return arGearDataCopy[0]; //Should be smallest least square distance
} //end if

//Otherwise use the full gear data array
else {
    arGearData.sort(ByLastElement);
    return arGearData[0]; //Should be smallest least square distance
} //end else

} //end function



///////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////        ByFirstElement        ////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////

//Sorting function, ascending

function ByFirstElement(a,b) {
		return a[0]-b[0];
		}


///////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////        ByLastElement        ////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////

//Sorting function, ascending

function ByLastElement(a,b) {
		return a[6]-b[6];
		}


///////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////        ByFRingThenRPM       ////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////

//Sorting function, descending on two values

function ByFRingThenRPM(a,b) {
		if (a[4] == b[4]) return (b[2] - a[2]); //sort RPM descending if same front ring
		if (a[4] == 'Big') return -1; //sort front rings descending
		if (b[4] == 'Big') return 1;
		if (a[4] == 'Middle') return -1;
		if (b[4] == 'Middle') return 1;
		}