/**
 * Map controls
 */

var gmap = null;         /* Main MyGmap object */
var loaded = null;       /* Checks if map's already loaded on a page */
var send_load = null;    /* Check if map is loading */
var PL_LAT = 51.9194380; /* Init latitude */
var PL_LNG = 19.1451360; /* Init longitude */
var INIT_ZOOM = 5;       /* Init zoom */
var DEBUG = false;       /* Debug mode */
var GGL_W = 'ggl_w';     /* Hidden input id to set new marker latitude */
var GGL_H = 'ggl_h';     /* Hidden input id to set new marker longitude */

/**
 * Initializes google map (when only one map is shown on single page). Exposes 
 * MyGmap object as global variable gmap.
 * @param {String} divId container id for a map
 * @param {Number} lat latitude of shown marker
 * @param {Number} lng longitude of shown marker
 * @param {Boolean} editable marker are editable
 * @param {Boolean} small small map controls
 */
function initializeOnce(divId, lat, lng, editable, small) {
	
	/* Check if map is loaded */	
	if(!loaded) { /* Load api and initialize on callback */
		if(send_load) return; /* Map is loading */
		send_load = true;
		google.load('maps', '2.x', {'callback': function() { loaded = true; initializeOnce(divId, lat, lng, editable, small); } });
		return;
	}
	/* Check browser compatibility */
	if (google.maps.BrowserIsCompatible()) {
		
		/* Div content element */
		var div = document.getElementById(divId);
		
		/* Geocoder search */
		var myGeocoder = new MyGeocoder('PL', div);
	
		/* Create Geocoder */
		var myGmap = new MyGmap(div, myGeocoder);
		myGeocoder.map = myGmap;
		
		if ((lat) && (lng)) { /* Marker should be added */
			myGmap.loadMap(new google.maps.LatLng(parseFloat(lat), parseFloat(lng)), editable, 16, small, true);
		} else { /* Normal add mode */
			myGmap.loadMap(new google.maps.LatLng(PL_LAT, PL_LNG), editable, INIT_ZOOM, small, false);
			myGmap.startAddMarkerMode();
		}
		
		/* Expose map object */
		gmap = myGmap;
	}
}


/**
 * Function for center map on given location. Works only if
 * map fully loaded. Uses geocode search engine.
 * @param {String} state
 * @param {String} city
 * @param {String} street
 * @param {String} street_number
 */
function centerMap(state, city, street, street_number) {
	if(gmap && loaded) { /* Map is loaded */
		gmap.setupAddress(trim(state), trim(city), trim(street)+' '+trim(street_number));
		/* Try to focus on given location */
		gmap.focusOnLocation();	
	}
}

/*========================= GoogleMap ================================*/

/**
 * Constructor of map
 * @param {DOMObject} div element to insert within the map
 */
function MyGmap(div, geocoder) {
	this.debug = DEBUG;
	this.div = div;
	this.map = null;
	this.mapPoints = new Array();
	this.myGeocoder = geocoder;
	this.newMarker = null;
	this.ggl_lat = document.getElementById(GGL_H);
	this.ggl_lng = document.getElementById(GGL_W);
}

/**
 * Loads map with given point and zoom
 * @param {GLatLng} point to add
 * @param {Boolean} draggable is point editable
 * @param {Number} zoom map zoom
 * @param {Boolean} small small map controls (true/false)
 * @param {Boolean} show_point point to show or only center map
 */
