function Estimator (options) {
	// The form that actually contains input fields
	this.form = options.form;

	// The form that will contain data passed to the print results page.
	// We move that data into a separate form to reduce the chance of
	// breaking request URL length limitations.
	this.results_form = options.results_form;

	this.has_multiple_systems = options.has_multiple_systems;

	var that = this;

	this.results_form.onsubmit = function () {
		that.calculate();
		if (!that.validate_tax_deduction_section()) {
			return false;
		}
		that.update_tax_qualifications();
		if (that.has_multiple_systems) {
			that.update_tax_deduction(true);
			that.update_energy_costs(true);
		}
		else {
			that.update_tax_deduction(true);
			that.update_light_levels(true);
			that.update_energy_savings(true);
		}
		that.results_form.target = "_blank";
		that.results_form.action = that.has_multiple_systems ?
			"eligibility_estimator_multiple_printable.htm" :
			"eligibility_estimator_printable.htm";
		return true;
	};

	var printLink = document.getElementById("printLink");
	if (printLink) {
		printLink.href = "#";
		printLink.onclick = function () {
			if (!that.results_form.onsubmit()) {
				return false;
			}
			that.results_form.submit();
			return false;
		};
	}
	
	this.populate_building_type_dropdown();
	var u1 = function () { 	// called after each change to the form.
		that.calculate();
		that.update_tax_qualifications();
	};

	// when changed, some form elements also change certain radio buttons.
	this.form.H5_as_area.onchange = function () {
		if (/\S/.test(this.value)) {
			that.form.H5_by[0].click();
		}
		u1();
	};
	this.form.H5_as_length.onchange = function () {
		if (/\S/.test(this.value)) {
			that.form.H5_by[1].click();
		}
		u1();
	};
	this.form.H5_as_width.onchange = function () {
		if (/\S/.test(this.value)) {
			that.form.H5_by[1].click();
		}
		u1();
	};

	if (!this.has_multiple_systems) {
		this.form.H6_as_number.onchange = function () {
			if (/\S/.test(this.value)) {
				that.form.H6_by[1].click();
			}
			u1();
		};
		this.form.E31_as_per_year.onchange = function () {
			if (/\S/.test(this.value)) {
				that.form.E31_by[0].click();
			}
			u1();
		};
		this.form.E31_as_per_day.onchange = function () {
			if (/\S/.test(this.value)) {
				that.form.E31_by[1].click();
			}
			u1();
		};
	}

	if (this.has_multiple_systems) {
		document.getElementById("addMoreSystemsLink").onclick =
			function () {
				that.add_more_systems_to_form();
				return false;
			};
	}

	// all other form elements get standard behavior when changed.
	var e;
	for (var i = 0; i < this.form.elements.length; ++i) {
		e = this.form.elements[i];
		if (e.type === "radio" || e.type === "checkbox") {
			if (!e.onclick) {
				e.onclick = u1;
			}
		}
		else {
			if (!e.onchange) {
				e.onchange = u1;
			}
		}
	}

	if (this.has_multiple_systems) {
		document.getElementById("calculate_tax_deduction_button").onclick =
			function () {
				that.calculate();
				that.update_tax_qualifications();
				if (that.update_tax_deduction()) {
					that.update_energy_costs();
				}
				else {
					// Since this results in the form
					// validation function being called
					// again, we pass the no_alert flag.
					that.update_energy_costs(true);
				}
				return false;
			};
	}
	else {
		document.getElementById("calculate_tax_deduction_button").onclick =
			function () {
				that.calculate();
				that.update_tax_qualifications();
				that.update_tax_deduction();
				return false;
			};
		document.getElementById("calculate_light_levels_button").onclick =
			function () {
				that.calculate();
				that.update_light_levels();
				return false;
			};
		document.getElementById("calculate_energy_savings_button").onclick =
			function () {
				that.calculate();
				that.update_energy_savings();
				return false;
			};
	}

	// initialization, incase already populated
	this.calculate();
	this.update_tax_qualifications();
	this.update_tax_deduction_to_blank();
	if (this.has_multiple_systems) {
		this.update_energy_costs_to_blank();
		this.add_more_systems_to_form();
	}
	else {
		this.update_light_levels_to_blank();
		this.update_energy_savings_to_blank();
	}

}

Estimator.prototype.results_form_hidden = function (name, value) {
	if (!this.results_form_hidden) {
		this.results_form_hidden = {};
	}
	var hidden;
	if (!(name in this.results_form_hidden)) {
		if (value !== null && value !== undefined) {
			hidden = document.createElement("input");
			hidden.type = "hidden";
			hidden.name = name;
			hidden.value = value;
			this.results_form.appendChild(hidden);
			this.results_form_hidden[name] = hidden;
		}
	}
	else {
		if (value === null || value === undefined) {
			this.results_form_hidden[name].value = null;
			this.results_form.removeChild(
				this.results_form_hidden[name]);
			delete this.results_form_hidden[name];
		}
		else {
			this.results_form_hidden[name].value = value;
		}
	}
};

/* Because sometimes, foo / bar * bar !== foo, and sometimes,
   foo - bar + bar !== foo */
Estimator.isApprox = function (a, b) {
	return (Math.abs(a - b) < 0.000001);
};

Estimator.prototype.populate_building_type_dropdown = function () {
	var e = this.form.H4;
	while (e.length) {
		e.remove(0);
	}
	var option = new Option("Select", null);
	try {
		e.add(option, null);	// MSIE chokes here.
	}
	catch (ex) {
		e.add(option);	// for MSIE
	}
	var types = this.get_building_types_list();
	for (var i = 0; i < types.length; ++i) {
		option = new Option(types[i], types[i]);
		try {
			e.add(option, null); // MSIE chokes here.
		}
		catch (ex2) {
			e.add(option); // for MSIE
		}
	}
};

Estimator.prototype.add_more_systems_to_form = function () {
	this.add_a_system_to_form();
	this.add_a_system_to_form();
	this.add_a_system_to_form();
};

