/* 
	GMapW class - wrapper over google maps that allows:
		* adding bulk markers
		* custom callbacks for map navigation and marker click
		* marker data
		* resolving addresses to coordinates (geocoding)
*/

/* Constructor */
function GMapW(containerId, center, options, viewportChangedCallback, markerMouseOverCallback, markerMouseOutCallback, markerClickedCallback) {

	// Map options
    var mapOptions = {
      zoom: 12,
      center: new google.maps.LatLng(center.lat, center.lng),
      mapTypeId: google.maps.MapTypeId.ROADMAP
    }

	for(var p in options) {
		mapOptions[p] = options[p];
	}

	// Create the map
	this.map = new google.maps.Map(document.getElementById(containerId), mapOptions);

	// Set an event flag
	this._event_initializing = true;
	
	// Listen for events
	var that = this;

	// Listen to "idle" events
	google.maps.event.addListener(this.map, "idle", function(e) {
		
		// Extract bounds
		var bounds = that.map.getBounds();
		
		var swLatLng = bounds.getSouthWest();
		var neLatLng = bounds.getNorthEast();
		
		var swLat = swLatLng.lat();
		var swLng = swLatLng.lng();
		
		var neLat = neLatLng.lat();
		var neLng = neLatLng.lng();
		
		// Callback - only if defined and not suppressed by other events
		if(viewportChangedCallback) {
			if(that._event_zoom_changed) {
				that._event_zoom_changed = false;
			} else if(that._event_set_center) {
				that._event_set_center = false;
			} else if(that._event_initializing) {
				that._event_initializing = false;
			} else {
				viewportChangedCallback([swLat, swLng, neLat, neLng]);
			}
		}
		
	});
	
	// Listen to "zoom_changed" events
	google.maps.event.addListener(this.map, "zoom_changed", function(e) {
		that._event_zoom_changed = true;
	});
	
	// Store marker callbacks
	this.markerClickedCallback = markerClickedCallback;
	this.markerMouseOverCallback = markerMouseOverCallback;
	this.markerMouseOutCallback = markerMouseOutCallback;
	
	// Initialize markers array
	this.markers = [];

	// Don't use letters by default
	this.useLetters = false;
	
};  // GMapW

/* Fires the marker click event. */
GMapW.prototype.fireMarkerClick = function(marker) {
	if(this.markerClickedCallback) {
		this.markerClickedCallback(marker._gmapw_info);
	}
};

/* Fires the marker mouseover event. */
GMapW.prototype.fireMarkerMouseOver = function(marker) {
	if(this.markerMouseOverCallback) {
		this.markerMouseOverCallback(marker._gmapw_info);
	}
};

/* Fires the marker mouseoout event. */
GMapW.prototype.fireMarkerMouseOut = function(marker) {
	if(this.markerMouseOutCallback) {
		this.markerMouseOutCallback(marker._gmapw_info);
	}
};

/* "Private" utility method for attaching a mouseover listener to the given marker */
GMapW._attachMarkerMouseOverListener = function(marker, gmapw) {

	var markerMouseOverListener = google.maps.event.addListener(marker, "mouseover", function() {
		gmapw.fireMarkerMouseOver(marker);
	});

	return markerMouseOverListener;

};  // _attachMarkerMouseOverListener

/* "Private" utility method for attaching a mouseout listener to the given marker */
GMapW._attachMarkerMouseOutListener = function(marker, gmapw) {

	var markerMouseOutListener = google.maps.event.addListener(marker, "mouseout", function() {
		gmapw.fireMarkerMouseOut(marker);
	});

	return markerMouseOutListener;

};  // _attachMarkerMouseOutListener

/* "Private" utility method for attaching a mouseout listener to the given marker */
GMapW._attachMarkerClickListener = function(marker, gmapw) {

	var markerClickListener = google.maps.event.addListener(marker, "click", function() {
		gmapw.fireMarkerClick(marker);
	});

	return markerClickListener;

};  // _attachMarkerClickListener

/* Hides the markers and deletes them. */
GMapW.prototype.deleteMarkers = function() {

	for(var i = 0; i < this.markers.length; i++) {
	
		// Remove listeners
		google.maps.event.removeListener(this.markers[i]._gmapw_info.markerClickListener);
		google.maps.event.removeListener(this.markers[i]._gmapw_info.markerMouseOverListener);
		google.maps.event.removeListener(this.markers[i]._gmapw_info.markerMouseOutListener);
		
		// Remove from map
		this.markers[i].setMap(null);
	}
	
	// Delete the markers
	this.markers.length = 0;
	
};  // deleteMarkers

