var unitType = { cm:0, m:1, inches:2 }; // Use like this: unitType.cm will give 0

var customFieldsUsed = false; // when set to true, this will cause doctorFields to ignore the writing of certain variables.

var debuggingOutput = "<br>";



var exampleMotor;

var exampleBattery;

var exampleWheelSize;

var exampleController;

var exampleThrottle;



///////////////////////////////////

//                               //

//     DATA INITIALIZATION       //

//                               //

///////////////////////////////////

function init_data()

{

	exampleMotor = new motor("M406", "406", 1, 0.6, 0.6, 0, 0, 0, 0, 0.001, 8);

	exampleBattery = new battery("B3612MH", "36V 12Ah NiMH", 36, "MH", 8, 36, 0.2);

	exampleWheelSize = new wheelSize(26, unitType.inches);

	exampleController = new controller("C20", "Generic 20A Controller", 20, 0.03, 0.9);

	exampleThrottle = 1.0 // This is a percentage of total, from 0 to 1

	return true;

};



///////////////////////////////////

//                               //

//        DATA STRUCTURES        //

//                               //

///////////////////////////////////



// Although these look like functions, they are actually something like constructors.



function motor(id, name, K, Rmotor, A0, A1, A2, FW, T1, L, poles)

{

	this._id = id;

	this._name = name; // motor name, eg. 404



	// The following parameters should be kept on the database rather than out in the open.

	// Once the motor is defined as above, the database request should

	// fill in the rest.

	

	this._K = K;						// Motor constant in N-m/A

	this._Rmotor = Rmotor;				// Winding resistance in Ohms

	this._A0 = A0;						// Quadratic coefficients for the motor cogging torque in N-m

	this._A1 = A1;

	this._A2 = A2;

	this._FW = FW;

	this._T1 = T1;

	this._L = L;

	this._Poles = poles;

	return this;

};



function battery(id, name, voltage, type, amph, Voc, Rbatt)

{

	this._id = id;						// From DB - first call (non-AJAX)

	this._name = name;					// From DB - first call

	this._voltage = voltage; 			// in Volts

	this._type = type; 					// string, one of MH, CD for now

	this._amph = amph;		 	// in A-h

	

	// The following parameters should be kept on the database rather than out in the open.

	// Same caveat as for motor data

	this._Voc = Voc; // The open circuit battery voltage at 50% SOC

	this._Rbatt = Rbatt; // The battery's internal resistance at 50% SOC

	return this;

};



function wheelSize(size, units)

{

	// The diameter of the wheel is to be stored in metres.

	if (size)

	{

		switch(units) {

			case unitType.cm :

				this._size = ( size / 100.0 );

				break;

			case unitType.m :

				this._size = size;

				break;

			case unitType.inches :

				this._size = (size * 0.0254);

				break;

			default : alert("Strange unit type");

		}

	}

	return this;

};



function controller(id, name, maxCurrent, Rcont, Vdiode)

{

	this._id = id;

	this._name = name;

	this._maxCurrent = maxCurrent; // This is either 20 or 35, public data

	

	// The following parameters should be kept on the database rather than out in the open.

	// Same caveat as for battery data	



	this._Rcont = Rcont;

	this._Vdiode = Vdiode;

	return this;

};





///////////////////////////////////

//                               //

//     INTERPRET FORM DATA       //

//                               //

///////////////////////////////////



function customFields(batteryVoltage, batteryResistance, customWheelSize, controllerAmperage, controllerResistance, motorText, batteryText, wheelText, controllerText, throttleText)