Estimator.prototype.add_a_system_to_form = function () {
	var c = document.getElementById("multipleSystemsContainer");
	if (!c) {
		return;
	}
	if (!("systems_in_form" in this)) {
		this.systems_in_form = 0;
	}

	var i    = this.systems_in_form;
	var next = this.systems_in_form + 1;

	var html = "";
	html += '<p>';
	html += '<span class="highlight required">*</span>';
	html += '<b>System ' + next + '</b>';
	html += '</p>';
	html += '<table border="0" cellspacing="0" cellpadding="0" ' + 
		'class="systemTable">';
	html += '<tr>';
	html += '<td class="first">System Description</td>';
	html += '<td><input type="text" name="E24_' + i +
		'" style="width: 90%;" /></td>';
	html += '</tr>';
	html += '<tr>';
	html += '<td class="first">Number of Fixtures</td>';
	html += '<td><input type="text" name="H6_' + i + '" size="6" /></td>';
	html += '</tr>';
	html += '<tr>';
	html += '<td class="first">Watts / Fixture</td>';
	html += '<td><input type="text" name="H12_' + i + '" size="5" /></td>';
	html += '</tr>';
	html += '<tr>';
	html += '<td class="first">Burning Hours / Year</td>';
	html += '<td><input type="text" name="E31_' + i + '" size="5" /></td>';
	html += '</tr>';
	html += '</table>';

	this.systems_in_form += 1;

	c.innerHTML += html;

	var that = this;
	var u1 = function () {
		that.calculate();
		that.update_tax_qualifications();
	};
	this.form["H6_" + i].onchange = u1;
	this.form["H12_" + i].onchange = u1;
	this.form["E31_" + i].onchange = u1;
};

Estimator.prototype.update_tax_qualifications = function () {
	document.getElementById("H7").innerHTML =
		isNaN(this.H7) ? "" :
		this.H7.toFixed(2).withCommas() + " watts / sq. ft.";
	document.getElementById("C8").innerHTML =
		isNaN(this.C8) ? "" :
		this.C8.toFixed(0) + "%";
	document.getElementById("H8").innerHTML =
		isNaN(this.H8) ? "" :
		this.H8.toFixed(2).withCommas() + " watts / sq. ft.";
	if (!this.has_multiple_systems) {
		document.getElementById("H10").innerHTML =
			isNaN(this.H10) ? "" :
			this.H10.toFixed(2).withCommas() + " watts";
		document.getElementById("C11").innerHTML =
			isNaN(this.C11) ? "" :
			"$" + this.C11.toFixed(2).withCommas();
		document.getElementById("H11").innerHTML =
			isNaN(this.H11) ? "" :
			this.H11.toFixed(2).withCommas() + " watts";
	}
};

Estimator.prototype.validate_tax_deduction_section = function (no_alert) {
	var i;

	if (this.form.H4.selectedIndex < 1) {
		/* first option is titled "Select" with null value */
		if (!no_alert) {
			window.alert("Please select a building type.");
			this.form.H4.focus();
		}
		return false;
	}
	if (this.form.H5_by[0].checked) {
		if (isNaN(parseFloat(this.form.H5_as_area.value))) {
			if (!no_alert) {
				window.alert("Please enter the building size.");
				this.form.H5_as_area.focus();
				this.form.H5_as_area.select();
			}
			return false;
		}
	}
	else if (this.form.H5_by[1].checked) {
		if (isNaN(parseFloat(this.form.H5_as_length.value))) {
			if (!no_alert) {
				window.alert("Please enter the building length.");
				this.form.H5_as_length.focus();
				this.form.H5_as_length.select();
			}
			return false;
		}
		else if (isNaN(parseFloat(this.form.H5_as_width.value))) {
			if (!no_alert) {
				window.alert("Please enter the building width.");
				this.form.H5_as_width.focus();
				this.form.H5_as_width.select();
			}
			return false;
		}
	}
	else {
		if (!no_alert) {
			window.alert("Please choose Square Footage or Length.");
			this.form.H5_by[0].focus();
		}
		return false;
	}

	if (this.has_multiple_systems) {
		if (isNaN(parseFloat(this.form.I31.value))) {
			if (!no_alert) {
				window.alert("Please enter the energy rate.");
				this.form.I31.focus();
				this.form.I31.select();
			}
			return false;
		}
		for (i = 0; i < this.systems_in_form; ++i) {
			if (!this.validate_system(i, no_alert)) {
				return false;
			}
		}
		if (!this.systems.length) {
			if (!no_alert) {
				window.alert("Please enter the " + 
					     "number of fixtures, " + 
					     "fixture wattage, " + 
					     "and burning hours per year " + 
					     "for at least one system.");
				this.form.H6_0.focus();
				this.form.H6_0.select();
			}
			return false;
		}
	}
	else {
		if (this.form.H6_by[0].checked) {
			// do nothing
		}
		else if (this.form.H6_by[1].checked) {
			if (isNaN(parseFloat(this.form.H6_as_number.value))) {
				if (!no_alert) {
					window.alert("Please enter the " + 
						     "number of fixtures.");
					this.form.H6_as_number.focus();
					this.form.H6_as_number.select();
				}
				return false;
			}
		}
		else {
			if (!no_alert) {
				window.alert("Please choose \"Determine " +
					     "Based on Building Size\" or\n" + 
					     "\"Enter Number of Fixtures\".");
				this.form.H6_by[0].focus();
			}
			return false;
		}
		if (isNaN(parseFloat(this.form.H12.value))) {
			if (!no_alert) {
				window.alert("Please enter the fixture wattage.");
				this.form.H12.focus();
				this.form.H12.select();
			}
			return false;
		}
	}
	return true;
};

