Clusterer=function(map){
	this.map=map;
	this.markers=[];
	this.clusters=[];
	this.timeout=null;
	this.currentZoomLevel=map.getZoom();
	this.maxVisibleMarkers=Clusterer.defaultMaxVisibleMarkers;
	this.gridSize=Clusterer.defaultGridSize;
	this.minMarkersPerCluster=Clusterer.defaultMinMarkersPerCluster;
	this.maxLinesPerInfoBox=Clusterer.defaultMaxLinesPerInfoBox;
	this.icon=Clusterer.defaultIcon;
	GEvent.addListener(map,'zoomend',Clusterer.MakeCaller(Clusterer.Display,this));
	GEvent.addListener(map,'moveend',Clusterer.MakeCaller(Clusterer.Display,this));
	GEvent.addListener(map,'infowindowclose',Clusterer.MakeCaller(Clusterer.PopDown,this));
};
Clusterer.defaultMaxVisibleMarkers=150;
Clusterer.defaultGridSize=20;
Clusterer.defaultMinMarkersPerCluster=5;
Clusterer.defaultMaxLinesPerInfoBox=10;
Clusterer.defaultIcon=new GIcon();
Clusterer.defaultIcon.image = bpath+'images/markers/cluster.png';
Clusterer.defaultIcon.shadow = bpath+'images/markers/cluster_shadow.png';
Clusterer.defaultIcon.iconSize=new GSize(50,44);
Clusterer.defaultIcon.shadowSize=new GSize(50,44);
Clusterer.defaultIcon.iconAnchor=new GPoint(28,44);
Clusterer.defaultIcon.infoWindowAnchor=new GPoint(13,44);
Clusterer.defaultIcon.infoShadowAnchor=new GPoint(80,44);
Clusterer.prototype.SetIcon=function(icon){this.icon=icon;};
Clusterer.prototype.SetMaxVisibleMarkers=function(n){this.maxVisibleMarkers=n;};
Clusterer.prototype.SetMinMarkersPerCluster=function(n){this.minMarkersPerCluster=n;};
Clusterer.prototype.SetMaxLinesPerInfoBox=function(n){this.maxLinesPerInfoBox=n;};
Clusterer.prototype.AddMarker=function(marker,title){	
	if(marker.setMap!=null) marker.setMap(this.map);
	marker.title=title;
	marker.onMap=false;
	this.markers.push(marker);
	this.DisplayLater();
};
Clusterer.prototype.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.markers.splice(k,1);
							--cluster.markerCount;
							break;
						}
						
					if(cluster.markerCount==0){
						this.ClearCluster(cluster);
						//this.clusters[j]=null;
						this.clusters.splice(j,1);
					} else if(cluster==this.poppedUpCluster) Clusterer.RePop(this);
				}
			}
			//this.markers[i]=null;
			this.markers.splice(i,1);
			break;
		}

	this.DisplayLater();
};
Clusterer.prototype.DisplayLater=function(){
//Clusterer.Display(this);
	 if(this.timeout!=null) clearTimeout(this.timeout);
	this.timeout=setTimeout(Clusterer.MakeCaller(Clusterer.Display,this),50);
};
Clusterer.Display=function(clusterer){
	var i,j,marker,cluster;
	clearTimeout(clusterer.timeout);
	var newZoomLevel=clusterer.map.getZoom();
	if(newZoomLevel!=clusterer.currentZoomLevel){
		for(i=0;i<clusterer.clusters.length;++i)
			if(clusterer.clusters[i]!=null){
				clusterer.ClearCluster(clusterer.clusters[i]);
				clusterer.clusters[i]=null;
			}
		clusterer.clusters.length=0;
		clusterer.currentZoomLevel=newZoomLevel;
	}
	var bounds=clusterer.map.getBounds();
	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));
	}
	var visibleMarkers=[];
	var nonvisibleMarkers=[];
	for(i=0;i<clusterer.markers.length;++i){
		marker=clusterer.markers[i];
		if(marker!=null)
		if(bounds.contains(marker.getPoint()))
			visibleMarkers.push(marker);
		else
			nonvisibleMarkers.push(marker);
	}
	for(i=0;i<nonvisibleMarkers.length;++i){
		marker=nonvisibleMarkers[i];
		if(marker.onMap){
			clusterer.map.removeOverlay(marker);
			marker.onMap=false;
		}
	}
	for(i=0; i<clusterer.clusters.length;++i){
		cluster=clusterer.clusters[i];
		if(cluster!=null&&!bounds.contains(cluster.marker.getPoint())&&cluster.onMap){
			clusterer.map.removeOverlay(cluster.marker);
			cluster.onMap=false;
		}
	}
	//if(visibleMarkers.length>clusterer.maxVisibleMarkers){
		var latRange=bounds.getNorthEast().lat()-bounds.getSouthWest().lat();
		var latInc=latRange/clusterer.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=clusterer;
				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;
				clusterer.clusters.push(cluster);
			}
			
			for(i=0;i<visibleMarkers.length;++i){
				marker=visibleMarkers[i];
				if(marker!=null&&!marker.inCluster){
					for(j=0;j<clusterer.clusters.length;++j){
						cluster=clusterer.clusters[j];
						if(cluster!=null&&cluster.bounds.contains(marker.getPoint())){
							cluster.markers.push(marker);
							++cluster.markerCount;
							marker.inCluster=true;
						}
					}
				}
			}
			
			for(i=0;i<clusterer.clusters.length;++i)
				if(clusterer.clusters[i]!=null&&clusterer.clusters[i].markerCount<clusterer.minMarkersPerCluster){
					clusterer.ClearCluster(clusterer.clusters[i]);
					clusterer.clusters[i]=null;
				}
				
			for(i=clusterer.clusters.length-1;i>=0;--i)
				if(clusterer.clusters[i]!=null)
					break;
				else
					--clusterer.clusters.length;
					for(i=0;i<clusterer.clusters.length;++i){
						cluster=clusterer.clusters[i];
						if(cluster!=null){
							for(j=0;j<cluster.markers.length;++j){
								marker=cluster.markers[j];
								if(marker!=null&&marker.onMap){
									clusterer.map.removeOverlay(marker);
									marker.onMap=false;
								}
							}
						}
					}
					
			for(i=0;i<clusterer.clusters.length;++i){
				cluster=clusterer.clusters[i];
				if(cluster!=null&&cluster.marker==null){
					var xTotal=0.0,yTotal=0.0;
					for(j=0;j<cluster.markers.length;++j){
						var 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);
					
					cluster = clusterer.makeClusterMarker(cluster,location);
				}
			}
	//}
	for(i=0;i<visibleMarkers.length;++i){
		marker=visibleMarkers[i];
		if(marker!=null&&!marker.onMap&&!marker.inCluster){
			clusterer.map.addOverlay(marker);
			if(marker.addedToMap!=null) marker.addedToMap();
			marker.onMap=true;
		}
	}
	for(i=0;i<clusterer.clusters.length;++i){
		cluster=clusterer.clusters[i];
		if(cluster!=null&&!cluster.onMap&&bounds.contains(cluster.marker.getPoint())){
			clusterer.map.addOverlay(cluster.marker);
			cluster.onMap=true;
		}
	}
	Clusterer.RePop(clusterer);
};
Clusterer.prototype.makeClusterMarker = function(cluster,location){
	var markerC = new GMarker(location,{icon:this.icon});
	markerC.latlng = location;
	cluster.location = location;
	cluster.marker=markerC;
	GEvent.addListener(markerC,'click',Clusterer.MakeCaller(Clusterer.PopUp,cluster));
	GEvent.addListener(markerC, "mouseover", function(){
		markerC.openToolTip("Multiple Sites");
		return false;
	});
	GEvent.addListener(markerC, "mouseout", function(){
		markerC.closeToolTip();
		return false;
	});
	return cluster;
};
Clusterer.PopUp=function(cluster){
	var clusterer=cluster.clusterer;
	var html = "<div class='map_popup'>";
	html += "<h1>"+cluster.markerCount+" locations grouped due to close proximity.</h1>";
	html += "<p>Click a location in the list to view the details.<br/>Zoom in to view individual locations.</p>";
	html += '<div class="location_list"><div class="scroll-pane">';
	html +='<table width="100%">';
	var n=0;
	var aMarker = cluster.markers[0];
	var iconHeight = aMarker.getIcon().iconSize.height/2;
	var iconWidth = aMarker.getIcon().iconSize.width/2;
	for(var i=0;i<cluster.markers.length;++i){
		var marker=cluster.markers[i];
		if(marker!=null){
			++n;
			if(n%2 == 0) row_class = "row1";
			else row_class = "row2";
			html+='<tr class="'+row_class+'"><td>';
			if(marker.getIcon().smallImage!=null) html+='<img src="'+marker.getIcon().smallImage+'">';
			else {
				html+='<img src="'+marker.getIcon().image+'" width="'+iconWidth+'" height="'+iconHeight+'">';
			}
			var title_url = marker.location.title;
			var title = unescape(String(marker.location.title).replace(/\+/g," "));
			html+="</td><td><a href='location/"+i+"' title=\"Click for details\" id='location_"+i+"'>"+title+"</a></td></tr>";
			
			/*if(n==clusterer.maxLinesPerInfoBox-1&&cluster.markerCount>clusterer.maxLinesPerInfoBox){
				html+='<tr><td colspan="2">...and '+(cluster.markerCount-n)+' more</td></tr>';
				break;
			}*/
		}
	}
	html+='</table></div></div></div>';
	
	clusterer.map.closeInfoWindow();
	var Tab1 = new GInfoWindowTab("Locations",html);
	
	//var Tab2 = new GInfoWindowTab("Details",displayPopup(1,true));
	//var myTabs = [Tab1, Tab2];
	//var latlng = new GLatLng(info.lat,info.lng);

	displayPopup(aMarker.location.loc_id,cluster,Tab1);
	
	clusterer.poppedUpCluster=cluster;
};
Clusterer.RePop=function(clusterer){
	if(clusterer.poppedUpCluster!=null){
		Clusterer.PopUp(clusterer.poppedUpCluster);
		}
	};
Clusterer.PopDown=function(clusterer){
	clusterer.poppedUpCluster=null;
};
Clusterer.prototype.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;
	}
};
Clusterer.MakeCaller=function(func,arg){
	return function(){
		func(arg);
	};
};
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);
};