MyGmap.prototype.loadMap = function(point, draggable, zoom, small, show_point) {
	/* Initializing map */
	this.map = new google.maps.Map2(this.div);
	this.map.setCenter(point, zoom);
	if(this.debug) google.maps.Log.write('Center to point '+point);
	if(small) this.map.addControl(new google.maps.SmallZoomControl3D());
	else this.map.addControl(new google.maps.LargeMapControl3D());
    var m = null;
	var that = this;
	if (show_point) {
		if (draggable) {
			this.map.enableScrollWheelZoom();
			m = new google.maps.Marker(point, {
				draggable: true,
				autoPan: false
			});
			this.newMarker = m;
			google.maps.Event.addListener(m, 'dragend', function(position){
				if (that.ggl_lat) 
					that.ggl_lat.value = position.lat();
				if (that.ggl_lng) 
					that.ggl_lng.value = position.lng();
				if (that.debug) 
					google.maps.Log.write('Marker position ' + position.lat() + ':' + position.lng());
			});
			google.maps.Event.addListener(that.map, 'click', function(overlay, cursorPoint){
				if (that.ggl_lat) 
					that.ggl_lat.value = cursorPoint.lat();
				if (that.ggl_lng) 
					that.ggl_lng.value = cursorPoint.lng();
				that.newMarker.setLatLng(cursorPoint);
				if (that.myGeocoder) 
					that.myGeocoder.hideTooltip();
			});
		}
		else {
			this.newMarker = m;
			m = new google.maps.Marker(point);
		}
		this.map.addOverlay(m);
		this.mapPoints.push(m);
	}
	var that = this;
	google.maps.Event.addListener(this.map, 'movestart', function() {
		if(that.myGeocoder) {
			that.myGeocoder.hideTooltip();	
		}
	});
	google.maps.Event.addListener(this.map, 'zoomend', function() {
		if(that.myGeocoder) {
			that.myGeocoder.hideTooltip();	
		}
	});
}

/**
 * Center map
 * @param {Object} point
 * @param {Object} zoom
 */
MyGmap.prototype.center = function(point, zoom) {
	this.map.setCenter(point, zoom);
//	var that = this;
//	/* Add map click event */
//	var mapClick = google.maps.Event.addListener(that.map, 'click', function(overlay, cursorPoint){
//		if(that.ggl_lat) that.ggl_lat.value = cursorPoint.lat();
//		if(that.ggl_lng) that.ggl_lng.value = cursorPoint.lng();
//		that.newMarker.setLatLng(cursorPoint);
//		if(that.myGeocoder) that.myGeocoder.hideTooltip();
//		google.maps.Event.removeListener(mapClick);
//	});
}

/**
 * Setup address for geocode location.
 * @param {String} state
 * @param {String} city
 * @param {String} street
 */
MyGmap.prototype.setupAddress = function(state, city, street) {
	/* Set address parts */
	this.myGeocoder.state = trim(state);
	this.myGeocoder.city = trim(city);
	this.myGeocoder.street = trim(street);
}

/**
 * Focuses map on given location, provided via setupAddress method.
 * If address not set map view remains.
 */
MyGmap.prototype.focusOnLocation = function() {
	if(this.debug) google.maps.Log.write('Try to focus on '+this.myGeocoder.state+':'+this.myGeocoder.city+':'+this.myGeocoder.street);
	this.myGeocoder.performSearch(true);
}
	
/**
 * Unloads map - for memory leaks
 */
MyGmap.prototype.unload = function() {
	for(var i=0; i<this.mapPoints.lenght; i++) {
		this.map.removeOverlay(this.mapPoints[i]);
		google.maps.Event.clearListeners(this.mapPoints[i]); 
		delete this.mapPoints[i];
	}
	this.mapPoints.splice(0, this.mapPoints.lenght);
}

/**
 * Triggers function to start adding new marker
 */
MyGmap.prototype.startAddMarkerMode = function () {
	this.follow();
}

/**
 * Method starts adding new marker on a map. Click on map to add
 * draggable marker. Click on map to set new location. Updates values
 * of lat and lng of new marker stored in two input fields (if available).
 */