Estimator.prototype.validate_system = function (i, no_alert) {
	var e1 = this.form["H6_" + i];
	var e2 = this.form["H12_" + i];
	var e3 = this.form["E31_" + i];
	var v1 = parseFloat(e1.value);
	var v2 = parseFloat(e2.value);
	var v3 = parseFloat(e3.value);
	if (!(/\S/.test(e1.value) || /\S/.test(e2.value) ||
	      /\S/.test(e3.value))) {
		return true;
	}
	if (isNaN(parseFloat(e1.value))) {
		if (!no_alert) {
			window.alert("Please enter the number of fixtures.");
			e1.focus();
			e1.select();
		}
		return false;
	}
	if (isNaN(parseFloat(e2.value))) {
		if (!no_alert) {
			window.alert("Please enter the watts per fixture.");
			e2.focus();
			e2.select();
		}
		return false;
	}
	if (isNaN(parseFloat(e3.value))) {
		if (!no_alert) {
			window.alert("Please enter the burning hours/year.");
			e3.focus();
			e3.select();
		}
		return false;
	}
	return true;
};

Estimator.prototype.validate_light_levels_section = function (no_alert) {
	if (!this.validate_tax_deduction_section(no_alert)) {
		return false;
	}
	if (isNaN(parseFloat(this.form.E25.value))) {
		if (!no_alert) {
			window.alert("Please enter the number of lamps per fixture.");
			this.form.E25.focus();
			this.form.E25.select();
		}
		return false;
	}
	if (isNaN(parseFloat(this.form.E26.value))) {
		if (!no_alert) {
			window.alert("Please enter the coefficient of utilization.");
			this.form.E26.focus();
			this.form.E26.select();
		}
		return false;
	}
	if (isNaN(parseFloat(this.form.I24.value))) {
		if (!no_alert) {
			window.alert("Please enter the rated mean lumens.");
			this.form.I24.focus();
			this.form.I24.select();
		}
		return false;
	}
	if (isNaN(parseFloat(this.form.I25.value))) {
		if (!no_alert) {
			window.alert("Please enter the ballast factor.");
			this.form.I25.focus();
			this.form.I25.select();
		}
		return false;
	}
	return true;
};

Estimator.prototype.validate_energy_savings_section = function (no_alert) {
	if (!this.validate_tax_deduction_section(no_alert)) {
		return false;
	}

	if (isNaN(parseFloat(this.form.E30.value))) {
		if (!no_alert) {
			window.alert("Please enter the fixture wattage.");
			this.form.E30.focus();
			this.form.E30.select();
		}
		return false;
	}

	if (this.form.E31_by[0].checked) {
		if (isNaN(parseFloat(this.form.E31_as_per_year.value))) {
			if (!no_alert) {
				window.alert("Please enter burning hours per year.");
				this.form.E31_as_per_year.focus();
				this.form.E31_as_per_year.select();
			}
			return false;
		}
	}
	else if (this.form.E31_by[1].checked) {
		if (isNaN(parseFloat(this.form.E31_as_per_day.value))) {
			if (!no_alert) {
				window.alert("Please enter burning hours per day.");
				this.form.E31_as_per_day.focus();
				this.form.E31_as_per_day.select();
			}
			return false;
		}
	}
	else {
		if (!no_alert) {
			window.alert("Please choose day or year.");
			this.form.E31_by[0].focus();
		}
		return false;
	}

	if (isNaN(parseFloat(this.form.I31.value))) {
		if (!no_alert) {
			window.alert("Please enter the energy rate.");
			this.form.I31.focus();
			this.form.I31.select();
		}
		return false;
	}

	return true;
};

Estimator.prototype.update_tax_deduction = function (no_alert) {
	if (this.validate_tax_deduction_section(no_alert)) {
		if (this.H16) {
			this.update_tax_deduction_from_calculations();
			return true;
		}
		else {
			this.update_tax_deduction_with_apology();
			return true;
		}
	}
	else {
		this.update_tax_deduction_to_blank();
		return false;
	}
};

Estimator.prototype.update_light_levels = function (no_alert) {
	if (this.validate_light_levels_section(no_alert)) {
		this.update_light_levels_from_calculations();
		return true;
	}
	else {
		this.update_light_levels_to_blank();
		return false;
	}
};

Estimator.prototype.update_energy_savings = function (no_alert) {
	if (this.validate_energy_savings_section(no_alert)) {
		this.update_energy_savings_from_calculations();
		return true;
	}
	else {
		this.update_energy_savings_to_blank();
		return false;
	}
};

Estimator.prototype.update_energy_costs = function (no_alert) {
	if (this.validate_tax_deduction_section(no_alert)) {
		this.update_energy_costs_from_calculations();
		return true;
	}
	else {
		this.update_energy_costs_to_blank();
		return false;
	}
};

Estimator.prototype.update_tax_deduction_to_blank = function () {
	var that = this;

	var a = document.createElement("a");
	a.appendChild(document.createTextNode("Calculate My Tax Savings"));
	a.href = "#";
	a.onclick = function () {
		that.calculate();
		that.update_tax_qualifications();
		that.update_tax_deduction();
		return false;
	};

	var p = document.createElement("p");
	p.className = "tight";
	p.appendChild(a);

	var e = document.getElementById("tax_deduction_result_container");
	removeChildrenFrom(e);
	e.appendChild(p);
};

Estimator.prototype.update_light_levels_to_blank = function () {
	var that = this;

	var a = document.createElement("a");
	a.appendChild(document.createTextNode("Calculate My Light Levels"));
	a.href = "#";
	a.onclick = function () {
		that.calculate();
		that.update_light_levels();
		return false;
	};

	var p = document.createElement("p");
	p.className = "tight";
	p.appendChild(a);

	var e = document.getElementById("light_levels_result_container");
	removeChildrenFrom(e);
	e.appendChild(p);
};

Estimator.prototype.update_energy_savings_to_blank = function () {
	var that = this;

	var a = document.createElement("a");
	a.appendChild(document.createTextNode("Calculate My Energy Savings"));
	a.href = "#";
	a.onclick = function () {
		that.calculate();
		that.update_energy_savings();
		return false;
	};

	var p = document.createElement("p");
	p.className = "tight";
	p.appendChild(a);

	var e = document.getElementById("energy_savings_result_container");
	removeChildrenFrom(e);
	e.appendChild(p);
};