/* Compares the given markerInfo with the given marker and returns true if the id and the coordinates are the same or false otherwise. */
GMapW.prototype.sameMarker = function(markerInfo, marker) {

	// We suppose the markers are the same
	var sameMarker = true;

	// Different id, lat or lng
	if((markerInfo.data.id != marker._gmapw_info.data.id) || (markerInfo.lat != marker.getPosition().lat()) || (markerInfo.lng != marker.getPosition().lng())) {
		sameMarker = false;
	}
	
	// Return result
	return sameMarker;
	
};  // sameMarker

GMapW.getGroup = function(markerInfo, groups) {

	var group;
	var found = false;

	for(var i = 0; i < groups.length; i++) {
		if(groups[i].lat == markerInfo.lat && groups[i].lng == markerInfo.lng) {
			group = groups[i];
			group.isNew = false;
			found = true;
			break;
		}
	}
	
	if(!found) {
		group = [];
		group.isNew = true;
		group.lat = markerInfo.lat;
		group.lng = markerInfo.lng;
	}
	
	return group;
	
};  // getGroup

GMapW.groupMarkersInfo = function(markersInfo) {

	var groups = [];

	for(var i = 0; i < markersInfo.length; i++) {
		markersInfo[i].letterIndex = i;
		var group = GMapW.getGroup(markersInfo[i], groups);
		if(group.isNew) {
			groups.push(group);
		}
		group.push(markersInfo[i]);
	}
	
	return groups;
	
};  // groupMarkersInfo

/* Letters for the markers. */
GMapW.MARKER_LETTERS = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J"];

GMapW.MARKER_ANCHORS = {
	"0":   [27, 49],
	"20":  [ 6, 49],
	"40":  [ 0, 44],
	"60":  [ 1, 35],
	"80":  [ 0, 24],
	"280": [50, 24],
	"300": [48, 35],
	"320": [39, 39],
	"340": [29, 46]
};

GMapW.MARKER_ANGLES = [
	[],
	[0],
	[340, 20],
	[320, 0, 40],
	[300, 340, 20, 60],
	[280, 320, 0, 40, 80],
	[280, 300, 340, 20, 60, 80],
	[280, 300, 320, 0, 40, 60, 80],
	[280, 300, 320, 340, 20, 40, 60, 80],
	[280, 300, 320, 340, 0, 20, 40, 60, 80],
	[280, 300, 320, 340, 0, 0, 20, 40, 60, 80]
];

/*
	This method sets the given markers by the given markers info array.
	A marker info object has the following atributes:
		* lat
		* lng
		* tooltip
		* data
*/
GMapW.prototype.setMarkers = function(markersInfo) {

	// When setting the markers (WITH LETTERS) there are basically two situations:
	// * when the markers are identical -> we just update the data (might be fresh)
	// * when the markers are different -> when we remove all the markers and plot the new ones

	// TODO: This sould be enhanced and determine what to keep and what to remove
		
	// Delete the old markers
	this.deleteMarkers();

	if(this.useLetters) {
		this._setMarkersUsingLetters(markersInfo);
	} else {
		this._setMarkersSimple(markersInfo);
	}
	
};  // setMarkers

// TODO: Refactor & unify the following two methods

GMapW.prototype._setMarkersUsingLetters = function(markersInfo) {

	// Groups the markers based on lat/lng
	var groups = GMapW.groupMarkersInfo(markersInfo);
	
	// For each group
	for(var i = 0; i < groups.length; i++) {
	
		// For each marker
		for(var j = 0; j < groups[i].length; j++) {
			
			// Get the marker info
			var markerInfo = groups[i][j];
			
			// Get marker letter
			var markerLetter = GMapW.MARKER_LETTERS[markerInfo.letterIndex];
			
			// Get marker angle
			var markerAngle = GMapW.MARKER_ANGLES[groups[i].length][j];
			
			var markerImage = new google.maps.MarkerImage("img/gmap/markers/marker_" + markerLetter + "_" + markerAngle + ".png");
			var anchor = GMapW.MARKER_ANCHORS["" + markerAngle];
			markerImage.anchor = new google.maps.Point(anchor[0], anchor[1]);
			
			// Setup marker
			var marker = new google.maps.Marker({
							position: new google.maps.LatLng(markerInfo.lat, markerInfo.lng),
							draggable: false,
							title: markerInfo.tooltip,
							icon: markerImage
						});
			marker._gmapw_info = markerInfo;
			
			// Plot on map
			marker.setMap(this.map);
			
			// Listen for events
			var markerClickListener = GMapW._attachMarkerClickListener(marker, this);
			var markerMouseOverListener = GMapW._attachMarkerMouseOverListener(marker, this);
			var markerMouseOutListener = GMapW._attachMarkerMouseOutListener(marker, this);
			
			// Store listeners
			marker._gmapw_info.markerClickListener = markerClickListener;
			marker._gmapw_info.markerMouseOverListener = markerMouseOverListener;
			marker._gmapw_info.markerMouseOutListener = markerMouseOutListener;
			
			// Store the marker
			this.markers.push(marker);
				
		}  // for each marker
		
	}  // for each group

};  // _setMarkersUsingLetters