{

	/*	motor(series, windings, name, K, Rmotor, A0, A1, A2)

	battery(voltage, amph, type, manufacturer, Voc, Rbatt)

	wheelSize(size, units)

	controller(maxCurrent, Rcont, Vdiode) */



	// Need to do sanity checking on new data - user types in weird stuff sometimes.

	exampleMotor = new motor(motorText, 1, 0.6, 0.6, 0, 0, 0, 0, 0.001, 8);

	// Sanity check here?

	exampleThrottle = throttleText / 100.0;



	if (customLook)

	{

//		alert("custom look.");

		if (isNaN(batteryVoltage))

		{
			alert("Error: Pack voltage must be a number.");
			document.getElementById('packVoltageField').focus();
			return;
		}

		else if (isNaN(batteryResistance))

		{
			alert("Error: Battery resistance must be a number.");
			document.getElementById('batteryResistanceField').focus();		
			return;
		}

		else if (isNaN(customWheelSize))

		{
			alert("Error: Wheel size must be in inches (example: \"26\")");
			document.getElementById('customWheelSizeField').focus();		
			return;
		}

		else if (isNaN(controllerAmperage))

		{
			alert("Error: Controller amperage must be a number.");	
			document.getElementById('controllerAmperageField').focus();		
			return;
		}

		else if (isNaN(controllerResistance))
		{
			alert("Error: Controller resistance must be a number.");	
			document.getElementById('controllerResistanceField').focus();		
			return;
		}

		// Make sure to put focus on element that needs correcting.



		exampleBattery = new battery("BCust", "Custom Battery", 36, "CD", 8, parseFloat(batteryVoltage), parseFloat(batteryResistance));
		exampleController = new controller("CCust", "CCust", parseFloat(controllerAmperage), parseFloat(controllerResistance), 0.9);
		exampleWheelSize = new wheelSize(parseFloat(customWheelSize), unitType.inches);

		var postVars = 'action=loadjson&motorid=' + encodeURIComponent(exampleMotor._id) +
						'&batteryid=B3612MH' +
					   '&controllerid=C20';
	}

	else

	{

//		alert("normal look.");

		var textUnitType;
		switch(wheelText.substring(wheelText.length - 1,wheelText.length))

		{

			case('i'):
				textUnitType = unitType.inches;
				break;

			case('c'):
				textUnitType = unitType.cm;
				break;

			case('m'):
				textUnitType = unitType.m;
				break;

			default:
				alert("Improper wheel diameter; it should be one of inches, cm, or m.");

		}

		exampleWheelSize = new wheelSize(wheelText.substring(0, wheelText.length - 1), textUnitType);

//		var firstDot = batteryText.indexOf('.');
//		var secondDot = batteryText.indexOf('.', (firstDot + 1));
//		alert (batteryText + ' ' + motorText + ' ' + controllerText);

		exampleBattery = new battery(batteryText, "Generic Name", 

									 99, "Fe",
									 98, 99, // voc
									 0.2); // rbatt

		// So, we really need to replace all of these things from the DB now, since those make no sense.

		exampleController = new controller(controllerText, "Replaceable Name", 20, 0.03, 0.9);

//		alert("How are we doing?");

		var postVars = 'action=loadjson&motorid=' + encodeURIComponent(exampleMotor._id) +
						'&batteryid=' + encodeURIComponent(batteryText) + 
					   '&controllerid=' + encodeURIComponent(exampleController._id);

//		alert (postVars);

	}

	// The data below is encoded according to a simplification of the standard SKU scheme.
	// examples: B365CD, M404

	ajaxObj.call(postVars, respJSON);
	return true;

}





///////////////////////////////////

//                               //

//     JAVASCRIPT CALLBACK       //

//                               //

///////////////////////////////////



// Check myBic to see if we can have a flag enter here. Then, check it and only set the appropriate values.

