(function($)
{	
	//============================================================================
	// The mortgage calculator class
	//============================================================================
	MortgageCalculator = function() { this.initialize.apply(this, arguments) };

	//============================================================================
	// Define the styles used by the calculator 
	//============================================================================
	MortgageCalculator.rules =
	{
		'.mc-container': 'cursor: default; font-family: Verdana, Arial, san-serif; text-align: left',
		'.mc-title': 'font-family: Arial, san-serif; font-weight: bold; font-size: 12pt',
		'.mc-body': 'padding: 10px; border: 1px solid; -moz-border-radius: 7px; -webkit-border-radius: 7px',
		'.mc-clear': 'clear: both',
		'.mc-logo': 'float: right; margin: 0 0 5px 0; border: 0',
		'.mc-graph': 'display: none; margin: 10px 0 0 0',
		'.mc-legend': 'display: inline-block; margin: 0 5px 0 0; line-height: 7pt; font-size: 7pt; width: 7pt; height: 7pt',
		'.mc-scroll': 'margin: 0; overflow-x: hidden; overflow-y: scroll;',
		'.mc-message': 'font-size: 8pt',
		'.mc-button': 'cursor: pointer; float: right; width: 100%; height: 30px; padding: 1px; border: 1px solid; -moz-border-radius: 4px; -webkit-border-radius: 4px; font-family: Arial, san-serif; font-size: 11pt; font-weight: bold;',
		'.mc-footer': 'margin: 2px 0 0 0; font-family: Arial, san-serif; font-size: 7.5pt; text-align: right',
		'.mc-navs': 
		{
			self: 'margin: 0; padding: 0; line-height: 25px; font-family: Arial, san-serif; font-size: 11pt; font-weight: bold; text-align: center',
			'.mc-nav': 'width: 25px; height: 25px; margin: 0; padding: 1px; border: 1px solid; -moz-border-radius: 3px; -webkit-border-radius: 3px; font-family: Arial, san-serif; font-size: 11pt; font-weight: bold',
			'.mc-pressed': 'padding: 2px 0 0 2px'
		},
		'.mc-small': 'height: 25px; font-size: 9pt',
		'.mc-pressed': 'padding: 2px 0 0 2px',
		'.mc-tables': 'position: relative; overflow: hidden; height: 92px !important; margin: 10px 0 0 0; text-align: left',
		'.mc-fields':
		{
			self: 'float: left; margin: 0 10px 0 0',
			'*': 'float: left',
			label: 'width: 110px; line-height: 20px; font-size: 9pt; font-weight: bold; text-align: left',
			input: 'margin: 0 0 5px 0; padding: 2px; width: 90px; border: 1px solid; font-family: Arial, san-serif; font-size: 10pt; text-align: right',
			select: 'margin: 0 0 5px 0; padding: 2px; width: 96px; border: 1px solid; font-family: Arial, san-serif; font-size: 10pt',
			div: 'padding: 0 0 0 5px; width: 25px; line-height: 20px; font-size: 8pt; text-align: left',
			br: 'clear: left'
		},
		'.mc-tabs':
		{
			self: 'position: absolute; margin: 10px 0 0 0; padding: 0; list-style-type: none; z-index: 100',
			'.mc-tab': 'cursor: pointer; float: left; margin: 0; padding: 0 10px 0 10px; line-height: 28px; border: 1px solid; -moz-border-radius: 5px; -webkit-border-radius: 5px; -moz-border-radius-bottomleft: 0; -moz-border-radius-bottomright: 0; -webkit-border-bottom-left-radius: 0; -webkit-border-bottom-right-radius: 0; font-family: Arial, san-serif; font-size: 11pt; font-weight: bold',
			'.mc-active': 'line-height: 28px; border-bottom: 0'
		},
		'.mc-panes': 
		{
			'.mc-pane': 'display: none; position: relative; top: 38px; margin: 0 0 38px 0; padding: 10px; overflow: hidden; border: 1px solid; -moz-border-radius: 5px; -webkit-border-radius: 5px; -moz-border-radius-topleft: 0; -webkit-border-top-left-radius: 0; font-size: 9pt',
			'p:first-child': 'margin: 0',
			p: 'margin: 1em 0 0 0',
			a: 'font-size: 7.5pt',
			ul: 'margin: 0 0 5px 20px; padding: 0',
			li: 'padding: 1em 0 0 0'
		},
		'.mc-errors':
		{
			self: 'margin: 0 0 5px 20px; padding: 0; list-style-type: square',
			li: 'padding: 1em 0 0 0'
		},
		'.mc-table':
		{ 
			self: 'table-layout: fixed; width: 100%; border-collapse: collapse',
			th: 'padding: 0 0 2px 0; font-size: 9pt; font-weight: bold; text-align: center',
			td: 'padding: 0; line-height: 2em; border: 1px solid; font-size: 9pt; text-align: center',
			sup: 'font-family: Verdana, Arial, san-serif; vertical-align: top'
		},
		'.mc-widget':
		{
			'.mc-title': 'font-size: 11pt; text-align: center',
			'.mc-body': 'padding: 5px; -moz-border-radius: 5px; -webkit-border-radius: 5px; text-align: center',
			'.mc-logo': 'float: none; text-align: center',
			'.mc-graph': 'margin: 0',
			'.mc-legend': 'margin: 0 4px 0 0; line-height: 5pt; font-size: 5pt; width: 5pt; height: 5pt',
			'.mc-button': 'float: none; clear: left; width: 100%; margin: 5px 0 0 0',
			'.mc-footer': 'text-align: left',
			'.mc-fields':
			{
				self: 'margin: 0',
				label: 'margin: 0 0 0 2px; width: 65px; line-height: 18px; font-family: Arial, san-serif; font-size: 9pt',
				input: 'margin: 0 0 4px 0; width: 66px; font-size: 8pt',
				select: 'margin: 0 0 4px 0; width: 72px; font-size: 8pt',
				div: 'padding: 0 0 0 4px; width: 18px; line-height: 18px; font-family: Arial, san-serif; font-size: 7pt'
			},
			'.mc-panes':
			{
				'.mc-pane': 'position: static; top: 0; margin: 10px 0 0 0; padding: 5px; -moz-border-radius: 5px; -webkit-border-radius: 5px'
			},
			'.mc-tab': 'line-height: 28px; margin: 0; padding: 0 5px 0 5px; font-family: Arial, san-serif; font-size: 11pt; font-weight: bold',
			'.mc-table':
			{
				self: 'position: absolute',
				th: 'padding: 0; font-family: Arial, san-serif; font-size: 9pt; text-align: left',
				td: 'padding: 0 2px 0 2px; line-height: 2em; font-family: Arial, san-serif; font-size: 8pt; text-align: right'
			}
		}
	};

	//============================================================================
	// Extend the calculator with functionality
	//============================================================================
	$.extend(MortgageCalculator.prototype,
	{
		//--------------------------------------------------------------------------
		// Helper functions
		//--------------------------------------------------------------------------
		F: function() { return false },
		Z: function(x, y, p) { x = this.N(x, p); return isNaN(x) ? y: x },
		N: function(x, p) { x = parseFloat(x instanceof $ ? x.val(): x); if (isNaN(x) || !p) return x; p = Math.pow(10, p); return Math.round(x * p) / p },
		$: function(x) { x = this.N(x, 2); var s = x < 0 ? '-': '', i = parseInt(x = Math.abs(x), 10) + '', j = (j = i.length) > 3 ? j % 3 : 0; return '$' + s + (j ? i.substr(0, j) + ',' : '') + i.substr(j).replace(/(\d{3})(?=\d)/g, '$1,') + '.' + Math.abs(x - i).toFixed(2).slice(2) },
		P: function(o) { var a = []; $.each(o, function(k, v) { a.push(k + '=' + v) }); return a.join('&') },
		B: function() { var a = $.makeArray(arguments), o = a.shift(), f = a.shift(); return function() { return f.apply(o, a.concat($.makeArray(arguments))) } },
		E: function(x, m, s) { var a = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-.', r = []; m = m || Math.max.apply({}, x); s = s || 4095; for(var i = 0; i < x.length; i++) { if (x[i] != null) { var v = Math.round(x[i] * s / m), j = Math.floor(v / a.length), k = v - a.length * j; r.push(a.charAt(j) + a.charAt(k)); } else r.push('__') } return r.join('') },
		C: function(x) { var c = this.colors.value; return $(x).map(function(i, e) { return c[e.constructor == Array ? e[0]: e].substr(1) }).get().join() },

		proper: function(v) { return v.substr(0, 1).toUpperCase() + v.substr(1).toLowerCase() },
		table: function(o) { return $('<table/>').addClass('mc-table').attr({ cellspacing: 0, cellpadding: 0 }).appendTo(o) },
		column: function(o, i, v) { $.each(o, function() { $(this).find('tr').find(':nth-child(' + i + ')')[v ? 'show': 'hide']() }) },
		row: function(o, i, v) { if (i) o.find('tr:nth-child(' + i + ')')[v ? 'show': 'hide'](); else return $('<tr/>').appendTo(o) },
		cell: function(o, t) { return $('<td/>').css(this.colors.css.table).html(t || '').appendTo(o) },
		head: function(o, t, l) { o = $('<th/>').appendTo(o); if (l) this.legend(o, l); return o.append($('<span/>').text(t)).append('<sup/>') },
		legend: function(o, l) { $('<span/>').addClass('mc-legend').css('background', this.colors.value[l.toLowerCase()]).html('&nbsp;').appendTo(o) },

		//--------------------------------------------------------------------------
		// Render the messages to the current pane
		//--------------------------------------------------------------------------
		message: function(m, p, t)
		{
			var $t = this, $v = $t.values, i = $v.input, v = $v.value, l;

			/* If necessary, add the PMI message */
			p.find('sup').html(i.pmi > 0 ? ' &dagger;': '');
			if (i.pmi > 0)
			{
				l = v.down >= 0.2 ? 'PMI is not included with a down payment of 20% or greater': 'PMI is cancelled after ' + $t.N(v.pmiTerm / 12, 1).toFixed(1) + ' years when 20% of the principal has been paid';
				if (m) m.append('&dagger; ' + l + '<br/>'); else p.attr('title', l)
			}

			/* If necessary, add the adjusted term message */
			t.find('sup').html(v.extra > 0 ? ' &Dagger;': '');
			if (v.extra > 0)
			{
				l = 'The loan is paid off in ' + $t.N(v.paymentTerm / 12, 1).toFixed(1) + ' years with the extra monthly payments';
				if (m) m.append('&Dagger; ' + l); else t.attr('title', l)
			}
		},

		//--------------------------------------------------------------------------
		// Activate the specified pane
		//--------------------------------------------------------------------------
		pane: function(i)
		{
			var $t = this, $o = $t.objects, p = $o.pane;

			if ($t.errors.length > 0) i = 'error';
			else if (!$t.processed) return;

			/* Render the specified pane */
			$t[i]();

			/* Activate the specified pane */
			if (p) p.stop().hide();
			p = $o.panes[i];
			if ($t.animate)
			{
				var o = $t.quirks ? ($t.widget ? 12: 22): 0, h = $o.pane ? ($o.pane.height() + o): 0;
				$o.pane = p.show()
					.data('height', p.data('height') || (p.height() + o))
					.height(h + o)
					.preAnim()
					.animate({ height: p.data('height') }, { duration: 750, easing: 'mc', complete: p.postAnim })
			}
			else $o.pane = p.show()
		},

		//--------------------------------------------------------------------------
		// Render the error pane
		//--------------------------------------------------------------------------
		error: function()
		{
			var $t = this, $e = $t.objects.errors; 

			/* If any exist, add the error messages to the pane */
			if ($e) 
			{
				$e.empty();
				$.each($t.errors, function() { $('<li></li>').html(this.replace(/'(.+)'/, '<b>$1</b>')).appendTo($e) }) 
			} 
		},

		//--------------------------------------------------------------------------
		// Render the payment pane
		//--------------------------------------------------------------------------
		payment: function(d)
		{
			/* Build the labels for the payment graphs */
			label = function(l, d, m) { return $(l).map(function(i, e) { return e[1] + ' (' + (d[i] * 100 / m).toFixed(0) + '%)' }).get().join('|') };

			var $t = this, $o = $t.objects, $p = $o.payment, $v = $t.values, $c = $t.colors, $f = $t.fields, $w = $t.widget, pg = $p.groups, ph = $p.headers;

			/* If indicated, show the next/previous payment table */
			if (d)
			{
				var i = $p.index || 0, j = $p.index = (i + d + pg.length) % pg.length, l = pg[j], t = $p[pg[i]].table.stop(true, true), t2 = $p[l].table.stop(true, true); 

				$p.title.text($t.proper(l) + (j == 2 ? '': 'ly'));
				if ($t.animate) 
				{ 
					var w = (t.width() + 10) * d; 
					t.animate({ left: w }, { duration: 750, easing: 'mc', complete: function() { t.hide().css('left', 0); } }); 
					t2.css({ left: -w }).show().animate({ left: 0 }, { duration: 750, easing: 'mc' }); 
				} 
				else { t.hide(); t2.show() }
			}

			/* Otherwise, render the payment pane */
			else
			{
				var v = $v.value, p = $v.payment, t = p.total, te = $f.taxes.enabled, ie = $f.insurance.enabled, pe = $f.pmi.enabled, ae = te || ie || pe, il = ie ? (pe ? 'Ins/PMI': 'Insurance'): 'PMI';
				if (p.processed) return;

				/* Render the payment table */
				$.each(pg, function(i, e) { $.each(ph, function(i, f) { $p[e][f].html($t.$(p[e][f])) }) });   				

				if ($w)
				{
					$.each(pg, function() 
					{
						var o = $p[this], t = o.table; 
						$t.message(null, o.head.insurance.find('span:last').text(il).end(), o.head[ae ? 'total': 'payment']); 
						$t.row(t, 2, te); 
						$t.row(t, 3, ie || pe); 
						$t.row(t, 4, ae) 
					});
					$p.tables.height(($p.total.table.find('tr:visible').size() * 23) + ($t.browser.opera ? 2: 0));
				}
				else
				{
					var o = $p.table;
					$t.message($p.message.empty(), $p.head.insurance.find('span:last').text(il).end(), $p.total.head); 
					$t.column(o, 3, te);
					$t.column(o, 4, ie || pe);
					$t.column(o, 5, ae)
				}

				/* If necessary, render the payment graph */
				var w = $t.width - ($w ? 24: 44), h = 90, o = { cht: 'p3', chf: 'bg,s,' + $c.pane.back.substr(1) }, l, d;	

				if (v.taxes + v.insurance + v.pmi > 0)
				{
					l = [['payment', 'Payment']];
					d = [t.payment];
					if (v.taxes > 0) { l.push(['taxes', 'Taxes']); d.push(t.taxes) }
					if (v.insurance > 0 || v.pmi > 0) { l.push(['insurance', il]); d.push(t.insurance) }

					$p.legend.show();
					$p.graph
						.width(w = $w ? w: $t.N(w/ 2, 0))
						.height(h)
						.attr('src', $t.google + $t.P($.extend({}, o, { chs: w + 'x' + h, chco: $t.C(l), chd: 'e:' + $t.E(d), chl: $w ? '': label(l, d, t.total) })))
						.show()
				}
				else { $p.graph.hide(); $p.legend.hide() }

				/* If necessary, render the principal/interest graph */
				if (!$w)
				{
					l = [['principal', 'Principal'], ['interest', 'Interest']];
					d = [t.principal, t.interest];
					if (v.extra > 0) { l.push(['extra', 'Extra']); d.push(t.extra) }
					
					$p.graph2
						.width(w)
						.height(h)
						.attr('src', $t.google + $t.P($.extend({}, o, { chs: w + 'x' + h, chco: $t.C(l), chd: 'e:' + $t.E(d), chl: label(l, d, t.payment) })))
						.show()
				}

				p.processed = true;
			}
		},

		//--------------------------------------------------------------------------
		// Render the amortization pane
		//--------------------------------------------------------------------------
		amort: function(w)
		{
			var $t = this, $o = $t.objects, $a = $o.amort, $v = $t.values, $c = $t.colors, $f = $t.fields, $w = $t.widget, i = $v.input, v = $v.value, p = $v.payment, pt = p.total, a = $v.amort, m = a.month, y = a.year, te = $f.taxes.enabled, ie = $f.insurance.enabled, pe = $f.pmi.enabled, ee = $f.extra.enabled, pl = ee ? 'Prin/Xtra': 'Principal', il = ie ? (pe ? 'Ins/PMI': 'Insurance'): 'PMI';

			/* If indicated, show the amortization popup */
			if (w)
			{
				var w = $t.popup, i = 0, j = Math.max(Math.ceil($f.count / 2), 3), r, b, d = $('<div/>'), t;

				/* If necessary, create the window */
				if (!w || w.closed)
				{
					var ie = $.browser.msie, s = MortgageCalculator.style, r = ie ? s.cssText: $(s.cssRules).map(function(i, e) { return this.cssText; }).get().join('');         
					w = $t.popup = window.open('', ie ? null: 'mc-popup', 'width=700,height=400,menubar=yes,location=no,status=no,scrollbars=yes');         
					w.document.write('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html><head><style type="text/css">body{margin:0;padding: 15px}' + r + '</style></head><body class="mc-container" style="width: auto"></body></html>');
					w.document.close();
				}
				else w.focus();

				b = $(w.document).find('body').empty();

				/* Build and render the fields table */
				t = $t.table(d)
					.css('table-layout', 'auto')
					.width('auto')
					.append('<col width="120"/><col width="140"/><col width="140"/><col width="140"/>');

				r = [];
				$.each($f, function(k, v)
				{
					if (!v.enabled) return;

					var l = v.label.split('|')[0], v = $v.input[k], x = i % j, o = r[x] || (r[x] = $t.row(t));
					$t.head(o, l).css('text-align', 'left');
					$t.cell(o, (k == 'term' ? v + ' years': (k == 'interest' ? v = v.toFixed(2) + '%': v = $t.$(v) + (k == 'taxes' || k == 'insurance' ? ' /yr': (k == 'pmi' || k == 'extra' ? ' /mo': '')))));
					i++ 
				});

				for(var i = $f.count; i < j * 2; i++) $t.head(r[i % j], '').attr('colSpan', 2);
				t.find('th:odd').css('padding', '0 0 0 20px');

				/* Build and render the amortization table */
				t = $t.table(d).css('margin', '15px 0 0 0'), r = $t.row(t);
				$.each(['Month', pl, 'Interest', 'Taxes', il, 'Balance'], function(i, e) { $t.head(r, e).width(i == 0 ? 50: 'auto'); });

				for(var k = 0; k < $v.value.paymentTerm; k++) 
				{
					$t.cell(r = $t.row(t), k + 1); 
					$.each([m.principal[k] + m.extra[k], m.interest[k], m.taxes[k], m.insurance[k], m.balance[k]], function() { $t.cell(r, $t.$(this)) })
				}

				$t.head(r = $t.row(t), 'Total'); 
				$t.head(r, $t.$(pt.principal + pt.extra)); 
				$t.head(r, $t.$(pt.interest)); 
				$t.head(r, $t.$(pt.taxes)); 
				$t.head(r, $t.$(pt.insurance));
				r.find('th').css('padding', '10px 0 0 0');

				t.find('td:last-child').css({ background: $c.value.total, borderLeftWidth: 2 }).end().find('tr:nth-child(12n+2):not(:first) td').css({ borderTopWidth: 2 });
				$t.column(t, 4, te); $t.column(t, 5, ie || pe);

				b.append(d.html());
				if (!$w) window.setTimeout(function() { w.print(); }, 100);
			}

			/* Otherwise, render the amortization pane */
			else if (!$w)
			{
				if (a.processed) return;

				var t = $a.table, h = $a.head, f = $a.foot;

				/* Render the amortization table */
				$.each(t.find('tr'), function(k) 
				{
					var r = $(this); 
					if (k < v.paymentTerm) r.show().children(':first')
						.next().text($t.$(m.principal[k] + m.extra[k]))
						.next().text($t.$(m.interest[k]))
						.next().text($t.$(m.taxes[k]))
						.next().text($t.$(m.insurance[k]))
						.next().text($t.$(m.balance[k])); 
					else r.hide();
				});

				$t.column([h, t, f], 4, te); $t.column([h, t, f], 5, ie || pe);
				f.find('th:first').next().text($t.$(pt.principal + pt.extra)).next().text($t.$(pt.interest)).next().text($t.$(pt.taxes)).next().text($t.$(pt.insurance));
				h.principal.find('span:last').text(pl);
				
				/* Render the message */
				$t.message($a.message.empty(), h.insurance.find('span:last').text(il).end(), h.total);

				/* Render the amortization graph */
				var w = $t.width - 44, h = 130, m = p.year.total, o, c = ['principal', 'interest'];

				if (v.extra > 0) c.unshift('extra');
				if (v.taxes > 0) c.push('taxes');
				if (v.pmi > 0 || v.insurance > 0) c.push('insurance');

				o =
				{
				  cht: 'bvs', 
					chm: 'D,' + $c.value.balance.substr(1) + ',' + c.length + ',0,2,0', 
					chbh: 'a,4',
					chs: w + 'x' + h, 
					chxt: 'x,y', 
					chxl: '0:|' + (function(r, k) { while(k++ <= $v.input.term) r.push(k); return r.join('|') })([], 0) + '|1:|$0|$' + p.month.total.toFixed(0), 
					chf: 'bg,s,' + $c.pane.back.substr(1), 
					chco: $t.C(c), 
					chd: 'e' + c.length + ':' + (v.extra > 0 ? $t.E(y.extra, m) + ',': '') + $t.E(y.principal, m) + ',' + $t.E(y.interest, m) + (v.taxes > 0 ? ',' + $t.E(y.taxes, m): '') + (v.pmi > 0 || v.insurance > 0 ? ',' + $t.E(y.insurance, m): '') + ',' + $t.E(y.balance)
				};
				$a.graph.width(w).height(h).attr('src', $t.google + $t.P(o)).show();

				a.processed = true
			}
		},

		//--------------------------------------------------------------------------
		// Validate the fields
		//--------------------------------------------------------------------------
		validate: function()
		{
			/* Validates the specified field using the specified values */
			V = function(o, p, x, y, r)
			{
				if (!$t.fields[o].enabled) return; 

				var o = $o[o], v = o.val(), l = o.data('label'), e;

				if (v.length > 0)
				{
					v = parseFloat(v);
					if (isNaN(v)) e = "The '" + l + "' field must be a number.";
					else if (v < x || v > y) e = "The '" + l + "' field must be " + (v < x ? 'greater': 'less') + ' than or equal to ' + (v < x ? x: y).toFixed(p) + '.'
				}
				else if (r) e = "The '" + l + "' field is required."; 

				o.css('background', $t.colors.input[e ? 'error': 'back']).attr('title', e); 
				if (e) $e.push(e)
			};

			/* Peform a validation on each field */
			var $t = this, $e = $t.errors, $o = $t.objects, $b = $t.bounds;

			$e.length = 0;
			V('principal', 2, $b.min.principal || 0, $b.max.principal || Number.MAX_VALUE, true);
			V('interest', 2, $b.min.interest || 0, $b.max.interest || Number.MAX_VALUE, true);
			V('term', 0, $b.min.term || 0, $b.max.term || Number.MAX_VALUE, true);
			var p = $t.Z($o.principal, Number.MAX_VALUE);
			V('down', 2, $b.min.down || 0, $b.max.down || p, false);
			V('taxes', 2, $b.min.taxes || 0, $b.max.taxes || p, false);
			V('insurance', 2, $b.min.insurance || 0, $b.max.insurance || p, false);
			V('pmi', 2, $b.min.pmi || 0, $b.max.pmi || p, false);
			V('extra', 2, $b.min.extra || 0, $b.max.extra || p, false);

			return $e.length == 0;
		},

		//--------------------------------------------------------------------------
		// Perform the calculations
		//--------------------------------------------------------------------------
		calculate: function()
		{
			/* Adds the specified value to the specified array */
			add = function(o, i, v) { o[i] = (o[i] || 0) + v };

			/* Compute the accumulated interest for the specified month */
			accum = function(k) { return Math.pow(1 + v.interest, k - v.term) };

			/* Compute the principal, interest, and extra paid for the specified month */
			paid = function(k)
			{
				var i, p, e = v.extra, a, b;

				/* Compute using the standard formula if no extra payments */
				if (e == 0) 
				{ 
					a = accum(k - 1); 
					i = v.payment * (1 - a); 
					p = v.payment * a 
				}
				
				/* Otherwise, compute manually */
				else
				{
					b = v.balance;
					i = $t.N(v.interest * b, 2);
					p = v.payment - i;
					if (b - p - e < 0)
					{
						if (b > p) e = b - p; else { p = b; e = 0 }
					}
					v.balance = b - p - e
				}

				return { interest: i, principal: p, extra: e }
			};

			/* Compute the remaining balance for the specified month */
			balance = function(k) 
			{
				/* Compute using the standard formula if no extra payments */
				if (v.extra == 0) return v.payment * (1 - accum(k)) /  v.interest; 

				/* Otherwise, return the manually computed balance */
				return v.balance 
			};

			var $t = this, $o = $t.objects, $v = $t.values, $f = $t.fields, i = $v.input = {}, v = $v.value = {}, p = $v.payment = { month: {}, year: {} }, t = p.total = { principal: 0, extra: 0, interest: 0 }, a = $v.amort = {}, m = a.month = { principal: [], extra: [], interest: [], taxes: [], insurance: [], balance: [] }, y = a.year = { principal: [], extra: [], interest: [], taxes: [], insurance: [], balance: [] };

			/* Validate the fields and, if errors exist, go to the errors pane */
			if (!$t.validate()) return $t.pane('error');

			/* Save a copy of all of the input values */
			$.each($f, function(k, v) { i[k] = $t.Z($o[k], 0) });

			/* Compute and save the values that will be used to perform the rest of the calculations */
			v.principal = i.principal;
			v.interest = i.interest / 1200.0;
			v.term = i.term * 12;		
			v.down = i.down / i.principal;
			v.taxes = $t.N(i.taxes / 12, 2);
			v.insurance = $t.N(i.insurance / 12, 2);
			v.pmi = v.down >= 0.2 ? 0 : i.pmi;
			v.pmiTerm = v.term;
			v.extra = i.extra;
			v.balance = i.principal - i.down;
			v.payment = $t.N(v.balance * v.interest / (1 - accum(0)), 2);
			v.paymentTerm = 0;

			/* Compute and save the values for the amortization */
			for(var k = 1; k <= v.term; k++)
			{
				var d = paid(k), b = balance(k), j = Math.floor((k - 1) / 12), x = v.taxes, x2 = v.insurance + (k > v.pmiTerm ? 0: v.pmi);

				/* Fix any roundoff errors on the last payment */
				if (b == 0 && d.principal > 0) d.principal += $t.N(v.principal - t.principal - d.principal - t.extra - d.extra, 2);

				/* Add the values to the monthly totals */
				m.principal.push(d.principal);
				m.extra.push(d.extra);
				m.interest.push(d.interest);
				m.taxes.push(x);
				m.insurance.push(x2);
				m.balance.push(b);

				/* Add the values to the yearly totals */
				add(y.principal, j, d.principal);
				add(y.extra, j, d.extra);
				add(y.interest, j, d.interest);
				add(y.taxes, j, x);
				add(y.insurance, j, x2);
				y.balance[j] = v.paymentTerm ? null: b;

				/* Add the values to the totals for the entire term */
				t.principal += d.principal;
				t.extra += d.extra;
				t.interest += d.interest;

				if (k < v.pmiTerm && b < v.principal * 0.8) v.pmiTerm = k;
				if (!v.paymentTerm && b == 0) v.paymentTerm = k;
			}
			y.balance[Math.ceil(v.paymentTerm / 12) - 1] = 0;

			/* Compute and save the values for the monthly payment */
			m = p.month;
			m.payment = v.payment + v.extra;
			m.taxes = v.taxes;
			m.insurance = v.insurance + v.pmi;
			m.total = $t.N(m.payment + m.taxes + m.insurance, 2);

			/* Compute and save the values for the yearly payment */
			y = p.year;
			y.payment = $t.N(m.payment * 12, 2);
			y.taxes = $t.N(m.taxes * 12, 2);
			y.insurance = v.pmiTerm < 12 ?  $t.N((v.insurance * 12) + (v.pmi * v.pmiTerm), 2): $t.N(m.insurance * 12, 2);
			y.total = $t.N(y.payment + y.taxes + y.insurance, 2);

			/* Compute and save the values for the payments for the entire term */
			t.payment = t.principal + t.extra + t.interest;
			t.taxes = $t.N(m.taxes * v.term, 2);
			t.insurance = $t.N((v.insurance * v.term) + (v.pmi * v.pmiTerm), 2);
			t.total = $t.N(t.payment + t.taxes + t.insurance, 2);

			/* Activate the appropriate pane to show the results */
			$t.processed = true;
			$v.payment.processed = $v.amort.processed = null;
			$t.pane($t.widget ? 'payment' : $o.tab.data('id'));
		},

		//--------------------------------------------------------------------------
		// Build the calculator
		//--------------------------------------------------------------------------
		build: function()
		{
			/* Builds the specified field and adds it to the specified object */
			field = function(o, t, n, l, d, s)
			{
				l = l.split('|'); 
				$('<label/>')
					.attr('title', d)
					.text(l[$w ? l.length - 1: 0])
					.click(function() { $o[n].focus() })
					.appendTo(o);

				$o[n] = $('<' + t + '/>')
					.css($c.css.field)
					.data('id', n)
					.data('label', l[0])
					.focus(function() { $(this).select() })
					.blur(function(e) { var t = e.target; if (t.type == 'text') { var o = $(t), v = parseFloat(o.val()); if (isNaN((o.val()))) { v = o.val(); v = v.replace(",",""); v = parseFloat(v);  o.val(v.toFixed(n == 'term' ? 0: 2)) } else if(!isNaN(v)) { o.val(v.toFixed(n == 'term' ? 0: 2)) } } })
					.keypress(function(e) { if (e.which == '13') { e = $(e.target); e.trigger(e.data('id') == 'principal' ? 'change': 'blur'); $t.calculate() } })
					.appendTo(o);

				if (t == 'select') for(var k = $b.min[n], t = $o[n].append($('<option/>').val('')), d = $b.step[n], p = (p = d.toString().split('.'))[1] ? p[1].length: 0; k <= $b.max[n]; k += d) $('<option/>').val(k).text($t.N(k).toFixed(p)).appendTo(t); 

				$('<div/>').text(s || '').appendTo(o); $('<br/>').appendTo(o)
			};

			/* Builds a button and adds it to the specified object */
			button = function(o, l) 
			{
				var b = $t.browser, m = b.msie || b.opera; 
				return $('<button/>')
					.addClass('mc-button')
					.css($c.css.button)
					.text(l)
					.mousedown(m ? $t.F: function(e) { $(e.target).addClass('mc-pressed') })
					.mouseup(m ? $t.F: function(e) { $(e.target).removeClass('mc-pressed') })
					.appendTo(o)
			};

			/* Builds a nav button and adds it to the specified object */
			nav = function(o, l)
			{
				return button(o, l)
					.removeClass('mc-button')
					.addClass('mc-nav')
					.css($c.css.nav)
					.css('float', l == '<' ? 'left': 'right')
					.click($t.B($t, $t.payment, l == '<' ? -1: 1))
			};

			/* Builds a tab and adds it to the specified object */
			tab = function(o, n, l, a)
			{
				var t = $o.tabs[n] = $('<li/>')
					.addClass('mc-tab')
					.css($c.css.tab(a))
					.data('id', n)
					.text(l)
					.click(function(e) 
					{
						$o.tab.css($c.css.tab()).removeClass('mc-active'); 
						var i = ($o.tab = $(e.target).css($c.css.tab(true)).addClass('mc-active')).data('id'), r = (i == 'payment' ? 0 : 5);
						$.each($o.panes, function() { this.css({ '-moz-border-radius-topleft': r, '-webkit-border-top-left-radius': r }) });
						$t.pane(i)
					})
					.mousedown($t.F).bind('selectstart', $t.F)
					.appendTo(o);

				if (a) $o.tab = t.addClass('mc-active') 
			};

			/* Builds a pane and adds it to the specified object */
			pane = function(o, n, a, s, h, ba, aa)
			{
				var p = $o.panes[n] = $('<div/>')
					.addClass('mc-pane')
					.css($c.css.pane)
					.data('id', n)
					.appendTo(o)
					, fs = p.show, fh = p.hide; 

				p.show = $t.B(p, function() { fs.apply(this); this.data('height', null).height('auto'); if (s) s.apply(this); return this }); 
				p.hide = $t.B(p, function() { fh.apply(this); if (h) h.apply(this); return this }); 
				p.preAnim = $t.B(p, function() { if (ba) ba.apply(this); return this });
				p.postAnim = $t.B(p, function() { if (aa) aa.apply(this); return this }); 

				if (a) $o.pane = p.show(); 
				return p
			};

			/* Builds a graph and adds it to the specified object */
			graph = function(o, t) { return $('<img/>').addClass('mc-graph').attr('title', t).appendTo(o) };

			/* Builds a clear div and adds it to the specified object */
			clear = function(o) { $('<div/>').addClass('mc-clear').appendTo(o) };

			var $t = this, $o = $t.objects, $p = $o.payment = { head: {} }, $a = $o.amort = {}, $l = $t.logo, $c = $t.colors, $f = $t.fields, $d = $t.defaults, $b = $t.bounds, $w = $t.widget;

			var o = $o.obj.addClass('mc-container' + ($w ? ' mc-widget': '')).width($t.width), b = $t.browser, pg = $p.groups = ['month', 'year', 'total'], ph = $p.headers = ['payment', 'taxes', 'insurance', 'total'], ah = $a.headers = ['month', 'principal', 'interest', 'taxes', 'insurance', 'balance']

			/* If necessary, build the title */
			if ($t.title) $('<div/>').addClass('mc-title').css($c.css.title).html($t.title).appendTo(o);

			o = $('<div/>').addClass('mc-body').css($c.css.body).append('<div/>').appendTo(o);

			/* If necessary, build the logo */
			if ($l.path) { var l = $('<img/>').addClass('mc-logo').attr('src', $l.path).appendTo(o); if ($l.url) l.wrap($('<a/>').attr({ href: $l.url, target: $l.target || '_blank' })) }

			/* Build the fields */
			var f, i = 0, j = Math.max(Math.ceil($f.count / 2), 3);
			$.each($f, function(k, v) { if (v.enabled) { if (!(i++ % j)) f = $('<div/>').addClass('mc-fields').appendTo(o); field(f, v.type, k, v.label, v.desc, v.suffix) } });
			button(o, 'Calculate').css('width', 160).click($t.B($t, $t.calculate));

			/* If necessary, build the tabs */
			if (!$w)		
			{
				clear(o);
				var t = $('<ul/>').addClass('mc-tabs').appendTo(o);
				tab(t, 'payment', 'Payments', true);
				tab(t, 'amort', 'Amortization Schedule');
			}

			/* If widget mode, build the panes for the widget calculator */			
			o = $('<div/>').addClass('mc-panes').appendTo(o);
			if ($w) 			
			{
				/* Build the payment pane */
				var p = pane(o, 'payment'), n = $('<div/>').addClass('mc-navs').css('color', $c.tab.text).appendTo(p), s; 
				nav(n, '<'); nav(n, '>'); $p.title = $('<span/>').text('Monthly').appendTo(n);
				
				s = $p.tables = $('<div/>').addClass('mc-tables').appendTo(p); 
				$.each(pg, function(i, e) 
				{
					var o = $p[e] = { head: {} }, t = o.table = $t.table(s).css('display', i == 0 ? 'table': 'none'); 
					$.each(ph, function(i, e)
					{
						var r = $t.row(t);
						o.head[e] = $t.head(r, $t.proper(e), i == 3 ? null: e);
						o[e] = $t.cell(r).css({ background: i == 3 ? $c.value.total: $c.table.back, borderTopWidth: i == 3 ? 2: 1 })
					})
				});
				
				$p.legend = p.find('.mc-legend');
				$p.graph = graph(p, 'Distribution of payments over the term of the loan');    
				$('<a/>').css($c.css.link).attr('href', '#').html('Amortization schedule').click($t.B($t, $t.amort, true)).appendTo(p).wrap($('<div/>').css('margin', '10px 0 0 0'));
			}

			/* Otherwise, build the panes for the normal calculator */
			else
			{
				/* Build the introduction pane */
				pane(o, 'intro', true).html($t.intro);

				/* Build the errors pane */
				pane(o, 'error').html('<p><b>Oops, there seems to some problems with the values you have entered:</b></p>').append($o.errors = $('<ul/>').addClass('mc-errors'));

				/* Build the payment pane */
				var p = pane(o, 'payment'), t = $p.table = $t.table(p), r = $t.row(t), s;
				
				$.each([''].concat(ph), function(i, e) { $p.head[e] = $t.head(r, $t.proper(e), i > 0 && i < 4 ? e: null).width(i == 0 ? 70: 'auto') });
				$.each(pg, function(i, e)
				{
					var o = $p[e] = {}, r = $t.row(t);
					o.head = $t.head(r, $t.proper(e) + (i < 2 ? 'ly': '')).css({ textAlign: 'left', padding: '0' });
					$.each(ph, function(i) { o[this] = $t.cell(r).css({ background: e == 'total' || i == 3 ? $c.value.total: $c.table.back, borderLeftWidth: i == 3 ? 2: 1, borderTopWidth: e == 'total' ? 2: 1 }) })
				});
				
				$p.legend = p.find('.mc-legend');
				$p.message = $('<div/>').addClass('mc-message').css('padding', '4px 0 0 70px').appendTo(p);
				$p.graph = graph(p, 'Distribution of payments over the term of the loan');
				$p.graph2 = graph(p, 'Distribution of payments to principal and interest over the term of the loan');

				/* Build the amortization pane */
				p = pane(o,
					'amort',
					false, 
					function() 
					{
						if (!this.shown)
						{
							var b = $t.browser, t = $a.table, w = t.width();
							$a.head.width(w);
							$a.foot.width(w);
							if (b.mozilla) t.width(w - 1);
							$a.scroll.height(b.msie ? t.find('tr:nth-child(12)').position().top: (t.find('tr:first').height() * 12) + 1);
							this.shown = true
						} 
					}, 
					null,
					function() { if ($t.animate && $t.browser.mozilla) { this.height($a.scroll.position().top + $a.scroll.height()); $a.table.hide() } },
					function() { if ($t.animate && $t.browser.mozilla) $a.table.show() });

				t = $a.head = $t.table(p); r = $t.row(t); $.each(ah, function(i, e) { $a.head[e] = $t.head(r, $t.proper(e), i < 1 ? null: e).width(i == 0 ? 50: 'auto') });
				t = $a.table = $t.table($a.scroll = $('<div/>').addClass('mc-scroll').height(100).appendTo(p)); for(var k = 1; k <= $b.max.term * 12; k++) t.append('<tr><td>' + k + '</td><td/><td/><td/><td/><td/></tr>'); t.find('td').css($c.css.table).eq(0).width(50); t.find('td:last-child').css({ background: $c.value.total, borderLeftWidth: 2 }).end().find('tr:nth-child(12n+1):not(:first) td').css({ borderTopWidth: 2 }); 		
				t = $a.foot = $t.table(p); r = $t.row(t); $a.head.total = $t.head(r, 'Total').width(50); r.append('<th/><th/><th/><th/>'); button($t.head(r).css('padding', '5px 0 0 0'), 'Print').addClass('mc-small').click($t.B($t, $t.amort, true));
				$a.legend = p.find('.mc-legend');
				$a.message = $('<div/>').addClass('mc-message').appendTo(p);
				$a.graph = $('<img/>').addClass('mc-graph').attr('title', 'Distribution of payments over the course of the loan').appendTo(p);	
			}

			/* If necessary, build the footer */
			if ($t.footer) $('<div/>').addClass('mc-footer').css($c.css.footer).html($t.footer).appendTo($o.obj);

			/* Perform some additional tweaks for various browsers */
			o = $o.obj;
			if (b.msie) 
			{
				o.find('p:first').css('margin', '0');
				o.find('.mc-nav').css('line-height', '20px');
				if ($t.quirks) o.find('input').width(o.find('select').width() + 6)
			}
			else if (b.opera)
			{
				o.find('.mc-button').css('line-height', '26px');
				o.find('.mc-nav').css('line-height', '22px')
			}
			else if (b.mozilla)
			{
				o.find('table').css('margin', '1px 0 0 ' + ($w ? '0': '1px'))
			}

			/* Set the defaults for the fields */
			$o.principal
				.change(function(e)
				{
					var o = $(e.target).trigger('blur'), p = parseFloat(o.val());
					if (!isNaN(p)) 
					{
						if ($f.taxes.enabled && $d.taxes) $o.taxes.val((p * $d.taxes / 100.0).toFixed(2));
						if ($f.insurance.enabled && $d.insurance) $o.insurance.val((p * $d.insurance / 100.0).toFixed(2));
						if ($f.down.enabled && $d.down) $o.down.val((p * $d.down / 100.0).toFixed(2))
					}
				})
				.val($t.Z($d.principal, ''))
				.trigger('change');

			$o.interest.val($t.Z($d.interest, '')).trigger('blur');
			$o.term.val($t.Z($d.term, '')).trigger('blur');
			if ($f.pmi.enabled) $o.pmi.val($t.Z($d.pmi, '')).trigger('blur');
			if ($f.extra.enabled) $o.extra.val($t.Z($d.extra, '')).trigger('blur')
		},

		//--------------------------------------------------------------------------
		// Initialize the calculator
		//--------------------------------------------------------------------------
		initialize: function(obj, settings)
		{
			var $t = this, s = settings, $c = s.colors, $f = s.fields, $d = s.defaults, $b = s.bounds, a = navigator.userAgent.toLowerCase();

			/* Scrub the numbers in the settings */
			$.each([$d, $b.min, $b.max, $b.step], function()
			{
				var t = this;
				$.each(t, function(k, v) { t[k] = $t.N(v, k == 'term' ? 0: 2) })
			});

			/* Scrub the colors in the settings */
			$.each($c, function()
			{
				var t = this;
				$.each(t, function(k, v)
				{
					v = v ? v.toLowerCase().replace('transparent', '').replace(/#?(.+)/, '#$1'): '';
					if (v.length == 4) v = v.replace(/([0-9a-f])/g, '$1$1');
					t[k] = v
				})
			});

			/* Add some CSS objects to help with setting styles */
			$.extend($c.css = {},
			{
				title: { color: $c.title.text, background: $c.title.back },
				body: { color: $c.main.text, background: $c.main.back, borderColor: $c.main.border },
				field: { color: $c.input.text, background: $c.input.back, borderColor: $c.input.border },
				button: { color: $c.button.text, background: $c.button.back, borderColor: $c.button.border },
				nav: { color: $c.nav.text, background: $c.nav.back, borderColor: $c.nav.border },
				tab: function(a) { return { color: $c[a ? 'tab': 'main'].text, background: a ? $c.tab.back: 'transparent', borderColor: a ? $c.tab.border: 'transparent' } },
				pane: { color: $c.pane.text, background: $c.pane.back, borderColor: $c.pane.border },
				table: { color: $c.table.text, background: $c.table.back, borderColor: $c.table.border },
				footer: { color: $c.footer.text, background: $c.footer.back },
				link: { color: $c.link.text }
			});

			/* Scrub the fields in the settings */
			$f.principal.enabled = $f.interest.enabled = $f.term.enabled = true;
			$.each($f, function(k, v)
			{
				if (v.type == 'select' && isNaN($b.min[k] + $b.max[k] + $b.step[k])) v.type = 'input';
				$f.count = ($f.count || 0) + (v.enabled ? 1: 0)
			});

			/* Add the settings to calculator */
			$.each(s, function(k, v) { $t[k] = v });
			
			/* Add additional settings to the calculator */
			$t.widget = ($t.mode == 'widget');
			$t.width = $t.Z(s.width, $t.widget ? 175: 680);
			$t.quirks = (document.compatMode == 'BackCompat');
			$t.browser = $.extend({}, $.browser, { chrome: /chrome/.test(a), safari: /webkit/.test(a) && !/chrome/.test(a) });
			$t.objects = { obj: $(obj), tabs: {}, panes: {}, nav: {} };
			$t.values = {};
			$t.errors = [];
			$t.google = 'http://chart.apis.google.com/chart?';

			/* Build the calculator */
			$t.build()
		}
	});

	//============================================================================
	// Adds the calculator to jQuery
	//============================================================================
	$.fn.MortgageCalculator = function(settings)
	{
		var n = null, c = MortgageCalculator, e = $.easing, d =
		{
			/* The general settings used by the calculator */
			mode: 'normal',
			animate: true,
			width: n,
			title: n,
			intro: "<p>With this calculator you can put in your values and estimate the amount of your loan and get an estimate on the amount of your monthly payments.</p><p>Click on the 'Calculate' button when you're ready.</p>",
			footer: n,

			/* The logo used by the calculator */
			logo: { path: n, url: n, target: n },

			/* The colors used by the calculator */
			colors:
			{
				main: { text: '#000', back: '#eef2fd', border: '#39c' },
				title: { text: '#39c', back: n },
				tab: { text: '#39c', back: '#fff', border: '#39c' },
				pane: { text: '#000', back: '#fff', border: '#39c' },
				input: { text: '#000', back: '#fff', border: '#aaa', error: '#fcc' },
				button: { text: '#fff', back: '#46a026', border: '#1f731a' },
				nav: { text: '#fff', back: '#39c', border: '#39c' },
				table: { text: '#000', back: '#fff', border: '#aaa' },
				footer: { text: '#999', back: n },
				link: { text: '#39c' },
				value: { payment: '#6a9f35', taxes: '#0584af', insurance: '#ff9300', total: '#eee', principal: '#6a9f35', extra: '#90d948', interest: '#f00', balance: '#333' }
			},

			/* The fields used by the calculator */
			fields: 
			{
				principal: { enabled: true, type: 'input', label: 'Principal', desc: 'The total value of the loan (in dollars)' },
				interest: { enabled: true, type: 'input', label: 'Interest Rate|Interest', desc: 'The annual interest rate of the loan', suffix: '%' },
				term: { enabled: true, type: 'select', label: 'Term', desc: 'The term of the loan (in years)', suffix: 'yrs' },
				down: { enabled: true, type: 'input', label: 'Down Payment|Down', desc: 'The down payment on the loan (in dollars)' },
				taxes: { enabled: true, type: 'input', label: 'Property Taxes|Taxes', desc: 'The annual property taxes (in dollars)', suffix: '/yr' },
				insurance: { enabled: true, type: 'input', label: 'Insurance', desc: 'The annual homeowner\'s insurance (in dollars)', suffix: '/yr' },
				pmi: { enabled: true, type: 'input', label: 'PMI', desc: 'The monthly private mortgage insurance (in dollars)', suffix: '/mo' },
				extra: { enabled: true, type: 'input', label: 'Extra Payment|Extra', desc: 'Extra to pay to the principal per month (in dollars)', suffix: '/mo' }
			},

			/* The default values for the fields */
			defaults: { principal: n, interest: n, term: n, down: n, taxes: 1.5, insurance: 0.5, pmi: n, extra: n },

			/* The range of valid values for the fields */
			bounds:
			{
				min: { principal: 0, interest: 0, term: 15, down: n, taxes: n, insurance: n, pmi: n, extra: n },
				max: { principal: 15000000, interest: 15, term: 40, down: n, taxes: n, insurance: n, pmi: n, extra: n },
				step: { principal: n, interest: n, term: 5, down: n, taxes: n, insurance: n, pmi: n, extra: n }
			}
		};

		/* If necessary, build the stylesheet for the calculator */
		if (!c.style) (function(s, r, n, i) 
		{
			var f = arguments.callee, i = i || 0;

			$.each(r, function(k, v) 
			{
				k = (n ? n + ' ': '') + (k == 'self' ? '': k);
				if (typeof v == 'object') i = f(s, v, k, i);
				else if ($.browser.msie) s.addRule(k, v, i++);
				else s.insertRule(k + ' {' + v + '}', i++)
			});

			return i
		})(c.style = $('<style/>').attr('type', 'text/css').appendTo('head').get(0)[$.browser.msie ? 'styleSheet': 'sheet'], c.rules);

		/* If necessary, add the easing used by the calculator to jQuery */
		if (!e.mc) $.extend(e, { mc: function (x, t, b, c, d) { return c * Math.sqrt(1 - (t = t / d - 1) * t) + b } });

		/* Create an instance of the calculator */
		settings = $.extend(true, {}, d, settings);
		this.each(function() { new MortgageCalculator(this, settings) });
		return this
	};

})(jQuery);
