/*
 Ext.promise.Consequence adapted from:
 [DeftJS](https://github.com/deftjs/deftjs5)
 Copyright (c) 2012-2013 [DeftJS Framework Contributors](http://deftjs.org)
 Open source under the [MIT License](http://en.wikipedia.org/wiki/MIT_License).
 */

/**
 * Consequences are used internally by a Deferred to capture and notify callbacks, and
 * propagate their transformed results as fulfillment or rejection.
 *
 * Developers never directly interact with a Consequence.
 *
 * A Consequence forms a chain between two Deferreds, where the result of the first
 * Deferred is transformed by the corresponding callback before being applied to the
 * second Deferred.
 *
 * Each time a Deferred's `then` method is called, it creates a new Consequence that will
 * be triggered once its originating Deferred has been fulfilled or rejected. A Consequence
 * captures a pair of optional onFulfilled and onRejected callbacks.
 *
 * Each Consequence has its own Deferred (which in turn has a Promise) that is resolved or
 * rejected when the Consequence is triggered. When a Consequence is triggered by its
 * originating Deferred, it calls the corresponding callback and propagates the transformed
 * result to its own Deferred; resolved with the callback return value or rejected with any
 * error thrown by the callback.
 *
 * @since 6.0.0
 * @private
 */
Ext.define('Ext.promise.Consequence', function(Consequence) { return {
    /**
     * @property {Ext.promise.Promise}
     * Promise of the future value of this Consequence.
     */
    promise: null,

    /**
     * @property {Ext.promise.Deferred} deferred Internal Deferred for this Consequence.
     *
     * @private
     */
    deferred: null,

    /**
     * @property {Function} onFulfilled Callback to execute when this Consequence is triggered
     * with a fulfillment value.
     *
     * @private
     */
    onFulfilled: null,

    /**
     * @property {Function} onRejected Callback to execute when this Consequence is triggered
     * with a rejection reason.
     *
     * @private
     */
    onRejected: null,

    /**
     * @property {Function} onProgress Callback to execute when this Consequence is updated
     * with a progress value.
     *
     * @private
     */
    onProgress: null,

    /**
     * @param {Function} onFulfilled Callback to execute to transform a fulfillment value.
     * @param {Function} onRejected Callback to execute to transform a rejection reason.
     */
    constructor: function(onFulfilled, onRejected, onProgress) {
        var me = this;

        me.onFulfilled = onFulfilled;
        me.onRejected = onRejected;
        me.onProgress = onProgress;
        me.deferred = new Ext.promise.Deferred();
        me.promise = me.deferred.promise;
    },

    /**
     * Trigger this Consequence with the specified action and value.
     *
     * @param {String} action Completion action (i.e. fulfill or reject).
     * @param {Mixed} value Fulfillment value or rejection reason.
     */
    trigger: function(action, value) {
        var me = this,
            deferred = me.deferred;

        switch (action) {
            case 'fulfill':
                me.propagate(value, me.onFulfilled, deferred, deferred.resolve);
                break;

            case 'reject':
                me.propagate(value, me.onRejected, deferred, deferred.reject);
                break;
        }
    },

    /**
     * Update this Consequence with the specified progress value.
     *
     * @param {Mixed} value Progress value.
     */
    update: function(progress) {
        if (Ext.isFunction(this.onProgress)) {
            progress = this.onProgress(progress);
        }
        
        this.deferred.update(progress);
    },

    /**
     * Transform and propagate the specified value using the
     * optional callback and propagate the transformed result.
     *
     * @param {Mixed} value Value to transform and/or propagate.
     * @param {Function} [callback] Callback to use to transform the value.
     * @param {Function} deferred Deferred to use to propagate the value, if no callback
     * was specified.
     * @param {Function} deferredMethod Deferred method to call to propagate the value,
     * if no callback was specified.
     *
     * @private
     */
    propagate: function(value, callback, deferred, deferredMethod) {
        if (Ext.isFunction(callback)) {
            this.schedule(function() {
                try {
                    deferred.resolve(callback(value));
                }
                catch (e) {
                    deferred.reject(e);
                }
            });
        }
        else {
            deferredMethod.call(this.deferred, value);
        }
    },

    /**
     * Schedules the specified callback function to be executed on the next turn of the
     * event loop.
     *
     * @param {Function} callback Callback function.
     *
     * @private
     */
    schedule: function(callback) {
        var n = Consequence.queueSize++;

        Consequence.queue[n] = callback;

        if (!n) { // if (queue was empty)
            Ext.asap(Consequence.dispatch);
        }
    },

    statics: {
        /**
         * @property {Function[]} queue The queue of callbacks pending. This array is never
         * shrunk to reduce GC thrash but instead its elements will be set to `null`.
         *
         * @private
         */
        queue: new Array(10000),

        /**
         * @property {Number} queueSize The number of callbacks in the `queue`.
         *
         * @private
         */
        queueSize: 0,

        /**
         * This method drains the callback queue and calls each callback in order.
         *
         * @private
         */
        dispatch: function() {
            var queue = Consequence.queue,
                fn, i;

            // The queue could grow on each call, so we cannot cache queueSize here.
            for (i = 0; i < Consequence.queueSize; ++i) {
                fn = queue[i];
                queue[i] = null; // release our reference on the callback
                fn();
            }

            Consequence.queueSize = 0;
        }
    }
}});
