var tipsters = {
  exitDelay: 0.5,
  
  start: function() {
    if (!this.observer) {
      this.observer = this.onMouseMovement.bind(this);
      Event.observe(document, "mouseover", this.observer);
    }
  },
  
  stop: function() {
    if (this.observer) {
      Event.stopObserving(document, "mouseover", this.observer);
      delete this.observer;
    }
  },
  
  onMouseMovement: function(event) {
    var element = this.activeElement = Element.extend(Event.element(event));
    var region  = this.getRegionForElement(element);
    
    if (region) {
      this.activateRegion(region);
    } else if (this.activeRegion) {
      this.startExitTimeout();
    }
  },
  
  activateRegion: function(region) {
    this.cancelExitTimeout();
    
    if (this.activeRegion) {
      if (this.activeRegion == region) return;
      this.deactivateRegion();
    }
    
    this.activeRegion = region;
    this.activeRegion.addClassName("hover");
  },
  
  deactivateRegion: function() {
    this.activeRegion.removeClassName("hover");
    this.activeRegion = null;
  },
  
  startExitTimeout: function() {
    this.timeout = this.timeout || window.setTimeout(function() {
      var region = this.getRegionForElement(this.activeElement);
      if (region != this.activeRegion)
        this.deactivateRegion();
        
    }.bind(this), this.exitDelay * 1000);
  },
  
  cancelExitTimeout: function() {
    if (this.timeout) {
      window.clearTimeout(this.timeout);
      this.timeout = null;
    }
  },
  
  getRegionForElement: function(element) {
    if (element.hasAttribute && !element.hasAttribute("tipster_region")) {
      var target = this.getTargetForElement(element);
      var region = this.getRegionForTarget(target);
      this.cacheRegionFromElementToTarget(region, element, target);
    }

    return $(element.getAttribute("tipster_region"));
  },
  
  getTargetForElement: function(element) {
    return this.upwardFrom(element, function(e) {
      if (e.hasClassName) return e.hasClassName("tipster(?:_target)?");
    });
  },

  getRegionForTarget: function(element) {
    return this.upwardFrom(element, function(e) {
      if (e.hasClassName) return e.hasClassName("tipster_region");
    });
  },
  
  cacheRegionFromElementToTarget: function(region, element, target) {
    if (region && target) {
      this.upwardFrom(element, function(e) {
        e.setAttribute("tipster_region", region.id);
        if (e == target) return true;
      });
    }
  },
  
  upwardFrom: function(element, visitor) {
    while (element = $(element)) {
      if (visitor(element)) return element;
      element = element.parentNode;
    }
  }
}

Event.observe(window, "load", function() {
  tipsters.start();
});
