/* ============================================================================
*  Author: Alex Oh, Grok, Inc.
*  Copyright: 2008 Montway Auto Transport, Inc.
*  Date: 2008-09-12-17:46
*
*  This file is to activate the Google Maps API interface. It takes care of
*  the initialization of the interface and handles all the data going back and
*  forth from Google Maps.
*  ========================================================================= */

// The directions and geo variables are used to reference the Google Maps API
// objects later in the code.
var directions;
var geo;

// The continueDirections variable is a simple boolean that prevents a
// GDirections lookup if there are any problems with the from or to addresses.
var continueDirections;

// The from, to, and route variables store the from and to strings used for
// directions and distance. The route variable specifically just concatenates
// the from and to with a string literal " to ". The countryRestrict variable
// is an extra search restriction appended at the end of the from and to
// variable, before sending to Google. The optionsCallback is a function
// pointer to call a method that will be called whenever there are errors in
// trying to determine distance, or if there are alternatives for the from and
// to input values.
var from;
var fromCity;
var fromState;
var fromZip;
var fromLat;
var fromLong;
var to;
var toCity;
var toState;
var toZip;
var toLat;
var toLong;
var route;
var countryRestrict = "USA";
var countryCode = "US";
var optionsCallback;
var distanceCallback;

// The reasons array variable is to store the GClientGeocoder error types to
// make error messages more human readable and informative.
var reasons=[];
reasons[G_GEO_SUCCESS]            = "Success";
reasons[G_GEO_MISSING_ADDRESS]    = "The address was either missing or had no value.";
reasons[G_GEO_UNKNOWN_ADDRESS]    = "No corresponding geographic location could be found for the specified address.";
reasons[G_GEO_UNAVAILABLE_ADDRESS]= "The geocode for the given address cannot be returned due to legal or contractual reasons.";
reasons[G_GEO_BAD_KEY]            = "The API key is either invalid or does not match the domain for which it was given";
reasons[G_GEO_TOO_MANY_QUERIES]   = "The daily geocoding quota for this site has been exceeded.";
reasons[G_GEO_SERVER_ERROR]       = "Server error: The geocoding request could not be successfully processed.";

// Custom codes
var ALTERNATIVES_FOUND_FROM = 9991;
var ALTERNATIVES_FOUND_TO = 9992;
var UNAVAILABLE_CITY = 9999;
var BLANK_ADDRESS = 9998;
reasons[ALTERNATIVES_FOUND_FROM]  = "Could not find the city/zip you indicated. Please consider choosing these alternatives, or Reset and try again.";
reasons[ALTERNATIVES_FOUND_TO]    = "Could not find the city/zip you indicated. Please consider choosing these alternatives, or Reset and try again.";
reasons[UNAVAILABLE_CITY]         = "Please try again, using the zip code. Otherwise, please check the spelling of city and state and try again.";
reasons[BLANK_ADDRESS]            = "Please make sure both from and to addresses are filled.";

// All states and abbreviations for being able to determine abbreviations for
// when Google does not return the abbreviations
var allStates = new Array("ALABAMA","ALASKA","AMERICAN SAMOA","ARIZONA","ARKANSAS","CALIFORNIA","COLORADO","CONNECTICUT","DELAWARE","DISTRICT OF COLUMBIA","FEDERATED STATES OF MICRONESIA","FLORIDA","GEORGIA","GUAM","HAWAII","IDAHO","ILLINOIS","INDIANA","IOWA","KANSAS","KENTUCKY","LOUISIANA","MAINE","MARSHALL ISLANDS","MARYLAND","MASSACHUSETTS","MICHIGAN","MINNESOTA","MISSISSIPPI","MISSOURI","MONTANA","NEBRASKA","NEVADA","NEW HAMPSHIRE","NEW JERSEY","NEW MEXICO","NEW YORK","NORTH CAROLINA","NORTH DAKOTA","NORTHERN MARIANA ISLANDS","OHIO","OKLAHOMA","OREGON","PALAU","PENNSYLVANIA","PUERTO RICO","RHODE ISLAND","SOUTH CAROLINA","SOUTH DAKOTA","TENNESSEE","TEXAS","UTAH","VERMONT","VIRGIN ISLANDS","VIRGINIA","WASHINGTON","WEST VIRGINIA","WISCONSIN","WYOMING","N CAROLINA","N DAKOTA","S CAROLINA","S DAKOTA","W VIRGINIA", "RHODE ISL");
var allStateAbbrev = new Array("AL","AK","AS","AZ","AR","CA","CO","CT","DE","DC","FM","FL","GA","GU","HI","ID","IL","IN","IA","KS","KY","LA","ME","MH","MD","MA","MI","MN","MS","MO","MT","NE","NV","NH","NJ","NM","NY","NC","ND","MP","OH","OK","OR","PW","PA","PR","RI","SC","SD","TN","TX","UT","VT","VI","VA","WA","WV","WI","WY","NC","ND","SC","SD","WV","RI");

