Show:
/**
 * @module proact-flow
 */

/**
 * <p>
 *   Creates a queue of actions or action queue.
 * </p>
 * <p>
 *  The idea of the action queues is to decide the order of the actions pushed into them.
 *  For example if an action should be executed only once, but is pushed for a second time,
 *  it is moved in the end of the queue and its parameters are updated.
 * </p>
 * <p>
 *  The ProAct.Queue is a priority queue, meaning every action has a numeric priority.
 *  The actions with the numerically lowest priority are with highes prority when executed.
 * </p>
 * <p>
 *  The {{#crossLink "ProAct.Queue/go:method"}}{{/crossLink}} method deques all the actions from the queue and executes them in the right
 *  order, using their priorities.
 * </p>
 * <p>
 *  A ProAct.Queue can be used to setup the action flow - the order of the actions must be executed.
 *  ProAct.js uses it to create an action flow if something changes.
 * </p>
 *
 * TODO Default name should be extracted to a constant. ~meddle@2014-07-10
 *
 * @class ProAct.Queue
 * @constructor
 * @param {String} name
 *    The name of the queue, every ProAct.Queue must have a name.
 *    The default value of the name is 'proq'. {{#crossLink "ProAct.Queues"}}{{/crossLink}} uses the names to manage its queues.
 * @param {Object} options
 *    Various options for the queue.
 *    <p>Available options:</p>
 *    <ul>
 *      <li>before - A callback called before each call of {{#crossLink "ProAct.Queue/go:method"}}{{/crossLink}}.</li>
 *      <li>after - A callback called after each call of {{#crossLink "ProAct.Queue/go:method"}}{{/crossLink}}.</li>
 *      <li>err - A callback called every time an error is thrown.</li>
 *    </ul>
 */
ProAct.Queue = P.Q = function (name, options) {
  this.name = name || 'proq';
  this.options = options || {};

  this._queue = [];
};

/**
 * Executes the passed <i>action</i>.
 *
 * @for ProAct.Queue
 * @method runAction
 * @static
 * @param {ProAct.Queue} queue
 *      The queue managing the action to execute.
 * @param {Object} context
 *      The context in which the action should be executed.
 *      <p>
 *        The action is a normal JavaScript function and the context is the object
 *        that should be bound to <i>this</i> when calling it.
 *      </p>
 *      <p>
 *        It can be null or undefined.
 *      </p>
 * @param {Function} action
 *      The action to execute.
 * @param {Array} args
 *      The parameters to be passed to the action.
 * @param {Function} errHandler
 *      It is called if an error is thrown when executing the action.
 *      <p>
 *        It can be null if the error should be catched from the outside.
 *      </p>
 */
ProAct.Queue.runAction = function (queue, context, action, args, errHandler) {
  if (args && args.length > 0) {
    if (errHandler) {
      try {
        action.apply(context, args);
      } catch (e) {
        if (!e.fromFlow) {
          e.fromFlow = true;
          errHandler(queue, e);
        } else {
          throw e;
        }
      }
    } else {
      action.apply(context, args);
    }
  } else {
    if (errHandler) {
      try {
        action.call(context);
      } catch(e) {
        if (!e.fromFlow) {
          e.fromFlow = true;
          errHandler(queue, e);
        } else {
          throw e;
        }
      }
    } else {
      action.call(context);
    }
  }
};

