// Generated by CoffeeScript 1.3.3
(function() {
  var balUtilFlow, balUtilTypes,
    __slice = [].slice,
    __hasProp = {}.hasOwnProperty,
    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };

  balUtilTypes = (typeof require === "function" ? require(__dirname + '/types') : void 0) || this.balUtilTypes;

  balUtilFlow = {
    wait: function(delay, fn) {
      return setTimeout(fn, delay);
    },
    extractOptsAndCallback: function(opts, next) {
      if (balUtilTypes.isFunction(opts) && (next != null) === false) {
        next = opts;
        opts = {};
      } else {
        opts || (opts = {});
      }
      next || (next = opts.next || null);
      return [opts, next];
    },
    fireWithOptionalCallback: function(method, args, context) {
      var callback, err, result;
      args || (args = []);
      callback = args[args.length - 1];
      context || (context = null);
      result = null;
      if (method.length === args.length) {
        try {
          result = method.apply(context, args);
        } catch (caughtError) {
          callback(caughtError);
        }
      } else {
        err = null;
        try {
          result = method.apply(context, args);
          if (balUtilTypes.isError(result)) {
            err = result;
          }
        } catch (caughtError) {
          err = caughtError;
        }
        callback(err, result);
      }
      return result;
    },
    extend: function() {
      var args;
      args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
      return this.shallowExtendPlainObjects.apply(this, args);
    },
    shallowExtendPlainObjects: function() {
      var key, obj, objs, target, value, _i, _len;
      target = arguments[0], objs = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
      for (_i = 0, _len = objs.length; _i < _len; _i++) {
        obj = objs[_i];
        obj || (obj = {});
        for (key in obj) {
          if (!__hasProp.call(obj, key)) continue;
          value = obj[key];
          target[key] = value;
        }
      }
      return target;
    },
    deepExtendPlainObjects: function() {
      var key, obj, objs, target, value, _i, _len;
      target = arguments[0], objs = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
      for (_i = 0, _len = objs.length; _i < _len; _i++) {
        obj = objs[_i];
        obj || (obj = {});
        for (key in obj) {
          if (!__hasProp.call(obj, key)) continue;
          value = obj[key];
          if (balUtilTypes.isPlainObject(value)) {
            if (!balUtilTypes.isPlainObject(target[key])) {
              target[key] = {};
            }
            balUtilFlow.deepExtendPlainObjects(target[key], value);
          } else {
            target[key] = value;
          }
        }
      }
      return target;
    },
    dereference: function(source) {
      var target;
      target = JSON.parse(JSON.stringify(source));
      return target;
    },
    each: function(obj, callback, context) {
      var item, key, _i, _len;
      context || (context = obj);
      if (balUtilTypes.isArray(obj)) {
        for (key = _i = 0, _len = obj.length; _i < _len; key = ++_i) {
          item = obj[key];
          if (callback.call(context, item, key, obj) === false) {
            break;
          }
        }
      } else {
        for (key in obj) {
          if (!__hasProp.call(obj, key)) continue;
          item = obj[key];
          if (callback.call(context, item, key, obj) === false) {
            break;
          }
        }
      }
      return this;
    },
    flow: function(opts) {
      var action, actions, args, next, object, tasks;
      object = opts.object, action = opts.action, args = opts.args, tasks = opts.tasks, next = opts.next;
      if (!action) {
        throw new Error('balUtilFlow.flow called without any action');
      }
      actions = action.split(/[,\s]+/g);
      tasks || (tasks = new balUtilFlow.Group(next));
      balUtilFlow.each(actions, function(action) {
        return tasks.push(function(complete) {
          var argsClone, fn;
          argsClone = (args || []).slice();
          argsClone.push(complete);
          fn = object[action];
          return fn.apply(object, argsClone);
        });
      });
      tasks.sync();
      return this;
    }
  };

  /*
  Usage:
  	# Add tasks to a queue then fire them in parallel (asynchronously)
  	tasks = new Group (err) -> next err
  	tasks.push (complete) -> someAsyncFunction(arg1, arg2, complete)
  	tasks.push (complete) -> anotherAsyncFunction(arg1, arg2, complete)
  	tasks.async()
  
  	# Add tasks to a queue then fire them in serial (synchronously)
  	tasks = new Group (err) -> next err
  	tasks.push (complete) -> someAsyncFunction(arg1, arg2, complete)
  	tasks.push (complete) -> anotherAsyncFunction(arg1, arg2, complete)
  	tasks.sync()
  */


  balUtilFlow.Group = (function() {

    _Class.prototype.total = 0;

    _Class.prototype.completed = 0;

    _Class.prototype.running = 0;

    _Class.prototype.exited = false;

    _Class.prototype.breakOnError = true;

    _Class.prototype.autoClear = false;

    _Class.prototype.queue = [];

    _Class.prototype.mode = 'async';

    _Class.prototype.lastResult = null;

    _Class.prototype.results = [];

    _Class.prototype.errors = [];

    _Class.prototype.next = function() {
      throw new Error('Groups require a completion callback');
    };

    function _Class() {
      var arg, args, autoClear, breakOnError, mode, next, _i, _len;
      args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
      this.clear();
      for (_i = 0, _len = args.length; _i < _len; _i++) {
        arg = args[_i];
        if (balUtilTypes.isString(arg)) {
          this.mode = arg;
        } else if (balUtilTypes.isFunction(arg)) {
          this.next = arg;
        } else if (balUtilTypes.isObject(arg)) {
          next = arg.next, mode = arg.mode, breakOnError = arg.breakOnError, autoClear = arg.autoClear;
          if (next) {
            this.next = next;
          }
          if (mode) {
            this.mode = mode;
          }
          if (breakOnError) {
            this.breakOnError = breakOnError;
          }
          if (autoClear) {
            this.autoClear = autoClear;
          }
        } else {
          throw new Error('Unknown argument sent to Groups constructor');
        }
      }
    }

    _Class.prototype.clear = function() {
      this.total = 0;
      this.completed = 0;
      this.running = 0;
      this.exited = false;
      this.queue = [];
      this.results = [];
      this.errors = [];
      this.lastResult = null;
      return this;
    };

    _Class.prototype.hasTasks = function() {
      return this.queue.length !== 0;
    };

    _Class.prototype.hasCompleted = function() {
      return this.total !== 0 && this.total === this.completed;
    };

    _Class.prototype.isRunning = function() {
      return this.running !== 0;
    };

    _Class.prototype.hasExited = function(value) {
      if (value != null) {
        this.exited = value;
      }
      return this.exited === true;
    };

    _Class.prototype.logError = function(err) {
      if (this.errors[this.errors.length - 1] !== err) {
        this.errors.push(err);
      }
      return this;
    };

    _Class.prototype.complete = function() {
      var args, err;
      args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
      err = args[0] || void 0;
      this.lastResult = args;
      if (err) {
        this.logError(err);
      }
      this.results.push(args);
      if (this.running !== 0) {
        --this.running;
      }
      if (this.hasExited()) {

      } else {
        if (err && this.breakOnError) {
          this.exit();
        } else {
          ++this.completed;
          if (this.hasTasks()) {
            this.nextTask();
          } else if (this.isRunning() === false && this.hasCompleted()) {
            this.exit();
          }
        }
      }
      return this;
    };

    _Class.prototype.completer = function() {
      var _this = this;
      return function() {
        var args;
        args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
        return _this.complete.apply(_this, args);
      };
    };

    _Class.prototype.exit = function(err) {
      var errors, lastResult, results;
      if (err == null) {
        err = null;
      }
      if (err) {
        this.logError(err);
      }
      if (this.hasExited()) {

      } else {
        lastResult = this.lastResult;
        results = this.results;
        if (this.errors.length === 0) {
          errors = null;
        } else if (this.errors.length === 1) {
          errors = this.errors[0];
        } else {
          errors = this.errors;
        }
        if (this.autoClear) {
          this.clear();
        } else {
          this.hasExited(true);
        }
        this.next(errors, lastResult, results);
      }
      return this;
    };

    _Class.prototype.tasks = function(tasks) {
      var task, _i, _len;
      for (_i = 0, _len = tasks.length; _i < _len; _i++) {
        task = tasks[_i];
        this.push(task);
      }
      return this;
    };

    _Class.prototype.push = function() {
      var args;
      args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
      ++this.total;
      this.queue.push(args);
      return this;
    };

    _Class.prototype.pushAndRun = function() {
      var args;
      args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
      if (this.mode === 'sync' && this.isRunning()) {
        this.push.apply(this, args);
      } else {
        ++this.total;
        this.runTask(args);
      }
      return this;
    };

    _Class.prototype.nextTask = function() {
      var task;
      if (this.hasTasks()) {
        task = this.queue.shift();
        this.runTask(task);
      }
      return this;
    };

    _Class.prototype.runTask = function(task) {
      var me, run;
      me = this;
      try {
        run = function() {
          var complete, _context, _task;
          ++me.running;
          complete = me.completer();
          if (balUtilTypes.isArray(task)) {
            if (task.length === 2) {
              _context = task[0];
              _task = task[1];
            } else if (task.length === 1) {
              _task = task[0];
              _context = null;
            } else {
              throw new Error('balUtilFlow.Group an invalid task was pushed');
            }
          } else {
            _task = task;
          }
          return balUtilFlow.fireWithOptionalCallback(_task, [complete], _context);
        };
        if (this.completed !== 0 && (this.mode === 'async' || (this.completed % 100) === 0)) {
          setTimeout(run, 0);
        } else {
          run();
        }
      } catch (err) {
        this.complete(err);
      }
      return this;
    };

    _Class.prototype.run = function() {
      var task, _i, _len, _ref;
      if (this.isRunning() === false) {
        this.hasExited(false);
        if (this.hasTasks()) {
          if (this.mode === 'sync') {
            this.nextTask();
          } else {
            _ref = this.queue;
            for (_i = 0, _len = _ref.length; _i < _len; _i++) {
              task = _ref[_i];
              this.nextTask();
            }
          }
        } else {
          this.exit();
        }
      }
      return this;
    };

    _Class.prototype.async = function() {
      this.mode = 'async';
      this.run();
      return this;
    };

    _Class.prototype.sync = function() {
      this.mode = 'sync';
      this.run();
      return this;
    };

    return _Class;

  })();

  balUtilFlow.Block = (function(_super) {

    __extends(_Class, _super);

    _Class.prototype.blockBefore = function(block) {};

    _Class.prototype.blockAfter = function(block, err) {};

    _Class.prototype.blockTaskBefore = function(block, task, err) {};

    _Class.prototype.blockTaskAfter = function(block, task, err) {};

    function _Class(opts) {
      var block, complete, fn, name, parentBlock;
      block = this;
      name = opts.name, fn = opts.fn, parentBlock = opts.parentBlock, complete = opts.complete;
      block.blockName = name;
      if (parentBlock != null) {
        block.parentBlock = parentBlock;
      }
      block.mode = 'sync';
      block.fn = fn;
      _Class.__super__.constructor.call(this, function(err) {
        block.blockAfter(block, err);
        return typeof complete === "function" ? complete(err) : void 0;
      });
      block.blockBefore(block);
      if (block.fn != null) {
        if (block.fn.length === 3) {
          block.total = Infinity;
        }
        try {
          block.fn(function(name, fn) {
            return block.block(name, fn);
          }, function(name, fn) {
            return block.task(name, fn);
          }, function(err) {
            return block.exit(err);
          });
          if (block.fn.length !== 3) {
            block.run();
          }
        } catch (err) {
          block.exit(err);
        }
      } else {
        block.total = Infinity;
      }
      this;

    }

    _Class.prototype.block = function(name, fn) {
      var block, pushBlock;
      block = this;
      pushBlock = function(fn) {
        if (block.total === Infinity) {
          return block.pushAndRun(fn);
        } else {
          return block.push(fn);
        }
      };
      pushBlock(function(complete) {
        var subBlock;
        return subBlock = block.createSubBlock({
          name: name,
          fn: fn,
          complete: complete
        });
      });
      return this;
    };

    _Class.prototype.createSubBlock = function(opts) {
      opts.parentBlock = this;
      return new balUtilFlow.Block(opts);
    };

    _Class.prototype.task = function(name, fn) {
      var block, pushTask;
      block = this;
      pushTask = function(fn) {
        if (block.total === Infinity) {
          return block.pushAndRun(fn);
        } else {
          return block.push(fn);
        }
      };
      pushTask(function(complete) {
        var preComplete;
        preComplete = function(err) {
          block.blockTaskAfter(block, name, err);
          return complete(err);
        };
        block.blockTaskBefore(block, name);
        return balUtilFlow.fireWithOptionalCallback(fn, [preComplete]);
      });
      return this;
    };

    return _Class;

  })(balUtilFlow.Group);

  balUtilFlow.Runner = (function() {

    _Class.prototype.runnerBlock = null;

    function _Class() {
      var _ref;
      if ((_ref = this.runnerBlock) == null) {
        this.runnerBlock = new balUtilFlow.Block();
      }
    }

    _Class.prototype.getRunnerBlock = function() {
      return this.runnerBlock;
    };

    _Class.prototype.block = function() {
      var args, _ref;
      args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
      return (_ref = this.getRunnerBlock()).block.apply(_ref, args);
    };

    _Class.prototype.task = function() {
      var args, _ref;
      args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
      return (_ref = this.getRunnerBlock()).task.apply(_ref, args);
    };

    return _Class;

  })();

  if (typeof module !== "undefined" && module !== null) {
    module.exports = balUtilFlow;
  } else {
    this.balUtilFlow = balUtilFlow;
  }

}).call(this);