MyGmap.prototype.follow = function() {
	var dog = true;
  	var noMore = false;
	var that = this;
	/* Start listening mouse move event */
	var mouseMove = google.maps.Event.addListener(that.map, 'mousemove', function(cursorPoint){
		if(!that.newMarker) { /* Create marker */
			that.newMarker = new google.maps.Marker(cursorPoint,{draggable:true, autoPan:false});
			that.map.addOverlay(that.newMarker);
		}
		that.newMarker.setLatLng(cursorPoint);
		if(that.ggl_lat) that.ggl_lat.value = cursorPoint.lat();
		if(that.ggl_lng) that.ggl_lng.value = cursorPoint.lng();		
		if(that.debug) google.maps.Log.write('Marker position '+cursorPoint.lat() + ':' + cursorPoint.lng());
	});
	
	/* Add map click event */
	var mapClick = google.maps.Event.addListener(that.map, 'click', function(overlay, cursorPoint){
		if(overlay) return;
		if(that.ggl_lat) that.ggl_lat.value = cursorPoint.lat();
		if(that.ggl_lng) that.ggl_lng.value = cursorPoint.lng();
		that.newMarker.setLatLng(cursorPoint);
		

		that.newMarker.setLatLng(cursorPoint);
		if(that.myGeocoder) {
			that.myGeocoder.hideTooltip();
		}
		if(mouseMove) {
			google.maps.Event.removeListener(mouseMove);
			mouseMove = null;
			/* Add dragend event */
			google.maps.Event.addListener(that.newMarker, 'dragend', function(position) {
				if(that.ggl_lat) that.ggl_lat.value = position.lat();
				if(that.ggl_lng) that.ggl_lng.value = position.lng();
				if(that.debug) google.maps.Log.write('Marker position '+position.lat() + ':' + position.lng());
			});
		}
	});
}

/*========================= GeoCoding ================================*/
/**
 * Main class for handling geocode request to google API
 * @param {Object} locale locales for searching locations
 */
function MyGeocoder(locale, div) {
	this.div = div;
	this.debug = DEBUG;									//Debugging for MyGeocoder                                   
	this.geocoder = new google.maps.ClientGeocoder();   //Client geocoder
	this.geocoder.setBaseCountryCode(locale);
	this.map = null;
	this.tooltip = null;
	this.timer = null;
	/* Geocode location parts */
	this.state = '';
 	this.city = '';
    this.street = '';
    this.street_number = '';
}

/**
 * Performs geocode search (if necessary multiple times with various combinations
 * of partial addresses).
 * @param {Boolean} focus if true focuses on first found location from geocode
 */
MyGeocoder.prototype.performSearch = function(focus) {
	if(this.geocoder) { /* Geocoder */
		/* First validation */
		if((this.state.length == 0) && (this.city.length == 0) && (this.street.length == 0)) {
			this.handleErrors(G_GEO_MISSING_QUERY);
			return;
		} else {
			this.searchLocation(this.state, this.city, this.street, focus);
		}
	} else {
    	if(this.debug) google.maps.Log.write('MyGeocoder DEBUG: ' + 'geocoder is null'); 
		that.showTooltip(that.map.map.getCenter(), 'Centrowanie mapy niedostępne');
		that.timer = setTimeout(function(){that.hideTooltip();}, 4000);
    }
}

/**
 * Main search engine function
 * @param {String} state
 * @param {String} city
 * @param {String} street
 * @param {Boolean} focus
 */