Estimator.prototype.update_energy_costs_to_blank = function () {
	var that = this;

	var a = document.createElement("a");
	a.appendChild(document.createTextNode("Calculate My Energy Costs"));
	a.href = "#";
	a.onclick = function () {
		that.calculate();
		that.update_energy_costs();
		return false;
	};

	var p = document.createElement("p");
	p.className = "tight";
	p.appendChild(a);

	var e = document.getElementById("energy_costs_result_container");
	removeChildrenFrom(e);
	e.appendChild(p);
};

Estimator.prototype.update_tax_deduction_from_calculations = function () {
	var d1, d2, d3, d4;

	d1 = this.H18.toFixed().withCommas();
	if (!this.has_multiple_systems) {
		d2 = this.H19.toFixed(2).withCommas();
	}
	d3 = this.H17.toFixed(2).withCommas();
	d4 = (this.H14 * 100).toFixed(2).withCommas();

	var html = "";
	html += '<h3 class="tight">Congratulations!</h3>';
	html += '<p class="tight">If IRS requirements are met, ' +
		'you will qualify for the tax deduction.</p>';
	html += '<h2>Potential Tax Deduction</h2>';
	html += '<table border="0" cellspacing="0" cellpadding="0" ' + 
		'class="resultTable taxDeductionTable">';
	html += '<tbody>';
	html += '<tr class="totalIncentiveRow">';
	html += '<td class="first"><b>Total Incentive</b></td>';
	html += '<td><b>$' + d1 + '</b></td>';
	html += '</tr>';
	if (!this.has_multiple_systems) {
		html += '<tr>';
		html += '<td class="first">Incentive per Fixture</td>';
		html += '<td>$' + d2 + '</td>';
		html += '</tr>';
	}
	html += '<tr>';
	html += '<td class="first">Incentive per Sq. Ft.</td>';
	html += '<td>$' + d3 + '</td>';
	html += '</tr>';
	html += '</tbody>';
	html += '<tbody class="foot">';
	html += '<tr>';
	html += '<td class="first">Percent below ASHRAE/<wbr />' + 
		'IESNA 90.1 (2001) limit:</td>';
	html += '<td>' + d4 + '%</td>';
	html += '</tr>';
	html += '</tbody>';
	html += '</table>';
	
	var e = document.getElementById("tax_deduction_result_container");
	e.innerHTML = html;
};

Estimator.prototype.update_tax_deduction_with_apology = function () {
	var html = "";
	html += "<h3 class='tight'>Sorry.</h3>";
	html += "<p class='tight'>Based on the information you entered, " +
		"you do not qualify for a tax deduction " +
		"at this time.</p>";

	var e = document.getElementById("tax_deduction_result_container");
	e.innerHTML = html;
};

Estimator.prototype.update_light_levels_from_calculations = function () {
	var d1 = this.I26.toFixed(2).withCommas();

	var html = "";

	html += '<table border="0" cellspacing="0" cellpadding="0" ' + 
		'class="resultTable lightLevelsTable">';
	html += '<tbody>';
	html += '<tr>';
	html += '<td class="first"><b>Maintained Foot Candles</b></td>';
	html += '<td><b>' + d1 + '</b></td>';
	html += '</tr>';
	html += '</tbody>';
	html += '</table>';
	
	var e = document.getElementById("light_levels_result_container");
	e.innerHTML = html;
};

Estimator.prototype.update_energy_savings_from_calculations = function () {
	var d1 = this.H32.toFixed().withCommas();
	var d2 = this.H33.toFixed().withCommas();
	var d3 = this.H34.toFixed().withCommas();

	var html = "";
	
	html += '<table border="0" cellspacing="0" cellpadding="0" ' + 
		'class="resultTable energySavingsTable">';
	html += '<tbody>';
	html += '<tr>';
	html += '<td class="first"><b>Energy Costs</b></td>';
	html += '<td>&nbsp;</td>';
	html += '</tr>';
	html += '<tr>';
	html += '<td class="first">&bull; Existing System</td>';
	html += '<td>$' + d1 + '/yr.</td>';
	html += '</tr>';
	html += '<tr>';
	html += '<td class="first">&bull; Proposed System</td>';
	html += '<td>$' + d2 + '/yr.</td>';
	html += '</tr>';
	html += '</tbody>';
	html += '<tbody class="foot">';
	html += '<tr>';
	html += '<td class="first"><b>Energy Savings</b></td>';
	html += '<td><b>$' + d3 + '/yr.</b></td>';
	html += '</tr>';
	html += '</tbody>';
	html += '</table>';
	
	var e = document.getElementById("energy_savings_result_container");
	e.innerHTML = html;
};

Estimator.prototype.update_energy_costs_from_calculations = function () {
	var d1, d2, i;
	var d3 = this.H33.toFixed().withCommas();

	var html = "";

	html += '<table border="0" cellspacing="0" cellpadding="0" ' + 
		'class="resultTable energySavingsTable">';
	html += '<tbody>';
	html += '<tr>';
	html += '<td class="first"><b>Energy Costs</b></td>';
	html += '<td>&nbsp;</td>';
	html += '</tr>';
	for (i = 0; i < this.systems.length; ++i) {
		d1 = this.systems[i].description;
		if (!d1 || !/\S/.test(d1)) {
			d1 = "System " + (i + 1);
		}
		d2 = this.systems[i].cost.toFixed().withCommas();
		html += '<tr>';
		html += '<td class="first">&bull; ' + d1 + '</td>';
		html += '<td>$' + d2 + '/yr.</td>';
		html += '</tr>';
	}
	html += '</tbody>';
	html += '<tbody class="foot">';
	html += '<tr>';
	html += '<td class="first"><b>Total Energy Costs</b></td>';
	html += '<td><b>$' + d3 + '/yr.</b></td>';
	html += '</tr>';
	html += '</tbody>';
	html += '</table>';
	
	var e = document.getElementById("energy_costs_result_container");
	e.innerHTML = html;
};

