    /**
     * @author Ed Spencer
     * @class Ext.data.Batch
     *
     * <p>Provides a mechanism to run one or more {@link Ext.data.Operation operations} in a given order. Fires the 'operationcomplete' event
     * after the completion of each Operation, and the 'complete' event when all Operations have been successfully executed. Fires an 'exception'
     * event if any of the Operations encounter an exception.</p>
     *
     * <p>Usually these are only used internally by {@link Ext.data.proxy.Proxy} classes</p>
     *
     */
    Ext.define('Ext.data.Batch', {
        mixins: {
            observable: 'Ext.util.Observable'
        },

        /**
         * @cfg {Boolean} autoStart
         * True to immediately start processing the batch as soon as it is constructed (defaults to false)
         */
        autoStart: false,

        /**
         * @cfg {Boolean} pauseOnException
         * True to pause the execution of the batch if any operation encounters an exception
         * (defaults to false). If you set this to true you are responsible for implementing the appropriate
         * handling logic and restarting or discarding the batch as needed. There are different ways you could
         * do this, e.g. by handling the batch's {@link #exception} event directly, or perhaps by overriding
         * {@link Ext.data.AbstractStore#onBatchException onBatchException} at the store level. If you do pause
         * and attempt to handle the exception you can call {@link #retry} to process the same operation again.
         *
         * Note that {@link Ext.data.Operation operations} are atomic, so any operations that may have succeeded
         * prior to an exception (and up until pausing the batch) will be finalized at the server level and will
         * not be automatically reversible. Any transactional / rollback behavior that might be desired would have
         * to be implemented at the application level. Pausing on exception will likely be most beneficial when
         * used in coordination with such a scheme, where an exception might actually affect subsequent operations
         * in the same batch and so should be handled before continuing with the next operation.
         *
         * If you have not implemented transactional operation handling then this option should typically be left
         * to the default of false (e.g. process as many operations as possible, and handle any exceptions
         * asynchronously without holding up the rest of the batch).
         */
        pauseOnException: false,

        /**
         * @property {Number} current
         * The index of the current operation being executed. Read only
         */
        current: -1,

        /**
         * @property {Number} total
         * The total number of operations in this batch. Read only
         */
        total: 0,

        /**
         * @property {Boolean} isRunning
         * True if the batch is currently running. Read only
         */
        isRunning: false,

        /**
         * @property {Boolean} isComplete
         * True if this batch has been executed completely. Read only
         */
        isComplete: false,

        /**
         * @property {Boolean} hasException
         * True if this batch has encountered an exception. This is cleared at the start of each operation. Read only
         */
        hasException: false,

        /**
         * Creates new Batch object.
         * @param {Object} [config] Config object
         */
        constructor: function(config) {
            var me = this;

            /**
             * @event complete
             * Fired when all operations of this batch have been completed
             * @param {Ext.data.Batch} batch The batch object
             * @param {Object} operation The last operation that was executed
             */

            /**
             * @event exception
             * Fired when a operation encountered an exception
             * @param {Ext.data.Batch} batch The batch object
             * @param {Object} operation The operation that encountered the exception
             */

            /**
             * @event operationcomplete
             * Fired when each operation of the batch completes
             * @param {Ext.data.Batch} batch The batch object
             * @param {Object} operation The operation that just completed
             */

            me.mixins.observable.constructor.call(me, config);

            /**
             * Ordered array of operations that will be executed by this batch
             * @property {Ext.data.Operation[]} operations
             */
            me.operations = [];

            /**
             * Ordered array of operations that raised an exception during the most recent
             * batch execution and did not successfully complete
             * @property {Ext.data.Operation[]} exceptions
             */
            me.exceptions = [];
        },

        /**
         * Adds a new operation to this batch at the end of the {@link #operations} array
         * @param {Object} operation The {@link Ext.data.Operation Operation} object
         * @return {Ext.data.Batch} this
         */
        add: function(operation) {
            this.total++;

            operation.setBatch(this);

            this.operations.push(operation);

            return this;
        },

        /**
         * Kicks off execution of the batch, continuing from the next operation if the previous
         * operation encountered an exception, or if execution was paused. Use this method to start
         * the batch for the first time or to restart a paused batch by skipping the current
         * unsuccessful operation.
         *
         * To retry processing the current operation before continuing to the rest of the batch (e.g.
         * because you explicitly handled the operation's exception), call {@link #retry} instead.
         *
         * Note that if the batch is already running any call to start will be ignored.
         *
         * @return {Ext.data.Batch} this
         */
        start: function(/* private */ index) {
            var me = this;

            if (me.isRunning) {
                return me;
            }

            me.exceptions.length = 0;
            me.hasException = false;
            me.isRunning = true;

            return me.runOperation(Ext.isDefined(index) ? index : me.current + 1);
        },

        /**
         * Kicks off execution of the batch, continuing from the current operation. This is intended
         * for restarting a {@link #pause paused} batch after an exception, and the operation that raised
         * the exception will now be retried. The batch will then continue with its normal processing until
         * all operations are complete or another exception is encountered.
         *
         * Note that if the batch is already running any call to retry will be ignored.
         *
         * @return {Ext.data.Batch} this
         */
        retry: function() {
            return this.start(this.current);
        },

        /**
         * @private
         * Runs the next operation, relative to this.current.
         * @return {Ext.data.Batch} this
         */
        runNextOperation: function() {
            return this.runOperation(this.current + 1);
        },

        /**
         * Pauses execution of the batch, but does not cancel the current operation
         * @return {Ext.data.Batch} this
         */
        pause: function() {
            this.isRunning = false;
            return this;
        },

        /**
         * Executes an operation by its numeric index in the {@link #operations} array
         * @param {Number} index The operation index to run
         * @return {Ext.data.Batch} this
         */
        runOperation: function(index) {
            var me = this,
                operations = me.operations,
                operation = operations[index],
                onProxyReturn;

            if (operation === undefined) {
                me.isRunning = false;
                me.isComplete = true;
                me.fireEvent('complete', me, operations[operations.length - 1]);
            } else {
                me.current = index;

                onProxyReturn = function(operation) {
                    var hasException = operation.hasException();

                    if (hasException) {
                        me.hasException = true;
                        me.exceptions.push(operation);
                        me.fireEvent('exception', me, operation);
                    }

                    if (hasException && me.pauseOnException) {
                        me.pause();
                    } else {
                        operation.setCompleted();
                        me.fireEvent('operationcomplete', me, operation);
                        me.runNextOperation();
                    }
                };

                operation.setStarted();

                me.proxy[operation.action](operation, onProxyReturn, me);
            }

            return me;
        }
    });