function respJSON(resp) {

	if(resp) 

	{

//		alert("got to the response");

		exampleMotor._name = resp.motorname;
		exampleMotor._K = parseFloat(resp.motork);
		exampleMotor._Rmotor = parseFloat(resp.motorrmotor);
		exampleMotor._A0 = parseFloat(resp.motora0);
		exampleMotor._A1 = parseFloat(resp.motora1);
		exampleMotor._A2 = parseFloat(resp.motora2);
		exampleMotor._FW = resp.motorfw;
		exampleMotor._T1 = resp.motort1;
		exampleMotor._L = resp.motorl;
		exampleMotor._Poles = resp.motorpoles;

		if (!customLook)

		{

			// Set the rest of battery stuff here.

			// Added: battery name, battery voltage,

			exampleBattery._name = resp.batteryname;
			exampleBattery._voltage = resp.batteryvoltage;
			exampleBattery._type = resp.batterytype;
			exampleBattery._amph = resp.batteryamph;
			exampleBattery._Voc = parseFloat(resp.batteryvoc);
			exampleBattery._Rbatt = parseFloat(resp.batteryrbatt);

			exampleController._name = resp.controllername;
			exampleController._maxCurrent = parseFloat(resp.controllermaxcur);
			exampleController._Rcont = parseFloat(resp.controllerrcont);
			exampleController._Vdiode = parseFloat(resp.controllervdiode);
		}
	} else {

		alert('json error');

	}



	calculateAll();
	draw_all_charts();			
	return true;

};


///////////////////////////////////

//                               //

//     MAIN CALCULATIONS         //

//                               //

///////////////////////////////////


function calculateAll()