P.Q.prototype = {

  /**
   * Reference to the constructor of this object.
   *
   * @property constructor
   * @type ProAct.Queue
   * @final
   * @for ProAct.Queue
   */
  constructor: ProAct.Queue,

  /**
   * Retrieves the lenght of this `ProAct.Queue`.
   *
   * @for ProAct.Queue
   * @instance
   * @method length
   * @return {Number}
   *      The number of actions queued in this queue.
   */
  length: function () {
    return this._queue.length / 4;
  },

  /**
   * Checks if this `ProAct.Queue` is empty.
   *
   * @for ProAct.Queue
   * @instance
   * @method isEmpty
   * @return {Boolean}
   *      True if there are no actions in this queue.
   */
  isEmpty: function () {
    return this.length() === 0;
  },

  /**
   * Pushes an action to this queue.
   * This method can enque the same action multiple times and always with priority of '1'.
   * <p>
   *  `defer`, `enque` and `add` are aliases of this method.
   * </p>
   *
   * @for ProAct.Queue
   * @instance
   * @method push
   * @param {Object} context
   *      The context of the action.
   *      It can be null.
   *      <p>
   *        If the method is called with a Function context, the context becomes the action.
   *        This way the method can be called with only one parameter for actions without context.
   *      </p>
   * @param {Function} action
   *      The action to enque.
   *      <p>
   *        If there is no context and the action is passed in place of the context,
   *        this parameter can hold the arguments of the action.
   *      </p>
   * @param {Array} args
   *      Arguments to be passed to the action when it is executed.
   */
  push: function (context, action, args) {
    if (context && (typeof(context) === 'function')) {
      args = action;
      action = context;
      context = null;
    }

    this._queue.push(context, action, args, 1);
  },

  /**
   * Pushes an action to this queue only once.
   * <p>
   *  If the action is pushed for the second time using this method, instead of
   *  adding it to the queue, its priority goes up and its arguments are updated.
   *  This means that this action will be executed after all the other actions, pushed only once.
   * </p>
   * <p>
   *  `deferOnce`, `enqueOnce` and `addOnce` are aliases of this method.
   * </p>
   *
   * @for ProAct.Queue
   * @instance
   * @method pushOnce
   * @param {Object} context
   *      The context of the action.
   *      It can be null.
   *      <p>
   *        If the method is called with a Function context, the context becomes the action.
   *        This way the method can be called with only one parameter for actions without context.
   *      </p>
   * @param {Function} action
   *      The action to enque.
   *      <p>
   *        If there is no context and the action is passed in place of the context,
   *        this parameter can hold the arguments of the action.
   *      </p>
   * @param {Array} args
   *      Arguments to be passed to the action when it is executed.
   */
  pushOnce: function (context, action, args) {
    if (context && (typeof(context) === 'function')) {
      args = action;
      action = context;
      context = null;
    }

    var queue = this._queue, current, currentMethod,
        i, length = queue.length;

    for (i = 0; i < length; i += 4) {
      current = queue[i];
      currentMethod = queue[i + 1];

      if (current === context && currentMethod === action) {
        queue[i + 2] = args;
        queue[i + 3] = queue[i + 3] + 1;
        return;
      }
    }

    this.push(context, action, args);
  },

  /**
   * Starts the action flow.
   * <p>
   *  Executes the actions in this queue in the order they were enqued, but also uses the priorities
   *  to execute these with numerically higher priority after these with numerically lower priority.
   * </p>
   * <p>
   *  If some of the actions enques new actions in this queue and the parameter <i>once</i> is set to false
   *  this method is recursively called executing the new actions.
   * </p>
   * <p>
   *  `run` is alias of this method.
   * </p>
   *
   * @for ProAct.Queue
   * @instance
   * @method go
   * @param {Boolean} once
   *      True if 'go' should not be called for actions generated by the executed ones.
   */
  go: function (once) {
    var queue = this._queue,
        options = this.options,
        runs = this.runs,
        length = queue.length,
        before = options && options.before,
        after = options && options.after,
        err = options && options.err,
        i, l = length, going = true, priority = 1,
        tl = l,
        obj, method, args, prio;

    if (length && before) {
      before(this);
    }

    while (going) {
      going = false;
      l = tl;
      for (i = 0; i < l; i += 4) {
        obj = queue[i];
        method = queue[i + 1];
        args = queue[i + 2];
        prio = queue[i + 3];

        if (prio === priority) {
          P.Q.runAction(this, obj, method, args, err);
        } else if (prio > priority) {
          going = true;
          tl = i + 4;
        }
      }
      priority = priority + 1;
    }

    if (length && after) {
      after(this);
    }

    if (queue.length > length) {
      this._queue = queue.slice(length);

      if (!once) {
        this.go();
      }
    } else {
      this._queue.length = 0;
    }
  }
};

P.Q.prototype.defer = P.Q.prototype.enque = P.Q.prototype.add = P.Q.prototype.push;
P.Q.prototype.deferOnce = P.Q.prototype.enqueOnce = P.Q.prototype.addOnce = P.Q.prototype.pushOnce;
P.Q.prototype.run = P.Q.prototype.go;