/* ============================================================================
*  Called from HTML with the body's onLoad() method. Initalizes variables,
*  including the Google Maps API objects. It also sets up the proper GEvent
*  handlers and displays results or forwards the error handling. A callback
*  function pointer needs to be provided as an argument for initialize. This
*  callback function will be called anytime where is a successful query for
*  distance with the provided addresses.
*  ========================================================================= */
function initializeDistance(inDistanceCallback)
{
	//if (!GBrowserIsCompatible())
	//{
	//	alert("Sorry, the Google Maps API is not compatible with this browser");
	//}
	//else
	{

		continueDirections = false;
		distanceCallback = inDistanceCallback;

		geo = new GClientGeocoder();
		geo.setBaseCountryCode(countryCode);
	}
}


/* ============================================================================
*  Called from HTML after the user has finished the form. The from and to
*  addresses are checked individually to see if they can be singly resolved to
*  one city/state combination so that they can be used reliably to find proper
*  distances. The listAlternatives() method is used. The parameters fromtext
*  and totext are values to be used to determine distance. The callback
*  parameter is a callback function pointer that will be called after
*  processing, along with parameters. If the query generates and error, the
*  parameters to the callback will be an error code and error message. If the
*  query generates alternative addresses, the callback parameters will be a
*  string to indicate "from" or "to" and an Array of address alternatives.
*  ========================================================================= */
function getMiles(fromtext, totext, callback)
{
	continueDirections = false;

	optionsCallback = callback;

	if (fromtext && totext)
	{
		fromtext += ", " + countryRestrict;
		totext += ", " + countryRestrict;

		geo.getLocations(fromtext, function(result)
			{
				listAlternatives("from", result);
			});

		geo.getLocations(totext, function(result)
			{
				listAlternatives("to", result);
			});
	}
	else
	{
		displayError(BLANK_ADDRESS, fromtext + " to " + totext);
	}
}


