var rvals96 = [ 
	1, 1.02, 1.05, 1.07, 1.1, 1.13, 1.15, 1.18, 1.21, 1.24, 1.27, 1.3,
	1.33, 1.37, 1.4, 1.43, 1.47, 1.5, 1.54, 1.58, 1.62, 1.65, 1.69,
	1.74, 1.78, 1.82, 1.87, 1.91, 1.96, 
	
	2.0, 2.05, 2.1, 2.15, 2.21, 2.26, 2.32, 2.37, 2.43, 2.49, 2.55,
	2.61, 2.67, 2.74, 2.8, 2.87, 2.94,
	
	3.01, 3.09, 3.16, 3.24, 3.32, 3.4, 3.48, 3.57, 3.65, 3.74, 3.83,
	3.92,
	
	4.02, 4.12, 4.22, 4.32, 4.42, 4.53, 4.64, 4.75, 4.87, 4.99,
	
	5.11, 5.23, 5.36, 5.49, 5.62, 5.76, 5.9,
	
	6.04, 6.19, 6.34, 6.49, 6.65, 6.81, 6.98, 

	7.15, 7.32, 7.5, 7.68, 7.87,
	
	8.06, 8.25, 8.45, 8.66, 8.87, 

	9.09, 9.31, 9.53, 9.76,

	10
];

var rvals24 = [
	1, 1.1, 1.2, 1.3, 1.5, 1.6, 1.8, 2, 2.2, 2.4, 2.7, 3, 3.2, 3.3, 3.6,
	3.9, 4.3, 4.7, 5.1, 5.6, 6.2, 6.8, 7.5, 8.2, 9.1, 10
];

var min_pot_value = 10;

var indicator_images = new Object;
			
function convert_power(value)
{
	var last = value.charAt(value.length - 1);

	if (last == 'k' || last == 'K') {
		return Number(value.substring(0, value.length - 1)) * 1000;
	}
	else if (last == 'm' || last == 'M') {
		return Number(value.substring(0, value.length - 1)) * 1000000;
	}
	else {
		return Number(value);
	}
}


function convert_fraction(value)
{
	var last = value.charAt(value.length - 1);
	var number = Number(value.substring(0, value.length - 1));

	if (last == 'p' || last == 'P') {
		return number / 1e12;
	}
	else if (last == 'n' || last == 'N') {
		return number / 1e9;
	}
	else if (last == 'u' || last == 'U') {
		return number / 1e6;
	}
	else if (last == 'm' || last == 'M') {
		return number / 1e3;
	}
	else {
		return Number(value);
	}
}


function power_notation(a)
{
	if ((a / 1000000) >= 1) {
		return a / 1000000 + 'M';
	}
	else if ((a / 1000) >= 1) {
		return a / 1000 + 'K';
	}
	else {
		return a;
	}
}


function format_values_answer(r3, r4, diff)
{
	return power_notation(r3) + ' and ' +
			power_notation(r4) + '     ' +
			diff + '% off';
}


function simple_gain(r3, r4)
{
	return (r4 / r3) + 1;
}


function multiloop_gain(r3, r4, r5, r6)
{
	// (R3 * (R4 + R5 + R6)) + (R4 * (R5 + R6))
	// ----------------------------------------
	// (R3 * (R4 + R5 + R6)) + (R4 * R5)

	var t = r3 * (r4 + r5 + r6);
	var num = t + (r4 * (r5 + r6));
	var den = t + (r4 * r5);

	return num / den;
}


function parallel_resistors(ra, rb, rc, rd, re, rf)
{
	var accum = 0;
	if (ra > 0) accum += 1/ra;
	if (rb > 0) accum += 1/rb;
	if (rc > 0) accum += 1/rc;
	if (rd > 0) accum += 1/rd;
	if (re > 0) accum += 1/re;
	if (rf > 0) accum += 1/rf;

	if (accum > 0) {
		return 1 / accum;
	}
	else {
		return 0;
	}
}


function resistor_divider(ra, rb)
{
	return rb / (ra + rb);
}


function percent_difference(a, b)
{
	// Make sure 'b' is larger than 'a'
	if (a > b) {
		var temp = b;
		b = a;
		a = temp;
	}

	return 100 * ((b / a) - 1);
}


function compare_diffs(a, b) 
{
	return a.diff - b.diff; 
}


