var globalMap = null;

Clusterer=function(map)
{
  this.map=map;
  globalMap = 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='http://www.acme.com/resources/images/markers/blue_large.PNG';
Clusterer.defaultIcon.shadow='http://www.acme.com/resources/images/markers/shadow_large.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=[];
  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)
        {
          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});
        marker=new LabeledMarker(location,{icon:clusterer.icon,labelClass:"markerLabel",labelText:cluster.markers.length,labelOffset:new GSize(-16, -40)});
        cluster.marker=marker;

        GEvent.addListener(marker,'click',Clusterer.zoom,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.zoom=function(point){
  globalMap.setCenter(point,globalMap.getZoom()+2);
}

Clusterer.PopUp=function(cluster)
{
  var clusterer=cluster.clusterer;
  var html='<div style="max-height:370px;overflow:auto;margin-right:15px;max-width:450px;" ><table>';
  var n=0;

  for(var i=0;i<cluster.markers.length;++i)
  {
    var marker=cluster.markers[i];
    if(marker!=null)
    {
      ++n;
      html+='<tr><td style="vertical-align: middle;width:85px;padding:5px;">';
      var ageTitle = marker.title.split("###");
      if(marker.getIcon().smallImage!=null)
        html+= ageTitle[0];
      else
        html+= ageTitle[0];
      html+='</td><td style="vertical-align: middle"><span>'+ageTitle[1]+'</span></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>';
  clusterer.map.closeInfoWindow();
  cluster.marker.openInfoWindowHtml(html);
  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);
};