/* ============================================================================
*  Called from getMiles(), this method does the bulk of the work with the
*  Google Maps API objects. It first checks to make sure both the from and to
*  addresses can be singly resolved, and presents alternatives if there are
*  multiple possibilities with either from or to addresses. If both from and
*  to addresses are unique enough to determine distance, it uses the proper
*  Google Maps API calls to determine distance between the addresses.
*  ========================================================================= */
function listAlternatives(which, googleObject)
{
	//var message = "";

	if (googleObject.Status.code == G_GEO_SUCCESS)
	{
		// ===== If there was more than one result, Ask "did you mean" on them all =====
		if (googleObject.Placemark.length > 1)
		{
			//message += "Ambiguous " + which + " address. Did you mean: <br />";
			var options = new Array();

			// Loop through the results
			for (var i = 0; i < googleObject.Placemark.length; i++)
			{
				var tempAddr = googleObject.Placemark[i].address;
				//alert("PREPROCESS: New alternative: [" + tempAddr + "]");
				//alert("Fixed result: street[" + getStreetFromPlacemark(googleObject.Placemark[i]) + "] city[" + getCityFromPlacemark(googleObject.Placemark[i]) + "] state[" + getStateFromPlacemark(googleObject.Placemark[i]) + "] zip[" + getZipFromPlacemark(googleObject.Placemark[i]) + "] county[" + getCountyFromPlacemark(googleObject.Placemark[i]) + "] country[" + getCountryFromPlacemark(googleObject.Placemark[i]) + "]");

				// Only addresses in the USA
				if (getCountryFromPlacemark(googleObject.Placemark[i]) == countryCode)
				{
					var newAddr = '';
					var tempCity = getCityFromPlacemark(googleObject.Placemark[i]);
					if (tempCity == "") tempCity = getCountyFromPlacemark(googleObject.Placemark[i]);	// Probably a bug in the Gmaps API, that it returns the city in the county field. Blah.
					var tempState = getStateFromPlacemark(googleObject.Placemark[i]);

					if (tempCity != "") newAddr += tempCity + ", ";
					newAddr += tempState;

					//alert("POSTPROCESS: New alternative: [" + newAddr + "]");

					// Only in the contiguous states!
					if (tempState != "" && tempState != "HI" && tempState != "AK") options[options.length] = newAddr;
				}
			}

			if (options.length > 0) optionsCallback(which, options);
			else displayError(UNAVAILABLE_CITY, which);

			continueDirections = false;
		}
		else if (googleObject.Placemark.length == 1)
		{
			// ===== If we're here, there was only one placemark result for the address =====
			//alert("Only one result for " + which + " address: [" + googleObject.Placemark[0].address + "]");
			//alert("Fixed result: street[" + getStreetFromPlacemark(googleObject.Placemark[0]) + "] city[" + getCityFromPlacemark(googleObject.Placemark[0]) + "] state[" + getStateFromPlacemark(googleObject.Placemark[0]) + "] zip[" + getZipFromPlacemark(googleObject.Placemark[0]) + "] county[" + getCountyFromPlacemark(googleObject.Placemark[0]) + "] country[" + getCountryFromPlacemark(googleObject.Placemark[0]) + "]");

			var tempAddr = googleObject.Placemark[0].address;
			//alert("PREPROCESS: Found one result for: [" + tempAddr + "]");

			// Only addresses in the USA
			if (getCountryFromPlacemark(googleObject.Placemark[0]) == countryCode)
			{
				var newAddr = '';
				var tempCity = getCityFromPlacemark(googleObject.Placemark[0]);
				var tempState = getStateFromPlacemark(googleObject.Placemark[0]);
				var tempZip = getZipFromPlacemark(googleObject.Placemark[0]);

				if (tempCity != "") newAddr += tempCity + ", ";
				newAddr += tempState;
				if (tempZip != "") newAddr += " " + tempZip;

				var tempLong = googleObject.Placemark[0].Point.coordinates[0];
				var tempLat = googleObject.Placemark[0].Point.coordinates[1];

				//alert("POSTPROCESS: Found one result for: [" + tempCity + "], [" + tempState + "] [" + tempZip + "]");

				// Only in the contiguous states!
				if (tempState != "" && tempState != "HI" && tempState != "AK")
				{
					// Store from and to.
					if (which == "from")
					{
						from = newAddr;
						fromCity = tempCity;
						fromState = tempState;
						fromZip = tempZip;
						fromLat = tempLat;
						fromLong = tempLong;
					}
					else if (which == "to")
					{
						to = newAddr;
						toCity = tempCity;
						toState = tempState;
						toZip = tempZip;
						toLat = tempLat;
						toLong = tempLong;
					}

					// The continueDirections variable is a simple boolean that prevents a GDirections lookup
					// if there are any problems with the from or to addresses, or if the validation of both
					// addresses is yet incomplete.
					if (continueDirections)
					{
						//from += ", " + countryRestrict;
						//to += ", " + countryRestrict;
						//route =  from + " to " + to;

						//alert("PREDIRECTIONS: Getting directions for route: [" + route + "]");

						//directions.load(route);

						//You don't always get a zipcode
						getDistanceKML(fromCity+", "+fromState, toCity+", "+toState);
						continueDirections = false;
					}
					else
					{
						continueDirections = true;
					}
				}
				else
				{
					displayError(UNAVAILABLE_CITY, which);
				}
			}
			else
			{
				displayError(UNAVAILABLE_CITY, which);
			}
		}
		else
		{
			displayError(UNAVAILABLE_CITY, which);
		}
	}
	else
	{
		// ====== Decode the error status ======
		if (googleObject.Placemark) displayError(googleObject.Status.code, googleObject.Placemark[0].address);
		else displayError(googleObject.Status.code);
	}
}


function getCountryFromPlacemark(inPlacemark)
{
	try { return inPlacemark.AddressDetails.Country.CountryNameCode; } catch(err) { return "" }
}


function getStateFromPlacemark(inPlacemark)
{
	try { return inPlacemark.AddressDetails.Country.AdministrativeArea.AdministrativeAreaName; } catch(err) { return "" }
}


function getCountyFromPlacemark(inPlacemark)
{
	try { return inPlacemark.AddressDetails.Country.AdministrativeArea.SubAdministrativeArea.SubAdministrativeAreaName; } catch(err) { return "" }
}


function getStreetFromPlacemark(inPlacemark)
{
	try { return inPlacemark.AddressDetails.Country.AdministrativeArea.SubAdministrativeArea.Locality.Thoroughfare.ThoroughfareName; } catch(err) { return "" }
}


function getCityFromPlacemark(inPlacemark)
{
	try { return inPlacemark.AddressDetails.Country.AdministrativeArea.SubAdministrativeArea.Locality.LocalityName; } catch(err) { return "" }
}


function getZipFromPlacemark(inPlacemark)
{
	try { return inPlacemark.AddressDetails.Country.AdministrativeArea.SubAdministrativeArea.Locality.PostalCode.PostalCodeNumber; } catch(err) { return "" }
}


