/**
 * @fileOverview HomeValuations - Support for HomeValuations.cfm
 * 
 * @requires GoogleMap.js
 * @requires jquery.js
 * @requires jquery.color.js
 * 
 * @author Larry Reinhard
 */
var HomeValuation = function() {

	var MapManager = new GoogleMap();

	/**
	 * Formats the given number with commas separating groups of three digits
	 * 
	 * @param {Number}
	 *            value The number to be formatted
	 * @return {String} The formatted number
	 */
	function fmtNumberCommas(value) {
		value += '';
		var rgx = /(\d+)(\d{3})/;
		while (rgx.test(value)) {
			value = value.replace(rgx, '$1' + ',' + '$2');
		}
		return value;
	}

	/**
	 * Requests HTML content from the server. Expects to receive HTML fragments
	 * that are rows (TR) for the Home Valuation table.
	 * 
	 * @param {String} cls
	 *            Row class for selector that will choose rows to be
	 *            replaced by the HTML received from the server
	 * @param {String} action
	 *            Name of method to call on server
	 * @param {String} address
	 *            The street address of the property being evaluated
	 * @param {String} city
	 *            The city of the property being evaluated
	 * @param {String} state
	 *            The state of the property being evaluated
	 * @param {String} zip
	 *            The zip code of the property being evaluated
	 * @param {String} doavg
	 *            The flag that enables valuation average calculation
	 * @private
	 */
	function getValuationHTML(cls, action, address, city, state, zip, doavg) {
		$.ajax( {
			type : 'POST',
			url : "HomeValuations.cfm",
			data : {
				action : action,
				address : address,
				city : city,
				state : state,
				zip : zip,
				average: doavg
			},
			dataType : "html",
			success : function(html, textStatus) {
				$('tr.' + cls).eq(0).before(html).end().remove();
				setupCheckBoxClick();
				computeAverageValuation();
			}
		});
	}

	/**
	 * Requests HTML content and JavaScript Map data for the Nearby Sales
	 * section. Since the return data is heterogeneous, it is expected in a JSON
	 * package.
	 * 
	 * @param {String} cls
	 *            Row class for selector that will choose rows to be
	 *            replaced by the HTML received from the server
	 * @param {String} action
	 *            Name of method to call on server
	 * @param {String} address
	 *            The street address of the property being evaluated
	 * @param {String} city
	 *            The city of the property being evaluated
	 * @param {String} state
	 *            The state of the property being evaluated
	 * @param {String} zip
	 *            The zip code of the property being evaluated
     * @param {String} doavg
     *            The flag that enables valuation average calculation
	 * @private
	 */
	function getSoldsHTMLandMapData(cls, action, address, city, state, zip,
			maxsolds, doavg) {
		$.ajax( {
			type : 'POST',
			url : "HomeValuations.cfm",
			data : {
				action : action,
				address : address,
				city : city,
				state : state,
				zip : zip,
				maxsolds : maxsolds,
				average: doavg
			},
			dataType : "json",
			success : function(data, textStatus) {
				$('tr.' + cls).eq(0).before(data.html).end().remove();
				setupCheckBoxClick();
				computeAverageValuation();
				if (data.opt.center) {
					MapManager.initMap(data.el, data.opt);
					MapManager.zoomToMarkers();
				}
			}
		});
	}

	/**
	 * Creates an object suitable for passing as an argument to the initMap
	 * method of the Google Map Manager.
	 * 
	 * @param {Object}
	 *            pm A Google Maps placemark object
	 * @param {String}
	 *            frmId The ID of a form element that will be used to create the
	 *            appropriate form submission command in the markers' info
	 *            window HTML.
	 * @return {Object} The map options
	 * @private
	 */
	function buildMapOptsFromPlacemarks(pm, frmId) {
		var mapOpts = {};
		var place = pm[0];
		mapOpts.center = {};
		mapOpts.center.latitude = place.Point.coordinates[1];
		mapOpts.center.longitude = place.Point.coordinates[0];
		mapOpts.markers = [];
		for (var i = 0;i < pm.length; i++) {
			var mrkr = {};
			place = pm[i];
			var pAddr = MapManager.parseStdAddress(place.address);
			mrkr.id = i;
			mrkr.latitude = place.Point.coordinates[1];
			mrkr.longitude = place.Point.coordinates[0];
			mrkr.html = '<div class="infoWindow"><p class="addr">'
					+ (pAddr.street || '&nbsp;')
					+ '</p>'
					+ '<p>'
					+ (pAddr.city || '&nbsp;')
					+ ', '
					+ (pAddr.state || '&nbsp;')
					+ ' '
					+ (pAddr.zip || '&nbsp;')
					+ '</p>'
					+ ((place.AddressDetails.Accuracy == 8)
							? ''
							: ('<p class="note">This is a '
									+ MapManager
											.decodeGeoAccuracy(place.AddressDetails.Accuracy) + ' address.</p>'))
					+ '<img class="clickable" src="/Images/ClipArt/VALU_SubmitOn.gif" onclick="HomeValuation.submitSearchForm(\''
					+ place.address + '\');" /></div>';
			mrkr.title = place.address;
			mapOpts.markers.push(mrkr);
		}
		return mapOpts;
	}

	/**
	 * Parses a Google-formatted address and puts the appropriate parts in the
	 * form fields.
	 * 
	 * @param {Object}
	 *            frm The form element
	 * @param {String}
	 *            addr The address, as formatted from Google's geocoder service:
	 *            123 Main St, Your Town, ST 00000, USA
	 */
	function updateFormAddress(frm, addr) {

		var parsed = MapManager.parseStdAddress(addr);

		if (parsed) {
			frm.Address.value = parsed.street;
			frm.City.value = parsed.city.length ? parsed.city : frm.City.value;
			$(frm.State).val(parsed.state);
			frm.ZIP.value = parsed.zip;
		}

	}

	/**
	 * Computes the average value of all of the checked boxes that are
	 * associated with valuation estimates and then deposits the value in
	 * display DIV element.
	 */
	function computeAverageValuation() {
		var eligible = $('input.valuationChkbx:checked');
		var avg = 0;
		var numchecked = eligible.length;
		if (numchecked) {
			eligible.each(function(i) {
				avg += parseInt(this.value);
			});
			avg = avg / numchecked;
		} else {
			avg = 0;
		}
		$('#averageValuation').text('$' + fmtNumberCommas(Math.round(avg)));
	}

	/**
	 * Adds a click event handler to each valuation checkbox
	 */
	function setupCheckBoxClick() {
		$('input.valuationChkbx').unbind().click(handleCheckBoxClick);
	}

	/**
	 * Handles the click event for valuation checkboxes
	 */
	function handleCheckBoxClick() {
		computeAverageValuation();
		$('#AverageValue tr')
			.css({ backgroundColor : "#FFFF64" }, 100)
			.animate({ backgroundColor : "white" }, 500);
	}
	
	/**
	 * Handles keyup event on certain address form fields
	 */	
	function checkForChanges(e) {
		if (this.value !== e.data)
			HomeValuation.swapSubmitButton(this.form,'Submit');
	}

	// public interface
	return {
		
		showDebug : false,

		getHomeGain : function(cls, address, city, state, zip, doavg) {
			getValuationHTML(cls, 'getHomeGainValue', address, city, state, zip, doavg);
		},

		getEppraisal : function(cls, address, city, state, zip, doavg) {
			getValuationHTML(cls, 'getEppraisalValue', address, city, state, zip, doavg);
		},

		getZillow : function(cls, address, city, state, zip, doavg) {
			getValuationHTML(cls, 'getZillowValue', address, city, state, zip, doavg);
		},

		getTrulia : function(cls, address, city, state, zip, doavg) {
			getValuationHTML(cls, 'getTruliaValue', address, city, state, zip, doavg);
		},

		getNearbySolds : function(cls, address, city, state, zip, maxsolds, doavg) {
			getSoldsHTMLandMapData(cls, 'getNearbySolds', address, city, state,
					zip, maxsolds, doavg);
		},

		/**
		 * Verifies the contact info provided by the consumer. Ensures that the
		 * required fields are provided and then collects the valuation data
		 * and submits the form. This is activated by the clicking on the contact
		 * form's submit button.
		 * 
		 * @param {Object}
		 *            frm The form that contains the contact info fields.
		 */
		verifyContact: function(frm) {

			// validate required form fields
			var err = false;
			var rptVal = 0;

			if (frm.FirstName.value) {
				$('#FirstNameError').hide();
			} else {
				$('#FirstNameError').text("First Name is required.").show("fast");
				err = true;
			}

			if (frm.LastName.value) {
				$('#LastNameError').hide();
			} else {
				$('#LastNameError').text("Last Name is required.").show("fast");
				err = true;
			}

			if (frm.Phone.value) {
				$('#PhoneError').hide();
			} else {
				$('#PhoneError').text("Phone is required.").show("fast");
				err = true;
			}
			if (frm.Email.value) {
				$('#EmailError').hide();
			} else {
				$('#EmailError').text("Email is required.").show("fast");
				err = true;
			}
			
			if (!err) {
				// collect Home Valuation values
				rptVal = $('#hgValChk').val() || '';
				frm.hgVal.value = rptVal.length ? '$' + fmtNumberCommas(rptVal) : 'n/a';
				rptVal = $('#epValChk').val() || '';
				frm.epVal.value = rptVal.length ? '$' + fmtNumberCommas(rptVal) : 'n/a';
				rptVal = $('#zValChk').val() || '';
				frm.zillowVal.value = (rptVal.length ? '$' + fmtNumberCommas(rptVal) : 'n/a') + ' ' + $('#zDesc').text();
				rptVal = $('#truValChk').val() || '';
				frm.truliaVal.value = (rptVal.length ? '$' + fmtNumberCommas(rptVal) : 'n/a') + ' ' + $('#truDesc').text();
				// submit the form
				frm.submit();
			}
			
			return false;
		},
		
		/**
		 * Verifies the address provided by the consumer. Ensures that the
		 * required fields are provided and then uses the Google Geocoder
		 * service to test if the address is usable for computing a valuation.
		 * Provides error messages and, if necessary, creates a map to accept
		 * input from the consumer on ambiguous addresses. If the address is
		 * verified and accurately geocoded, the form is submitted. This is
		 * activated by the clicking on the address form's submit button.
		 * 
		 * @param {Object}
		 *            frm The form that contains the address fields.
		 */
		verifyAddress : function(frm) {

			// validate required form fields
			var err = false;

			if (frm.Address.value) {
				$('#addressError').hide();
			} else {
				$('#addressError').text("An address is required.").show("fast");
				err = true;
			}

			if ((frm.City.value && frm.State.selectedIndex > 0)
					|| frm.ZIP.value) {
				$('#CszError').hide();
			} else {
				$('#CszError')
						.text("Either City and State OR a ZIP is required.")
						.show("fast");
				err = true;
			}

			// submit for geocoding
			if (!err) {
				var address = frm.Address.value + ',' + frm.City.value + ','
						+ $(frm.State).val() + ' ' + frm.ZIP.value;

				$('#GeoMap').hide();

				MapManager.geocodeAddress(
					address,
					function(result) {
						if (result.success) {
	
							if (HomeValuation.showDebug) {
								// debug stuff
								var dbg = result.message
										+ ' - Found '
										+ result.placemarks.length
										+ ' place'
										+ ((result.placemarks.length == 1)
												? ''
												: 's') + '.<br />';
								for (var i = 0;i < result.placemarks.length; i++) {
									var place = result.placemarks[i];
									dbg += place.address
											+ ' ['
											+ MapManager.decodeGeoAccuracy(place.AddressDetails.Accuracy)
											+ '] : ('
											+ place.Point.coordinates[1]
											+ ','
											+ place.Point.coordinates[0]
											+ ')<br/>';
								}
								
								$('#DbgMessage').html(dbg).show();
							}
	
							// Ok, some sucessful geocode took place, now it's a matter
							// of how accurate and how many results were returned
							if (result.placemarks.length == 1
									&& result.placemarks[0].AddressDetails.Accuracy == 8) {
								// an accurate hit!
								updateFormAddress(
										frm,
										result.placemarks[0].address);
								frm.submit();
							} else {
								// show message & map
								var msg = (result.placemarks.length > 1)
										? 'Multiple locations found for this address.'
										: 'This address doesn\'t match a specific location.';
								msg += ' Choose a location from the map below or adjust the address and try again.';
								$('#addressError')
										.text(msg)
										.show('fast');
								HomeValuation.swapSubmitButton(frm, 'SubmitDisabled');
								$('#GeoMap').show();
								MapManager.clearMarkers();
								MapManager.initMap('GeoMap',
										buildMapOptsFromPlacemarks(
												result.placemarks,
												'SearchForm'));
								MapManager.zoomToMarkers();
							}
	
						} else {
							$('#addressError')
									.text("This address could not be located. Please adjust and try again or click the CONTINUE button to use this address.")
									.show('fast');
							HomeValuation.swapSubmitButton(frm, 'Continue');
						}
					}
				);
			}

			return false;
		},

		/**
		 * Updates the search form with a new address and submits the form. Used
		 * as a helper to the info window ballons.
		 * 
		 * @param {String}
		 *            address The address as needed by the updateFormAddress
		 *            function; formatted from Google's geocoder service: 123
		 *            Main St, Your Town, ST 00000, USA
		 */
		submitSearchForm : function(address) {
			var frm = $('#SearchForm').get(0);
			updateFormAddress(frm, address);
			frm.submit();
		},

		/**
		 * Checks or unchecks all valuation checkboxes; click event handler for
		 * the checkbox in the column header of the Nearby Sales table.
		 * 
		 * @param {Object}
		 *            master The checkbox in the column header that controls the
		 *            other checkboxes in that table
		 */
		toggleNbsCb : function(master) {
			$('tr.nearbySoldsRows input:checkbox').attr("checked", master.checked);
			handleCheckBoxClick();
		},
		
		/**
		 * Displays either the "CONTINUE", "SUBMIT", or a disabled "SUBMIT" button.
		 * If the "CONTINUE" or disabled "SUBMIT" button is displayed then event
		 * handlers are installed to watch for changes to the form which will cause
		 * the normal "SUBMIT" button to be displayed.
		 */
		swapSubmitButton : function (frm, btn) {
			
			if (btn === 'Continue') {
				$(frm.Address).bind("keyup", frm.Address.value, checkForChanges);
				$(frm.City).bind("keyup", frm.City.value, checkForChanges);
				$(frm.ZIP).bind("keyup", frm.ZIP.value, checkForChanges);
				$(frm.State).bind("change", function() {
					HomeValuation.swapSubmitButton(this.form, 'Submit')
				});
				$('#btnSubmitEnabled,#btnSubmitDisabled').hide();
				$('#btnContinue').show();

			} else if (btn === 'SubmitDisabled') {
				$(frm.Address).bind("keyup", frm.Address.value, checkForChanges);
				$(frm.City).bind("keyup", frm.City.value, checkForChanges);
				$(frm.ZIP).bind("keyup", frm.ZIP.value, checkForChanges);
				$(frm.State).bind("change", function() {
					HomeValuation.swapSubmitButton(this.form, 'Submit')
				});
				$('#btnSubmitEnabled,#btnContinue').hide();
				$('#btnSubmitDisabled').show();

			} else {
				$(frm.Address).unbind("keyup");
				$(frm.City).unbind("keyup");
				$(frm.ZIP).unbind("keyup");
				$(frm.State).unbind("change");
				$('#btnContinue,#btnSubmitDisabled').hide();
				$('#btnSubmitEnabled').show();
			}
		}
	}
}();
