/*
ThingOverlay is a Google Map Overlay (GOverlay) that renders a div in a map, and autopositions itself so that it does not overlap any other ThingOverlays.

This class requires prototype.js
*/
(function() {
  var arrayRemove = function(arr, v) {
    for (var i = arr.length-1; i >= 0; i--) {
      if (arr[i] === v) {
        arr[i] = arr[arr.length-1];
        arr.length--;
      }
    }
  }

  var things = [];
  var arranging = false;
  var arrangeThings = function() {
    if (!arranging) setTimeout(_arrangeThings, 0);
    arranging = true;
  };
  var _arrangeThings = function() {
    var t = things;

    // Clear the offset info
    for (var i = 0; i < t.length; i++) {
      var thing = t[i];
      thing._doff.x = thing._doff.y = 0;
      thing._nhits = 0;
    }

    var totalHits = 0, thingsHit = 0;
    for (var j = 0; j < t.length; j++) {
      // Get the reference thing and it's bounds
      var t0 = t[j], b0 = t0.getBounds();

      // Compare it to all other things
      for (var i = j+1; i < t.length; i++) {
        var t1 = t[i], b1 = t1.getBounds();
        if (b1.intersects(b0)) {
          var impulse = b0.getCenter().subtract(b1.getCenter()).setLength(1);
          t0._doff.add(impulse);
          t0._nhits++;
          t1._doff.subtract(impulse);
          t1._nhits++;
          totalHits++;
          if (t0._nhits == 1) thingsHit++;
          if (t1._nhits == 1) thingsHit++;
        }
      }
    }

    var pushLength = 4*Math.sqrt(totalHits/thingsHit);
    for (var i = 0; i < t.length; i++) {
      var thing = t[i];
      if (thing._nhits > 0) {
        var doff = thing._doff;
        if (doff.getLength() == 0) {
          doff.x = Math.random() - .5;
          doff.y = Math.random() - .5;
        }
        doff.setLength(pushLength);

        // Add a bit of resistance to movement
        //doff.subtract(thing._offset.clone().setLength(2));

        // Offset and reposition
        thing._offset.add(doff);
        thing.reposition();
        doff.x = doff.y = 0;
      }
    }

    arranging = thingsHit > 0;
    if (arranging) {
      setTimeout(_arrangeThings, 20);
    } else {
      // Invoke callback when arrange finishes
      ThingOverlay.onArranged();
    }
  }

  var ThingOverlay = window.ThingOverlay = function(latlng, thing) {
    this._latlng = latlng;
    this._thing = thing;
    this._offset = new Point();
    this._doff = new Point();
  };

  Object.extend(ThingOverlay, {
    onArranged: function() {},

    autoArrange: false,

    setAutoArrange: function(flag) {
      ThingOverlay.autoArrange = flag;
      if (flag) arrangeThings();
    }
  });

  Object.extend(ThingOverlay.prototype = new GOverlay(), {
    initialize: function(map) {
      // Create the DIV representing our rectangle
      var el = $(document.createElement('div'));
      el.style.position = 'absolute';
      this._thing.render(el);

      map.getPane(G_MAP_MAP_PANE).appendChild(el);

      this._map = map;
      this._el = el;
      things.push(this);
    },

    remove: function() {
      this._el.remove();
      arrayRemove(things, this);
    },

    copy: function() {
      return new ThingOverlay(this._latlng);
    },

    redraw: function(force) {
      // We only need to redraw if the coordinate system has changed
      if (!force) return;

      // Get div coords
      this._point = this._map.fromLatLngToDivPixel(this._latlng);
      this._offset.x = this._offset.y = 0;
      this.reposition();
      if (ThingOverlay.autoArrange) arrangeThings();
    },

    reposition: function() {
      // Now position our DIV based on the DIV coordinates of our bounds
      var dx = this._offset ? this._offset.x : 0;
      var dy = this._offset ? this._offset.y : 0;
      this._el.style.left = Math.round(this._point.x + dx) + 'px';
      this._el.style.top = Math.round(this._point.y + dy) + 'px';
      this._bounds = null;
    },

    getBounds: function() {
      if (!this._bounds) {
        this._bounds = new Rect(
          this._el.offsetLeft, this._el.offsetTop,
          this._el.offsetWidth, this._el.offsetHeight
        );
      }
      return this._bounds;
    }
  });

  var Point = function(x,y) {
    this.x = x || 0;
    this.y = y || 0;
  };

  Object.extend(Point.prototype, {
    clone: function() {
      return new Point(this.x, this.y);
    },

    equals: function(p) {
      return p && this.x == p.x && this.y == p.y;
    },

    add: function(p) {
      this.x += p.x;
      this.y += p.y;
      return this;
    },

    subtract: function(p) {
      this.x -= p.x;
      this.y -= p.y;
      return this;
    },

    reorient: function(orient) {
      orient = orient || 0;
      var x = this.x;
      this.x = this.y * ((orient & 0x1) ? 1 : -1);
      this.y = x * ((orient & 0x2) ? 1 : -1);
      return this;
    },

    getLength: function() {
      return Math.sqrt(this.x*this.x + this.y*this.y);
    },

    setLength: function(l) {
      var d = this.getLength();
      if (d > 0) {
        this.x *= l/d;
        this.y *= l/d;
      }
      return this;
    }
  });

  var Rect = function(x,y,w,h) {
    this.x = x;
    this.y = y;
    this.w = w;
    this.h = h;
  };

  Object.extend(Rect.prototype, {
    clone: function() {
      return new Rect(this.x, this.y, this.w, this.h);
    },

    getCenter: function() {
      return new Point(this.x + this.w/2, this.y + this.h/2);
    },

    intersection: function(rect) {
      var x = Math.max(this.x, rect.x), r = Math.min(this.x + this.w, rect.x + rect.w);
      var y = Math.max(this.y, rect.y), b = Math.min(this.y + this.h, rect.y + rect.h);
      return {x: x, y: y, w: Math.max(r - x, 0), h: Math.max(b - y, 0)}
    },

    intersects: function(r) {
      var r = this.intersection(r);
      return r.w > 0 && r.h > 0;
    }
  });
})();