MyGeocoder.prototype.searchLocation = function(state, city, street, focus) {
	/* Construct query */
	var location = state+','+city+','+street;
	if(this.debug) google.maps.Log.write('Searching: '+location+' ...');	
	var that = this;
	/* ===== Perform search 1 ===== */
	this.geocoder.getLocations(location, 
		function(response) { /* Response handler */
			/* Check the response */
			if(response.Status.code == G_GEO_SUCCESS) {
				if(that.debug) google.maps.Log.write('MyGeocoder DEBUG: ' + response.Status.code);
				if(that.debug) google.maps.Log.write('Found: '+location+' ...');
				if(focus) { /* Center map */
					var zc = that.getZoomAndCenter(response.Placemark[0]);
					that.map.center(zc[0], zc[1]); //map.setCenter(zc[0], zc[1]);
					/* Set one click listener */
					//TODO
				}
			} else if(response.Status.code == G_GEO_UNKNOWN_ADDRESS) {
				/* ===== Perform search 2 ===== */
				if(city.length > 0) location = state+','+city;
				else location = state+','+street;
				that.geocoder.getLocations(location, 
					function(response){ /* Response handler */
						/* Check the response */
						if (response.Status.code == G_GEO_SUCCESS) {
							if (that.debug) google.maps.Log.write('MyGeocoder DEBUG: ' + response.Status.code);
							if (that.debug) google.maps.Log.write('Found: ' + location + ' ...');
							if (focus) { /* Center map */
								var zc = that.getZoomAndCenter(response.Placemark[0]);
								that.map.center(zc[0], zc[1]);//that.map.map.setCenter(zc[0], zc[1]);
								/* Set one click listener */
								//TODO
							}
						} else if(response.Status.code == G_GEO_UNKNOWN_ADDRESS) {
							/* ===== Perform search ===== 3*/
							location = state;
							that.geocoder.getLocations(location, 
								function(response){ /* Response handler */
									/* Check the response */
									if (response.Status.code == G_GEO_SUCCESS) {
										if (that.debug) google.maps.Log.write('MyGeocoder DEBUG: ' + response.Status.code);
										if (that.debug) google.maps.Log.write('Found: ' + location + ' ...');
										if (focus) { /* Center map */
											var zc = that.getZoomAndCenter(response.Placemark[0]);
											that.map.center(zc[0], zc[1]);//that.map.map.setCenter(zc[0], zc[1]);
											/* Set one click listener */
											//TODO
										}
									} else {
										that.handleErrors(response.Status.code);
										if(that.debug) google.maps.Log.write('MyGeocoder DEBUG: ' + 'not found!');
										that.showTooltip(that.map.map.getCenter(), "Nie można wycentrować,\n błędna lokalizacja!");
										that.timer = setTimeout(function(){that.hideTooltip();}, 4000);
									}
								}); /* ===== end of search 3 ===== */
						} else {
							that.handleErrors(response.Status.code);
							if(that.debug) google.maps.Log.write('MyGeocoder DEBUG: ' + 'not found!');
						}
					}); /* ===== end of search 2 ===== */
			} else {
				that.handleErrors(response.Status.code);
				if(that.debug) google.maps.Log.write('MyGeocoder DEBUG: ' + 'not found!');
			}
		}); /* ==== end of search 1 ==== */
}

/**
 * Decodes zoom and center for placemark from geocode response.
 * @param {Object} place
 * @return {Array}
 */
MyGeocoder.prototype.getZoomAndCenter = function(place) {
	var zoom = 4;
	if(place.AddressDetails.Accuracy <= 4) zoom = 11;
	else if(place.AddressDetails.Accuracy <= 5) zoom = 12;
	else if(place.AddressDetails.Accuracy <= 6) zoom = 13;
	else if(place.AddressDetails.Accuracy <= 7) zoom = 14; 
	else zoom = 15;
	var center = new google.maps.LatLng(place.Point.coordinates[1], place.Point.coordinates[0]);
	return [center, zoom];				
} 


/**
 * Display tooltip on a map object
 * @param {GLatLng} pos position to show tooltip
 * @param {String} text text of tooltip
 */
MyGeocoder.prototype.showTooltip = function(pos, text) { // Display tooltips
	if(this.timer) clearTimeout(this.timer);
	if(this.map) {
		if(!this.tooltip) {
			/* Add a div element for toolips */
 			this.tooltip = document.createElement("div");
 			this.tooltip.className="tooltip";
 			this.map.map.getPane(G_MAP_MARKER_PANE).appendChild(this.tooltip);	
		}
	
		this.tooltip.innerHTML = text;
	 	this.tooltip.style.display = "block";	 

		// Tooltip transparency specially for IE
 		if(typeof(this.tooltip.style.filter) == "string") {
			this.tooltip.style.filter = "alpha(opacity:70)";
		}

		var currtype = this.map.map.getCurrentMapType().getProjection();
		var point = currtype.fromLatLngToPixel(pos, this.map.map.getZoom());
		var offset= currtype.fromLatLngToPixel(this.map.map.getBounds().getSouthWest(),this.map.map.getZoom());
		if(this.debug) google.maps.Log.write((point.x - offset.x) + " : " +  (offset.y - point.y));
		var width = this.tooltip.clientWidth/2;
		var height = this.tooltip.clientHeight/2;
		var pos = new google.maps.ControlPosition(G_ANCHOR_TOP_LEFT, new google.maps.Size((point.x - offset.x - width), (offset.y - point.y - height))); 
		pos.apply(this.tooltip);
	}
	
}

