var delay_in_progress = null;
var calc_mode = null;
var help_strings = {
	'psource':
		'"Raw AC" means either a bare transformer, or an AC-AC wall ' +
			"wart.\n\n" +
		"An unregulated DC wall wart is the cheap kind, which only " +
			"rectifies the transformer's AC ouptut and does basic " +
			"filtering. It's quite sensible to add a regulator, " +
			"which is probably why you're here.\n\n" +
		"A regulated DC supply is the expensive kind. It has its own " +
			"regulator inside (thus the name), separate from the one " +
			"this calculator is concerned with. You can double-regulate " +
			"if you want, but unless the second regulator is something " +
			"special, there's usually not much value in this. The " +
			"option is included here mainly for completeness.",
	'hi_v': {
		'ac': "This is the transformer's unloaded secondary voltage.\n\n" +
			"The distributor's web site usually only gives the " +
			"transformer's fully-loaded voltage, not what we want here." +
			"\n\nYou can usually find this value in the datasheet. " +
			"Sometimes it isn't given directly, but instead you get " +
			"a percent regulation value; if it's rated for 25 VAC and " +
			"it says 10% regulation, that means the unloaded voltage is " +
			"27.5 VAC. If you have to measure it, you can't have " +
			"anything attached to the secondaries, not even a diode " +
			"bridge; that'll throw off the measurement.\n\n" +
			"The default is for an Amveco 70022 PCB-mount toroid.",
		'udc': "This is the wall wart's unloaded ouptut voltage. " +
			"You might find this value in the datasheet, but more " +
			"likely you'll have to measure it.",
		'rdc': "This is the power source's regulated output voltage."
	},
	'lo_v': {
		'ac': "This is the transformer's rated secondary voltage. " +
			"It's the value you see prominently on the distributor's " +
			"web site and at the top of the datasheet.\n\nThe default " +
			"is for an Amveco 70022 PCB-mount toroid.",
		'udc': "This is the wall wart's rated output voltage. " +
			"If you can only find one voltage number, it's this one."
	},
	'src_a': {
		'ac': "This is the transformer's maximum rated secondary " +
			"current.\n\nThe default is for an Amveco 70022 PCB-" +
			"mount toroid.",
		'udc': "This is the wall wart's maximum rated output current."
	},
	'v_var': "This is the maximum amount of AC line voltage variation " +
		"you want your supply to tolerate. It's used to decide whether " +
		"the regulator will drop out in a mild brown-out or not. In " +
		"AC-input supplies, it's also used to estimate pre-regulator " +
		"ripple.\n\nThe default is a typical design value here in the " +
		"US: 10% is 108-132 V, assuming a nominal AC line voltage of " +
		"120 V.",
	'freq': "This is your country's AC line frequency. It's only used " +
		"for estimating pre-regulator ripple.\n\nThe default is the " +
		"value used in North America, and a few other countries around " +
		"the world.",
	'reg_v': "This is the regulator's dropout voltage.\n\nYou'll " +
		"need to study the regulator's datasheet to get an accurate " +
		"value here, because dropout will vary depending on many " +
		"factors, including current and operating temperature.\n\n" +
		"The default is for an LM317 at room temperature with about " +
		"1 A being drawn from it.",
	'reg_tr': "This is the regulator's junction-to-case thermal " +
		"resistance.\n\nThe field label is the most common symbol used " +
		"in datasheets, but there is some variation.\n\nThe default " +
		"is for an LM317 in the TO-220 package.",
	'hsc_tr': "This is the thermal resistance between the regulator's " +
		"case and the heat sink.\n\nThe default is for standard heat " +
		"sink compound. Other compounds and pads will have different " +
		"values. If you're going to use an insulator kit, just add " +
		"its thermal resistance value in here.\n\nThe field label is " +
		"pretty much arbitrary. The datasheets for products that go " +
		"between the regulator and heat sink usually don't use any " +
		"sort of symbol at all. They just give the thermal resistance " +
		"number.",
	'hsa_tr' : "This is the heat sink's thermal resistance. You'll " +
		"find this value in the heat sink's datasheet.\n\nThe default " + 
		"is for an Aavid-Thermalloy 529802B25, the heat sink used in " +
		"the STEPS power supply.\n\nThe field label is pretty much " +
		"arbitrary. Heat sink datasheets usually don't use any sort " +
		"of symbol. They just give the thermal resistance number.",
	'cap': "This is the value of the main filter cap, that being the " +
		"one right after the diode bridge. It is C5 in both the " +
		"STEPS and TREAD power supply circuits.\n\nThe default is the " +
		"standard value recommended for these supplies.",
	'rtype' : "This is the diode type used for the supply's bridge.\n\n" +
		"The calculator assumes you're using a full-wave rectifier, " +
		"as in the STEPS and TREAD circuits."
};


