if (typeof gHotSpotLocale != "object") {
	throw new Error("GHotSpot.js: You must load GHotSpotLocale.js!");
}

if (typeof Dealer != "function") {
	throw new Error("GHotSpot.js: You must load Dealer.js!");
}

function GHotSpot(element, mapElement, config)
{
	this.element = element;
	this.mapElement = mapElement;
	this.config = $.extend({}, GHotSpot.defaultConfig, config || {});
	this.map = null;
	this.index = GHotSpot.instances.length;
	this.response = null; // stores the response object returned by The Google Maps API (GClientGeocoder.prototype.getLocations) - is set in the callback method
	this.results = []; // array containing the number (this.config.matches) of dealer closest to the given address
	this.geocoder = new GClientGeocoder();
	if (this.config.countryCode) {
		this.geocoder.setBaseCountryCode(this.config.countryCode);		
	}
	this.index = GHotSpot.instances.length;
	this.initialized = false;
}

GHotSpot.defaultConfig = {
	eventType: "submit", // which kind of event in the hotspot should update the map (could also be 'keyup')?
	noChars: 0, // if > 0 the maxlength attribute on the input field is set accordingly
	matches: 5, // number of matches to return to the user
	initialAddress: "", // must be specified (a non-empty string) and must be a location in the region/country on the map
	countryCode: "", // restricts the search to the specified country/region on the map - should be specified otherwise the user may end up anywhere
	paramName: "addr", // the query string parameter name in which a possible address is passed
	regExp: "", // a pattern (string) or a regular expression that the user input must match to query Google for an address/location
	initialZoomLevel: 0, // is used to set the zoom level of the map - should be 0 (the world) or a larger integer, see http://cfis.savagexi.com/articles/2006/05/03/google-maps-deconstructed
	dealerMarkerEventType: "", // which kind of event on each marker should trigger the info window? - typical values are "click" and "mouseover"
	mapControls: null,// array of GMapControls
	dealerIcon: null, // must be GIcon object (or null)
	mapSearchIcon: null, // must be GIcon object (or null)
	enableScrollWheelZoom: true // should the map be zoomed when scrolling the mouse?
}

GHotSpot.instances = [];

GHotSpot.newInstance = function(element, mapElement, config) 
{
	var instance = new GHotSpot(element, mapElement, config);
	GHotSpot.instances.push(instance);
	return instance;
}

GHotSpot.readAddressFromURL = function(paramName) {
	var addr = "";
	var query = location.search; // is the empty string if no ? in location.href, but includes the ? if present
	var index = query.indexOf(paramName);
	if (index > -1) {
		addr = query.substring(index + paramName.length + 1); // remove = too
		index = addr.indexOf('&');
		if (index > -1) {
			addr = addr.substring(0, index);
		}
	}
	return decodeURIComponent(addr);
}

GHotSpot.initGHotSpots = function() {
	
	for (var i = 0, l = GHotSpot.instances.length; i < l; i++) {
		GHotSpot.instances[i].init();		
	}
}

GHotSpot.specialKeyCodes = [];
(function() {
	for (var i = 192; i < 256; i++) {
		GHotSpot.specialKeyCodes[i - 192] = i;
	}
})();

GHotSpot.getCaptionObject = function(caption, accesskeyClassName) {
	if (caption) {
		var obj = {};
		// find first occurrence of * and make sure it is not the last character and is not preceeded by another *
		var patternString = "\\*[a-z";
		for (var i = 0, l = GHotSpot.specialKeyCodes.length; i < l; i++) {
			patternString += String.fromCharCode(GHotSpot.specialKeyCodes[i]);
		}
		patternString += "]{1}";
		var found = new RegExp(patternString, "i").exec(caption);
		if (found && caption.indexOf('**') == -1) {
			obj['accesskey'] = found[0].substring(1);
			obj['accesskeyClassName'] = accesskeyClassName || "Accesskey";
			obj['plain'] = caption.replace('*', '');
			caption = caption.replace(found[0], '<span class="' + obj['accesskeyClassName'] + '">' + obj['accesskey'] + '<\/span>');
		}
		obj['caption'] = caption;
		return obj;
	}
	return null;
}