/**
 * Hides tooltip
 */
MyGeocoder.prototype.hideTooltip = function() {
	if(this.map) {
		if (this.tooltip) {
			this.tooltip.style.display = "none";
			this.timer = null;
		}
	}
}

/**
 * Sets message to div element instead of loadin map
 * with result
 */
MyGeocoder.prototype.setMessage = function(text) {
	this.div.innerHTML = '<b>' + text + '</b>';
}
 

/**
 * Status codes hadler
 * @param {Object} statusCode
 */
MyGeocoder.prototype.handleErrors = function(statusCode) {
	if (statusCode == G_GEO_UNKNOWN_ADDRESS) {
		if (this.debug)	google.maps.Log.write("MyGeocoder DEBUG: " + "No corresponding geographic location could be found for one of the specified addresses. This may be due to the fact that the address is relatively new, or it may be incorrect.\nError code: " + statusCode);
		
	} else if (statusCode == G_GEO_SERVER_ERROR) { 
		if (this.debug) google.maps.Log.write("MyGeocoder DEBUG: " + "A geocoding or directions request could not be successfully processed, yet the exact reason for the failure is not known.\n Error code: " + statusCode);
	
	} else if (statusCode == G_GEO_MISSING_QUERY) { 
		if (this.debug)	google.maps.Log.write("MyGeocoder DEBUG: " + "The HTTP q parameter was either missing or had no value. For geocoder requests, this means that an empty address was specified as input. For directions requests, this means that no query was specified in the input.\n Error code: " + statusCode);
	
	} else if (statusCode == G_GEO_BAD_KEY) { 
		if (this.debug) google.maps.Log.write("MyGeocoder DEBUG: " + "The given key is either invalid or does not match the domain for which it was given. \n Error code: " + statusCode);
	
	} else if (statusCode == G_GEO_BAD_REQUEST) { 
		if (this.debug) google.maps.Log.write("MyGeocoder DEBUG: " + "A directions request could not be successfully parsed.\n Error code: " + statusCode);
	
	} else if (statusCode == G_GEO_MISSING_ADDRESS) { 
		if (this.debug)	google.maps.Log.write("MyGeocoder DEBUG: " + "The HTTP q parameter was either missing or had no value. For geocoder requests, this means that an empty address was specified as input. For directions requests, this means that no query was specified in the input.\n Error code: " + statusCode);
	
	} else if (statusCode == G_GEO_UNAVAILABLE_ADDRESS) { 
		if (this.debug)	google.maps.Log.write("MyGeocoder DEBUG: " + "The geocode for the given address or the route for the given directions query cannot be returned due to legal or contractual reasons.\n Error code: " + statusCode);
	
	} else if (statusCode == G_GEO_UNKNOWN_DIRECTIONS) { 
		if (this.debug) google.maps.Log.write("MyGeocoder DEBUG: " + "The GDirections object could not compute directions between the points mentioned in the query. This is usually because there is no route available between the two points, or because we do not have data for routing in that region.\n Error code: " + statusCode);
	
	} else if (statusCode == G_GEO_TOO_MANY_QUERIES) { 
		if (this.debug)	google.maps.Log.write("MyGeocoder DEBUG: " + "The given key has gone over the requests limit in the 24 hour period or has submitted too many requests in too short a period of time.\n Error code: " + statusCode);
		this.setMessage('Przekroczono 24h limit żądań dla map Google!');
		return;
	} else {
		if (this.debug) google.maps.Log.write("MyGeocoder DEBUG: " + "An unknown error occurred.\n Error code: " + statusCode);
	}
	this.setMessage('Brak dokładnych informacji o lokalizacji');	
}

/*========================= Utils ================================*/
/**
 * Trims string 
 * @param {String} str
 * @return {String} trimmmed string
 */
function trim(str) {
	return str.replace(/^\s+|\s+$/g,"");
}



