/**
 * The MarkerManager is a mediator that intercepts all the "addOverlay" calls that 
 * are usually made on a GoogleMap instance directly. It then sorts markers into groups, 
 * to prevent massive amounts of markers to show simultaneously, and bog things down.
 */
ArenaMap.Manager = (function($){

	var MANAGER_MAX_MARKERS =  10, // max visible markers before grouping starts at all. This does NOT mean that no more than 10 markers are ever visible.
		MANAGER_MAX_PERGRID =   2, // max visible markers before a grid area groups markers
		MANAGER_GRIDSIZE    =  96; // grid size

	function Manager(map, settings) {
		this.map = map;
		this.markers = [];
		this.gridAreas = [];
		this.types = {};

		var set = settings || {};

		this.maxVisible = set.maxVisible || MANAGER_MAX_MARKERS;
		this.gridSize = set.gridSize || MANAGER_GRIDSIZE;
		this.clickHandler = set.clickHandler;

		GEvent.bind(map, 'moveend', this, this.update);

		this.settings = set;
	}

	Manager.prototype = {
		getOverlays:function(options) {
			return this.markers;
		},

		addToMap:function(overlay) { this.map.addOverlay(overlay); },
		removeFromMap:function(overlay) { this.map.removeOverlay(overlay); },
		
		addOverlay:function(overlay, groupable){
			overlay._groupable = groupable;
			this.markers.push(overlay);
		},

		removeOverlay:function(overlay) {
			var markers = this.getOverlays(), l = markers.length;
			for(var i=0; i<l; i++) {
				if(markers[i] == overlay) {
					this.removeFromMap(markers[i]);
					markers.splice(i, 1);
					break;
				}
			}
		},	

		clearOverlays:function() {
			this.map.clearOverlays();
			this.markers = [];
		},

		setTypeEnabled:function(type, enabled) {
			this.types[type] = enabled;
		},

		update:function() {
			var zoom = this.map.getZoom(),
				zoomChanged = zoom != this.currentZoom;
			
			this.gridAreas = this.getGridAreas(zoomChanged);
			
			var	mapBounds = this.map.getBounds(),
				markers = this.getOverlays(),
				visibleMarkers = [], 
				l = markers.length;

			/**
			 * Create an array of visible markers, remove all others from the map.
			 */
			for(var i=0; i<l; i++) {
				var marker = markers[i],
					point = marker.getLatLng();
					markerBounds = marker.hasBounds? marker.getBounds() : null;

				var enabled = true;
				if(marker.type) {
					enabled = (this.types[marker.type] === false)? false : true;
				}

				if(enabled && mapBounds.containsLatLng(point) && (!markerBounds || mapBounds.containsBounds(markerBounds))) {

					if(marker._groupable) {
						visibleMarkers.push(marker);
						if(marker.visible()) {
							marker.hide();
						}
					} else {
						if(!marker.visible()) {
							this.addToMap(marker);
						}	marker.show(true);
					}
					

				} else {
					if(marker.visible()) {
						this.removeFromMap(marker);
					}
				}
			}

			var l = visibleMarkers.length, 
				ml = this.gridAreas.length;

			/**
			 * If there are more visible markers than allowed by the manager, start grouping them.
			 */
			if(l > this.maxVisible) {
				for(var i=0; i<l; i++) {
					var marker = visibleMarkers[i],
						point = marker.getLatLng();
					
					/**
					 * Sort markers on the grid
					 */
					for(var j=0; j<ml; j++) {
						var group = this.gridAreas[j];
						if(group.containsLatLng(point)) {
							group.addMarker(marker);
							break;
						}
					}
				}

				/**
				 * check all groups for contained markers
				 */
				for(var i=0; i<ml; i++) {
					var group = this.gridAreas[i],
						gridMarker = group.getMarker(),
						markers = group.getMarkers();

					if(markers.length == 0) {
						continue;
					}
					
					/**
					 * If a group contains 2 (default) or more markers, group them
					 */
					if(markers.length > MANAGER_MAX_PERGRID) {
						if(!gridMarker) {
							gridMarker = group.createMarker(this.GridMarker);
							var click = this.settings.clickHandler,
								hover = this.settings.hoverHandler,
								leave = this.settings.leaveHandler;
							
							click && gridMarker.click(click);
							hover && gridMarker.hover(hover, leave);
							
							this.addToMap(gridMarker);
						}						
						
						gridMarker.show(true);
					
					/**
					 * Otherwise, just show the marker
					 */
					} else {
						if(gridMarker) {
							/**
							 * Groupmarkers are hidden for possible reuse.
							 */
							gridMarker.hide();
						}

						for (var j=0; j<markers.length; j++) {
							var marker = markers[j];
							if(!marker.visible()) {
								this.addToMap(marker);
							}	marker.show(true);
						}
					}
				}

			/**
			 * If there are less than the allowed amount of markers, show them all
			 */
			} else {
				for(var i=0; i<l; i++) {
					var marker = visibleMarkers[i];
					if(!marker.visible()) {
						this.addToMap(marker);
					}
					marker.show(true);
				}
			}

			this.currentZoom = zoom;
		},

		/**
		 * Returns the grid areas based on the current gridsize
		 */
		getGridAreas:function(zoomChanged) {			
			/**
			 * If zoom changed, make a new list of groups. Otherwise, get the active ones first.
			 */
			var grids = this.filterGridAreas(zoomChanged);
			
			var x, y, sw, ne, d = this.gridSize,
				size = this.map.getSize(),
				cols = Math.ceil(size.width / d),
				rows = Math.ceil(size.height / d);

			/**
			 * Apply the grid
			 */
			for(var i=0; i<cols; i++) {
				for(var j=0; j<rows; j++) {
					x = i * d;
					y = j * d;
					sw = this.map.fromContainerPixelToLatLng(new GPoint(x, y + d));
					ne = this.map.fromContainerPixelToLatLng(new GPoint(x + d, y));

					grids.push(
						new ArenaMap.Markers.GridArea(sw, ne)
					);
				}
			}

			return grids;
		},

		/**
		 * Groups that contained markers the last time the view was updated get priority for the next update.
		 * This prevents a completely new view of groupmarkers from being created when panning the map.
		 */
		filterGridAreas: function(zoomChanged) {
			var activeGrids = [],
				l = this.gridAreas.length;

			var bounds = this.map.getBounds();

			for(var i=0; i<l; i++) {
				var group = this.gridAreas[i],
					marker = group.getMarker(),
					markers = group.getMarkers();

				group.removeMarkers();

				/**
				 * if zoom didn't change, the area is still visible, and it contained markers; remember it
				 */
				if(!zoomChanged && bounds.intersects(group) && markers.length > 0) {
					activeGrids.push(group);
					marker && marker.hide();

				/**
				 * otherwise, don't
				 */
				} else if(marker) {
					this.removeFromMap(marker);
				}
			}

			return activeGrids;
		},

		/**
		 * Get GBounds object containing all current markers
		 */
		getBounds:function() {
			var bounds = new GLatLngBounds();
			var markers = this.getOverlays();
			for(var i=0; i<markers.length; i++) {
				var marker = markers[i];
				var b = marker.getBounds? marker.getBounds() : null;
				if(b) {
					bounds.extend(b.getNorthEast());
					bounds.extend(b.getSouthWest());
				} else {
					bounds.extend(marker.getLatLng());
				}
			}
			return bounds;
		}
	};
	

	return Manager;

})(jQuery);