GHotSpot.formatMessage = function(msg, valuesArray, del) {
	var a = msg.split(del || gHotSpotLocale.msgDel);
	if (a.length == 1 || valuesArray == null || typeof valuesArray.length != "number" || a.length != valuesArray.length + 1) {
		return msg;
	}
	msg = "";
	for (var i = 0, l = valuesArray.length; i < l; i++) {
		msg += a[i] + valuesArray[i];
	}
	return msg + a[a.length - 1];
}

GHotSpot.prototype.toString = function() {
	return "[object GHotSpot] " + ((this.element.id != null) ? this.element.id : this.element);
}

GHotSpot.prototype.init = function() {
	
	if (!this.initialized)
	{
		
		if (typeof this.element == "string") {
			this.element = $(this.element)[0];
		}
		if (typeof this.mapElement == "string") {
			this.mapElement = $(this.mapElement)[0];
		}
		this.setup();
		var addr = this.readAddressFromURL();
		
		if (addr) 
		{	
			this.getLocation(addr);
			this.element.value = addr;
		}
		this.initialized = true;
	}
}

GHotSpot.prototype.readAddressFromURL = function()
{
	return GHotSpot.readAddressFromURL(this.config.paramName);
}

GHotSpot.prototype.onSubmit = function(e)
{
	//Base.Trace(this.toString());
	if (e) {
		if (typeof e.preventDefault == "function") {
			e.preventDefault();
		} else {
			e.returnValue = false;
		}
	}
	if (this.validate())
	{
		this.getLocation();
	}
}

GHotSpot.prototype.getValue = function()
{
	var value = this.element.value;
	if (value && this.config.countryCode) {
		value += ", " + this.config.countryCode;
	}
	return value;
}

GHotSpot.prototype.validate = function()
{
	var ok = true;
	if (this.config.regExp) {
		ok = (this.config.regExp.constructor == RegExp) ? this.config.regExp.test(this.element.value) : new RegExp(this.config.regExp).test(this.element.value);
	}
	if (!ok) {
		this.log(gHotSpotLocale.invalidAddress);
	}
	return ok;
}

GHotSpot.prototype.onKeyup = function(e)
{
	if (this.validate()) {
		this.getLocation();
	}
}

GHotSpot.prototype.setup = function()
{
	if (this.element && this.config.noChars > 0) {
		this.element.setAttribute("maxlength", "" + this.config.noChars);
	}
	if (this.mapElement) {
		this.map = new GMap2(this.mapElement);
		if (this.config.enableScrollWheelZoom) {
			this.map.enableScrollWheelZoom();
		}
		var controls = this.config.mapControls || [];
		for (var i = 0, l = controls.length; i < l; i++) {
  			this.map.addControl(controls[i]);
		}
		if (this.config.initialAddress) {			
			if(this.config.initialAddress =="my")
				this.geocoder.getLocations(Form.GetSelectedText("jumpMenu"), GEvent.callback(this, this.initMap));
			else
				this.geocoder.getLocations(this.config.initialAddress, GEvent.callback(this, this.initMap));
		}
	}
	var eType = this.config.eventType;
	if ("submit" == eType) {
		var form = this.element ? this.element.form : null;
		if (form) {
			//$("#frmHotspot").unbind();
			GEvent.addDomListener(form, "submit", GEvent.callback(this, this.onSubmit));
		}
	}
	else if (eType.indexOf("key") == 0) {
		$(this.element)[eType](GEvent.callback(this, this.onKeyup));
	}
}

GHotSpot.prototype.getLocation = function(value) {
	
	value = value || this.getValue();
	this.geocoder.getLocations(value, GEvent.callback(this, this.callback));
}