//// analysis ----------------------------------------------------------
// Given a set of calculated heat and voltage values, return a string
// explaining what they mean in real terms.

function analysis(src_v_lo, src_v_hi, reg_drop_min, reg_drop_max,
		reg_v, reg_temp, out_v, out_a, src_a)
{
	var comments = null;
	var warnings = new Array();
	var errors = new Array();

	if (reg_drop_min < reg_v) {
		if (src_v_lo <= out_v) {
			errors.push("no regulation at all");
		}
		else {
			warnings.push("regulator will drop out of regulation");
		}
	}

	if (reg_drop_max > 37) {
		warnings.push("only exceptional regulators can tolerate that " +
				"big a voltage drop");
	}
	else if (reg_drop_max > 30) {
		warnings.push("most regulators can&rsquo;t tolerate that big a " +
				"voltage drop");
	}
	else if (reg_drop_min > 15) {
		errors.push("configuration is highly inefficient; choose a " +
				"more appropriate transformer");
	}
	else if (reg_drop_min > (reg_v * 2)) {
		warnings.push("the voltage drop across the regulator is more " +
				"than needed to maintain good regulation; choose a " +
				"more appropriate transformer, or raise the " +
				"regulator&rsquo;s output voltage by at least " +
				tenths(reg_drop_min - reg_v * 2) + "&nbsp;V to " +
				"reduce the drop across the regulator");
	}

	if (reg_temp > 40) {
		if (reg_temp > 100) {
			errors.push("regulator is likely to overheat");
		}
		else if (reg_temp > 75) {
			warnings.push("regulator will get very hot");
		}
		else {
			comments = "you should try to get the temperature down";
		}
	}

	if (src_a && (out_a >= (src_a * 0.8))) {
		warnings.push("you shouldn&rsquo;t load a transformer to more than " +
				"80% of its rated maximum");
	}

	if (errors.length > 0) {
		if (errors.length == 1) {
			return '<p class=error><b>Error:</b> ' + errors[0] + '</p>';
		}
		else {
			var msg = '<p><b>Errors:</b><ol><ul type=disc>';
			for (var i = 0; i < errors.length; ++i) {
				msg += '<li class=error>' + errors[i];
			}
			return msg + '</ul></ol></p>';
		}
	}
	else if (warnings.length > 0) {
		var msg = '<p class=warning><b>Analysis:</b> The supply ' +
				"may work, but I&rsquo;m concerned: ";
		
		if (warnings.length == 1) {
			msg += warnings[0] + '</p>';
		}
		else {
			msg += '</p><ol><ul type=disc>';
			for (var i = 0; i < warnings.length; ++i) {
				msg += '<li class=warning>' + warnings[i];
			}
			msg += '</ul></ol></p>';
		}

		return msg;
	}
	else {
		var msg = '<p><b>Analysis:</b> Configuration looks sane';
		if (comments) {
			msg += ', but ' + comments;
		}
		return msg + '</p>';
	}
}


//// calc_ac -----------------------------------------------------------
// Main calculation function in raw AC mode

