var Clusterer = Class.create();
Clusterer.prototype = {

	// ******************************************************************************
	// Constants
	// ******************************************************************************
	Version : '0.1',
	listContainer : 'ajaxContainer', // ??


	// ******************************************************************************
	// vars
	// ******************************************************************************
	map : null,
		markers : [],
		clusters : [],
		timeout : null,
		currentZoomLevel : null,
		bubble : null,

	maxVisibleMarkers : 150,
	gridSize : 5,
	minMarkersPerCluster : 5,
	maxLinesPerInfoBox : 10,

	icon : {},


	// ******************************************************************************
	// Constructor
	// ******************************************************************************
	initialize: function(map, icon, bubble, options) {





// Augment GMarker so it handles markers that have been created but
// not yet addOverlayed.

GMarker.prototype.setMap = function ( map )
		{
		this.map = map;
		};

GMarker.prototype.addedToMap = function ()
		{
		this.map = null;
		};

GMarker.prototype.origOpenInfoWindow = GMarker.prototype.openInfoWindow;
GMarker.prototype.openInfoWindow = function ( node, opts )
		{
		if ( this.map != null )
	return this.map.openInfoWindow( this.getPoint(), node, opts );
		else
	return this.origOpenInfoWindow( node, opts );
		};

GMarker.prototype.origOpenInfoWindowHtml = GMarker.prototype.openInfoWindowHtml;
GMarker.prototype.openInfoWindowHtml = function ( html, opts )
		{
		if ( this.map != null )
	return this.map.openInfoWindowHtml( this.getPoint(), html, opts );
		else
	return this.origOpenInfoWindowHtml( html, opts );
		};

GMarker.prototype.origOpenInfoWindowTabs = GMarker.prototype.openInfoWindowTabs;
GMarker.prototype.openInfoWindowTabs = function ( tabNodes, opts )
		{
		if ( this.map != null )
	return this.map.openInfoWindowTabs( this.getPoint(), tabNodes, opts );
		else
	return this.origOpenInfoWindowTabs( tabNodes, opts );
		};

GMarker.prototype.origOpenInfoWindowTabsHtml = GMarker.prototype.openInfoWindowTabsHtml;
GMarker.prototype.openInfoWindowTabsHtml = function ( tabHtmls, opts )
		{
		if ( this.map != null )
	return this.map.openInfoWindowTabsHtml( this.getPoint(), tabHtmls, opts );
		else
	return this.origOpenInfoWindowTabsHtml( tabHtmls, opts );
		};

GMarker.prototype.origShowMapBlowup = GMarker.prototype.showMapBlowup;
GMarker.prototype.showMapBlowup = function ( opts )
		{
		if ( this.map != null )
	return this.map.showMapBlowup( this.getPoint(), opts );
		else
	return this.origShowMapBlowup( opts );
		};








			this.map = map;
			this.icon = icon;
			this.currentZoomLevel = map.getZoom();
			this.bubble = bubble;


		Object.extend(this, options);


			GEvent.addListener( this.map, 'zoomend', this.Display.bind(this));
			GEvent.addListener( this.map, 'moveend', this.Display.bind(this));
			GEvent.addListener( this.map, 'infowindowclose', this.PopDown.bind(this));

		},



	// Call this to add a marker.
	AddMarker : function ( marker, title )
	{
			for ( var i = 0; i < this.markers.length; ++i ) {
			if ( this.markers[i] == marker ){
				return false;
			}
			}


			if ( marker.setMap != null )
				marker.setMap( this.map );

			marker.title = title;
			marker.onMap = false;
			this.markers.push( marker );
			this.DisplayLater();
		},



	// Call this to remove a marker.
	RemoveMarker : function ( marker )
		{
		for ( var i = 0; i < this.markers.length; ++i )
	if ( this.markers[i] == marker )
			{
			if ( marker.onMap )
		this.map.removeOverlay( marker );
			for ( var j = 0; j < this.clusters.length; ++j )
		{
		var cluster = this.clusters[j];
		if ( cluster != null )
				{
				for ( var k = 0; k < cluster.markers.length; ++k )
			if ( cluster.markers[k] == marker )
					{
					cluster.markers[k] = null;
					--cluster.markerCount;
					break;
					}
				if ( cluster.markerCount == 0 )
			{
			this.ClearCluster( cluster );
			this.clusters[j] = null;
			}
				else if ( cluster == this.poppedUpCluster )
			this.RePop();
				}
		}


			this.markers[i].inCluster = false;
			this.markers[i].onMap = null;
			this.markers.splice(i,1);


			break;
			}
		this.DisplayLater();
		},


	DisplayLater : function ()
		{

			if ( this.timeout != null )
			clearTimeout( this.timeout );

			this.timeout = setTimeout( this.Display.bind(this), 50 );
		},

	clearClusters : function()
	{

		// When the zoom level changes, we have to remove all the clusters.
		for ( i = 0; i < this.clusters.length; ++i )
				if ( this.clusters[i] != null )
			{
				this.ClearCluster( this.clusters[i] );
				this.clusters[i] = null;
			}
		this.clusters.length = 0;
	},


	Display : function ()
		{
			var i, j, marker;

			clearTimeout( this.timeout );


			var newZoomLevel = this.map.getZoom();
			if ( newZoomLevel != this.currentZoomLevel )
		{
			this.clearClusters();
			this.currentZoomLevel = newZoomLevel;
		}

			// Get the current bounds of the visible area.
			var bounds = this.map.getBounds();

			// Expand the bounds a little, so things look smoother when scrolling
			// by small amounts.
			var sw = bounds.getSouthWest();
			var ne = bounds.getNorthEast();
			var dx = ne.lng() - sw.lng();
			var dy = ne.lat() - sw.lat();
			if ( dx < 300 && dy < 150 )
		{
			dx *= 0.10;
			dy *= 0.10;
			bounds = new GLatLngBounds(
					new GLatLng( sw.lat() - dy, sw.lng() - dx ),
					new GLatLng( ne.lat() + dy, ne.lng() + dx )
					);
		}

			// Partition the markers into visible and non-visible lists.
			var visibleMarkers = [];
			var nonvisibleMarkers = [];
			for ( i = 0; i < this.markers.length; ++i )
		{
			marker = this.markers[i];
			if ( marker != null )
					if ( bounds.contains( marker.getPoint() ) )
					visibleMarkers.push( marker );
					else
					nonvisibleMarkers.push( marker );
		}

			// Take down the non-visible markers.
			for ( i = 0; i < nonvisibleMarkers.length; ++i )
		{
			marker = nonvisibleMarkers[i];
			if ( marker.onMap )
				{
					this.map.removeOverlay( marker );
					marker.onMap = false;
				}
		}

			// Take down the non-visible clusters.
			for ( i = 0; i < this.clusters.length; ++i )
		{
			cluster = this.clusters[i];
			if ( cluster != null && ! bounds.contains( cluster.marker.getPoint() ) && cluster.onMap )
				{
					this.map.removeOverlay( cluster.marker );
					cluster.onMap = false;
				}
		}

			// Clustering!  This is some complicated stuff.  We have three goals
			// here.  One, limit the number of markers & clusters displayed, so the
			// maps code doesn't slow to a crawl.  Two, when possible keep existing
			// clusters instead of replacing them with new ones, so that the app pans
			// better.  And three, of course, be CPU and memory efficient.

			if ( visibleMarkers.length > this.maxVisibleMarkers )
		{

			// Add to the list of clusters by splitting up the current bounds
			// into a grid.
			var latRange = bounds.getNorthEast().lat() - bounds.getSouthWest().lat();
			var latInc = latRange / this.gridSize;
			var lngInc = latInc / Math.cos( ( bounds.getNorthEast().lat() + bounds.getSouthWest().lat() ) / 2.0 * Math.PI / 180.0 );
			for ( var lat = bounds.getSouthWest().lat(); lat <= bounds.getNorthEast().lat(); lat += latInc )
					for ( var lng = bounds.getSouthWest().lng(); lng <= bounds.getNorthEast().lng(); lng += lngInc )
				{
					cluster = new Object();
					cluster.clusterer = this;
					cluster.bounds = new GLatLngBounds( new GLatLng( lat, lng ), new GLatLng( lat + latInc, lng + lngInc ) );
					cluster.markers = [];
					cluster.markerCount = 0;
					cluster.onMap = false;
					cluster.marker = null;
					this.clusters.push( cluster );
				}

			// Put all the unclustered visible markers into a cluster - the first
			// one it fits in, which favors pre-existing clusters.
			for ( i = 0; i < visibleMarkers.length; ++i )
				{
					marker = visibleMarkers[i];
					if ( marker != null && ! marker.inCluster )
				{
					for ( j = 0; j < this.clusters.length; ++j )
						{
							cluster = this.clusters[j];
							if ( cluster != null && cluster.bounds.contains( marker.getPoint() ) )
						{
							cluster.markers.push( marker );
							++cluster.markerCount;
							marker.inCluster = true;
						}
						}
				}
				}


			for ( i = 0; i < this.clusters.length; ++i )

					if ( this.clusters[i] != null && this.clusters[i].markerCount < this.minMarkersPerCluster )
				{
					this.ClearCluster( this.clusters[i] );
					this.clusters[i] = null;
				}

			// Shrink the clusters list.
			for ( i = this.clusters.length - 1; i >= 0; --i )
					if ( this.clusters[i] != null )
					break;
					else
				--this.clusters.length;

			// Ok, we have our clusters.  Go through the markers in each
			// cluster and remove them from the map if they are currently up.
			for ( i = 0; i < this.clusters.length; ++i )
				{
					cluster = this.clusters[i];
					if ( cluster != null )
				{
					for ( j = 0; j < cluster.markers.length; ++j )
						{
							marker = cluster.markers[j];
							if ( marker != null && marker.onMap )
						{
							this.map.removeOverlay( marker );
							marker.onMap = false;
						}
						}
				}
				}

			// Now make cluster-markers for any clusters that need one.
			for ( i = 0; i < this.clusters.length; ++i )
				{
					cluster = this.clusters[i];


					if ( cluster != null && cluster.marker == null )
				{
					// Figure out the average coordinates of the markers in this
					// cluster.
					var xTotal = 0.0, yTotal = 0.0;
					for ( j = 0; j < cluster.markers.length; ++j )
						{
							marker = cluster.markers[j];
							if ( marker != null )
						{
							xTotal += ( + marker.getPoint().lng() );
							yTotal += ( + marker.getPoint().lat() );

						}
						}
					var location = new GLatLng( yTotal / cluster.markerCount, xTotal / cluster.markerCount );
					marker = new GMarker( location, { icon: this.icon } );
					cluster.marker = marker;
					//GEvent.addListener( marker, 'click', this.PopUp.bind(this, cluster ) );

					GEvent.addListener( marker, 'mouseover', this.PopUp.bind(this, cluster ) );
					GEvent.addListener( marker, 'mouseout', this.PopDownTimer.bind(this) );


					GEvent.addListener( marker, 'click', this.zoomToCluster.bind(this, cluster) );


				}
				}
		}

			// Display the visible markers not already up and not in clusters.
			for ( i = 0; i < visibleMarkers.length; ++i )
		{
			marker = visibleMarkers[i];
			if ( marker != null && ! marker.onMap && ! marker.inCluster )
				{
					this.map.addOverlay( marker );
					if ( marker.addedToMap != null )
					marker.addedToMap();
					marker.onMap = true;
				}
		}

			// Display the visible clusters not already up.
			for ( i = 0; i < this.clusters.length; ++i )
		{
			cluster = this.clusters[i];
			if ( cluster != null && ! cluster.onMap && bounds.contains( cluster.marker.getPoint() ) )
				{
					this.map.addOverlay( cluster.marker );
					cluster.onMap = true;
				}
		}

			// In case a cluster is currently popped-up, re-pop to get any new
			// markers into the infobox.
			this.RePop();
		},



	zoomToCluster : function(cluster)
	{
		if(cluster.markers.length <= 0) return;
		var markerIds = new Array();
		for ( var i = 0; i < cluster.markers.length; ++i )
		{
			var marker = cluster.markers[i];
			if ( marker != null ) {
				markerIds.push(marker.weId);
				}
		}
		weIdcsv = markerIds.join(',');

		myGeoSearch.zoomToCluster(weIdcsv)

	},

	PopUp : function ( cluster ) {
		myGeoSearch.immoBubble.hide()
		this.PopDownTimerClear()

		if(cluster.markers.length <= 0) {
			this.PopDown();
			return;
		}

		var markerIds = new Array();

		var html = '<div class="gs_bubbleContent_top css-round"><h3>Wählen Sie einen Immobilienstandort</h3><div class="gs_bubble_immoList">';
		var n = 0;
		for ( var i = 0; i < cluster.markers.length; ++i ) {
			var marker = cluster.markers[i];
			if ( marker != null ) {
				html += '<p>' + marker.title + '</p>';
				markerIds.push(marker.weId);
			}
		}
		html += '</div></div>';
		weIdcsv = markerIds.join(',');
		html += '<div class="gs_bubbleContent_bottom"><button class="inputSubmit" onclick="myGeoSearch.zoomToCluster(\'' + weIdcsv + '\');"><span><span>Ausschnitt zeigen</span></span></button></div>';

		this.bubble.setTimeoutFunc(this.PopDownTimer.bind(this, 10000))
		this.bubble.setTimeoutFunc2(this.PopDownTimer.bind(this, 1))

		this.bubble.openOnMarker(cluster.marker, html);
		this.poppedUpCluster = cluster;
	},



	RePop : function ( )
		{
			if(this.bubble.visible == true)
		this.PopUp( this.poppedUpCluster );
		},


	PopDown : function ()
		{
		this.bubble.hide();
			this.poppedUpCluster = null;
		},



	PopDownTimer : function(val)
	{
		if(!val) val = 500;
		this.PopDownTimerClear()
		this.timerHandler = window.setTimeout(this.PopDown.bind(this), val)
	},

	PopDownTimerClear : function()
	{
		if(typeof this.timerHandler != "undefined")
			window.clearTimeout(this.timerHandler)
	},




	ClearCluster : function ( cluster )
		{
			var i, marker;

			for ( i = 0; i < cluster.markers.length; ++i )
		if ( cluster.markers[i] != null )
			{
				cluster.markers[i].inCluster = false;
				cluster.markers[i] = null;
			}
			cluster.markers.length = 0;
			cluster.markerCount = 0;

			if ( cluster == this.poppedUpCluster )
			this.map.closeInfoWindow();
			if ( cluster.onMap )
		{
			this.map.removeOverlay( cluster.marker );
			cluster.onMap = false;
		}

		}

}



