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=5;
Clusterer.defaultMinMarkersPerCluster=5;
Clusterer.defaultMaxLinesPerInfoBox=10;
Clusterer.defaultIcon=new GIcon();
Clusterer.defaultIcon.image='../common/images/icons/nut.png';
Clusterer.defaultIcon.shadow='../common/images/icons/nut-shadow.png';
Clusterer.defaultIcon.iconSize=new GSize(30,51);
Clusterer.defaultIcon.shadowSize=new GSize(56,51);
Clusterer.defaultIcon.iconAnchor=new GPoint(13,34);
Clusterer.defaultIcon.infoWindowAnchor=new GPoint(13,3);
Clusterer.defaultIcon.infoShadowAnchor=new GPoint(27,37);
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.markerCount;
              break;
            }
          
          if(cluster.markerCount==0){
            this.ClearCluster(cluster);
            this.clusters[j]=null;
          } else if(cluster==this.poppedUpCluster)
            Clusterer.RePop(this);
          
        }
      }
      this.markers[i]=null;
      break;
    }
    
  this.DisplayLater();
};
Clusterer.prototype.DisplayLater=function(){
  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=[];
  for(i=0;i<clusterer.markers.length;++i){
    marker=clusterer.markers[i];
    if(marker!=null)
      if(bounds.contains(marker.getPoint()))
        visibleMarkers.push(marker);
      else{
        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){
          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:clusterer.icon});
        cluster.marker=marker;
        GEvent.addListener(marker,'click',Clusterer.MakeCaller(Clusterer.ZoomMapIn,cluster));
        //GEvent.addListener(marker,'click',Clusterer.MakeCaller(Clusterer.PopUp,cluster));
      }
    }
  }
  
  var sLabel = '';
  var funcAddLabelMarker = addLabelMarker;
  for(i=0;i<visibleMarkers.length;++i){
    marker=visibleMarkers[i];
    if(marker!=null&&!marker.onMap&&!marker.inCluster){
      sLabelData = lookupLabel(marker.title)
      sLabel = '<h3>Fastenal ' + marker.title + '<\/h3><div>' + sLabelData[0] + '<\/div><div>' +
        sLabelData[1] + ', ' + sLabelData[2] + ' ' + sLabelData[3] + '<\/div><div>' + sLabelData[4] + '<\/div><div>' +
        '<a href="../storeInfo.ex?branch=' + marker.title + '">Store Details<\/a><\/div>'
        
        
      marker = funcAddLabelMarker(marker, sLabel);
      clusterer.map.addOverlay(marker);
      if(marker.addedToMap!=null)
        marker.addedToMap();
      
      marker.onMap=true;
    }
  }
  visibleMarkers.length = 0;
  sLabel = null;
  funcAddLabelMaker = null;
  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.PopUp=function(cluster){
  var clusterer=cluster.clusterer;
  var html='<table width="300">';
  var n=0;
  for(var i=0;i<cluster.markers.length;++i){
    var marker=cluster.markers[i];
    if(marker!=null){
      ++n;
      html+='<tr><td>';
      if(marker.getIcon().smallImage!=null)
        html+='<img src="'+marker.getIcon().smallImage+'">';
      else
        html+='<img src="'+marker.getIcon().image+'" width="'+(marker.getIcon().iconSize.width/2)+'" height="'+(marker.getIcon().iconSize.height/2)+'">';
        
      html+='</td><td>'+marker.title+'</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>';
  clusterer.map.closeInfoWindow();
  cluster.marker.openInfoWindowHtml(html);
  clusterer.poppedUpCluster=cluster;
};
Clusterer.ZoomMapIn=function(cluster){
  var map = cluster.clusterer.map;
  var zoomInLevel = map.getZoom() + 1;
  map.setCenter(cluster.marker.getPoint(), zoomInLevel);
};
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);
    
};