function calc_ac(hi_v, lo_v, src_a, v_var, freq, reg_v, reg_tr, hsc_tr,
		hsa_tr, out_v, out_a, cap, diode)
{
	if (!check_positive(hi_v, lo_v, src_a, reg_v,
				reg_tr + hsc_tr + hsa_tr, out_v, out_a)) {
		return '<p class=warning>Missing input value</p>';
	}

	var unreg_ac = hi_v - ((hi_v - lo_v) * (out_a / src_a));
	var unreg_dc_min = (unreg_ac * (100 - v_var) / 100 * 1.414) - (diode * 2);
	var unreg_dc_max = (unreg_ac * (100 + v_var) / 100 * 1.414) - 1.4;

	var error =
			check_source_v_delta(hi_v, lo_v) ||
			check_line_variation(v_var) ||
			check_line_freq(freq) ||
			check_ouput_current(out_a, src_a) ||
			check_dropout_voltage(reg_v) ||
			check_thermal_resistances(reg_tr, hsc_tr, hsa_tr) ||
			check_filter_cap(cap);
	if (error) {
		return "<p><b>Error:</b> " + error + "</p>";
	}

	var ripple = Math.min(out_a / (2 * freq * cap / 1000000), unreg_dc_min);
	var reg_drop_min = unreg_dc_min - ripple - out_v;
	var reg_drop_max = unreg_dc_max - out_v;
	var reg_temp = reg_drop_max * out_a * (reg_tr + hsc_tr + hsa_tr);

	return "<p><b>Results:</b> " +
			"unregulated voltage" + v_range(unreg_dc_min, unreg_dc_max) +
			", pre-regulator ripple " + v_estimate(ripple) +
			", drop across regulator " + v_range(reg_drop_min, reg_drop_max) +
			", regulator temp &le; " + Math.round(reg_temp) + 
			" &deg;C over ambient</p>" +
			analysis(unreg_dc_min, unreg_dc_max, reg_drop_min,
				reg_drop_max, reg_v, reg_temp, out_v, out_a, src_a);
}


//// calc_regulated_dc -------------------------------------------------
// Main calculation function in regulated DC mode.

function calc_regulated_dc(src_v, src_a, reg_v, reg_tr, hsc_tr, hsa_tr,
		out_v, out_a)
{
	if (!check_positive(src_v, src_a, reg_v, reg_tr + hsc_tr + hsa_tr,
			out_v, out_a)) {
		return '<p class=warning>Missing input value</p>';
	}

	var error = check_ouput_current(out_a, src_a, false) ||
			check_dropout_voltage(reg_v) ||
			check_thermal_resistances(reg_tr, hsc_tr, hsa_tr);
	if (error) {
		return "<p><b>Error:</b> " + error + "</p>";
	}

	var reg_drop = src_v - out_v;
	var reg_temp = reg_drop * out_a * (reg_tr + hsc_tr + hsa_tr);

	return "<p><b>Results:</b> " +
			" drop across regulator " + v_estimate(reg_drop) +
			", regulator temp " + Math.round(reg_temp) + 
			" &deg;C over ambient</p>" +
			analysis(src_v, src_v, reg_drop, reg_drop, reg_v, reg_temp,
				out_v, out_a);
}


//// calc_unregulated_dc -----------------------------------------------
// Main calculation function in unregulated DC wall wart mode.

function calc_unregulated_dc(hi_v, lo_v, src_a, v_var, reg_v, reg_tr,
		hsc_tr, hsa_tr, out_v, out_a)
{
	if (!check_positive(hi_v, lo_v, src_a, reg_v,
				reg_tr + hsc_tr + hsa_tr, out_v, out_a)) {
		return '<p class=warning>Missing input value</p>';
	}

	var unreg_dc = hi_v - ((hi_v - lo_v) * (out_a / src_a));

	var error = check_source_v_delta(hi_v, lo_v) ||
			check_line_variation(v_var) ||
			check_ouput_current(out_a, src_a, true) ||
			check_dropout_voltage(reg_v) ||
			check_thermal_resistances(reg_tr, hsc_tr, hsa_tr);
	if (error) {
		return "<p><b>Error:</b> " + error + "</p>";
	}

	var reg_drop = unreg_dc - out_v;
	var reg_temp = reg_drop * out_a * (reg_tr + hsc_tr + hsa_tr);

	return "<p><b>Results:</b> " +
			" loaded voltage " + v_estimate(unreg_dc) +
			", drop across regulator " + v_estimate(reg_drop) +
			", regulator temp " + Math.round(reg_temp) + 
			" &deg;C over ambient</p>" +
			analysis(unreg_dc, unreg_dc, reg_drop, reg_drop, reg_v,
				reg_temp, out_v, out_a);
}


//// check_dropout_voltage ---------------------------------------------