Estimator.prototype.calculate = function () {
	this.calculate_tax_deduction();
	if (this.has_multiple_systems) {
		this.calculate_energy_costs();
	}
	else {
		this.calculate_light_levels();
		this.calculate_energy_savings();
	}
};

Estimator.prototype.calculate_tax_deduction = function () {
	// Lots of short variable names like H4 here.
	// They're used for both single systems and multiple systems, but
	// they're based on cell locations in the single systems spreadsheet.
	// See: http://www.geconsumerandindustrial.com/environmentalinfo/documents/EPACT18.xls

	var i;

	var e1 = this.form.H4;	// building type
	this.H4 = e1[e1.selectedIndex].value;
	var is_warehouse = (this.H4 === "Warehouse");

	var length = parseFloat(this.form.H5_as_length.value);
	var width  = parseFloat(this.form.H5_as_width.value);
	var area   = parseFloat(this.form.H5_as_area.value);

	/* square footage of facility */
	this.H5 = this.form.H5_by[0].checked ? area :
		this.form.H5_by[1].checked ? length * width :
		NaN;

	/* used for estimating number of fixtures if not known */
	var area_per_fixture = 90; /* see: cells M8 and N8 */

	if (!this.has_multiple_systems) {
		/* H6 = number of fixtures */
		this.H6 = this.form.H6_by[0].checked ?
			this.H5 / area_per_fixture : 
			this.form.H6_by[1].checked ?
			parseFloat(this.form.H6_as_number.value) :
			NaN;
	}

	/* ASHRAE/IESNA 90.1 (2001) building limit for category */
	this.H7 = (this.H4 && this.H4 in Estimator.MAX_WATTS_PER_SQUARE_FOOT) ?
		Estimator.MAX_WATTS_PER_SQUARE_FOOT[this.H4] :
		NaN;
	
	/* Percentage below limit to qualify for tax deduction. */
	this.C8 = is_warehouse ? 50 : 25;

	/* Max. watts/sqft to qualify for tax deduction */
	this.H8 = this.H7 * ((100 - this.C8) / 100);

	/* Potential tax deduction (first year)
	   if building is 25% below ASHRAE limit and other conditions met */
	this.H9 = is_warehouse ? (this.H5 * 0.6) : (this.H5 * 0.3);

	var v1, v2, v3, v4;
	if (this.has_multiple_systems) {
		this.systems = [];
		this.H6 = 0;	// number of fixtures
		for (i = 0; i < this.systems_in_form; ++i) {
			v1 = parseFloat(this.form["H6_" + i].value);
			v2 = parseFloat(this.form["H12_" + i].value);
			v3 = parseFloat(this.form["E31_" + i].value);
			v4 = this.form["E24_" + i].value;
			if (!isNaN(v1) && !isNaN(v2) && !isNaN(v3)) {
				this.systems.push({
					number_of_fixtures: v1,
					watts_per_fixture: v2,
					hours_per_year: v3,
					description: v4
				});
				this.H6 += v1;
			}
		}
	}
	
	/* H13 = watts per sqft with proposed system */
	var w;			// accumulates total watts
	if (this.has_multiple_systems) {
		if (this.systems.length) {
			w = 0;
			for (i = 0; i < this.systems.length; ++i) {
				w += this.systems[i].number_of_fixtures *
					this.systems[i].watts_per_fixture;
			}
			this.H13 = w / this.H5;
		}
		else {
			this.H13 = NaN;
		}
	}
	else {
		/* max. fixture wattage to qualify for deduction */
		this.H10 = this.H8 * this.H5 / this.H6;

		/* max. fixture wattage to qualify for
		   maximum tax deduction of $0.60 per sqft */
		this.H11 = is_warehouse ? this.H10 : this.H10 * 0.8;

		/* fixture wattage of proposed system */
		this.H12 = parseFloat(this.form.H12.value);

		this.H13 = this.H12 * this.H6 / this.H5;
	}

	/* ratio below ASHRAE limit */
	this.H14 = (this.H7 - this.H13) / this.H7;

	/* are bi-level switches installed? */
	this.D15 = this.form.D15[0].checked || this.form.D15[2].checked;
	
	/* do light levels satisfy IESNA guidelines? */
	this.G15 = this.form.G15[0].checked;

	/* was installation completed in 2006, 2007, or 2008? */
	this.I15 = this.form.I15[0].checked;

	/* do you qualify for the tax deduction? */
	this.H16 = (isNaN(this.H13) || isNaN(this.H8)) ? false :
		((this.H13 <= this.H8 ||
		  Estimator.isApprox(this.H13, this.H8)) &&
		 this.D15 && this.G15 && this.I15);
	
	/* maximum tax deduction per square foot
	   only used by update_tax_qualifications() method for display. */
	this.C11 = 0.6;

	/* used internally for computing potential tax deduction per sqft */
	this.N3 = 0.6;
	this.M3 = (this.H14 > 0.4 ? 0.6 :
		   (this.H14 >= 0.25 ||
		    Estimator.isApprox(this.H14, 0.25)) ?
		   (0.3 + 2 * (this.H14 - 0.25)) : 
		   NaN);

	/* potential tax deduction per square foot */
	this.H17 = is_warehouse ? this.N3 : this.M3;

	/* potential tax deduction incentive */
	this.H18 = this.H16 ? this.H17 * this.H5 : NaN;

	if (!this.has_multiple_systems) {
		/* potential tax deduction per fixture */
		this.H19 = this.H16 ? this.H18 / this.H6 : NaN;
	}
	
	this.results_form_hidden("H4", this.H4);
	this.results_form_hidden("H5", this.H5);
	if (!this.has_multiple_systems) {
		this.results_form_hidden("E24", this.form.E24.value);
	}
	this.results_form_hidden("H13", this.H13);
	this.results_form_hidden("D15", radioGroupValue(this.form.D15));
	this.results_form_hidden("G15", radioGroupValue(this.form.G15));
	this.results_form_hidden("I15", radioGroupValue(this.form.I15));

	this.results_form_hidden("H7", this.H7);
	this.results_form_hidden("C8", this.C8);
	this.results_form_hidden("H8", this.H8);
	this.results_form_hidden("C11", this.C11);
	this.results_form_hidden("H6", this.H6);

	if (!this.has_multiple_systems) {
		this.results_form_hidden("H12", this.H12);
		this.results_form_hidden("H10", this.H10);
		this.results_form_hidden("H11", this.H11);
	}

	if (!this.validate_tax_deduction_section(true)) {
		this.results_form_hidden("has_tax_deduction_section", null);
		this.results_form_hidden("H18", null);
		if (!this.has_multiple_systems) {
			this.results_form_hidden("H19", null);
		}
		this.results_form_hidden("H17", null);
		this.results_form_hidden("H14", null);
	}
	else if (this.H16) {
		this.results_form_hidden("has_tax_deduction_section", 1);
		this.results_form_hidden("qualifies_for_tax_deduction", 1);
		this.results_form_hidden("H18", this.H18);
		if (!this.has_multiple_systems) {
			this.results_form_hidden("H19", this.H19);
		}
		this.results_form_hidden("H17", this.H17);
		this.results_form_hidden("H14", this.H14);
	}
	else {
		this.results_form_hidden("has_tax_deduction_section", 1);
		this.results_form_hidden("qualifies_for_tax_deduction", null);
		this.results_form_hidden("H18", null);
		if (!this.has_multiple_systems) {
			this.results_form_hidden("H19", null);
		}
		this.results_form_hidden("H17", null);
		this.results_form_hidden("H14", null);
	}

	if (this.has_multiple_systems) {
		this.results_form_hidden("has_multiple_systems", 1);
		this.results_form_hidden("number_of_systems",
					 this.systems.length);
		for (i = 0; i < this.systems.length; ++i) {
			this.results_form_hidden(
				"H6_" + i, this.systems[i].number_of_fixtures);
			this.results_form_hidden(
				"H12_" + i, this.systems[i].watts_per_fixture);
			this.results_form_hidden(
				"E31_" + i, this.systems[i].hours_per_year);
			this.results_form_hidden(
				"E24_" + i, this.systems[i].description);
		}
		if ("old_number_of_systems" in this) {
			// erase any old data
			for (; i < this.old_number_of_systems; ++i) {
				this.results_form_hidden("H6_" + i, null);
				this.results_form_hidden("H12_" + i, null);
				this.results_form_hidden("E31_" + i, null);
				this.results_form_hidden("E24_" + i, null);
			}
		}
		this.old_number_of_systems = this.systems.length;
	}
};