function values_select_handler(form)
{
	var gain = Number(form.gain.value);
	var max_pct_diff = Number(form.max_pct_diff.value);
	var rvals;
	var rtol;

	if (form.rtol[0].checked) {
		rvals = rvals96;
		rtol = 1;
	}
	else {
		rvals = rvals24;
		rtol = 5;
	}

	// Make sure inputs are sane
	if ((gain >= 1) && (max_pct_diff >= 0.1) && (max_pct_diff <= 5)) {
		form.answer.value = 'Processing...\n';
	}
	else {
		form.answer.value = 'invalid input';
		return false;
	}

	// Find the resistor pair values that satisfy the input requirements
	var answer_num = 0;
	var answers = new Array();
	for (k = 0; k < 3; ++k) {
		var scale = Math.pow(10, k);

		for (j = 0; j < rvals.length; ++j) {
			var r4 = rvals[j] * scale;

			for (i = 0; i < rvals.length; ++i) {
				var r3 = rvals[i];
				var diff = percent_difference(simple_gain(r3, r4), gain);
				if (diff <= max_pct_diff) {
					var o = new Object;
					o.r3 = r3;
					o.r4 = r4;
					o.diff = diff;
					answers[answer_num++] = o;
				}
			}
		}
	}

	form.answer.value = '';

	var formatted_answer = 'Found ' + answer_num + ' answer';
	if (answer_num > 0) {
		if (answer_num > 1) {
			formatted_answer += 's';
			answers.sort(compare_diffs);
		}
		formatted_answer += ' for ' + rtol + '% resistors:\n';

		for (i = 0; i < answer_num; ++i) {
			formatted_answer += format_values_answer(
					Math.round(answers[i].r3 * 100000) / 100,
					Math.round(answers[i].r4 * 100000) / 100,
					Math.round(answers[i].diff * 10) / 10);
			formatted_answer += '\n';
		}

		form.answer.value = formatted_answer;
	}
	else {
		form.answer.value += "\nUnable to find any suitable combinations.";
		form.answer.value += "\nTry increasing the tolerance value.";
	}

	return false;	// make browser avoid actual "submit" operation
}


function gain_select_handler(form)
{
	var r3 = convert_power(form.r3.value);
	var r4 = convert_power(form.r4.value);
	var r5 = convert_power(form.r5.value);
	var r6 = convert_power(form.r6.value);
	
	var answer;
	if ((r3 > 0) && (r4 > 0)) {
		if ((r5 > 0) && (r6 > 0)) {
			answer = multiloop_gain(r3, r4, r5, r6);
		}
		else {
			answer = simple_gain(r3, r4);
		}
	}
	else {
		answer = "<font color=gray>enter R3 and R4</font>";
	}

	var div = document.getElementById("gain_answer");
	div.innerHTML = answer;

	return false;	// make browser avoid actual "submit" operation
}


// Equation from _Design with Operational Amplifiers and Analog
// Integrated Circuits_ 3/e, Sergio Franco, p. 219, expanded form
// of eq. 5.11.
function input_current_offset(iib, ios, r2, r3, r4)
{
	var pfr = parallel_resistors(r3, r4, 0, 0, 0, 0); // par. feedback res.
	return simple_gain(r3, r4) * Math.abs((iib * (pfr - r2)) -
			((ios / 2) * (pfr + r2)));

	// Intuitive form when ios == 0:
	//return simple_gain(r3, r4) * Math.abs((iib * r2) - (iib * pfr));
}


// Calculate offset due to both input bias current and input offset
// current in combination.  Returns worst-case value.
function calc_input_current_offset(iib, ios, r2, r3, r4, pot)
{
	if (pot == 0) {
		// No pot, so calculate using R2 as +IN impedance
		return input_current_offset(iib, ios, r2, r3, r4);
	}
	else {
		// Calculate +IN input impedance with R2 and variable pot value
		// in parallel, and use higher of the two offsets.
		var os1 = input_current_offset(iib, ios,
				parallel_resistors(min_pot_value, r2, 0, 0, 0, 0), r3, r4);
		var os2 = input_current_offset(iib, ios,
				parallel_resistors(pot, r2, 0, 0, 0, 0), r3, r4);
		return Math.max(os1, os2);
	}
}


function offset_select_handler(form)
{
	var r2 = convert_power(form.r2.value);
	var r3 = convert_power(form.r3.value);
	var r4 = convert_power(form.r4.value);
	var iib = convert_fraction(form.iib.value);
	var ios = convert_fraction(form.ios.value);
	var vos = convert_fraction(form.vos.value);
	var pot = convert_power(form.pot.value);
	
	var answer = '';
	if ((r2 > 0) && (r3 > 0) && (r4 > 0)) {
		var os_total = 0;
		var partial = false;

		// Calculate offset due to input currents
		if ((iib > 0) || (ios > 0)) {
			os_total += calc_input_current_offset(iib, ios, r2, r3, r4, pot);
		}
		else {
			partial = true;
		}

		// Calculate offset due to input offset voltage
		if (vos > 0) {
			os_total += simple_gain(r3, r4) * vos;
		}
		else {
			partial = true;
		}

		// Display answer
		if (os_total != 0) {
			if (partial) {
				answer = 'partial answer: ';
			}
			
			if (os_total >= 1) {
				answer += Math.round(os_total * 1000) / 1000;
				answer += ' V';
			}
			else {
				answer += Math.round(os_total * 100000) / 100;
				answer += ' mV';
			}
		}
		else {
			answer = '<font color=red>no datasheet values given</font>';
		}
	}
	else {
		answer = '<font color=gray>give resistor values and at least ' +
				'one datasheet value</font>';
	}

	var div = document.getElementById("offset_answer");
	div.innerHTML = answer;

	return false;	// make browser avoid actual "submit" operation
}