function check_dropout_voltage(reg_v)
{
	if (reg_v < 0.1) {
		return form_error("Regulator dropout voltage is too low " +
				"to be believed.");
	}
	else if (reg_v > 10) {
		return form_error("Regulator dropout voltage is way too " +
				"high. Surely it isn't that bad?");
	}
}


//// check_filter_cap --------------------------------------------------

function check_filter_cap(cap) 
{
	if (cap < 1) {
		return form_error("You need some filtration capacitance.");
	}
	else if (cap > 100000) {
		return form_error("Filtration capacitance is ridiculously " +
				"high for a regulated power supply. One only uses " +
				"such big banks in purely unregulated supplies.");
	}
}


//// check_line_freq ---------------------------------------------------

function check_line_freq(freq)
{
	if (freq < 40 || freq > 65) {
		return form_error("Line frequency must be between 40 and 65 Hz.");
	}
}


//// check_line_variation ----------------------------------------------

function check_line_variation(v_var)
{
	if (v_var < 0 || v_var > 25) {
		return form_error("Variation must be between 0 and 25%.");
	}
}


//// check_mode --------------------------------------------------------
// Handles changes to the mode radio buttons.  If there has been a
// change since the last call, rejiggers the field set appropriately.

function check_mode(form)
{
	for (var i = 0; i < form.psource.length; ++i) {
		if (form.psource[i].checked &&
				(form.psource[i].value != calc_mode)) {
			calc_mode = form.psource[i].value;

			switch (calc_mode) {
				case 'ac':
					$$('.aconly').each(function(e) { e.show(); });
					$$('.unregonly').each(function(e) { e.show(); });
					$('srctype').innerHTML = 'Transformer unloaded';
					$('vunit').innerHTML = 'VAC';
					break;

				case 'udc':
					$$('.aconly').each(function(e) { e.hide(); });
					$$('.unregonly').each(function(e) { e.show(); });
					$('srctype').innerHTML = 'Wall wart unloaded';
					$('vunit').innerHTML = 'VDC';
					break;

				case 'rdc':
					$$('.aconly').each(function(e) { e.hide(); });
					$$('.unregonly').each(function(e) { e.hide(); });
					$('srctype').innerHTML = 'Supply';
					$('vunit').innerHTML = 'VDC';
					break;
			}
		}
	}
}


//// check_ouput_current -----------------------------------------------

function check_ouput_current(out_a, src_a)
{
	if (out_a > src_a) {
		return form_error("Regulated output current must be lower " +
				"than the supply's rated current.");
	}
}


//// check_positive ----------------------------------------------------

function check_positive()
{
	var args = check_positive.arguments;
	for (var i = 0; i < args.length; ++i) {
		var v = parseFloat(args[i]);
		if (isNaN(v) || (v <= 0)) {
			return false;
		}
	}

	return true;
}


//// check_source_v_delta ----------------------------------------------

function check_source_v_delta(hi_v, lo_v)
{
	if (hi_v < lo_v) {
		return form_error("Transformer unloaded voltage must be " +
				"higher than the fully-loaded (rated) voltage.");
	}
	else if (hi_v == lo_v) {
		return form_error("C'mon now, I don't really believe " +
				"your transfomer is 100% efficient. Try again.");
	}
}


//// check_thermal_resistances -----------------------------------------

function check_thermal_resistances(reg_tr, hsc_tr, hsa_tr)
{
	if (reg_tr < 1) {
		return form_error("Regulator thermal resistance is too " +
				"low to be believed.");
	}
	else if (reg_tr > 500) {
		return form_error("Surely the regulator thermal " +
				"resistance isn't that bad?");
	}
	else if (hsc_tr < 0.01) {
		return form_error("Heat sink compound thermal resistance " +
				"is too low to be believed.");
	}
	else if (hsc_tr > 5) {
		return form_error("Surely the thermal resistance " +
				"between the regulator and the heat sink isn't that bad?");
	}
	else if (hsa_tr < 0.1) {
		return form_error("Heat sink thermal resistance is too " +
				"low to be believed.");
	}
	else if (hsa_tr > 50) {
		return form_error("Surely the heat sink thermal " +
				"resistance isn't that bad?");
	}
}


