text: 'use strict';
  
  /**
   * Module exports.
   */
  
  exports.EventEmitter = EventEmitter;
  
  /**
   * Object#toString reference.
   */
  var objToString = Object.prototype.toString;
  
  /**
   * Check if a value is an array.
   *
   * @api private
   * @param {*} val The value to test.
   * @return {boolean} true if the value is an array, otherwise false.
   */
  function isArray (val) {
    return objToString.call(val) === '[object Array]';
  }
  
  /**
   * Event emitter constructor.
   *
   * @api public
   */
  function EventEmitter () {}
  
  /**
   * Add a listener.
   *
   * @api public
   * @param {string} name Event name.
   * @param {Function} fn Event handler.
   * @return {EventEmitter} Emitter instance.
   */
  EventEmitter.prototype.on = function (name, fn) {
    if (!this.$events) {
      this.$events = {};
    }
  
    if (!this.$events[name]) {
      this.$events[name] = fn;
    } else if (isArray(this.$events[name])) {
      this.$events[name].push(fn);
    } else {
      this.$events[name] = [this.$events[name], fn];
    }
  
    return this;
  };
  
  EventEmitter.prototype.addListener = EventEmitter.prototype.on;
  
  /**
   * Adds a volatile listener.
   *
   * @api public
   * @param {string} name Event name.
   * @param {Function} fn Event handler.
   * @return {EventEmitter} Emitter instance.
   */
  EventEmitter.prototype.once = function (name, fn) {
    var self = this;
  
    function on () {
      self.removeListener(name, on);
      fn.apply(this, arguments);
    }
  
    on.listener = fn;
    this.on(name, on);
  
    return this;
  };
  
  /**
   * Remove a listener.
   *
   * @api public
   * @param {string} name Event name.
   * @param {Function} fn Event handler.
   * @return {EventEmitter} Emitter instance.
   */
  EventEmitter.prototype.removeListener = function (name, fn) {
    if (this.$events && this.$events[name]) {
      var list = this.$events[name];
  
      if (isArray(list)) {
        var pos = -1;
  
        for (var i = 0, l = list.length; i < l; i++) {
          if (list[i] === fn || (list[i].listener && list[i].listener === fn)) {
            pos = i;
            break;
          }
        }
  
        if (pos < 0) {
          return this;
        }
  
        list.splice(pos, 1);
  
        if (!list.length) {
          delete this.$events[name];
        }
      } else if (list === fn || (list.listener && list.listener === fn)) {
        delete this.$events[name];
      }
    }
  
    return this;
  };
  
  /**
   * Remove all listeners for an event.
   *
   * @api public
   * @param {string} name Event name.
   * @return {EventEmitter} Emitter instance.
   */
  EventEmitter.prototype.removeAllListeners = function (name) {
    if (name === undefined) {
      this.$events = {};
      return this;
    }
  
    if (this.$events && this.$events[name]) {
      this.$events[name] = null;
    }
  
    return this;
  };
  
  /**
   * Get all listeners for a given event.
   *
   * @api public
   * @param {string} name Event name.
   * @return {EventEmitter} Emitter instance.
   */
  EventEmitter.prototype.listeners = function (name) {
    if (!this.$events) {
      this.$events = {};
    }
  
    if (!this.$events[name]) {
      this.$events[name] = [];
    }
  
    if (!isArray(this.$events[name])) {
      this.$events[name] = [this.$events[name]];
    }
  
    return this.$events[name];
  };
  
  /**
   * Emit an event.
   *
   * @api public
   * @param {string} name Event name.
   * @return {boolean} true if at least one handler was invoked, else false.
   */
  EventEmitter.prototype.emit = function (name) {
    if (!this.$events) {
      return false;
    }
  
    var handler = this.$events[name];
  
    if (!handler) {
      return false;
    }
  
    var args = Array.prototype.slice.call(arguments, 1);
  
    if (typeof handler === 'function') {
      handler.apply(this, args);
    } else if (isArray(handler)) {
      var listeners = handler.slice();
  
      for (var i = 0, l = listeners.length; i < l; i++) {
        listeners[i].apply(this, args);
      }
    } else {
      return false;
    }
  
    return true;
  };
  