text: var AWS = require('./core');
  
  /**
   * @api private
   * @!method on(eventName, callback)
   *   Registers an event listener callback for the event given by `eventName`.
   *   Parameters passed to the callback function depend on the individual event
   *   being triggered. See the event documentation for those parameters.
   *
   *   @param eventName [String] the event name to register the listener for
   *   @param callback [Function] the listener callback function
   *   @param toHead [Boolean] attach the listener callback to the head of callback array if set to true.
   *     Default to be false.
   *   @return [AWS.SequentialExecutor] the same object for chaining
   */
  AWS.SequentialExecutor = AWS.util.inherit({
  
    constructor: function SequentialExecutor() {
      this._events = {};
    },
  
    /**
     * @api private
     */
    listeners: function listeners(eventName) {
      return this._events[eventName] ? this._events[eventName].slice(0) : [];
    },
  
    on: function on(eventName, listener, toHead) {
      if (this._events[eventName]) {
        toHead ?
          this._events[eventName].unshift(listener) :
          this._events[eventName].push(listener);
      } else {
        this._events[eventName] = [listener];
      }
      return this;
    },
  
    onAsync: function onAsync(eventName, listener, toHead) {
      listener._isAsync = true;
      return this.on(eventName, listener, toHead);
    },
  
    removeListener: function removeListener(eventName, listener) {
      var listeners = this._events[eventName];
      if (listeners) {
        var length = listeners.length;
        var position = -1;
        for (var i = 0; i < length; ++i) {
          if (listeners[i] === listener) {
            position = i;
          }
        }
        if (position > -1) {
          listeners.splice(position, 1);
        }
      }
      return this;
    },
  
    removeAllListeners: function removeAllListeners(eventName) {
      if (eventName) {
        delete this._events[eventName];
      } else {
        this._events = {};
      }
      return this;
    },
  
    /**
     * @api private
     */
    emit: function emit(eventName, eventArgs, doneCallback) {
      if (!doneCallback) doneCallback = function() { };
      var listeners = this.listeners(eventName);
      var count = listeners.length;
      this.callListeners(listeners, eventArgs, doneCallback);
      return count > 0;
    },
  
    /**
     * @api private
     */
    callListeners: function callListeners(listeners, args, doneCallback, prevError) {
      var self = this;
      var error = prevError || null;
  
      function callNextListener(err) {
        if (err) {
          error = AWS.util.error(error || new Error(), err);
          if (self._haltHandlersOnError) {
            return doneCallback.call(self, error);
          }
        }
        self.callListeners(listeners, args, doneCallback, error);
      }
  
      while (listeners.length > 0) {
        var listener = listeners.shift();
        if (listener._isAsync) { // asynchronous listener
          listener.apply(self, args.concat([callNextListener]));
          return; // stop here, callNextListener will continue
        } else { // synchronous listener
          try {
            listener.apply(self, args);
          } catch (err) {
            error = AWS.util.error(error || new Error(), err);
          }
          if (error && self._haltHandlersOnError) {
            doneCallback.call(self, error);
            return;
          }
        }
      }
      doneCallback.call(self, error);
    },
  
    /**
     * Adds or copies a set of listeners from another list of
     * listeners or SequentialExecutor object.
     *
     * @param listeners [map<String,Array<Function>>, AWS.SequentialExecutor]
     *   a list of events and callbacks, or an event emitter object
     *   containing listeners to add to this emitter object.
     * @return [AWS.SequentialExecutor] the emitter object, for chaining.
     * @example Adding listeners from a map of listeners
     *   emitter.addListeners({
     *     event1: [function() { ... }, function() { ... }],
     *     event2: [function() { ... }]
     *   });
     *   emitter.emit('event1'); // emitter has event1
     *   emitter.emit('event2'); // emitter has event2
     * @example Adding listeners from another emitter object
     *   var emitter1 = new AWS.SequentialExecutor();
     *   emitter1.on('event1', function() { ... });
     *   emitter1.on('event2', function() { ... });
     *   var emitter2 = new AWS.SequentialExecutor();
     *   emitter2.addListeners(emitter1);
     *   emitter2.emit('event1'); // emitter2 has event1
     *   emitter2.emit('event2'); // emitter2 has event2
     */
    addListeners: function addListeners(listeners) {
      var self = this;
  
      // extract listeners if parameter is an SequentialExecutor object
      if (listeners._events) listeners = listeners._events;
  
      AWS.util.each(listeners, function(event, callbacks) {
        if (typeof callbacks === 'function') callbacks = [callbacks];
        AWS.util.arrayEach(callbacks, function(callback) {
          self.on(event, callback);
        });
      });
  
      return self;
    },
  
    /**
     * Registers an event with {on} and saves the callback handle function
     * as a property on the emitter object using a given `name`.
     *
     * @param name [String] the property name to set on this object containing
     *   the callback function handle so that the listener can be removed in
     *   the future.
     * @param (see on)
     * @return (see on)
     * @example Adding a named listener DATA_CALLBACK
     *   var listener = function() { doSomething(); };
     *   emitter.addNamedListener('DATA_CALLBACK', 'data', listener);
     *
     *   // the following prints: true
     *   console.log(emitter.DATA_CALLBACK == listener);
     */
    addNamedListener: function addNamedListener(name, eventName, callback, toHead) {
      this[name] = callback;
      this.addListener(eventName, callback, toHead);
      return this;
    },
  
    /**
     * @api private
     */
    addNamedAsyncListener: function addNamedAsyncListener(name, eventName, callback) {
      callback._isAsync = true;
      return this.addNamedListener(name, eventName, callback);
    },
  
    /**
     * Helper method to add a set of named listeners using
     * {addNamedListener}. The callback contains a parameter
     * with a handle to the `addNamedListener` method.
     *
     * @callback callback function(add)
     *   The callback function is called immediately in order to provide
     *   the `add` function to the block. This simplifies the addition of
     *   a large group of named listeners.
     *   @param add [Function] the {addNamedListener} function to call
     *     when registering listeners.
     * @example Adding a set of named listeners
     *   emitter.addNamedListeners(function(add) {
     *     add('DATA_CALLBACK', 'data', function() { ... });
     *     add('OTHER', 'otherEvent', function() { ... });
     *     add('LAST', 'lastEvent', function() { ... });
     *   });
     *
     *   // these properties are now set:
     *   emitter.DATA_CALLBACK;
     *   emitter.OTHER;
     *   emitter.LAST;
     */
    addNamedListeners: function addNamedListeners(callback) {
      var self = this;
      callback(
        function() {
          self.addNamedListener.apply(self, arguments);
        },
        function() {
          self.addNamedAsyncListener.apply(self, arguments);
        }
      );
      return this;
    }
  });
  
  /**
   * {on} is the prefered method.
   * @api private
   */
  AWS.SequentialExecutor.prototype.addListener = AWS.SequentialExecutor.prototype.on;
  
  /**
   * @api private
   */
  module.exports = AWS.SequentialExecutor;
  