GHotSpot.prototype.callback = function(response) {
	this.response = response;
	if (response.Status.code != 200) {
		this.log(gHotSpotLocale.googleStatusObject["" + response.Status.code]);
		return;
	}
	var no = response.Placemark.length;
	if (no < 1) {
		this.log(gHotSpotLocale.noResults); // will probably never go here
	} else {
		if (this.updateSearchResults()) {
			this.calculate(0);
		}
	}
}

GHotSpot.prototype.calculate = function(placeIndex) {
	
	placeIndex = Math.max(parseInt(placeIndex, 10), 0);
	var oGLatLng = new GLatLng(this.response.Placemark[placeIndex].Point.coordinates[1], this.response.Placemark[placeIndex].Point.coordinates[0]);
	var results = [];
	for (var i = 0, l = Dealer.instances.length; i < l ; i++) {
		var dealer = Dealer.instances[i];
		if (dealer.gLatLng == null) {
			continue;
		}
		dealer.distance = dealer.gLatLng.distanceFrom(oGLatLng);
		results.push(dealer);
	}
	results.sort(Dealer.compare);
	this.results = results.slice(0, this.config.matches);
	this.updateMap(oGLatLng);
	$(this).oneTime(500, function() {//reactivate newEvent clickevents after 500ms					
		this.updateDealerSearchResults(placeIndex);	
	});
	
}

GHotSpot.prototype.updateSearchResults = function() {
	function isValidCountryCode(code, placeMarks) {
		if (!code) {
			return true;
		}
		for (var i = 0, l = placeMarks.length; i < l; i++) {
			var mark = placeMarks[i];
			if (code.toLowerCase() == mark.AddressDetails.Country.CountryNameCode.toLowerCase()) {
				return true;
			}
		}
		return false;
	}
	var rv = isValidCountryCode(this.config.countryCode, this.response.Placemark);
	var htm = '';
	if (rv) {
		var no = this.response.Placemark.length;
		//htm += '<dl><dt>' + GHotSpot.formatMessage(gHotSpotLocale.foundAddresses, [no, this.element.value]) + '<\/dt>';
		for (var i = 0; i < no; i++) {
			var place = this.response.Placemark[i];
		//	htm += '<dd>';
		//	htm += '<a href="" onclick="return GHotSpot.instances[' + this.index + '].onClick(event, ' + i + ');">' + place.address + '<\/a>';
		//	htm += '<\/dd>';
		}
		//htm += '<\/dl>';
	} else {
		htm += gHotSpotLocale.wrongCountryCode;
	}
	this.log(htm);
	return rv;
}

GHotSpot.prototype.updateDealerSearchResults = function(placeIndex) {
	$("#divSearchResults").html("");
	var htm = '<dl><dt>' + GHotSpot.formatMessage(gHotSpotLocale.foundDealers, [Math.min(this.config.matches, this.results.length), this.response.Placemark[placeIndex].address]) + '<\/dt>';
	for (var i = 0, l = this.results.length; i < l; i++) {
		var dealer = this.results[i];
		htm += '<dd>';
		htm += '<a href="" onclick="return GHotSpot.instances[' + this.index + '].showDealer(event, ' + i + ');" title="' + dealer.getAddress() + '">' + dealer.config.company + '<\/a>';
		htm += '<\/dd>';
	}
	htm += '<\/dl>';
	$("#divDealerSearchResults").html(htm);
}

GHotSpot.prototype.onClick = function(e, placeIndex) {
	
	if (typeof e.preventDefault == "function") {
		e.preventDefault();
	} else {
		e.returnValue = false;
	}
	this.calculate(placeIndex);
	return false;
}

GHotSpot.prototype.showDealer = function(e, index) {
	
	if (typeof e.preventDefault == "function") {
		e.preventDefault();
	} else {
		e.returnValue = false;
	}
	this.displayMarkerInfo(this.results[index]);
	return false;
}

