/*
    cluster_client.js
    
    Copyright 2008 and beyond by Udell Enterprises, Inc
    
    The client piece of my server-side clustering (SSC) architecture.

    There are two basic ways to implement this:
    
    - If the implementation doesn't need to change the basic behavior much, simply override 
      the indicated methods of Singleton and Cluster that return icons and text.
      
    - For more extensive modifications:
      - Create subclasses of Singleton and Cluster, extending/overriding the default methods as necessary.
      - Override clusterClient.prototype.createPoint to create instances of the appropriate subclasses.
*/

if (!String.prototype.jsHash)
  String.prototype.jsHash = function ()
  {
    // with thanks to http://www.partow.net/programming/hashfunctions/index.html

    var hash = 1315423911;

    for (var i = 0; i < this.length; i++)
      hash ^= ((hash << 5) + this.charCodeAt(i) + (hash >> 2));

    return String(hash);
  };

if (!Function.prototype.inheritsFrom)
  Function.prototype.inheritsFrom = function (parentClass)
  {
    this.prototype = new parentClass();
    this.prototype.constructor = this;
    this.prototype.parent = parentClass.prototype;
  };


clusterClient = function (map)
{
  this.map = map;
  this.points = {};
  this.urlParms = {};    // Additional URL parameters to filter the result set
  
  var self = this;
  this.moveHandler = GEvent.addListener(this.map, 'moveend', function () {self.refreshMap()});
};

clusterClient.prototype.quietMove = function (center, zoom)
{
  // Call this function if you want to alter the viewport WITHOUT retrieving new data
  GEvent.removeListener(this.moveHandler);
  this.map.setCenter(center, zoom);
  this.moveHandler = GEvent.addListener(this.map, 'moveend', function () {self.refreshMap()});
};

clusterClient.prototype.refreshMap = function ()
{
  var bounds = this.map.getBounds();
  var url = 'map/cluster_server.php?BBOX=' + bounds.getSouthWest().lng() + ',' +
            bounds.getSouthWest().lat() + ',' + bounds.getNorthEast().lng() + ',' +
            bounds.getNorthEast().lat() + '&nocache=' + Number(new Date());

  for (var p in this.urlParms)
    url = url + '&' + p + '=' + encodeURIComponent(this.urlParms[p]);

  var self = this;
  GDownloadUrl(url, function (response) {self.receiveQuad(response)});
};

clusterClient.prototype.receiveQuad = function (response)
{
  var newPoints = {};
  var isCluster;

  var responseData;  // in case response is empty
  eval(response);
  for (var i in responseData) 
  {
    responseData[i].parent = this;
    
    if (responseData[i].id)
      isCluster = false;
    else
    {
      isCluster = true;
      // Generate a pseudo-ID for clusters
      responseData[i].id = responseData[i].coords.toUrlValue().jsHash();
    }

    if (this.points[responseData[i].id])
    {
      // We already have this point, so just move it to the new list
      newPoints[responseData[i].id] = this.points[responseData[i].id];
      this.points[responseData[i].id] = null;
    }
    else
    {
      // Add this point to our new list
      newPoints[responseData[i].id] = this.createPoint(isCluster, responseData[i]);
      newPoints[responseData[i].id].show();
    }
  }

  for (var p in this.points)
    if (this.points[p])
    {
      this.points[p].remove();
      this.points[p] = null;
    }

  this.points = newPoints;
  
  this.afterRefresh();
};

clusterClient.prototype.createPoint = function (isCluster, pointData)
{
  // This function should be overridden if the implementation is using subclasses of Singleton and Cluster
  if (isCluster)
    return new clusterClient.cluster(pointData);
  else
    return new clusterClient.singleton(pointData);
};

clusterClient.prototype.afterRefresh = function ()
{
  // This function should be overridden if additional functionality is required after the map data loads
};


clusterClient.point = function (pointData)
{
  if (pointData)
  {
    this.id     = pointData.id;
    this.coords = pointData.coords;
    this.marker = null;
    this.parent = pointData.parent;
  }
};

clusterClient.point.prototype.show = function ()
{
  if (this.marker)
    this.marker.show();
  else
  {
    this.marker = new GMarker(this.coords, {icon: this.getIcon(), title: this.getTitle()});
    this.parent.map.addOverlay(this.marker);

    var self = this;
    this.markerClickHandler = GEvent.addListener(this.marker, 'click', function () {self.markerClick()});
  }
};

clusterClient.point.prototype.hide = function ()
{
  this.marker.closeInfoWindow();
  this.marker.hide();
};

clusterClient.point.prototype.remove = function ()
{
  this.marker.closeInfoWindow();
  this.parent.map.removeOverlay(this.marker);
  GEvent.removeListener(this.markerClickHandler);
  this.marker = null;
};


clusterClient.singleton = function (pointData)
{
  this.data = pointData.data;
  this.infoWindowOptions = {};    // Override this property for more control over the point's IW

  clusterClient.point.call(this, pointData);
};
clusterClient.singleton.inheritsFrom(clusterClient.point);

clusterClient.singleton.baseIcon = new GIcon(G_DEFAULT_ICON);


clusterClient.singleton.prototype.markerClick = function ()
{
  // Override this method if you want something other than a standard IW (one with tabs, for instane)
  if (this.marker)
    this.marker.openInfoWindow(this.getInfoWindowContent(), this.infoWindowOptions);
};

// The following methods will usually be overridden

clusterClient.singleton.prototype.getIcon = function ()
{
  return clusterClient.singleton.baseIcon;
};

clusterClient.singleton.prototype.getTitle = function ()
{
  return this.id;
};

clusterClient.singleton.prototype.getInfoWindowContent = function ()
{
  return this.data.toSource();
};


clusterClient.cluster = function (pointData)
{
  this.data = pointData.data;

  clusterClient.point.call(this, pointData);
};
clusterClient.cluster.inheritsFrom(clusterClient.point);

clusterClient.cluster.baseIcon = new GIcon(G_DEFAULT_ICON);
clusterClient.cluster.baseIcon.image = 'http://googlemapsbook.com/chapter7/icons/cluster.png';
clusterClient.cluster.baseIcon.iconSize = new GSize(26, 25);
clusterClient.cluster.baseIcon.iconAnchor = new GPoint(13, 25);

clusterClient.cluster.prototype.markerClick = function ()
{
  parent.map.closeInfoWindow();
  parent.map.setCenter(this.coords, parent.map.getZoom() + 2);
};

// The following methods will usually be overridden

clusterClient.cluster.prototype.getIcon = function ()
{
  return clusterClient.cluster.baseIcon;
};

clusterClient.cluster.prototype.getTitle = function ()
{
  return 'Multiple items in this area';
};