//// delay_estimate_ps_parms -------------------------------------------
// Calls estimate_ps_parms() after a fixed delay.  It also copes with
// the possibility that this function is called while we're in the
// middle of a delayed call, so that only one call goes through.  When
// this happens, the timer just gets reset.

function delay_estimate_ps_parms(form)
{
	if (delay_in_progress) {
		window.clearTimeout(delay_in_progress);
	}
	delay_in_progress = window.setTimeout(function() {
			estimate_ps_parms(form);
		}, 200);
}


//// estimate_ps_parms -------------------------------------------------
// Given standard linear regulated power supply characteristics,
// calculate some of its operating parameters.

function estimate_ps_parms(form)
{
	var hi_v = Number(form.hi_v.value);
	var lo_v = Number(form.lo_v.value);
	var src_a = Number(form.src_a.value);
	var v_var = Number(form.v_var.value);
	var freq = Number(form.freq.value);
	var reg_v = Number(form.reg_v.value);
	var reg_tr = Number(form.reg_tr.value);
	var hsc_tr = Number(form.hsc_tr.value);
	var hsa_tr = Number(form.hsa_tr.value);
	var out_v = Number(form.out_v.value);
	var out_a = Number(form.out_a.value);
	var cap = Number(form.cap.value);
	var diode = form.rtype[0].checked ? 1 : 0.5;

	if (delay_in_progress) {
		window.clearTimeout(delay_in_progress);
		delay_in_progress = null;
	}

	check_mode(form);
	var answer = null;
	switch (calc_mode) {
		case 'ac':
			answer = calc_ac(hi_v, lo_v, src_a, v_var, freq, reg_v,
					reg_tr, hsc_tr, hsa_tr, out_v, out_a, cap, diode);
			break;
			
		case 'udc':
			answer = calc_unregulated_dc(hi_v, lo_v, src_a, v_var, reg_v,
					reg_tr, hsc_tr, hsa_tr, out_v, out_a);
			break;
			
		case 'rdc':
			answer = calc_regulated_dc(hi_v, src_a, reg_v, reg_tr,
					hsc_tr, hsa_tr, out_v, out_a);
			break;

		default:
			answer = form_error('Unknown calculation mode!');
	}
	$('answer').innerHTML = answer;
}


//// form_error --------------------------------------------------------
// Wrap the given message in a font tag appropriate to an error message

function form_error(msg)
{
	return "<font color=red>" + msg + "</font>";
}


//// help_msg ----------------------------------------------------------

function help_msg(icon)
{
	if ((icon.id.length < 6) || (icon.id.substr(0, 5) != 'help_')) {
		return 'Erroneous call to help() for ' + icon.id;
	}

	var key = icon.id.substr(5);
	var msg = help_strings[key];
	if (msg) {
		if (typeof msg == 'object') {
			msg = msg[calc_mode];
		}
		alert(msg);
	}
	else {
		console.log('No help for ' + key);
	}
}


//// init_ps_estimator -------------------------------------------------
// Initializes the calculator: sets up fields, applies JS actions, etc.

function init_ps_estimator()
{
	var form = $('ps_est_form');
	for (var f = 0; f < form.elements.length; ++f) {
		form.elements[f].onchange = function() { estimate_ps_parms(form); }
		form.elements[f].onkeyup = function() { 
				delay_estimate_ps_parms(form);
			}
	}

	$$('.help').each(function(icon) {
			icon.onclick = function() { help_msg(icon); };
		});

	estimate_ps_parms(form);
}


//// tenths --------------------------------------------------------
// Returns the given value rounded to nearest tenth.

function tenths(v)
{
	return Math.round(v * 10) / 10;
}


//// v_estimate --------------------------------------------------------
// Rounds value to nearest tenth of a volt, and returns it in a string
// expressing that estimate.

function v_estimate(v)
{
	return " &asymp; " + tenths(v) + " V";
}


//// v_range ------------------------------------------------------------
// Return a string containing an approximate voltage range given the
// endpoints.

function v_range(v1, v2)
{
	if (Math.round(v1) != Math.round(v2)) {
		return " &asymp; " + tenths(v1) + "&ndash;" + tenths(v2) + " V";
	}
	else {
		return " &asymp; " + tenths(v1) + " V";
	}
}

