/**
 * Allows to chain method executions without having to rely on additional APIs.
 *
 * For example, say you have a class with an asynchronous method "performAction" which takes some parameters
 * and when computation is done invokes a "callback" parameter:
 *
 *     var myObj = {
 *         performAction: function(arg1, arg2, callback) {
 *             // doest something
 *         }
 *     };
 *
 *  And now you have a requirement that you need to make it possible to chain several methods, this can be easily achieved
 *  with SimpleChain:
 *
 *      var chain = Ext.create('MF.intercepting.SimpleChain', {
 *          interceptors: [
 *              {
 *                  performAction: function(arg1, arg2, arg3, next) {
 *                      // received arguments passed to "chain.start()" method
 *                      // arg1 = "foo"
 *                      // arg2 = "bar"
 *                      // arg3 = "baz"
 *                      next(arg1, "replaced-2nd-arg", arg3);
 *                  }
 *              },
 *              {
 *                  performAction: function(arg1, arg2, arg3, next) {
 *                      // received values from previous callback
 *                      // arg1 = "foo"
 *                      // arg2 = "replaced-2nd-arg"
 *                      // arg3 = "baz"
 *                      next("replaced-1st-arg", arg2, arg3);
 *                  }
 *              }
 *          ]
 *      }];
 *
 *      chain.start('pefromAction', ['foo', 'bar', 'baz'], function(arg1, arg2, arg3) {
 *          // values received from last interceptor
 *          // arg1 = "replaced-1st-arg"
 *          // arg2 = "replaced-2nd-arg"
 *          // arg3 = "baz"
 *      });
 *
 * @author Sergei Lissovski <sergei.lissovski@modera.org>
 */
Ext.define('MF.intercepting.SimpleChain', {
    requires: [
        'MF.Util'
    ],

    /**
     * Interceptors. When you invoke #start method then every's interceptor method with name "methodName" and arguments
     * specified in start's method "args" will be attempted to be invoked.
     *
     * @cfg {Object[]} interceptors
     */

    constructor: function (config) {
        MF.Util.validateRequiredConfigParams(this, config, ['interceptors']);

        Ext.apply(this, config);
    },

    /**
     * Starts executing the chain, a given *methodName* is going to be invoked on every interceptors with
     * *args*.
     *
     * @param {String} methodName  A name of a method to invoke on configured interceptors.
     * @param {Mixed[]} args  Interceptors are going to be invoked with this sets of arguments.
     * @param {Function} callback  When there are no interceptors left this callback will be invoked.
     * @param {Object} callbackContext  Optional. If provided then passed #callback is going to be invoked
     *                                  with this context, otherwise SimpleChain's context is used.
     */
    start: function(methodName, args, callback, callbackContext) {
        var me = this;

        var position = 0;

        var callNext = function() {
            if (me.interceptors[position]) {
                var itc = me.interceptors[position];

                if (!Ext.isFunction(itc[methodName])) {
                    throw Ext.String.format(
                        '{0}.start(): interceptor at position {1} does not have method "{2}".',
                        me.$className, position, methodName
                    );
                }

                args.push(function(receivedArgs) {
                    args = [];
                    Ext.each(arguments, function(arg) {
                        args.push(arg);
                    });

                    position++;

                    callNext();
                });

                itc[methodName].apply(itc, args);
            } else {
                callback.apply(callbackContext || me, args);
            }
        };

        callNext();
    }
});