// http://github.com/nakajima/event-delegation/tree/master
// Introduces Event delegation to Prototype.js
(function() {
  // Error to be thrown when trying to delegate an event
  // that doesn't bubble.
  var NonBubblingEventError = function(evntNme, submit) {
    this.message = 'Cannot delegate non-bubbling event: "' + evntNme + '" event does not bubble.';
    if (submit) { this.message += ' Use the "form:submit" custom event instead.'; }
    this.name = 'NonBubblingEventError';
  };
  
  // Prettier.
  NonBubblingEventError.prototype.toString = function() {
    return this.name + ': "' + this.message + '"';
  };
  
  // Checks an event to make sure it bubbles. If not, throws a
  // ensureEventBubbles error.
  var ensureEventBubbles = function(anEvent) {
    if (m = anEvent.match(/load|unload|focus|blur|^submit/)) {
      throw new NonBubblingEventError(m, m.toString().match(/^submit/));
    }
  }
  
  // Handles an event if event target matches selector
  var conditionHandler = function(selector, handler, event) {
    var origin = event.element();
    if (origin.match(selector)) { return handler(event); }
    if (origin = origin.up(selector)) {
      event.element = function() { return origin; }
      return handler(event);
    }
    return false;
  };
  
  // Sets up an appropriate delegation for a certain event
  var createDelegation = function(anEvent, element, pair) {
    var selector = pair.key, handler = pair.value;
    element.observe(anEvent, conditionHandler.curry(selector, handler));
  };
  
  // Container for #delegate method
  var methods = {
    delegate: function(element, anEvent, delegations) {
      element = $(element);
      ensureEventBubbles(anEvent);
      $H(delegations).each(createDelegation.curry(anEvent, element));
      return element;
    }
  };
  
  // Extend elements
  Element.addMethods(methods);
  
  // Extend document
  document.delegate = methods.delegate.methodize();
  
  // Allow for passing true to Event#stop, which will stop both
  // the custom event, and the originEvent.
  // FIXME This doesn't work in IE due to event behavior
  document.observe('form:submit', function(event) {
    var stopWithoutOriginal = event.stop;
    event.stop = function(both) {
      if (both) { event.memo['originEvent'].stop(); }
      stopWithoutOriginal.apply(event);
    };
  });  
  
  // Fires form:submit if the origin is within a form element
  var tryForm = function(origin, event) {
    if (form = origin.up('form')) {
      form.fire('form:submit', { originEvent: event });
    }
  };

  // Emulates form submission bubble events for clicks
  document.observe('click', function(event) {
    var origin = event.element();

    // SVG/canvas elements do not have a match attribute
    if (origin.match) {
      if (origin.match('input[type=submit]')) { tryForm(origin, event); }
      if (origin.match('button')) { tryForm(origin, event); }
    }
  });

  // Emulates form submission bubble events for keypresses
  document.observe('keypress', function(event) {
    var origin = event.element();
    if (event.keyCode != Event.KEY_RETURN) { return false; }
    if (origin.match('input[type=~(password|text)]')) { tryForm(origin, event); }
  });
})();