/*
 * Fluster2 0.1.1
 * Copyright (C) 2009 Fusonic GmbH
 *
 * This file is part of Fluster2.
 *
 * Fluster2 is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * Fluster2 is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
 */

/**
 * Creates a new Fluster to manage masses of markers in a Google Maps v3.
 *
 * @constructor
 * @param {google.maps.Map} the Google Map v3
 * @param {bool} run in debug mode or not
 */
function Fluster3(_map, defaultStyles, _initialBounds, _debug)
{
	// Private variables
	var map = _map;
	var projection; // = new Fluster3ProjectionOverlay(map);
	var me = this;
	//this is where the clusters are stored.  
	//its indexed by zoom level.
	this.clusters =  new Object();
	var markersLeft = new Object();
	
	// Properties
	this.debugEnabled = _debug;
	this.initialBounds = _initialBounds;
	this.gridSize = 60;
	this.markers = new Object();
	this.currentZoomLevel = -1;
	this.styles = defaultStyles;  //defined in mapcontrolconfig.php
	this.map = _map;

	this.inUse = false;
	
	// Timeouts
	var zoomChangedTimeout = null;
	
	/**
	 * Create clusters for the current zoom level and assign markers.
	 */
	//function createClusters()
	this.createClusters = function()
	{	
		var zoom = map.getZoom();
		
		if(me.clusters[zoom])
		{
			me.debug('Clusters for zoom level ' + zoom + ' already initialized.');
		}
		else
		{
			// Create clusters array
			var clustersThisZoomLevel = new Array();
			
			// Set cluster count
			var clusterCount = 0;
			
			// Walk all markers
			for(var key in me.markers)
			{
				if(key == null || me.markers[key] == null)
				{
					continue;	
				}
				var marker = me.markers[key].marker;
				var categoryId = me.markers[key].categoryId;
				var markerPosition = marker.getPosition();
				var done = false;
				
				// Find a cluster which contains the marker
				for(var j = clusterCount - 1; j >= 0; j--)
				{
					var cluster = clustersThisZoomLevel[j];
					if(cluster.contains(markerPosition) 
						&& cluster.getCategoryId() == categoryId)
					{
						cluster.addMarker(marker);
						done = true;
						break;
					}
				}
				
				if(!done)
				{
					// No cluster found, create a new one
					var cluster = new Fluster3Cluster(me, marker, categoryId);
					clustersThisZoomLevel.push(cluster);
					
					// Increase cluster count
					clusterCount++;
				}
			}
			
			me.clusters[zoom] = clustersThisZoomLevel;
					
			me.debug('Initialized ' + me.clusters[zoom].length + ' clusters for zoom level ' + zoom + '.');
		}
		var previousZoomLevel = me.currentZoomLevel;
		// Set current zoom level
		me.currentZoomLevel = zoom;
		 
		// Hide markers of previous zoom level
		if(me.clusters[previousZoomLevel])
		{
			for(var i = 0; i < me.clusters[previousZoomLevel].length; i++)
			{
				me.clusters[previousZoomLevel][i].hide();
			}
		}
		// Show clusters
		me.showClustersInBounds();
	}
	
	/**
	 * Displays all clusters inside the current map bounds.
	 */
	this.showClustersInBounds = function(mapBounds)
	{
		if (mapBounds == undefined) {
			var mapBounds = map.getBounds();
		}
		//we need to "fix up" the cluster locations so that clusters for different 
		//categories don't overlap
		//to start, we need a list of all the clusters and all the unclustered markers.
		if(me.clusters[me.currentZoomLevel] == null)
		{
			return;	
		}
		
		for(var i = 0; i < me.clusters[me.currentZoomLevel].length; i++)
		{
			var cluster = me.clusters[me.currentZoomLevel][i];
			if(mapBounds.contains(cluster.getPosition()))
			{
				cluster.show();
			}
		}
	}
	
	/**
	 * Callback which is executed 500ms after the map's zoom level has changed.
	 */
	this.zoomChanged = function()
	{
		//alert(me.map.getZoom());
		window.clearInterval(zoomChangedTimeout);
		//zoomChangedTimeout = window.setTimeout(createClusters, 500);
		//createClusters();
	};
	
	/**
	 * Returns the map assigned to this Fluster.
	 */
	this.getMap = function()
	{
		return map;
	};
	
	/**
	 * Returns the map projection.
	 */
	this.getProjection = function()
	{
		return projection.getP();
	};
	
	/**
	 * Prints debug messages to console if debugging is enabled.
	 */
	this.debug = function(message)
	{
		if(me.debugEnabled)
		{
			//console.log('Fluster2: ' + messa	ge);
		}
	};
	
	/**
	 * Adds a marker to the Fluster.
	 */
	this.addMarker = function(_marker, _categoryId, locationId)
	{
		
		me.markers[locationId] = new Object();
		me.markers[locationId].marker =  _marker;
		me.markers[locationId].categoryId = _categoryId;
	
	};
	
	this.clearMarkers = function()
	{
		
		//delete the clusters
		//go through each of the zoom levels
		for (var zoomIndex = 0; zoomIndex < 16; zoomIndex++)
		{
			
			//loop through all the clusters at the zoom level
			if(me.clusters[zoomIndex] != null)
			{
				for(var index = 0; index < me.clusters[zoomIndex].length; index++)
				{
					me.clusters[zoomIndex][index].debugHideMarkers();
					me.clusters[zoomIndex][index].remove();
					me.clusters[zoomIndex][index].markers.length = 0;
				}
				delete me.clusters[zoomIndex];				
			}
		}
		//clear the local list of markers
		me.markers = new Object();
		me.inUse = false;
		
	}
	
	this.removeMarker = function(id)
	{
		if(me.markers.hasOwnProperty(id) == null
			|| me.markers[id] == null )
		{
			return;
		}
		var marker = me.markers[id].marker;
		
	
		//remove the marker from the main markers list
		me.markers[id]= null;
		delete me.markers[id];
		
		//remove the marker from all the clusters
		for(var zoomLevel = 0; zoomLevel < 16; zoomLevel++)
		{
			var cluster;
			cluster = this.getClusterForMarker(marker, zoomLevel);
			if (cluster != null)
			{
				for(var index = 0; index < cluster.markers.length; index++)
				{
					if(cluster.markers[index] == marker)
					{   
						cluster.markers.splice(index, 1);
						var count = cluster.getMarkerCount();
						index--;
						//update the cluster icon
						if(cluster.marker != null)
						{
							cluster.marker.div.innerHTML = "x" + count;
						}
						//if we now have only one marker, we show it and 
						//hide the cluster icon
						if(count == 1)
						{
							if(cluster.marker != null)
							{
								cluster.marker.hide();
							}
							cluster.markers[0].setMap(this.map);
							cluster.markers[0].setVisible(true);
							
						}
						else if(!count)
						{
							if(cluster.marker != null)
							{
								cluster.marker.hide();
							}
						}
					}//end if this is our marker
				}//end for all the markers in this cluster
			}//end if the cluster is not null
			
		}//end for all the zoom levels
		
	}//end removeMarker()
	
	this.addMarkerAfterDraw = function(marker, categoryId, locationId)
	{
		//add the marker to the main marker list
		this.addMarker(marker, categoryId, locationId);
		
		var currentDisplayedCluster;
		
		//add the marker into the relevant clusters and update their
		//icon to reflect the new number of markers in them
		for(var zoomLevel = 0; zoomLevel < 16; zoomLevel++)
		{
			if(me.clusters[zoomLevel] == null)
			{
				continue;	
			}
			var createNewCluster = true;
			for(var index = 0; index < me.clusters[zoomLevel].length; index++)
			{
				if(me.clusters[zoomLevel][index] == null)
				{
					continue;
				}
				
				if(me.clusters[zoomLevel][index].getCategoryId() == categoryId
					&& me.clusters[zoomLevel][index].contains(marker.getPosition()))
					{
						//add the marker to the cluster
						me.clusters[zoomLevel][index].addMarker(marker);
						//update the icon
						if(me.clusters[zoomLevel][index].marker != null)
						{
							me.clusters[zoomLevel][index].marker.div.innerHTML = "x" + me.clusters[zoomLevel][index].getMarkerCount();
						}
						createNewCluster = false;
						if(zoomLevel == this.map.getZoom())
						{
							me.clusters[zoomLevel][index].show();
						}
						//we need to break or we could end up adding
						//the marker to more than one cluster
						//the bounds often overlap...
						break;
					}
			
			}//end for the clusters at each zoom level
			if(createNewCluster == true)
			{
					// No cluster found, create a new one
					var cluster = new Fluster3Cluster(this, marker, categoryId);
					me.clusters[zoomLevel].push(cluster);
					cluster.show();
					
			
			}//end if
			   
		}//end for zoom level

		
		
	} //end add marker after draw
	
	
	this.getClusterForMarker = function(marker, zoomLevel)
	{
		var currentClusters = me.clusters[zoomLevel];
		if(currentClusters == null)
		{
			//we have no clusters at this zoom level
			return null;	
		}
		
		for(var index = 0; index < currentClusters.length; index++)
		{
			var cluster = currentClusters[index];
			var markers = cluster.markers;
			for(var markerIndex = 0; markerIndex < markers.length; markerIndex++)
			{
				if(markers[markerIndex] == marker)
				{
					//return this marker's cluster
					return currentClusters[index];
				}//end if
			}//end for marker list
		}//end for currentClusters
	}//end getClusterForMarker
		this.shouldMarkerBeDisplayed = function(marker)
	{
		//get the marker's cluster
		var cluster = this.getClusterForMarker(marker, this.map.getZoom());
		if(cluster.markers.length == 1 || this.map.getZoom() == cluster.maximumZoom)
		{
			return true;
		}
		else
		{
			return false;	
		}
	}
	this.isMarkerInACluster = function(locationId)
	{
		if(this.markers.hasOwnProperty(locationId) && this.markers[locationId] != null)
		{
			return true;	
		}
		
		return false;
	}
	
	
	/**
	 * Returns the currently assigned styles.
	 * the categoryId is the category og the marker location
	 */
	this.getStyles = function(categoryId)
	{
		return me.styles[categoryId];
	};
	
	/*
		locationId is in the format "id" + number
		it returns - 1 if the location Id is not in the 
		fluster Object.
	*/
	this.getMarkerCategory = function(locationId)
	{
		if(me.markers.hasOwnProperty(locationId) && me.markers[locationId] != null)
		{
			return me.markers[locationId].categoryId;	
		}
		else
		{
			return - 1;	
		}
	}
	
	this.updateCategoryId = function(marker, categoryId, locationId)
	{
		var oldcategoryId = me.getMarkerCategory(locationId);
		if(oldcategoryId != -1
			&& oldcategoryId != categoryId)
		{
			me.removeMarker(locationId);
			me.addMarkerAfterDraw(marker, categoryId, locationId);
		}
	} //end updateMarkerId
	
	/**
	 * Sets map event handlers and setup's the markers for the current
	 * map state.
	 */
	this.initialize = function()
	{	
		this.inUse = true;
		// Add event listeners
		//google.maps.event.addListener(map, 'zoom_changed', this.zoomChanged);
		google.maps.event.addListener(map, 'dragend', this.showClustersInBounds);
		
		//createClusters is called when the ovelay is drawn
		projection = new Fluster3ProjectionOverlay(map, this);
		
		//window.setTimeout(createClusters, 1000);
		//createClusters();
		
	};
}
