function QuoteSet(location, lat, lng, id, etype) {
  this.location = location;
  this.lat = lat;
  this.lng = lng;
  this.id = id;
  if(etype.length > 1){
	this.esubtype = etype.substr(1);
	this.etype = etype.substr(0,1);
  }else{
	this.esubtype = null;
	this.etype = etype;
  }
  this.point = new GLatLng(this.lat, this.lng);
  this.iconNode = null;
  this.infoWindowNode = null;
  //if (this.etype = "E") {
	//alert(this.point);
	//}
}

QuoteSet.prototype.getPoint = function() {
  return this.point;
}

QuoteSet.prototype.getIconNode = function() {
  if (!this.iconNode) 
  {
    this.iconNode = newNode("div");  

    this.iconNode.quoteSet = this;
    var tooltipLabel = this.location;
    if(defaultCentre.label != ''){
		tooltipLabel += '<br />'+this.distanceTo(defaultCentre.point)+' miles from '+defaultCentre.label;
    }
    this.iconNode.setAttribute('tiptitle', tooltipLabel);
    this.iconNode.setAttribute('tipclass', this.etype);
	this.iconNode.onmouseover = function() {
      tooltip.show(this.getAttribute('tiptitle'),this.getAttribute('tipclass'));
	};
	this.iconNode.onmouseout = function() {tooltip.hide()};
    //var className = "marker";
	var className = this.etype;
    this.iconNode.className = className;
  }
  
  return this.iconNode;
}

QuoteSet.prototype.distanceTo = function(point){
	var Earth = 3960; //miles
	var tLat = point.lat()*(Math.PI / 180);
	var tLng = point.lng()*(Math.PI / 180);
	var sLat = this.point.lat()*(Math.PI / 180);
	var sLng = this.point.lng()*(Math.PI / 180);
	//Haversine Formula
	var sinLat = Math.sin((tLat-sLat)/2);
	var sinLong = Math.sin((tLng-sLng)/2);
	var Ha = (sinLat*sinLat)+Math.cos(sLat)*Math.cos(tLat)*(sinLong*sinLong);
	var Hc = 2*Math.asin(Math.min(1,Math.sqrt(Ha)));
	return (Earth*Hc).toFixed(1);
}

QuoteSet.prototype.getIconNodeBounds = function() {
  var tl = new GPoint(this.iconNode.offsetLeft, this.iconNode.offsetTop);
  var br = new GPoint(tl.x + this.iconNode.offsetWidth, tl.y + this.iconNode.offsetHeight);
  
  return new GBounds([tl, br]);
}

QuoteSet.prototype.getQuotes = function() {
  if (this.matchedSearch) {
    return this.matchingQuotes;
  } else {
    return this.quotes;
  }
}

QuoteSet.prototype.showInfoWindow = function() {
  map.closeInfoWindow();
  map.openInfoWindow(this.point, 
                     this.getInfoWindowNode());
}

QuoteSet.prototype.getInfoWindowNode = function() {
  if (!this.infoWindowNode) {
    this.infoWindowNode = newNode("iframe");
	this.infoWindowNode.src = "getbubbleinfo.asp?id=" + this.id + "&etype=" + this.etype + "&esubtype=" + this.esubtype + "&dist="+this.distanceTo(defaultCentre.point)+"&loc="+defaultCentre.label;
	this.infoWindowNode.frameBorder = 0;
	this.infoWindowNode.width = 450;
	this.infoWindowNode.height = 250;
	this.infoWindowNode.style.margin = '0px 10px 0px 0px';
	this.infoWindowNode.style.padding = '0px 10px 0px 0px';
  }
  
  if (!map.getInfoWindow().isHidden()) {
      this.infoWindowNode.style.display = "none";
      map.openInfoWindow(this.point,
                         this.infoWindowNode,test);
      this.infoWindowNode.style.display = "";
   }
  return this.infoWindowNode;
}

function QuotesOverlay(quotes) {
  GOverlay.call(this);
  
  this.map = null;
  this.quotes = quotes;
  this.visibleQuotes = {};
}

QuotesOverlay.prototype.initialize = function(map) {
  this.map = map;
  this.parentNode = map.getPane(G_MAP_MARKER_PANE);
  
  // To handle clicks in the shadow, adding a simple handler to
  // G_MAP_MARKER_MOUSE_TARGET_PANE doesn't seem to work, and we don't want
  // to have to add DOM nodes there too. So instead we add a global click
  // handler to the map that looks for clicked markers if the regular
  // handler doesn't trigger
  GEvent.bindDom(this.parentNode, "click", this, this.handleDomClick);
  GEvent.bind(map, "click", this, this.handleMapClick);  
  GEvent.bind(map, "dragstart", this, this.beginMapDrag);
  GEvent.bind(map, "dragend", this, this.endMapDrag);
}

