function hbOverlayManager (map, opts)
{
  this.map = map;
  this.opts = opts;
  this.maxZoom = 17;

  if ((this.opts) && (this.opts.maxZoom))
  {
    this.maxZoom = this.opts.maxZoom;
  }

  this.borderPadding = 0;

  if ((this.opts) && (this.opts.borderPadding))
  {
    this.borderPadding = this.opts.borderPadding;
  }

  this.overlays = new Array (this.maxZoom);
  this.visible = new Array (this.maxZoom);
  this.lastKnownCenter = this.map.getCenter ();

  for (var zoom = 0; zoom <= this.maxZoom; ++zoom)
  {
    this.overlays [zoom] = new Array ();
    this.visible [zoom] = new Array ();
  }

  var thisManager = this;

  GEvent.addListener (map, "zoomend", function () {thisManager.onZoomEnd ();});
  GEvent.addListener (map, "moveend", function () {thisManager.onMoveEnd ();});
}

hbOverlayManager.prototype.getOptMaxZoom = function (opt_maxZoom)
{
  if (undefined == opt_maxZoom)
  {
    return this.maxZoom;
  }
  else
  {
    return opt_maxZoom;
  }
}

hbOverlayManager.prototype.removeOverlay = function (toRemove, minZoom, opt_maxZoom)
{
  var maxZoom = this.getOptMaxZoom (opt_maxZoom);

  for (var zoom = minZoom; zoom <= maxZoom; ++zoom)
  {
    for (var i = 0; i < this.overlays [zoom].length; ++i)
    {
      if (toRemove == this.overlays [zoom] [i])
      {
        this.overlays [zoom].splice (i, 1);
        this.visible [zoom].splice (i, 1);
      }
    }
  }

  this.map.removeOverlay (toRemove);
}

hbOverlayManager.prototype.addOverlay = function (overlay, minZoom, opt_maxZoom)
{
  var maxZoom = this.getOptMaxZoom (opt_maxZoom);

  for (var zoom = minZoom; zoom <= maxZoom; ++zoom)
  {
    this.overlays [zoom].push (overlay);
    this.visible [zoom].push (false);
  }
}

hbOverlayManager.prototype.addOverlays = function (overlays, minZoom, opt_maxZoom)
{
  var maxZoom = this.getOptMaxZoom (opt_maxZoom);

  for (var zoom = minZoom; zoom <= maxZoom; ++zoom)
  {
    for (var overlayIndex = 0; overlayIndex < overlays.length; ++overlayIndex)
    {
      this.overlays [zoom].push (overlays [overlayIndex]);
      this.visible [zoom].push (false);
    }
  }
}

hbOverlayManager.prototype.hide = function ()
{
  for (var zoom = 0; zoom <= this.maxZoom; ++zoom)
  {
    for (var overlayIndex = 0; overlayIndex < this.overlays [zoom].length; ++overlayIndex)
    {
      if (true == this.visible [zoom] [overlayIndex])
      {
        this.map.removeOverlay (this.overlays [zoom] [overlayIndex]);
        this.visible [zoom] [overlayIndex] = false;
      }
    }
  }
}

hbOverlayManager.prototype.show = function ()
{
  var active = this.map.getZoom ();
  var mapSize = this.map.getSize ();

  var projection = this.map.getCurrentMapType().getProjection();
  var center = projection.fromLatLngToPixel (this.map.getCenter (), active);

  var mapBounds = new GLatLngBounds (
    projection.fromPixelToLatLng (
        new GPoint (center.x - mapSize.width / 2 - this.borderPadding,
                    center.y + mapSize.height / 2 + this.borderPadding),
        active
        ),
    projection.fromPixelToLatLng (
        new GPoint (center.x + mapSize.width / 2 + this.borderPadding,
                    center.y - mapSize.height / 2 - this.borderPadding),
        active
        )
    );

  for (var overlayIndex = 0; overlayIndex < this.overlays [active].length; ++overlayIndex)
  {
    var intersects = mapBounds.intersects (this.overlays [active] [overlayIndex].getBounds ());

    if (false == this.visible [active] [overlayIndex])
    {
      if (true == intersects)
      {
        this.map.addOverlay (this.overlays [active] [overlayIndex]);
        this.visible [active] [overlayIndex] = true;
      }
    }
    else if (false == intersects)
    {
      this.map.removeOverlay (this.overlays [active] [overlayIndex]);
      this.visible [active] [overlayIndex] = false;
    }
  }
}

hbOverlayManager.prototype.refresh = function ()
{
  this.lastKnownCenter = this.map.getCenter ();
  this.hide ();
  this.show ();
}

hbOverlayManager.prototype.clearOverlays = function (overlay)
{
  this.hide ();

  for (var zoom = 0; zoom < this.maxZoom; ++zoom)
  {
    this.overlays [zoom] = new Array ();
  }
}

hbOverlayManager.prototype.onZoomEnd = function ()
{
  this.refresh ();
}

hbOverlayManager.prototype.onMoveEnd = function ()
{
  if ((this.lastKnownCenter) && (false == this.lastKnownCenter.equals (this.map.getCenter ())))
  {
    this.show ();
  }

  this.lastKnownCenter = this.map.getCenter ();
}
