text: /*
   * Utilities: A classic collection of JavaScript utilities
   * Copyright 2112 Matthew Eernisse (mde@fleegix.org)
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *         http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   *
  */
  
  var async = {};
  
  /*
  AsyncChain -- performs a list of asynchronous calls in a desired order.
  Optional "last" method can be set to run after all the items in the
  chain have completed.
  
    // Example usage
    var asyncChain = new async.AsyncChain([
      {
        func: app.trainToBangkok,
        args: [geddy, neil, alex],
        callback: null, // No callback for this action
      },
      {
        func: fs.readdir,
        args: [geddy.config.dirname + '/thailand/express'],
        callback: function (err, result) {
          if (err) {
            // Bail out completely
            arguments.callee.chain.abort();
          }
          else if (result.theBest) {
            // Don't run the next item in the chain; go directly
            // to the 'last' method.
            arguments.callee.chain.shortCircuit();
          }
          else {
            // Otherwise do some other stuff and
            // then go to the next link
          }
        }
      },
      {
        func: child_process.exec,
        args: ['ls ./'],
        callback: this.hitTheStops
      }
    ]);
  
    // Function to exec after all the links in the chain finish
    asyncChain.last = function () { // Do some final stuff };
  
    // Start the async-chain
    asyncChain.run();
  
  */
  async.execNonBlocking = function (func) {
    if (typeof process != 'undefined' && typeof process.nextTick == 'function') {
      process.nextTick(func);
    }
    else {
      setTimeout(func, 0);
    }
  };
  
  async.AsyncBase = new (function () {
  
    this.init = function (chain) {
      var item;
      this.chain = [];
      this.currentItem = null;
      this.shortCircuited = false;
      this.shortCircuitedArgs = undefined;
      this.aborted = false;
  
      for (var i = 0; i < chain.length; i++) {
        item = chain[i];
        this.addItem(item);
      }
    };
  
    this.addItem = function(item) {
      this.chain.push(new async.AsyncCall(
        item.func, item.args || [], item.callback, item.context));
    };
  
    // alias
    this.push = this.addItem;
  
    this.runItem = function (item) {
      // Reference to the current item in the chain -- used
      // to look up the callback to execute with execCallback
      this.currentItem = item;
      // Scopage
      var _this = this;
      // Pass the arguments passed to the current async call
      // to the callback executor, execute it in the correct scope
      var executor = function () {
        _this.execCallback.apply(_this, arguments);
      };
      // Append the callback executor to the end of the arguments
      // Node helpfully always has the callback func last
      var args = item.args.concat(executor);
      var func = item.func;
      // Run the async call
      func.apply(item.context, args);
    };
  
    this.next = function () {
      if (this.chain.length) {
        this.runItem(this.chain.shift());
      }
      else {
        this.last();
      }
    };
  
    this.execCallback = function () {
      // Look up the callback, if any, specified for this async call
      var callback = this.currentItem.callback;
      // If there's a callback, do it
      if (callback && typeof callback == 'function') {
        // Allow access to the chain from inside the callback by setting
        // callback.chain = this, and then using arguments.callee.chain
        callback.chain = this;
        callback.apply(this.currentItem.context, arguments);
      }
  
      this.currentItem.finished = true;
  
      // If one of the async callbacks called chain.shortCircuit,
      // skip to the 'last' function for the chain
      if (this.shortCircuited) {
        this.last.apply(null, this.shortCircuitedArgs);
      }
      // If one of the async callbacks called chain.abort,
      // bail completely out
      else if (this.aborted) {
        return;
      }
      // Otherwise run the next item, if any, in the chain
      // Let's try not to block if we don't have to
      else {
        // Scopage
        var _this = this;
        async.execNonBlocking(function () { _this.next.call(_this); });
      }
    }
  
    // Short-circuit the chain, jump straight to the 'last' function
    this.shortCircuit = function () {
      this.shortCircuitedArgs = arguments;
      this.shortCircuited = true;
    }
  
    // Stop execution of the chain, bail completely out
    this.abort = function () {
      this.aborted = true;
    }
  
    // Kick off the chain by grabbing the first item and running it
    this.run = this.next;
  
    // Function to run when the chain is done -- default is a no-op
    this.last = function () {};
  
  })();
  
  async.AsyncChain = function (chain) {
    this.init(chain);
  };
  
  async.AsyncChain.prototype = async.AsyncBase;
  
  async.AsyncGroup = function (group) {
    var item;
    var callback;
    var args;
  
    this.group = [];
    this.outstandingCount = 0;
  
    for (var i = 0; i < group.length; i++) {
      item = group[i];
      this.group.push(new async.AsyncCall(
          item.func, item.args, item.callback, item.context));
      this.outstandingCount++;
    }
  
  };
  
  /*
  Simpler way to group async calls -- doesn't ensure completion order,
  but still has a "last" method called when the entire group of calls
  have completed.
  */
  async.AsyncGroup.prototype = new function () {
    this.run = function () {
      var _this = this
        , group = this.group
        , item
        , createItem = function (item, args) {
            return function () {
              item.func.apply(item.context, args);
            };
          }
        , createCallback = function (item) {
            return function () {
              if (item.callback) {
                item.callback.apply(null, arguments);
              }
              _this.finish.call(_this);
            }
          };
  
      for (var i = 0; i < group.length; i++) {
        item = group[i];
        callback = createCallback(item);
        args = item.args.concat(callback);
        // Run the async call
        async.execNonBlocking(createItem(item, args));
      }
    };
  
    this.finish = function () {
      this.outstandingCount--;
      if (!this.outstandingCount) {
        this.last();
      };
    };
  
    this.last = function () {};
  
  };
  
  var _createSimpleAsyncCall = function (func, context) {
    return {
      func: func
    , args: []
    , callback: function () {}
    , context: context
    };
  };
  
  async.SimpleAsyncChain = function (funcs, context) {
    var chain = [];
    for (var i = 0, ii = funcs.length; i < ii; i++) {
      chain.push(_createSimpleAsyncCall(funcs[i], context));
    }
    this.init(chain);
  };
  
  async.SimpleAsyncChain.prototype = async.AsyncBase;
  
  async.AsyncCall = function (func, args, callback, context) {
    this.func = func;
    this.args = args;
    this.callback = callback || null;
    this.context = context || null;
  };
  
  async.Initializer = function (steps, callback) {
    var self = this;
    this.steps = {};
    this.callback = callback;
    // Create an object-literal of the steps to tick off
    steps.forEach(function (step) {
      self.steps[step] = false;
    });
  };
  
  async.Initializer.prototype = new (function () {
    this.complete = function (step) {
      var steps = this.steps;
      // Tick this step off
      steps[step] = true;
      // Iterate the steps -- if any are not done, bail out
      for (var p in steps) {
        if (!steps[p]) {
          return false;
        }
      }
      // If all steps are done, run the callback
      this.callback();
    };
  })();
  
  module.exports = async;
  
  