Estimator.prototype.calculate_light_levels = function () {
	/* number of lamps per fixture */
	this.E25 = parseFloat(this.form.E25.value);

	/* coefficient of utilization */
	this.E26 = parseFloat(this.form.E26.value);

	/* rated mean lumens */
	this.I24 = parseFloat(this.form.I24.value);

	/* ballast factor */
	this.I25 = parseFloat(this.form.I25.value);

	/* Estimated Light Levels, in footcandles */
	this.I26 = this.I24 * this.E25 * this.I25 * this.E26 *
		this.H6 / this.H5;

	if (!this.validate_light_levels_section(true)) {
		this.results_form_hidden("has_light_levels_section", null);
		this.results_form_hidden("E25", null);
		this.results_form_hidden("E26", null);
		this.results_form_hidden("I24", null);
		this.results_form_hidden("I25", null);
		this.results_form_hidden("I26", null);
	}
	else {
		this.results_form_hidden("has_light_levels_section", 1);
		this.results_form_hidden("E25", this.E25);
		this.results_form_hidden("E26", this.E26);
		this.results_form_hidden("I24", this.I24);
		this.results_form_hidden("I25", this.I25);
		this.results_form_hidden("I26", this.I26);
	}
};

Estimator.prototype.calculate_energy_savings = function () {
	/* Wattage of existing fixture */
 	this.E30 = parseFloat(this.form.E30.value);

	/* Burning hours per year */
 	this.E31 = this.form.E31_by[0].checked ?
		parseFloat(this.form.E31_as_per_year.value) :
		this.form.E31_by[1].checked ?
		parseFloat(this.form.E31_as_per_day.value) * 365 :
		NaN;

	/* Energy Rate, dollars per kWh */
 	this.I31 = parseFloat(this.form.I31.value);

	/* Annual energy cost with existing fixture */
	this.H32 = this.E30 * this.E31 * this.I31 * this.H6 / 1000;
	
	/* Annual energy cost with retrofitted fixture */
	this.H33 = this.H12 * this.E31 * this.I31 * this.H6 / 1000;

	/* Annual energy savings with retrofitted fixture */
	this.H34 = this.H32 - this.H33;

	if (!this.validate_energy_savings_section(true)) {
		this.results_form_hidden("has_energy_savings_section", null);
		this.results_form_hidden("E29", null);
		this.results_form_hidden("E30", null);
		this.results_form_hidden("E31", null);
		this.results_form_hidden("I31", null);
		this.results_form_hidden("H32", null);
		this.results_form_hidden("H33", null);
		this.results_form_hidden("H34", null);
	}
	else {
		this.results_form_hidden("has_energy_savings_section", 1);
		this.results_form_hidden("E29", this.form.E29.value);
		this.results_form_hidden("E30", this.E30);
		this.results_form_hidden("E31", this.E31);
		this.results_form_hidden("I31", this.I31);
		this.results_form_hidden("H32", this.H32);
		this.results_form_hidden("H33", this.H33);
		this.results_form_hidden("H34", this.H34);
	}
};

Estimator.prototype.calculate_energy_costs = function () {
	var i;

	/* Energy Rate */
 	this.I31 = parseFloat(this.form.I31.value);
	this.H33 = 0;

	if (this.systems.length) {
		this.results_form_hidden("has_energy_costs_section", 1);
		for (i = 0; i < this.systems.length; ++i) {
			this.systems[i].cost = 
				(this.systems[i].number_of_fixtures *
				 this.systems[i].watts_per_fixture *
				 this.systems[i].hours_per_year *
				 this.I31 / 1000);
			this.results_form_hidden("H33_" + i,
						 this.systems[i].cost);
			this.H33 += this.systems[i].cost;
		}
		this.results_form_hidden("H33", this.H33);
	}
	else {
		this.results_form_hidden("has_energy_costs_section", null);
		this.results_form_hidden("H33", null);
	}
};