QuotesOverlay.prototype.beginMapDrag = function() {
  // Don't count short drags, so that they still trigger marker clicks
  var self = this;
  this.beginDragTimeout = window.setTimeout(function() {
    self.beginDragTimeout = null;
    self.inDrag = true;
  }, 250);
}

QuotesOverlay.prototype.endMapDrag = function() {
  if (this.beginDragTimeout) {
    window.clearTimeout(this.beginDragTimeout);
  } else {
    // We reset the drag state in a timeout because we want the click event
    // (if any) to be processed first
    var self = this;
    window.setTimeout(function() {
      self.inDrag = false;
    }, 0);
  }
}

QuotesOverlay.prototype.handleMapClick = function(marker, point) {
  var self = this;
  this.handledClick = false;
  window.setTimeout(function() {
    if (self.handledClick) return;
    
    if (point != null)
    {
      var domPoint = map.fromLatLngToDivPixel(point);
      var domBounds = new GBounds([domPoint]);
    
      for (var location in self.visibleQuotes) {
        var quote = self.visibleQuotes[location];
      
        if (quote.getIconNodeBounds().containsBounds(domBounds)) {
          self.handleDomClick({target: quote.getIconNode()});
          break;
        }
      }
    }
  }, 0);
}

QuotesOverlay.prototype.handleDomClick = function(event) {
  this.handledClick = true;
  
  if (this.inDrag) {
    return;
  }
  
  for (var node = event.target; node; node = node.parentNode) {  
    if (node.quoteSet) {
      node.quoteSet.showInfoWindow();
      break;
    }
  }
}

QuotesOverlay.prototype.remove = function() {
  window.console.log("TODO: removing");
}

QuotesOverlay.prototype.copy = function() {
  window.console.log("TODO: copying");
  
  return this;
}

QuotesOverlay.prototype.redraw = function(force) {
  if (force) {
    var zoom = map.getZoom();
    addClass(containerNode, "quotes");
    
    this.resetVisibleQuotes();        
  } else {
    if (this.updateVisibleQuotesTimeout) {
      window.clearTimeout(this.updateVisibleQuotesTimeout);
    }
    var self = this;
    this.updateVisibleQuotesTimeout = window.setTimeout(function() {
      self.updateVisibleQuotesTimeout = null;
      self.updateVisibleQuotes();
    }, 100);
  }
}

QuotesOverlay.prototype.resetVisibleQuotes = function() {
  for (var location in this.visibleQuotes) {
    var quote = this.visibleQuotes[location];
    this.parentNode.removeChild(quote.getIconNode());
  }
  
  this.visibleQuotes = {};
  
  this.updateVisibleQuotes();
}

QuotesOverlay.prototype.updateVisibleQuotes = function() {
  var start = new Date().getTime();

  var quotesToRemove = [];
  var quotesToAdd = [];
  var newVisibleQuotes = {};
  var mapBounds = this.map.getBounds();
  
  // Enlarge bounds a bit so points at the edges don't flicker in and out
  var sw = mapBounds.getSouthWest();
  var ne = mapBounds.getNorthEast();
  
  sw = new GLatLng(sw.lat() - 0.001, sw.lng() - 0.001); 
  ne = new GLatLng(ne.lat() + 0.001, ne.lng() + 0.001);
  
  mapBounds = new GLatLngBounds(sw, ne);

  zoom = map.getZoom();
    
  visibleCount = 0;
  for (var i = 0, quoteSet; quoteSet = this.quotes[i]; i++) {
    var alreadyVisible = quoteSet.location in this.visibleQuotes;
    
    if (mapBounds.contains(quoteSet.getPoint())) 
	{
      visibleCount++;
      newVisibleQuotes[quoteSet.location] = quoteSet;
      if (!alreadyVisible) 
	  {
        quotesToAdd.push(quoteSet);
      }
    } 
	else if (alreadyVisible) 
	{
      quotesToRemove.push(quoteSet);
    }
  }
  
  var removeStart = new Date().getTime();


  for (var i = 0, quoteSet; quoteSet = quotesToRemove[i]; i++) {
    this.parentNode.removeChild(quoteSet.getIconNode());
  }


  this.visibleQuotes = newVisibleQuotes;
  
  var addStart = new Date().getTime();

  for (var i = 0, quoteSet; quoteSet = quotesToAdd[i]; i++) {
    var iconNode = quoteSet.getIconNode();
    var iconPosition = this.map.fromLatLngToDivPixel(quoteSet.getPoint());
    
    iconNode.style.left = iconPosition.x + "px";
    iconNode.style.top = iconPosition.y + "px";
    
    this.parentNode.appendChild(iconNode);
  }
  
  var end = new Date().getTime()
  
  window.console.log(
    "showing " + visibleCount + "/" + quotes.length + 
        " (" + (removeStart - start) + "ms)" +
    " added " + quotesToAdd.length + 
        " (" + (end - addStart) + "ms)" +
    " removed " + quotesToRemove.length +
        " (" + (addStart - removeStart) + "ms)");
}