function getDistanceKML(inFrom, inTo)
{
	// Generate Post
	var poststr = "from=" + encodeURI(inFrom)
			+ "&" + "to=" + encodeURI(inTo)
			;

	//alert ("Getting distances: [" + poststr + "]");

	// Using Prototype AJAX object
	var myAjax = new Ajax.Request
	(
		'/includes/gmaps_xml.php',
		{
			method: 'post',
			parameters: poststr,
			onSuccess: function(originalRequest)
			{
				var data = originalRequest.responseXML;
				//alert("Results: ["+originalRequest.responseText+"]");

				var inTotal = 0;
				var inSdni = 0;
				var inDdni = 0;
				var inDni = 0;

				var totElement = data.getElementsByTagName('totalDistance');
				var srcElement = data.getElementsByTagName('sourceFromI');
				var destElement = data.getElementsByTagName('destinationFromI');
				var tniElement = data.getElementsByTagName('totalNotI');

				if (totElement != null && totElement.length > 0
					&& srcElement != null && srcElement.length > 0
					&& destElement != null && destElement.length > 0
					&& tniElement != null && tniElement.length > 0)
				{
					inTotal = totElement[0].firstChild.data;
					inSdni = srcElement[0].firstChild.data;
					inDdni = destElement[0].firstChild.data;
					inDni = tniElement[0].firstChild.data;

					if (inTotal) inTotal = Number(inTotal).toFixed(2);
					if (inSdni) inSdni = Number(inSdni).toFixed(2);
					if (inDdni) inDdni = Number(inDdni).toFixed(2);
					if (inDni) inDni = Number(inDni).toFixed(2);
				}
				else
					alert("KMLMapError: Results did not include any distance data for supplied parameters.");


				var newFromLongElement = data.getElementsByTagName('fromLong');
				var newFromLatElement = data.getElementsByTagName('fromLat');
				var newToLongElement = data.getElementsByTagName('toLong');
				var newToLatElement = data.getElementsByTagName('toLat');

				if (newFromLongElement != null && newFromLatElement != null && newToLongElement != null && newToLatElement != null)
				{
					fromLong = newFromLongElement[0].firstChild.data;
					fromLat = newFromLatElement[0].firstChild.data;
					toLong = newToLongElement[0].firstChild.data;
					toLat = newToLatElement[0].firstChild.data;
				}
				else
					alert("KMLMapError: Results did not include any lat-long data for supplied parameters.");


				if (data.getElementsByTagName('error').length > 0) alert("KMLMapError: "+data.getElementsByTagName('error')[0].firstChild.data);

				distanceCallback(
							inTotal
							, inSdni
							, inDdni
							, inDni
							, fromCity
							, fromState
							, fromZip
							, fromLat
							, fromLong
							, toCity
							, toState
							, toZip
							, toLat
							, toLong
							);

			},
			onFailure: function()
			{
				alert("KMLMapError: Transmission error.");
			}
		}
	);
}


/* ============================================================================
*  Called whenever an error occurs. Used to format and display the appropriate,
*  human readable error message.
*  ========================================================================= */
function displayError(code, placemark)
{
	continueDirections = false;

	var errMessage = "";

	//if (placemark) errMessage = "ERROR [" + placemark + "]:[" + code + "]:";
	//else errMessage = "ERROR [" + route + "]:[" + code + "]:";

	if (reasons[code]) errMessage += reasons[code];

	if (placemark != null) code = placemark + "error";

	optionsCallback(code, errMessage);
}


/* ============================================================================
*  Utility function to map incoming states to their proper abbreviations.
*  ========================================================================= */
function assertStateAbbrev(inputState)
{
	if (inputState)
	{
		if (inputState.length > 2)
		{
			for (var i = 0; i < allStates.length; i++)
			{
				if (inputState.toUpperCase() == allStates[i].toUpperCase())
				{
					return allStateAbbrev[i];
				}
			}
		}
		return inputState.toUpperCase();
	}
	return inputState;
}

/* ============================================================================
*  Utility function to determine if a string represents a numeric.
*  ========================================================================= */
function isNumber(inText)
{
	var numChars = "0123456789.";
	var isNum = true;
	var thisChar;

	// Loop through the chars in the text you passed in, testing each char
	for (var idx = 0; idx < inText.length; idx++)
	{
		thisChar = inText.charAt(idx);

		// If the current char is not a number then exit returning false
		if (numChars.indexOf(thisChar) == -1) return false;
	}

	// If you've made it this far then incoming text is all numeric.
	return true;
}