{

	var voc,k,wsize,vdiode,rcont,rmotor,rbatt,a0,a1,a2,fw,t1,maxcur,dutycycle,maxW;
	voc = exampleBattery._Voc;
	k = exampleMotor._K;
	wsize = exampleWheelSize._size;
	vdiode = exampleController._Vdiode;
	rcont = exampleController._Rcont;
	rmotor = exampleMotor._Rmotor;
	rbatt = exampleBattery._Rbatt;
	a0 = exampleMotor._A0;
	a1 = exampleMotor._A1;
	a2 = exampleMotor._A2;
	fw = exampleMotor._FW;
	t1 = exampleMotor._T1;
	lmotor = exampleMotor._L;
	poles = exampleMotor._Poles;
	maxcur = exampleController._maxCurrent;


	var kmhflag, mphflag, rpmflag, nmflag, lbsflag;

	var aboveChart, aboveChart2;
	kmhflag = document.getElementById('speedUnitFlag').getElementsByTagName('input')[0].checked;
	mphflag = document.getElementById('speedUnitFlag').getElementsByTagName('input')[1].checked;
	rpmflag = document.getElementById('speedUnitFlag').getElementsByTagName('input')[2].checked;
	nmflag = document.getElementById('torqueUnitFlag').getElementsByTagName('input')[0].checked;
	lbsflag = document.getElementById('torqueUnitFlag').getElementsByTagName('input')[1].checked;

	// Correction for Safari.

	if (!((kmhflag) ||
	     (mphflag) ||
	     (rpmflag)))

	{
		kmhflag = true;
	}

	if (!(nmflag || lbsflag))

	{
		nmflag = true;
	}

	// Let's put the speed at 60 km/h as a minimum.

	maxW = 60 / (1.8 * wsize); // wsize is always in metres, and we need to get from km/h back to units of (1/s).
	maxCalcW = ((voc * 1.1) / k);
	if (maxCalcW > maxW)

	{

		// This will set the maximum angular momentum to be higher for cases where the speed is over 60 km/h.

		maxW = maxCalcW; // Don't think we need to round since that is happening later.
		if ((mphflag || kmhflag) && maxW > (100 / (1.8 * wsize)))
		{
			maxW = 100 / (1.8 * wsize);
		}
	}
	
	if (nmflag)
	{
		aboveChart = "Newton-Metres";
	}

	else
	{
		aboveChart = "Pounds Thrust";
	}

	aboveChart2 = "Watts<br>% Efficiency * 10";

	if (kmhflag) // km/h
	{

		debuggingOutput = "Kilometres Per Hour";
		tempMax = maxW * wsize * 1.8;
		if (maxCalcW >= maxW) { tempMax = parseInt((Math.floor(tempMax / 5) + 1) * 5); }
		else { tempMax = parseInt((Math.floor(tempMax / 5) * 5)); }
		maxW = tempMax / (wsize *1.8);
	}

	else if (mphflag) // mph

	{
		debuggingOutput = "Miles Per Hour";
		tempMax = maxW * wsize * 1.125;
		if (maxCalcW >= maxW) { tempMax = parseInt((Math.floor(tempMax / 5) + 1) * 5); }
		else { tempMax = parseInt((Math.floor(tempMax / 5) * 5)); }
		maxW = tempMax / (wsize * 1.125);
	}

	else // rpm

	{
		// horrible floating point inaccuracy introduced. There must be a better way.
		debuggingOutput = "Revolutions Per Minute";
		tempMax = maxW * 9.54929659; // (60 / 2pi), to go from angular momentum to rpm
		tempMax = parseInt((Math.floor(tempMax / 100) + 1) * 100);
		maxW = tempMax / 9.54929659;

	}

	
	//debuggingOutput = debuggingOutput + "<br>" +"Speed,Vbatt(V),Ibatt(A),Imotor(A),Torque(N-m),Power(w),Heat Loss(W)<br>"
	

  var a_term,b_term,c_term,exp_term,alpha,deltaI,Tperiod,Tcommutation,Trise,inot,iavg,ibatt
	var datadump = "Data Stream <br>";
	
	for (plotIndex = 0; plotIndex < NUMBER_OF_POINTS; plotIndex++)
	// If we have the index from 0 to MAX, just divide the index by MAX, then multiply by voc / k
	{

		w = (plotIndex * maxW) / (NUMBER_OF_POINTS - 1);

		// First calculation for stall torque is done ignoring motor inductance
		// This equation is also used if the commutation period is greater than 12 L/R time constants

		if ((plotIndex < 5)||poles<=1)//||(Tperiod > 15*lmotor/(rmotor+rcont)))
		{

				var a = ((parseFloat(voc) + parseFloat(vdiode)) - (maxcur * rbatt));
    		var b = 0 - (vdiode +  (w * k));
    		var c = 0 - ((rcont + rmotor) * maxcur);
    		//debuggingOutput = debuggingOutput + "a = " + a + "  b = " + b.toFixed(2) + "  c = " + c.toFixed(2) + "<br>";
    		operatingDutyCycle[plotIndex] = computePositiveQuadratic(a , b, c);
    		//debuggingOutput = debuggingOutput + "D (before testing against throttle): " + operatingDutyCycle[plotIndex].toFixed(3) + "<br>";
    		if ((operatingDutyCycle[plotIndex] > 0) && (operatingDutyCycle[plotIndex] < exampleThrottle))
    		{
    			// No problems, use calculated value for D
    		}
    		else
    		{
    			operatingDutyCycle[plotIndex] = exampleThrottle;
    		}
    		dutycycle = operatingDutyCycle[plotIndex];
    
    		// Now that the duty cycle is known, calculate motor outputs
    		if (w < (voc / k))
    		{
    			imotor = dutycycle * voc - ((1 - dutycycle) * vdiode) - (w * k);
    			imotor = Math.max(imotor / (rcont + rmotor + (dutycycle * dutycycle * rbatt)), 0);
    		}
    		else
    		{
    			imotor = voc + ((1 - dutycycle) * vdiode) - (w * k);
    			imotor = Math.min(imotor / (rcont + rmotor + rbatt), 0);
    		}
    		operatingTorque[plotIndex] = (imotor*k)*(1-t1) - (a0 + (a1 * w) + (a2 * w * w));
    		operatingPower[plotIndex] = operatingTorque[plotIndex] * w;
    		if (operatingTorque[plotIndex] > 0) // This is to avoid having negative infinity spoil the graph.
    		{
    			operatingEfficiency[plotIndex] = operatingPower[plotIndex] / (dutycycle * imotor * (voc - (rbatt * dutycycle * imotor)));
    		}
    		else
    		{
    			operatingEfficiency[plotIndex] = 0;
    		}
    
			Tcommutation = 0; // Do this because Tcommutation needs to be defined when using inductance model
			if(w != 0)
					{ Tperiod = 2*Math.PI/(6*parseFloat(poles)*w); 
					}
			else
					Tperiod = 100;
					
			ibatt = imotor*dutycycle;		
		}
		// Otherwise, we do the calcualtions based on inductive model
		else
		{
				 var duty_cnt=0;
				 do	// Outer do loop to itteratively hone in on Duty cycle value
				 {
    			var i, delta_ibatt;
    			i=0;
    			do	// We do the calculation loop multiple times to iteratively get closer to solution values
    			{
      			vcont = dutycycle*(voc-ibatt*rbatt)-vdiode*(1-dutycycle)// This uses previously stored value for ibatt
      			alpha = k*w/vcont;  // 
      		  Tperiod = 2*Math.PI/(6*parseFloat(poles)*w); // Calcuation of Commutation Period
      			Trise = Tperiod - Tcommutation;  // Use previous iteration value for Tcommutation
      			exp_term = Math.exp((rmotor+rcont)*Trise/lmotor);  // Store result of e^Rm/Rl*Tr,  since it is used a lot
      		
      			// Determine effective motor phase winding resistance, half the motor and controller resistance
      	
      			rwinding = (rcont + rmotor)/2; 
      	
      			// Calculations of a, b, and c terms for solving quadratic
      				 
      			a_term = -3*(rwinding)*(exp_term+1);
      			b_term = vcont*(2-4*alpha-(exp_term-1)*(0.5+3.5*alpha));
      			c_term = vcont*vcont*(1-alpha*alpha)*(exp_term-1)/rwinding;
 
 						// debuggingOutput = debuggingOutput +"<br>" + "a = " + a_term + "  b = " + b_term + "  c = " + c_term;
      			
      			// inot is the solution to this quadratic, and represents the peak motor phase current
      			inot = (-b_term -Math.sqrt(b_term*b_term - 4*a_term*c_term))/(2*a_term);
      	
      			// The current drop at each commutation (deltaI) is a function of inot
      			deltaI = inot*(vcont*(2-4*alpha)-6*inot*rwinding)/(2*vcont*(1+alpha)+3*inot*rwinding);
      			
      			// The time that is required for commutation to complete
      			Tcommutation = Math.abs(3*inot*lmotor/(2*vcont*(1+alpha)+3*inot*rwinding));		
      			Trise = Tperiod - Tcommutation;  // Get new value for Trise based on recently calculated Tcommutation
      			
      			// Average motor current is a function of inot, deltaI, and exponential time constant
      			i_infinity = vcont*(1-alpha)/(2*rwinding);
      			imotor = (Tcommutation/Tperiod)*(2*inot+deltaI)/2;
      			imotor = imotor +(Trise/Tperiod)*(-lmotor*(inot+deltaI-i_infinity)*(Math.exp(-(rmotor+rcont)*Trise/lmotor)-1)/((rmotor+rcont)*Trise) +i_infinity);
      				
      			// For now, battery current is the motor current, scaled slighlty by commutation time
      			delta_ibatt = Math.abs(ibatt-imotor*(1-0.5*(Tcommutation/Tperiod))*dutycycle); //record change in ibatt since last iteration
    				ibatt = 0.5*ibatt +0.5*(imotor*(1-0.5*(Tcommutation/Tperiod))*dutycycle); // The 0.8/0.2 factor is to reduce feedback gain so system converges even with high values for rbatt. 
      						
    				} while((i++<100)&&(delta_ibatt>0.01))//repeat up to 50 times to hone in on solution
					// Now we have honed in value for ibatt, imotor, etc. for a particular duty cycle, so
					// hone in to make battery current match the controller current
					
					dutycycle = dutycycle-0.2*(ibatt-maxcur)/maxcur;
					if(dutycycle > exampleThrottle)
						dutycycle = exampleThrottle;
	
			} while(duty_cnt++ < 6); // while(Math.abs(ibatt-maxcur)>0.1)

				// Don't allow graph to go into regen display unless voc> w/k

	  	if (imotor<0)
				{	
					imotor = voc + ((1 - dutycycle) * vdiode) - (w * k);
    			imotor = Math.min(imotor / (rcont + rmotor + rbatt), 0);
    		}

			operatingTorque[plotIndex] = (imotor*k)*(1-t1) -a0 -(a1*w) -(a2*w*w);
			operatingPower[plotIndex] = operatingTorque[plotIndex]*w;
			if(operatingTorque[plotIndex]<0)
					operatingEfficiency[plotIndex]=0;
			else		
					operatingEfficiency[plotIndex] = operatingPower[plotIndex]/((voc-ibatt*rbatt)*ibatt);
		}
		 
		if (operatingTorque[plotIndex] < 0 && fw == 1)

		{
				operatingTorque[plotIndex] = 0;
				operatingPower[plotIndex] = 0;
				operatingEfficiency[plotIndex] = 0;
		}

		// The speed, hard coded in km/h: (3.6 * pi / 2 * pi) * w * wsize

		if (kmhflag) // kph
		{
			operatingSpeed[plotIndex] = 1.8 * w * wsize;
		}

		else if (mphflag) // mph
		{
			operatingSpeed[plotIndex] = 1.125 * w * wsize;
		}

		else // rpm

		{
			operatingSpeed[plotIndex] = w * 9.54929659 // This looks like a hack, but it's faster. 60 / 2 * pi
		}

		if (!nmflag) // We are in Lbs

		{
			operatingTorque[plotIndex] = (operatingTorque[plotIndex] * 0.22449) * 2/ wsize; // Divide by radius; this explains the scaling factor of 2.
		}

//		debuggingOutput = debuggingOutput + "<br>" +"w: " + w.toFixed(2) + "\tSpeed: " + operatingSpeed[plotIndex].toFixed(1) + "\ti_batt: " + ibatt.toFixed(2) + "\ti_motor: " + imotor.toFixed(2) + "\tTorque: " + operatingTorque[plotIndex].toFixed(2) + "\tPout: " + operatingPower[plotIndex].toFixed(1) + "\tMotor Eff'y: " + operatingEfficiency[plotIndex].toFixed(3);

	}

  printDiags();
	document.getElementById("debugOutput").innerHTML = debuggingOutput;
	document.getElementById("aboveChart").innerHTML = aboveChart;
	document.getElementById("aboveChart2").innerHTML = aboveChart2;

	return true;

}