function johnson_noise(r)
{
	var k = 1.38e-23;
	var T = 298;
	return Math.sqrt(4 * k * T * r);
}


function root_sum_square(a, b, c, d, e, f)
{
	return Math.sqrt(
			a * a +
			b * b +
			c * c +
			d * d +
			e * e +
			f * f
			);
}


function dbv(v1, v2)
{
	return Math.log(v1 / v2) / Math.LN10 * 20;
}


// See p. 554 of _Op Amp Applications Handbook_, ed. by Jung

function noise_select_handler(form)
{
	var r2 = convert_power(form.r2.value);
	var r3 = convert_power(form.r3.value);
	var r4 = convert_power(form.r4.value);
	var pot = convert_power(form.pot.value);
	var pot = convert_power(form.pot.value);
	var ivn = form.ivn.value;
	var icn = form.icn.value;

	var answer = '';
	if ((r3 > 0) && (r4 > 0)) {
		var gain = simple_gain(r3, r4);
		var bw_root = Math.sqrt(20000 - 20);

		var n1 = johnson_noise(pot) * gain;
		var n2 = johnson_noise(r2) * gain;
		var n3 = johnson_noise(r3) * gain;
		var n4 = johnson_noise(r4) * gain;
		var n5 = ivn / 1e9;
		var n6 = icn * parallel_resistors(r3, r4, 0, 0, 0, 0) / 1e12;

		var n_total = root_sum_square(n1, n2, n3, n4, n5, n6) * bw_root;
		
		answer = Math.round(n_total * 1e6 * 100) / 100 + ' &micro;V, ' +
				Math.round(dbv(n_total, 1) - 0.5) +
				' dB relative to 1 Vrms';
	}
	else {
		answer = '<font color=gray>give feedback resistor values</font>';
	}

	var div = document.getElementById("noise_answer");
	div.innerHTML = answer;

	return false;	// make browser avoid actual "submit" operation
}


function parallel_select_handler(form)
{
	var ra = convert_power(form.ra.value);
	var rb = convert_power(form.rb.value);
	var rc = convert_power(form.rc.value);
	var rd = convert_power(form.rd.value);
	var re = convert_power(form.re.value);
	var rf = convert_power(form.rf.value);
	
	var answer = parallel_resistors(ra, rb, rc, rd, re, rf);
	if (answer == 0) {
		answer = "<font color=gray>give at least one value</font>";
	}

	var div = document.getElementById("parallel_answer");
	div.innerHTML = answer;

	return false;	// make browser avoid actual "submit" operation
}


function divider_select_handler(form)
{
	var ra = convert_power(form.ra.value);
	var rb = convert_power(form.rb.value);
	
	var answer;
	if ((ra > 0) && (rb > 0)) {
		answer = resistor_divider(ra, rb);
	}
	else {
		answer = '<font color=gray>give both resistor values</font>';
	}

	var div = document.getElementById("divider_answer");
	div.innerHTML = answer;

	return false;	// make browser avoid actual "submit" operation
}


function handle_clicked_header(e)
{
	var id = Event.element(e).id;
	var underscore = id.search('_ind');
	if (underscore > 0) {
		id = id.substr(0, underscore);
	}

	var div = id + '_div';
	var img = id + '_ind';
	if ($(div).style.display == 'none') {
		Effect.BlindDown(div, {duration: 0.5});
		indicator_images[img].src = "bitmaps/off-button.png";
	}
	else {
		Effect.BlindUp(div, {duration: 0.5});
		indicator_images[img].src = "bitmaps/on-button.png";
	}
}


function initialize_calculators()
{
	gain_select_handler(document.gain);
	offset_select_handler(document.offset);
	noise_select_handler(document.noise);
	parallel_select_handler(document.parallel);
	divider_select_handler(document.divider);

	$$('h3').each(function(h) {
			Event.observe(h, 'click', handle_clicked_header)
		});
	$$('img.indicator').each(function(i) {
			indicator_images[i.id] = i;
		});
}