Estimator.prototype.get_building_types_list = function () {
	var result = [];
	for (var i in Estimator.MAX_WATTS_PER_SQUARE_FOOT) {
		if (Estimator.MAX_WATTS_PER_SQUARE_FOOT.hasOwnProperty(i)) {
			result.push(i);
		}
	}
	result.sort();
	return result;
};

Estimator.MAX_WATTS_PER_SQUARE_FOOT = {
	"Automotive Facility":                  1.5, // "new"
	"Convention Center":                    1.4,
	"Court House":                          1.4, // "new"
	"Dining:Bar Lounge/Leisure":            1.5,
	"Dining: Cafeteria/Fast Food":          1.8,
	"Dining: Family":                       1.9,
	"Dormitory":                            1.5, // "new"
	"Exercise Center":                      1.4, // "new"
	"Gymnasium":                            1.7,
	"Hospital/Healthcare":                  1.6, // "new"
	"Hotel":                                1.7,
	"Library":                              1.5,
	"Manufacturing Facility":               2.2, // "new"
	"Motel":                                2.0,
	"Movie Theatre":                        1.6,
	"Multi-Family":                         1.0,
	"Museum":                               1.6,
	"Office":                               1.3,
	"Parking Garage":                       0.3,
	"Penitentiary":                         1.2,
	"Performing Arts Theater":              1.5, // "new"
	"Police/Fire Station":                  1.3, // "new"
	"Post Office":                          1.6,
	"Religious Building":                   2.2,
	"Retail":                               1.9,
	"School/University":                    1.5,
	"Sports Arena":                         1.5,
	"Town Hall":                            1.4,
	"Transportation":                       1.2,
	"Warehouse":                            1.2,
	"Workshop":                             1.7
};

/******************************************************************************
utility
******************************************************************************/

/* Not locale-dependent; allows us to specify number of decimal places.
   This function works on strings, such as the result of number.toFixed(). */
String.prototype.withCommas = function () {
	var result = this;
	var sign, digits, decimal;
	if (/^([\-\+]?)(\d+)(\.\d*)?$/.test(result)) {
		sign    = RegExp.$1;
		digits  = RegExp.$2;
		decimal = RegExp.$3;
		while (/^(\d+)(\d\d\d(?:,.*)?)$/.test(digits)) {
			digits = RegExp.$1 + "," + RegExp.$2;
		}
		result = sign + digits + decimal;
	}
	return result;
};

function removeChildrenFrom (node) {
	while (node.hasChildNodes()) {
		node.removeChild(node.childNodes[0]);
	}
}

function radioGroupValue (group) {
	var i;
	if ("type" in group) {	// test for single radio button
		if (group.checked) {
			return group.value;
		}
	}
	else {
		for (i = 0; i < group.length; ++i) {
			if (group[i].checked) {
				return group[i].value;
			}
		}
	}
	return null;
}

function openEstimatorPopup (url, width, height) {
	openPopUp(url, width, height, "resizable=yes,scrollbars=yes");
}

/******************************************************************************
A really quick-and-dirty template engine.
******************************************************************************/
function Template (id) {
	var e = document.getElementById(id);
	if (!e) {
		return null;
	}
	this.html = e.innerHTML;
}

Template.prototype.run = function (o) {
	var html = this.html;
	var replace = function (string, prop) {
		return (prop in o ?
			((o[prop] === null) ? "" : o[prop]) :
			string);
	};
	html = html.replace(/\{([A-Za-z0-9_]+)\}/g, replace);
	return html;
};

/******************************************************************************
For those parts of the printable page that can't be handled by the template
engine above.
******************************************************************************/
function energyCostsResultBox (systems, total_cost) {
	var d, i;
	
	var html = "";
	html += '<div class="resultBox energyCostsResultBox">';
	html += '<div class="sub1 strokeBox">';
	html += '<h3 class="tight strokeB">Estimated Energy Costs</h3>';
	html += '<table id="energyCostsTable" ' + 
		'class="resultTable energyCostsTable" ' + 
		'border="0" cellpadding="0" cellspacing="0">';
	html += '<tbody>';
	for (i = 0; i < systems.length; ++i) {
		d = systems[i].description;
		if (d === null || d === undefined || d === "" || 
		    !/\S/.test(d)) {
			d = "System " + (i + 1);
		}
		html += '<tr>';
		html += '<td class="first">' + d + '</td>';
		html += '<td>$' + systems[i].cost.toFixed().withCommas() +
			'/yr.</td>';
		html += '</tr>';
	}
	html += '</tbody>';
	html += '<tbody class="foot">';
	html += '<tr>';
	html += '<td class="first">Total Energy Costs</td>';
	html += '<td>$' + total_cost.toFixed().withCommas() + '/yr.</td>';
	html += '</tr>';
	html += '</tbody>';
	html += '</table>';
	html += '</div>';
	html += '</div> <!-- / resultBox -->';
	return html;
}

/******************************************************************************
Query Strings!
	var q         = new Query();
			// or new Query("?a=b&c=d");
			// or new Query("a=b&c=d");
	var moo       = q.param.moo;
	var moo_array = q.param_array["moo_array"];
******************************************************************************/
function Query (query_string) {
	if (!query_string) {
		query_string = document.location.search;
	}
	if (!query_string) {
		return null;
	}
	query_string = query_string.replace(/^\?/, "");
	if (!query_string) {
		return null;
	}
	this.param = {};
	this.param_array = {};
	var pairs = query_string.split("&");
	var k, v;
	for (var i = 0; i < pairs.length; ++i) {
		if (/\=/.test(pairs[i])) {
			k = RegExp.leftContext;
			v = RegExp.rightContext;
			k = decodeURIComponent(k.replace(/\+/g, " "));
			v = decodeURIComponent(v.replace(/\+/g, " "));
			this.param[k] = v;
			if (!(k in this.param_array)) {
				this.param_array[k] = [];
			}
			this.param_array[k].push(v);
		}
	}
	this.query_string = query_string;
}