GMapW.prototype._setMarkersSimple = function(markersInfo) {

	// For each markerInfo
	for(var i = 0; i < markersInfo.length; i++) {

		// Get marker info
		var markerInfo = markersInfo[i];

		// Setup marker
		var marker = new google.maps.Marker({
						position: new google.maps.LatLng(markerInfo.lat, markerInfo.lng),
						draggable: false,
						title: markerInfo.tooltip
					});
		marker._gmapw_info = markerInfo;
		
		// Plot on map
		marker.setMap(this.map);
		
		// Listen for events
		var markerClickListener = GMapW._attachMarkerClickListener(marker, this);
		var markerMouseOverListener = GMapW._attachMarkerMouseOverListener(marker, this);
		var markerMouseOutListener = GMapW._attachMarkerMouseOutListener(marker, this);
		
		// Store listeners
		marker._gmapw_info.markerClickListener = markerClickListener;
		marker._gmapw_info.markerMouseOverListener = markerMouseOverListener;
		marker._gmapw_info.markerMouseOutListener = markerMouseOutListener;
		
		// Store the marker
		this.markers.push(marker);
		
	}  // for each marker

}; // _setMarkersSimple

/* Sets the center of the map to the given coordinates. */
GMapW.prototype.setCenter = function(lat, lng) {
	this._event_set_center = true;
	this.map.setCenter(new google.maps.LatLng(lat, lng));
};

GMapW.prototype.getMarkerById = function(id) {

	for(var i = 0; i < this.markers.length; i++) {
		if(this.markers[i]._gmapw_info.data.id == id) {
			return this.markers[i];
		}
	}
	
	return null;
	
};

GMapW.prototype.animateMarker = function(id) {
	var marker = this.getMarkerById(id);
	if(marker) {
		marker.setAnimation(google.maps.Animation.BOUNCE);
	}
};

GMapW.prototype.clearMarkerAnimation = function(id) {
	var marker = this.getMarkerById(id);
	if(marker) {
		marker.setAnimation(null);
	}
};

/* This is a static method that returns a singleton Google Maps Geocoder. */
GMapW.getGeocoder = function() {

	if(GMapW.geocoder) {
		// no op
	} else {
		GMapW.geocoder = new google.maps.Geocoder();
	}
	
	return GMapW.geocoder;
	
};

/* 
	This is a static method for resolving an address using the Google Maps Geocoder API.
	An address object given as a parameter to this method must have the following attributes:
		* street
		* no
		* city
		* province
		* postalCode
	In order to solve the address it uses the following format:
		"street, city, no, postal code, province, country"
	
	TODO: country is hardcoded to "Spain" for now
	
*/
GMapW.resolveAddress = function(address, callback, errorCallback) {

	// Concatenate the address
	var concatenatedAddress = "";
	if(address.street) {
		concatenatedAddress = concatenatedAddress + address.street + ", ";
	}
	if(address.city) {
		concatenatedAddress = concatenatedAddress + address.city + ", ";
	}
	if(address.no) {
		concatenatedAddress = concatenatedAddress + address.no + ", ";
	}
	if(address.postalCode) {
		if(address.postalCode.length === 5 && !isNaN(address.postalCode)) {
			concatenatedAddress = concatenatedAddress + address.postalCode + ", ";
		}
	}
	if(address.province) {
		concatenatedAddress = concatenatedAddress + address.province + ", ";
	}
	concatenatedAddress = concatenatedAddress + "Spain"
	
	// Geocode it
	GMapW.getGeocoder().geocode({address: concatenatedAddress, region: "ES"}, function(results, status) {
		if (status == google.maps.GeocoderStatus.OK) {
			callback(results[0].geometry.location.lat(), results[0].geometry.location.lng());
		} else {
			errorCallback(status);
		}
	});

};  // resolveAddress