GHotSpot.prototype.updateMap = function(center) { // results is an array of Dealer objects
	
	this.map.clearOverlays();
	var bounds = new GLatLngBounds();
	bounds.extend(center);
	var opts = this.config.mapSearchIcon ? {icon: this.config.mapSearchIcon} : {};
	var marker = new GMarker(center, opts);
	this.map.addOverlay(marker);
	opts = this.config.dealerIcon ? {icon: this.config.dealerIcon} : {};
	for (var i = 0, l = this.results.length; i < l; i++) {
		var dealer = this.results[i];
		bounds.extend(dealer.gLatLng);
		dealer.marker = new GMarker(dealer.gLatLng, dealer.config.mapIcon ? {icon: dealer.config.mapIcon} : opts);
		this.map.addOverlay(dealer.marker);
		if (this.config.dealerMarkerEventType) {
			GEvent.bind(dealer.marker, this.config.dealerMarkerEventType, this, GEvent.callbackArgs(this, this.displayMarkerInfo, dealer));
		}
	}
	this.map.setCenter(bounds.getCenter());
	this.map.setZoom(this.map.getBoundsZoomLevel(bounds));
}

/*GHotSpot.prototype.displayMarkerInfo = function(dealer) {
	var htm = '<p class="DisplayName">' + dealer.getDisplayName() + '<\/p><p class="Address">' + dealer.getAddress() + '<\/p>';
	if (dealer.warning) {
		htm += '<p class="Warning">' + dealer.warning + '<\/p>';
	}
	dealer.marker.openInfoWindowHtml(htm);
}*/

GHotSpot.prototype.displayMarkerInfo = function(dealer) {
	var htm = '<div class="vcard">';
	htm += '<p class="org">' + dealer.config.company + '<\/p>';
	
	htm += '<div class="adr">';
	if (dealer.config.street) {
		htm += '<span class="street-address">' + dealer.config.street + '<\/span>';
	}
	if (dealer.config.city) {
		htm += '<span class="locality">, ' + dealer.config.city + '<\/span>';
	}
	if (dealer.config.zipcode) {
		htm += '<br /><span class="postal-code">' + dealer.config.zipcode + '<\/span>';
	}
	if (dealer.config.country) {
		htm += '<span class="country-name">, ' + dealer.config.country + '<\/span>';
	}
	htm += '<\/div>';
	if (dealer.config.phone) {
		htm += '<p class="tel phone">' + dealer.config.phone + '</p>';
	}
	if (dealer.config.email) {
		htm += '<p class="email"><a class="email" href="mailto:' + dealer.config.email + '">' + dealer.config.email + '<\/a><\/p>';
	}
	if (dealer.config.url) {
		htm += '<p class="url"><a class="url fn" href="' + dealer.config.url + '">' + dealer.config.url + '<\/a><\/p>';
	}		
	htm += '<\/div>';
	htm += '';
	htm += '<\/div>';
	if (dealer.warning) {
		htm += '<p class="Warning">' + dealer.warning + '<\/p>';
	}
	dealer.marker.openInfoWindowHtml(htm);
}

GHotSpot.prototype.initMap = function(response) {
	if (response.Status.code != 200) {
		this.log(gHotSpotLocale.googleStatusObject["" + response.Status.code]);
		return;
	}
	var place = response.Placemark[0];
	var point = new GLatLng(place.Point.coordinates[1], place.Point.coordinates[0]);
	this.map.setCenter(point, this.config.initialZoomLevel);
}

// override for customized logging
GHotSpot.prototype.log = function(msg) {
	$("#divSearchResults").html('<b>Your search result</b><br />'+msg);
	$("#divDealerSearchResults").html('');
}

$(document).ready(function() {
	if (GBrowserIsCompatible()) {
		GHotSpot.initGHotSpots();
	} else {
		alert('Your browser is not compatible with the Google Maps API!');
	}
});

$(window).bind("unload", GUnload);