/******************************************************************************
Printable pages
******************************************************************************/

function writePrintableResults () {
	var buildingInformationTemplate    =
		new Template("buildingInformationTemplate");
	var toQualifyTemplate              =
		new Template("toQualifyTemplate");
	var taxResultWithDeductionTemplate =
		new Template("taxResultWithDeductionTemplate");
	var taxResultNoDeductionTemplate   =
		new Template("taxResultNoDeductionTemplate");
	var energySavingsTemplate          =
		new Template("energySavingsTemplate");
	var lightLevelsTemplate            =
		new Template("lightLevelsTemplate");

	var q = new Query();
	var t = {};

	var pass_string = function (k) {
		t[k] = q.param[k];
	};
	var pass_float = function (k) {
		t[k] = parseFloat(q.param[k]).toFixed().withCommas();
	};
	var pass_float_2 = function (k) {
		t[k] = parseFloat(q.param[k]).toFixed(2).withCommas();
	};
	var pass_percentage = function (k) {
		t[k] = (parseFloat(q.param[k]) * 100).toFixed(2).withCommas();
	};

	pass_float("C8");
	pass_float("E25");
	pass_float("E31");
	pass_float("H18");
	pass_float("H32");
	pass_float("H33");
	pass_float("H34");
	pass_float("H5");
	pass_float("H6");
	pass_float("I24");
	pass_float_2("C11");
	pass_float_2("E26");
	pass_float_2("E30");
	pass_float_2("H10");
	pass_float_2("H11");
	pass_float_2("H17");
	pass_float_2("H19");
	pass_float_2("H7");
	pass_float_2("H8");
	pass_float_2("I25");
	pass_float_2("I26");
	pass_float_2("I31");
	pass_percentage("H14");
	pass_string("D15");
	pass_string("E29");
	pass_string("G15");
	pass_string("H4");
	pass_string("I15");
	pass_string("E24");
	pass_float_2("H12");
	pass_float_2("H13");

	document.write(buildingInformationTemplate.run(t));
	document.write(toQualifyTemplate.run(t));
	if (q.param.has_tax_deduction_section) {
		if (q.param.qualifies_for_tax_deduction) {
			document.write(taxResultWithDeductionTemplate.run(t));
		}
		else {
			document.write(taxResultNoDeductionTemplate.run(t));
		}
	}
	if (q.param.has_energy_savings_section) {
		document.write(energySavingsTemplate.run(t));
	}
	if (q.param.has_light_levels_section) {
		document.write(lightLevelsTemplate.run(t));
	}
}

function writeMultipleSystemPrintableResults () {
	var i;

	var buildingInformationTemplate    =
		new Template("buildingInformationTemplate");
	var toQualifyTemplate              =
		new Template("toQualifyTemplate");
	var taxResultWithDeductionTemplate =
		new Template("taxResultWithDeductionTemplate");
	var taxResultNoDeductionTemplate   =
		new Template("taxResultNoDeductionTemplate");
	var lightingSystemTemplate         =
		new Template("lightingSystemTemplate");
	var lightingSystemClearTemplate    =
		new Template("lightingSystemClearTemplate");
	var energyCostsTemplate            =
		new Template("energyCostsTemplate");

	var q = new Query();
	var t = {};

	var pass_string = function (k) {
		t[k] = q.param[k];
	};
	var pass_float = function (k) {
		t[k] = parseFloat(q.param[k]).toFixed().withCommas();
	};
	var pass_float_2 = function (k) {
		t[k] = parseFloat(q.param[k]).toFixed(2).withCommas();
	};
	var pass_percentage = function (k) {
		t[k] = (parseFloat(q.param[k]) * 100).toFixed(2).withCommas();
	};

	pass_float("C8");
	pass_float("E25");
	pass_float("E31");
	pass_float("H18");
	pass_float("H32");
	pass_float("H34");
	pass_float("H5");
	pass_float("H6");
	pass_float("I24");
	pass_float_2("C11");
	pass_float_2("E26");
	pass_float_2("H10");
	pass_float_2("H11");
	pass_float_2("H17");
	pass_float_2("H19");
	pass_float_2("H7");
	pass_float_2("H8");
	pass_float_2("I25");
	pass_float_2("I26");
	pass_float_2("I31");
	pass_percentage("H14");
	pass_string("D15");
	pass_string("E29");
	pass_string("G15");
	pass_string("H4");
	pass_string("I15");

	document.write(buildingInformationTemplate.run(t));

	var number_of_systems = parseInt(q.param.number_of_systems, 10);
	for (i = 0; i < number_of_systems; ++i) {
		document.write(lightingSystemTemplate.run({
			i        : (i + 1),
			each_E24 : q.param["E24_" + i],
			each_H6  : parseFloat(q.param["H6_" + i]),
			each_H12 : parseFloat(q.param["H12_" + i]),
			each_E31 : parseFloat(q.param["E31_" + i])
		}));
		if (i % 2) {
			document.write("<div style='clear: both;'></div>");
		}
	}
	if (number_of_systems % 2) {
		document.write("<div style='clear: both;'></div>");
	}

	document.write(toQualifyTemplate.run(t));
	if (q.param.has_tax_deduction_section) {
		if (q.param.qualifies_for_tax_deduction) {
			document.write(taxResultWithDeductionTemplate.run(t));
		}
		else {
			document.write(taxResultNoDeductionTemplate.run(t));
		}
	}
	if (q.param.has_energy_costs_section) {
		var systems = [];
		for (i = 0; i < number_of_systems; ++i) {
			systems.push({
				description: q.param["E24_" + i],
				cost: parseFloat(q.param["H33_" + i])
			});
		}
		document.write(energyCostsResultBox(systems,
						    parseFloat(q.param.H33)));
	}
}