function printDiags()

{

	debuggingOutput = debuggingOutput +"<br>" + "Motor:  id: " + exampleMotor._id + "   name: " + exampleMotor._name + "   K: " + exampleMotor._K + "   Rmotor: " + exampleMotor._Rmotor + "   A0: " + exampleMotor._A0 + " Poles: " + exampleMotor._Poles + "   L: " + exampleMotor._L + "<br>";
	debuggingOutput = debuggingOutput + "Battery:   id: " + exampleBattery._id + "   voltage: " + exampleBattery._voltage + "   amph: " + exampleBattery._amph + "   type: " + exampleBattery._type + "   Voc: " + exampleBattery._Voc + "   Rbatt: " + exampleBattery._Rbatt + "<br>";
	debuggingOutput = debuggingOutput + "Wheelsize:  size: " + exampleWheelSize._size.toFixed(2) + "<br>";
	debuggingOutput = debuggingOutput + "Controller:   id: " + exampleController._id + "   name: " + exampleController._name + "   maxCurrent: " + exampleController._maxCurrent + "   Rcont: " + exampleController._Rcont + "   Vdiode: " + exampleController._Vdiode + "<br>";
	debuggingOutput = debuggingOutput + "Throttle:   Percentage: " + exampleThrottle * 100 + "<br><br>";
	
	return true;

};

