/**
  * @module Chainer
  * @namespace com.rikkertkoppes.util
  * This module is public domain, see http://www.rikkertkoppes.com/thoughts/method-chainer/
  */

var com = com||{};
com.rikkertkoppes = com.rikkertkoppes||{};
com.rikkertkoppes.util = com.rikkertkoppes.util||{};

com.rikkertkoppes.util.Chainer = function() {
  /**
    * @class Chainer
    * @constructor
    * @param c class instance as a source for the chain
    * @param config configuration object literal:
    *  - methods: array of strings or array of objects containing: (mandatory)
    *    - name: string (mandatory)
    *    - index: number callback parameter index (optional)
    *  - scope: scope in which the callbacks should be executed
    */
  function Chainer(c,config) {
    if (c) {
      this._class = c;
      this.config = config||{};
      this.initialize();
    }
  }
  
  /**
    * @method initialize
    * initializes the chainer by creating chain methods
    */
  Chainer.prototype.initialize = function() {
    var self = this;
    this.chain = [];
    this.chainMethods = function() {
      self._do();
    };
    this.chainMethods._do = function() {
      self._do();
    };
    var i,m,name,callbackindex;
    for (i=0; (m=this.config.methods[i]); i++) {
      if (typeof m == 'string') {
        name = m;
      } else if (typeof m == 'object' && m.name) {
        name = m.name;
        callbackindex = m.index;
      }
      this.createChainMethod(name,callbackindex);
    }
  };
  
  /**
    * @method createChainMethod
    * @param name string name of the method
    * @param callbackindex number parameter index for the callback parameter, defaults to last
    * creates a chain method
    */
  Chainer.prototype.createChainMethod = function(name,callbackindex) {
    if (!this._class[name]) return;
    callbackindex = callbackindex||this._class[name].length-1;
    var self = this;
    var f = function() {
      var i,args = [];
      for (i=0; i<arguments.length; i++) args.push(arguments[i]);
      self.chain.push({
        name: name,
        args: args,
        callbackindex: callbackindex
      });
      return self.chainMethods;
    }
    this.chainMethods[name] = f;
    return f;
  };
  
  /**
    * @method _do
    * @param i index at which to start, default is 0
    * executes the chain
    */
  Chainer.prototype._do = function(i) {
    i = i||0;
    var self = this;
    var f = this.chain[i];
    var scope = this.config.scope||this._class;
    var callback = undefined;
    if (this.chain[i+1]) {
      callback = function() {
        self._do(i+1);
      }
    }
    var args = f.args;
    args[f.callbackindex] = callback;
    this._class[f.name].apply(scope,args);
  };
  
  /**
    * @method _chain
    * returns the chainer, this method should always be called first in the chain
    */
  Chainer.prototype._chain = function() {
    return this.chainMethods;
  };
  
  return Chainer;
}();
