/**
 * Manages context information during a layout.
 *
 * # Algorithm
 *
 * This class performs the following jobs:
 *
 *  - Cache DOM reads to avoid reading the same values repeatedly.
 *  - Buffer DOM writes and flush them as a block to avoid read/write interleaving.
 *  - Track layout dependencies so each layout does not have to figure out the source of
 *    its dependent values.
 *  - Intelligently run layouts when the values on which they depend change (a trigger).
 *  - Allow layouts to avoid processing when required values are unavailable (a block).
 *
 * Work done during layout falls into either a "read phase" or a "write phase" and it is
 * essential to always be aware of the current phase. Most methods in
 * {@link Ext.layout.Layout Layout} are called during a read phase:
 * {@link Ext.layout.Layout#calculate calculate},
 * {@link Ext.layout.Layout#completeLayout completeLayout} and
 * {@link Ext.layout.Layout#finalizeLayout finalizeLayout}. The exceptions to this are
 * {@link Ext.layout.Layout#beginLayout beginLayout},
 * {@link Ext.layout.Layout#beginLayoutCycle beginLayoutCycle} and
 * {@link Ext.layout.Layout#finishedLayout finishedLayout} which are called during
 * a write phase. While {@link Ext.layout.Layout#finishedLayout finishedLayout} is called
 * a write phase, it is really intended to be a catch-all for post-processing after a
 * layout run.
 * 
 * In a read phase, it is OK to read the DOM but this should be done using the appropriate
 * {@link Ext.layout.ContextItem ContextItem} where possible since that provides a cache
 * to avoid redundant reads. No writes should be made to the DOM in a read phase! Instead,
 * the values should be written to the proper ContextItem for later write-back.
 * 
 * The rules flip-flop in a write phase. The only difference is that ContextItem methods
 * like {@link Ext.layout.ContextItem#getStyle getStyle} will still read the DOM unless the
 * value was previously read. This detail is unknowable from the outside of ContextItem, so
 * read calls to ContextItem should also be avoided in a write phase.
 *
 * Calculating interdependent layouts requires a certain amount of iteration. In a given
 * cycle, some layouts will contribute results that allow other layouts to proceed. The
 * general flow then is to gather all of the layouts (both component and container) in a
 * component tree and queue them all for processing. The initial queue order is bottom-up
 * and component layout first, then container layout (if applicable) for each component.
 *
 * This initial step also calls the beginLayout method on all layouts to clear any values
 * from the DOM that might interfere with calculations and measurements. In other words,
 * this is a "write phase" and reads from the DOM should be strictly avoided.
 * 
 * Next the layout enters into its iterations or "cycles". Each cycle consists of calling
 * the {@link Ext.layout.Layout#calculate calculate} method on all layouts in the
 * {@link #layoutQueue}. These calls are part of a "read phase" and writes to the DOM should
 * be strictly avoided.
 *
 * # Considerations
 *
 * **RULE 1**: Respect the read/write cycles. Always use the {@link Ext.layout.ContextItem#getProp getProp}
 * or {@link Ext.layout.ContextItem#getDomProp getDomProp} methods to get calculated values;
 * only use the {@link Ext.layout.ContextItem#getStyle getStyle} method to read styles; use
 * {@link Ext.layout.ContextItem#setProp setProp} to set DOM values. Some reads will, of
 * course, still go directly to the DOM, but if there is a method in
 * {@link Ext.layout.ContextItem ContextItem} to do a certain job, it should be used instead
 * of a lower-level equivalent.
 *
 * The basic logic flow in {@link Ext.layout.Layout#calculate calculate} consists of gathering
 * values by calling {@link Ext.layout.ContextItem#getProp getProp} or
 * {@link Ext.layout.ContextItem#getDomProp getDomProp}, calculating results and publishing
 * them by calling {@link Ext.layout.ContextItem#setProp setProp}. It is important to realize
 * that {@link Ext.layout.ContextItem#getProp getProp} will return `undefined` if the value
 * is not yet known. But the act of calling the method is enough to track the fact that the
 * calling layout depends (in some way) on this value. In other words, the calling layout is
 * "triggered" by the properties it requests.
 *
 * **RULE 2**: Avoid calling {@link Ext.layout.ContextItem#getProp getProp} unless the value
 * is needed. Gratuitous calls cause inefficiency because the layout will appear to depend on
 * values that it never actually uses. This applies equally to
 * {@link Ext.layout.ContextItem#getDomProp getDomProp} and the test-only methods
 * {@link Ext.layout.ContextItem#hasProp hasProp} and {@link Ext.layout.ContextItem#hasDomProp hasDomProp}.
 *
 * Because {@link Ext.layout.ContextItem#getProp getProp} can return `undefined`, it is often
 * the case that subsequent math will produce NaN's. This is usually not a problem as the
 * NaN's simply propagate along and result in final results that are NaN. Both `undefined`
 * and NaN are ignored by {@link Ext.layout.ContextItem#setProp}, so it is often not necessary
 * to even know that this is happening. It does become important for determining if a layout
 * is not done or if it might lead to publishing an incorrect (but not NaN or `undefined`)
 * value.
 * 
 * **RULE 3**: If a layout has not calculated all the values it is required to calculate, it
 * must set {@link Ext.layout.Layout#done done} to `false` before returning from
 * {@link Ext.layout.Layout#calculate calculate}. This value is always `true` on entry because
 * it is simpler to detect the incomplete state rather than the complete state (especially up
 * and down a class hierarchy).
 * 
 * **RULE 4**: A layout must never publish an incomplete (wrong) result. Doing so would cause
 * dependent layouts to run their calculations on those wrong values, producing more wrong
 * values and some layouts may even incorrectly flag themselves as {@link Ext.layout.Layout#done done}
 * before the correct values are determined and republished. Doing this will poison the
 * calculations.
 *
 * **RULE 5**: Each value should only be published by one layout. If multiple layouts attempt
 * to publish the same values, it would be nearly impossible to avoid breaking **RULE 4**. To
 * help detect this problem, the layout diagnostics will trap on an attempt to set a value
 * from different layouts.
 *
 * Complex layouts can produce many results as part of their calculations. These values are
 * important for other layouts to proceed and need to be published by the earliest possible
 * call to {@link Ext.layout.Layout#calculate} to avoid unnecessary cycles and poor performance. It is
 * also possible, however, for some results to be related in a way such that publishing them
 * may be an all-or-none proposition (typically to avoid breaking *RULE 4*).
 * 
 * **RULE 6**: Publish results as soon as they are known to be correct rather than wait for
 * all values to be calculated. Waiting for everything to be complete can lead to deadlock.
 * The key here is not to forget **RULE 4** in the process.
 *
 * Some layouts depend on certain critical values as part of their calculations. For example,
 * HBox depends on width and cannot do anything until the width is known. In these cases, it
 * is best to use {@link Ext.layout.ContextItem#block block} or
 * {@link Ext.layout.ContextItem#domBlock domBlock} and thereby avoid processing the layout
 * until the needed value is available.
 *
 * **RULE 7**: Use {@link Ext.layout.ContextItem#block block} or
 * {@link Ext.layout.ContextItem#domBlock domBlock} when values are required to make progress.
 * This will mimize wasted recalculations.
 *
 * **RULE 8**: Blocks should only be used when no forward progress can be made. If even one
 * value could still be calculated, a block could result in a deadlock.
 *
 * Historically, layouts have been invoked directly by component code, sometimes in places
 * like an `afterLayout` method for a child component. With the flexibility now available
 * to solve complex, iterative issues, such things should be done in a responsible layout
 * (be it component or container).
 *
 * **RULE 9**: Use layouts to solve layout issues and don't wait for the layout to finish to
 * perform further layouts. This is especially important now that layouts process entire
 * component trees and not each layout in isolation.
 *
 * # Sequence Diagram
 *
 * The simplest sequence diagram for a layout run looks roughly like this:
 *
 *       Context         Layout 1     Item 1     Layout 2     Item 2
 *          |               |           |           |           |
 *     ---->X-------------->X           |           |           |
 *     run  X---------------|-----------|---------->X           |
 *          X beginLayout   |           |           |           |
 *          X               |           |           |           |
 *        A X-------------->X           |           |           |
 *          X  calculate    X---------->X           |           |
 *          X             C X  getProp  |           |           |
 *        B X               X---------->X           |           |
 *          X               |  setProp  |           |           |
 *          X               |           |           |           |
 *        D X---------------|-----------|---------->X           |
 *          X  calculate    |           |           X---------->X
 *          X               |           |           |  setProp  |
 *        E X               |           |           |           |
 *          X---------------|-----------|---------->X           |
 *          X completeLayout|           |         F |           |
 *          X               |           |           |           |
 *        G X               |           |           |           |
 *        H X-------------->X           |           |           |
 *          X  calculate    X---------->X           |           |
 *          X             I X  getProp  |           |           |
 *          X               X---------->X           |           |
 *          X               |  setProp  |           |           |
 *        J X-------------->X           |           |           |
 *          X completeLayout|           |           |           |
 *          X               |           |           |           |
 *        K X-------------->X           |           |           |
 *          X---------------|-----------|---------->X           |
 *          X finalizeLayout|           |           |           |
 *          X               |           |           |           |
 *        L X-------------->X           |           |           |
 *          X---------------|-----------|---------->X           |
 *          X finishedLayout|           |           |           |
 *          X               |           |           |           |
 *        M X-------------->X           |           |           |
 *          X---------------|-----------|---------->X           |
 *          X notifyOwner   |           |           |           |
 *        N |               |           |           |           |
 *          -               -           -           -           -
 *
 *
 * Notes:
 *
 * **A.** This is a call from the {@link #run} method to the {@link #run} method.
 * Each layout in the queue will have its {@link Ext.layout.Layout#calculate calculate}
 * method called.
 *
 * **B.** After each {@link Ext.layout.Layout#calculate calculate} method is called the
 * {@link Ext.layout.Layout#done done} flag is checked to see if the Layout has completed.
 * If it has completed and that layout object implements a
 * {@link Ext.layout.Layout#completeLayout completeLayout} method, this layout is queued to
 * receive its call. Otherwise, the layout will be queued again unless there are blocks or
 * triggers that govern its requeueing.
 * 
 * **C.** The call to {@link Ext.layout.ContextItem#getProp getProp} is made to the Item
 * and that will be tracked as a trigger (keyed by the name of the property being requested).
 * Changes to this property will cause this layout to be requeued. The call to
 * {@link Ext.layout.ContextItem#setProp setProp} will place a value in the item and not
 * directly into the DOM.
 * 
 * **D.** Call the other layouts now in the first cycle (repeat **B** and **C** for each
 * layout).
 * 
 * **E.** After completing a cycle, if progress was made (new properties were written to
 * the context) and if the {@link #layoutQueue} is not empty, the next cycle is run. If no
 * progress was made or no layouts are ready to run, all buffered values are written to
 * the DOM (a flush).
 *
 * **F.** After flushing, any layouts that were marked as {@link Ext.layout.Layout#done done}
 * that also have a {@link Ext.layout.Layout#completeLayout completeLayout} method are called.
 * This can cause them to become no longer done (see {@link #invalidate}). As with
 * {@link Ext.layout.Layout#calculate calculate}, this is considered a "read phase" and
 * direct DOM writes should be avoided.
 * 
 * **G.** Flushing and calling any pending {@link Ext.layout.Layout#completeLayout completeLayout}
 * methods will likely trigger layouts that called {@link Ext.layout.ContextItem#getDomProp getDomProp}
 * and unblock layouts that have called {@link Ext.layout.ContextItem#domBlock domBlock}.
 * These variants are used when a layout needs the value to be correct in the DOM and not
 * simply known. If this does not cause at least one layout to enter the queue, we have a
 * layout FAILURE. Otherwise, we continue with the next cycle.
 * 
 * **H.** Call {@link Ext.layout.Layout#calculate calculate} on any layouts in the queue
 * at the start of this cycle. Just a repeat of **B** through **G**.
 * 
 * **I.** Once the layout has calculated all that it is resposible for, it can leave itself
 * in the {@link Ext.layout.Layout#done done} state. This is the value on entry to
 * {@link Ext.layout.Layout#calculate calculate} and must be cleared in that call if the
 * layout has more work to do.
 * 
 * **J.** Now that all layouts are done, flush any DOM values and
 * {@link Ext.layout.Layout#completeLayout completeLayout} calls. This can again cause
 * layouts to become not done, and so we will be back on another cycle if that happens.
 * 
 * **K.** After all layouts are done, call the {@link Ext.layout.Layout#finalizeLayout finalizeLayout}
 * method on any layouts that have one. As with {@link Ext.layout.Layout#completeLayout completeLayout},
 * this can cause layouts to become no longer done. This is less desirable than using
 * {@link Ext.layout.Layout#completeLayout completeLayout} because it will cause all
 * {@link Ext.layout.Layout#finalizeLayout finalizeLayout} methods to be called again
 * when we think things are all wrapped up.
 *
 * **L.** After finishing the last iteration, layouts that have a
 * {@link Ext.layout.Layout#finishedLayout finishedLayout} method will be called. This
 * call will only happen once per run and cannot cause layouts to be run further.
 *
 * **M.** After calling finahedLayout, layouts that have a
 * {@link Ext.layout.Layout#notifyOwner notifyOwner} method will be called. This
 * call will only happen once per run and cannot cause layouts to be run further.
 *
 * **N.** One last flush to make sure everything has been written to the DOM.
 *
 * # Inter-Layout Collaboration
 * 
 * Many layout problems require collaboration between multiple layouts. In some cases, this
 * is as simple as a component's container layout providing results used by its component
 * layout or vise-versa. A slightly more distant collaboration occurs in a box layout when
 * stretchmax is used: the child item's component layout provides results that are consumed
 * by the ownerCt's box layout to determine the size of the children.
 *
 * The various forms of interdependence between a container and its children are described by
 * each components' {@link Ext.Component#getSizeModel size model}.
 *
 * To facilitate this collaboration, the following pairs of properties are published to the
 * component's {@link Ext.layout.ContextItem ContextItem}:
 *
 *  - width/height: These hold the final size of the component. The layout indicated by the
 *    {@link Ext.Component#getSizeModel size model} is responsible for setting these.
 *  - contentWidth/contentHeight: These hold size information published by the container
 *    layout or from DOM measurement. These describe the content only. These values are
 *    used by the component layout to determine the outer width/height when that component
 *    is {@link Ext.Component#shrinkWrap shrink-wrapped}. They are also used to
 *    determine overflow. All container layouts must publish these values for dimensions
 *    that are shrink-wrapped. If a component has raw content (not container items), the
 *    componentLayout must publish these values instead.
 * 
 * @private
 */
Ext.define('Ext.layout.Context', {
    requires: [
        //<debug> 
        'Ext.perf.Monitor',
        //</debug> 
        'Ext.util.Queue',
        'Ext.layout.ContextItem',
        'Ext.layout.Layout',
        'Ext.fx.Anim',
        'Ext.fx.Manager'
    ],
 
    remainingLayouts: 0,
 
    /**
     * @property {Number} state One of these values:
     *
     *  - 0 - Before run
     *  - 1 - Running
     *  - 2 - Run complete
     */
    state: 0,
 
    /**
     * @property {Number} cycleWatchDog 
     * This value is used to detect layouts that cannot progress by checking the amount of
     * cycles processed. The value should be large enough to satisfy all but exceptionally large
     * layout structures. When the amount of cycles is reached, the layout will fail. This should
     * only be used for debugging, layout failures should be considered as an exceptional occurrence.
     * @private
     * @since 5.1.1
     */
    cycleWatchDog: 200,
 
    constructor: function (config) {
        var me = this;
 
        Ext.apply(me, config);
 
        // holds the ContextItem collection, keyed by element id 
        me.items = {};
 
        // a collection of layouts keyed by layout id 
        me.layouts = {};
 
        // the number of blocks of any kind: 
        me.blockCount = 0;
        // the number of cycles that have been run: 
        me.cycleCount = 0;
        // the number of flushes to the DOM: 
        me.flushCount = 0;
        // the number of layout calculate calls: 
        me.calcCount = 0;
 
        me.animateQueue = me.newQueue();
        me.completionQueue = me.newQueue();
        me.finalizeQueue = me.newQueue();
        me.finishQueue = me.newQueue();
        me.flushQueue = me.newQueue();
 
        me.invalidateData = {};
 
        /**
         * @property {Ext.util.Queue} layoutQueue
         * List of layouts to perform.
         */
        me.layoutQueue = me.newQueue();
 
        // this collection is special because we ensure that there are no parent/child pairs 
        // present, only distinct top-level components 
        me.invalidQueue = [];
 
        me.triggers = {
            data: {
                /*
                layoutId: [
                    { item: contextItem, prop: propertyName }
                ]
                */
            },
            dom: {}
        };
    },
 
    callLayout: function (layout, methodName) {
        this.currentLayout = layout;
        layout[methodName](this.getCmp(layout.owner));
    },
 
    cancelComponent: function (comp, isChild, isDestroying) {
        var me = this,
            components = comp,
            isArray = !comp.isComponent,
            length = isArray ? components.length : 1,
            i, k, klen, items, layout, newQueue, oldQueue, entry, temp,
            ownerCtContext;
 
        for (= 0; i < length; ++i) {
            if (isArray) {
                comp = components[i];
            }
 
            if (isDestroying) {
                if (comp.ownerCt) {
                    // If the component is being destroyed, remove the component's ContextItem from its parent's contextItem.childItems array 
                    ownerCtContext = this.items[comp.ownerCt.el.id];
                    if (ownerCtContext) {
                        Ext.Array.remove(ownerCtContext.childItems, me.getCmp(comp));
                    }
                } else if (comp.rendered) {
                    me.removeEl(comp.el);
                }   
            }
 
            if (!isChild) {
                oldQueue = me.invalidQueue;
                klen = oldQueue.length;
 
                if (klen) {
                    me.invalidQueue = newQueue = [];
                    for (= 0; k < klen; ++k) {
                        entry = oldQueue[k];
                        temp = entry.item.target;
                        if (temp !== comp && !temp.up(comp)) {
                            newQueue.push(entry);
                        }
                    }
                }
            }
 
            layout = comp.componentLayout;
            me.cancelLayout(layout);
 
            if (layout.getLayoutItems) {
                items = layout.getLayoutItems();
                if (items.length) {
                    me.cancelComponent(items, true);
                }
            }
 
            if (comp.isContainer && !comp.collapsed) {
                layout = comp.layout;
                me.cancelLayout(layout);
 
                items = layout.getVisibleItems();
                if (items.length) {
                    me.cancelComponent(items, true);
                }
            }
        }
    },
 
    cancelLayout: function (layout) {
        var me = this;
 
        me.completionQueue.remove(layout);
        me.finalizeQueue.remove(layout);
        me.finishQueue.remove(layout);
        me.layoutQueue.remove(layout);
 
        if (layout.running) {
            me.layoutDone(layout);
        }
 
        layout.ownerContext = null;
    },
 
    clearTriggers: function (layout, inDom) {
        var id = layout.id,
            collection = this.triggers[inDom ? 'dom' : 'data'],
            triggers = collection && collection[id],
            length = (triggers && triggers.length) || 0,
            i, item, trigger;
 
        for (= 0; i < length; ++i) {
            trigger = triggers[i];
            item = trigger.item;
 
            collection = inDom ? item.domTriggers : item.triggers;
            delete collection[trigger.prop][id];
        }
    },
 
    /**
     * Flushes any pending writes to the DOM by calling each ContextItem in the flushQueue.
     */
    flush: function () {
        var me = this,
            items = me.flushQueue.clear(),
            length = items.length, i;
 
        if (length) {
            ++me.flushCount;
 
            for (= 0; i < length; ++i) {
                items[i].flush();
            }
        }
    },
 
    flushAnimations: function() {
        var me = this,
            items = me.animateQueue.clear(),
            len = items.length,
            i;
 
        if (len) {
            for (= 0; i < len; i++) {
                // Each Component may refuse to participate in animations. 
                // This is used by the BoxReorder plugin which drags a Component, 
                // during which that Component must be exempted from layout positioning. 
                if (items[i].target.animate !== false) {
                    items[i].flushAnimations();
                }
            }
 
            // Ensure the first frame fires now to avoid a browser repaint with the elements in the "to" state 
            // before they are returned to their "from" state by the animation. 
            Ext.fx.Manager.runner();
        }
    },
 
    flushInvalidates: function () {
        var me = this,
            queue = me.invalidQueue,
            length = queue && queue.length,
            comp, components, entry, i;
 
        me.invalidQueue = [];
 
        if (length) {
            components = [];
            for (= 0; i < length; ++i) {
                comp = (entry = queue[i]).item.target;
                // we filter out-of-body components here but allow them into the queue to 
                // ensure that their child components are coalesced out (w/no additional 
                // cost beyond our normal effort to avoid parent/child components in the 
                // queue) 
                if (!comp.container.isDetachedBody) {
                    components.push(comp);
 
                    if (entry.options) {
                        me.invalidateData[comp.id] = entry.options;
                    }
                }
            }
 
            me.invalidate(components, null);
        }
    },
 
    flushLayouts: function (queueName, methodName, dontClear) {
        var me = this,
            layouts = dontClear ? me[queueName].items : me[queueName].clear(),
            length = layouts.length,
            i, layout;
 
        if (length) {
            for (= 0; i < length; ++i) {
                layout = layouts[i];
                if (!layout.running) {
                    me.callLayout(layout, methodName);
                }
            }
            me.currentLayout = null;
        }
    },
 
    /**
     * Returns the ContextItem for a component.
     * @param {Ext.Component} cmp
     */
    getCmp: function (cmp) {
        return this.getItem(cmp, cmp.el);
    },
 
    /**
     * Returns the ContextItem for an element.
     * @param {Ext.layout.ContextItem} parent
     * @param {Ext.dom.Element} el
     */
    getEl: function (parent, el) {
        var item = this.getItem(el, el);
 
        if (!item.parent) {
            item.parent = parent;
 
            // all items share an empty children array (to avoid null checks), so we can 
            // only push on to the children array if there is already something there (we 
            // copy-on-write): 
            if (parent.children.length) {
                parent.children.push(item);
            } else {
                parent.children = [ item ]; // now parent has its own children[] (length=1) 
            }
        }
 
        return item;
    },
 
    getItem: function (target, el) {
        var id = el.id,
            items = this.items,
            item = items[id] ||
                  (items[id] = new Ext.layout.ContextItem({
                                    context: this,
                                    target: target,
                                    el: el
                                }));
 
        return item;
    },
 
    handleFailure: function () {
        // This method should never be called, but is need when layouts fail (hence the 
        // "should never"). We just disconnect any of the layouts from the run and return 
        // them to the state they would be in had the layout completed properly. 
        var layouts = this.layouts,
            layout, key;
 
        Ext.failedLayouts = (Ext.failedLayouts || 0) + 1;
 
        for (key in layouts) {
            layout = layouts[key];
 
            if (layouts.hasOwnProperty(key)) {
                layout.running      = false;
                layout.ownerContext = null;
            }
        }
 
        //<debug> 
        if (Ext.devMode === 2 && !this.pageAnalyzerMode) {
            Ext.raise('Layout run failed');
        } else {
            Ext.log.error('Layout run failed');
        }
        //</debug> 
    },
 
    /**
     * Invalidates one or more components' layouts (component and container). This can be
     * called before run to identify the components that need layout or during the run to
     * restart the layout of a component. This is called internally to flush any queued
     * invalidations at the start of a cycle. If called during a run, it is not expected
     * that new components will be introduced to the layout.
     * 
     * @param {Ext.Component/Array} components An array of Components or a single Component.
     * @param {Boolean} full True if all properties should be invalidated, otherwise only
     *  those calculated by the component should be invalidated.
     */
    invalidate: function (components, full) {
        var me = this,
            isArray = !components.isComponent,
            containerLayoutDone, ownerLayout,
            firstTime, i, comp, item, items, length, componentLayout, layout,
            invalidateOptions, token, skipLayout;
 
        for (= 0, length = isArray ? components.length : 1; i < length; ++i) {
            comp = isArray ? components[i] : components;
 
            if (comp.rendered && !comp.hidden) {
                ownerLayout = comp.ownerLayout;
                componentLayout = comp.componentLayout;
                skipLayout = false;
 
                if ((!ownerLayout || !ownerLayout.needsItemSize) && comp.liquidLayout) {
                    // our owning layout doesn't need us to run, and our componentLayout 
                    // wants to opt out because it uses liquid CSS layout. 
                    // We can skip invalidation for this component. 
                    skipLayout = true;
                }
 
                // if we are skipping layout, we can also skip creation of the context 
                // item, unless our owner layout needs it to set our size. 
                if (!skipLayout || (ownerLayout && ownerLayout.setsItemSize)) {
                    item = me.getCmp(comp);
                    firstTime = !item.state;
 
                    // If the component has had no changes which necessitate a layout, do not lay it out. 
                    // Temporarily disabled because this breaks dock layout (see EXTJSIV-10251) 
//                    if (item.optOut) { 
//                        skipLayout = true; 
//                    } 
 
                    layout = (comp.isContainer && !comp.collapsed) ? comp.layout : null;
 
                    // Extract any invalidate() options for this item. 
                    invalidateOptions = me.invalidateData[item.id];
                    delete me.invalidateData[item.id];
 
                    // We invalidate the contextItem's in a top-down manner so that SizeModel 
                    // info for containers is available to their children. This is a critical 
                    // optimization since sizeModel determination often requires knowing the 
                    // sizeModel of the ownerCt. If this weren't cached as we descend, this 
                    // would be an O(N^2) operation! (where N=number of components, or 300+/- 
                    // in Themes) 
                    token = item.init(full, invalidateOptions);
                }
 
                if (skipLayout) {
                    continue;
                }
 
                if (invalidateOptions) {
                    me.processInvalidate(invalidateOptions, item, 'before');
                }
 
                // Allow the component layout a chance to effect its size model before we 
                // recurse down the component hierarchy (since children need to know the 
                // size model of their ownerCt). 
                if (componentLayout.beforeLayoutCycle) {
                    componentLayout.beforeLayoutCycle(item);
                }
 
                if (layout && layout.beforeLayoutCycle) {
                    // allow the container layout take a peek as well. Table layout can 
                    // influence its children's styling due to the interaction of nesting 
                    // table-layout:fixed and auto inside each other without intervening 
                    // elements of known size. 
                    layout.beforeLayoutCycle(item);
                }
 
                // Finish up the item-level processing that is based on the size model of 
                // the component. 
                token = item.initContinue(token);
 
                // Start this state variable at true, since that is the value we want if 
                // they do not apply (i.e., no work of this kind on which to wait). 
                containerLayoutDone = true;
 
                // A ComponentLayout MUST implement getLayoutItems to allow its children 
                // to be collected. Ext.container.Container does this, but non-Container 
                // Components which manage Components as part of their structure (e.g., 
                // HtmlEditor) must still return child Components via getLayoutItems. 
                if (componentLayout.getLayoutItems) {
                    componentLayout.renderChildren();
 
                    items = componentLayout.getLayoutItems();
                    if (items.length) {
                        me.invalidate(items, true);
                    }
                }
 
                if (layout) {
                    containerLayoutDone = false;
                    layout.renderChildren();
 
                    if (layout.needsItemSize || layout.activeItemCount) {
                        // if the layout specified that it needs the layouts of its children 
                        // to run, or if the number of "liquid" child layouts is greater 
                        // than 0, we need to recurse into the children, since some or 
                        // all of them may need their layouts to run. 
                        items = layout.getVisibleItems();
                        if (items.length) {
                            me.invalidate(items, true);
                        }
                    }
                }
 
                // Finish the processing that requires the size models of child items to 
                // be determined (and some misc other stuff). 
                item.initDone(containerLayoutDone);
 
                // Inform the layouts that we are about to begin (or begin again) now that 
                // the size models of the component and its children are setup. 
                me.resetLayout(componentLayout, item, firstTime);
                if (layout) {
                    me.resetLayout(layout, item, firstTime);
                }
 
                // This has to occur after the component layout has had a chance to begin 
                // so that we can determine what kind of animation might be needed. TODO- 
                // move this determination into the layout itself. 
                item.initAnimation();
 
                if (invalidateOptions) {
                    me.processInvalidate(invalidateOptions, item, 'after');
                }
            }
        }
 
        me.currentLayout = null;
    },
 
    // Returns true is descendant is a descendant of ancestor 
    isDescendant: function(ancestor, descendant) {
        if (ancestor.isContainer) {
            for (var c = descendant.ownerCt; c; c = c.ownerCt) {
                if (=== ancestor) {
                    return true;
                }
            }
        }
        return false;
    },
    
    layoutDone: function (layout) {
        var ownerContext = layout.ownerContext;
 
        layout.running = false;
 
        // Once a component layout completes, we can mark it as "done". 
        if (layout.isComponentLayout) {
            if (ownerContext.measuresBox) {
                ownerContext.onBoxMeasured(); // be sure to release our boxParent 
            }
 
            ownerContext.setProp('done', true);
        } else {
            ownerContext.setProp('containerLayoutDone', true);
        }
 
        --this.remainingLayouts;
        ++this.progressCount; // a layout completion is progress 
    },
 
    newQueue: function () {
        return new Ext.util.Queue();
    },
 
    processInvalidate: function (options, item, name) {
        // When calling a callback, the currentLayout needs to be adjusted so 
        // that whichever layout caused the invalidate is the currentLayout... 
        if (options[name]) {
            var me = this,
                currentLayout = me.currentLayout;
 
            me.currentLayout = options.layout || null;
 
            options[name](item, options);
 
            me.currentLayout = currentLayout;
        }
    },
 
    /**
     * Queues a ContextItem to have its {@link Ext.layout.ContextItem#flushAnimations} method called.
     *
     * @param {Ext.layout.ContextItem} item
     * @private
     */
    queueAnimation: function (item) {
        this.animateQueue.add(item);
    },
 
    /**
     * Queues a layout to have its {@link Ext.layout.Layout#completeLayout} method called.
     *
     * @param {Ext.layout.Layout} layout
     * @private
     */
    queueCompletion: function (layout) {
        this.completionQueue.add(layout);
    },
 
    /**
     * Queues a layout to have its {@link Ext.layout.Layout#finalizeLayout} method called.
     *
     * @param {Ext.layout.Layout} layout
     * @private
     */
    queueFinalize: function (layout) {
        this.finalizeQueue.add(layout);
    },
 
    /**
     * Queues a ContextItem for the next flush to the DOM. This should only be called by
     * the {@link Ext.layout.ContextItem} class.
     *
     * @param {Ext.layout.ContextItem} item
     * @private
     */
    queueFlush: function (item) {
        this.flushQueue.add(item);
    },
 
    chainFns: function (oldOptions, newOptions, funcName) {
        var me = this,
            oldLayout = oldOptions.layout,
            newLayout = newOptions.layout,
            oldFn = oldOptions[funcName],
            newFn = newOptions[funcName];
 
        // Call newFn last so it can get the final word on things... also, the "this" 
        // pointer will be passed correctly by createSequence with oldFn first. 
        return function (contextItem) {
            var prev = me.currentLayout;
            if (oldFn) {
                me.currentLayout = oldLayout;
                oldFn.call(oldOptions.scope || oldOptions, contextItem, oldOptions);
            }
            me.currentLayout = newLayout;
            newFn.call(newOptions.scope || newOptions, contextItem, newOptions);
            me.currentLayout = prev;
        };
    },
        
    purgeInvalidates: function () {
        var me = this,
            newQueue = [],
            oldQueue = me.invalidQueue,
            oldLength = oldQueue.length,
            oldIndex, newIndex, newEntry, newComp, oldEntry, oldComp, keep;
        
        for (oldIndex = 0; oldIndex < oldLength; ++oldIndex) {
            oldEntry = oldQueue[oldIndex];
            oldComp = oldEntry.item.target;
            
            keep = true;
            for (newIndex = newQueue.length; newIndex--;) {
                newEntry = newQueue[newIndex];
                newComp = newEntry.item.target;
                
                if (oldComp.isLayoutChild(newComp)) {
                    keep = false;
                    break;
                }
                
                if (newComp.isLayoutChild(oldComp)) {
                    Ext.Array.erase(newQueue, newIndex, 1);
                }
            }
            
            if (keep) {
                newQueue.push(oldEntry);
            }
        }
        
        me.invalidQueue = newQueue;
    },
 
    /**
     * Queue a component (and its tree) to be invalidated on the next cycle.
     *
     * @param {Ext.Component/Ext.layout.ContextItem} item The component or ContextItem to invalidate.
     * @param {Object} options An object describing how to handle the invalidation (see
     *  {@link Ext.layout.ContextItem#invalidate} for details).
     * @private
     */
    queueInvalidate: function (item, options) {
        var me = this,
            newQueue = [],
            oldQueue = me.invalidQueue,
            index = oldQueue.length,
            comp, old, oldComp, oldOptions, oldState;
 
        if (item.isComponent) {
            comp = item;
            item = me.items[comp.el.id];
            if (item) {
                item.recalculateSizeModel();
            } else {
                item = me.getCmp(comp);
            }
        } else {
            comp = item.target;
        }
 
        item.invalid = true;
 
        // See if comp is contained by any component already in the queue (ignore comp if 
        // that is the case). Eliminate any components in the queue that are contained by 
        // comp (by not adding them to newQueue). 
        while (index--) {
            old = oldQueue[index];
            oldComp = old.item.target;
 
            if (!comp.isFloating && comp.up(oldComp)) {
                return; // oldComp contains comp, so this invalidate is redundant 
            }
 
            if (oldComp === comp) {
                // if already in the queue, update the options... 
                if (!(oldOptions = old.options)) {
                    old.options = options;
                } else if (options) {
                    if (options.widthModel) {
                        oldOptions.widthModel = options.widthModel;
                    }
                    if (options.heightModel) {
                        oldOptions.heightModel = options.heightModel;
                    }
                    if (!(oldState = oldOptions.state)) {
                        oldOptions.state = options.state;
                    } else if (options.state) {
                        Ext.apply(oldState, options.state);
                    }
 
                    if (options.before) {
                        oldOptions.before = me.chainFns(oldOptions, options, 'before');
                    }
                    if (options.after) {
                        oldOptions.after = me.chainFns(oldOptions, options, 'after');
                    }
                }
 
                // leave the old queue alone now that we've update this comp's entry... 
                return;
            }
 
            if (!oldComp.isLayoutChild(comp)) {
                newQueue.push(old); // comp does not contain oldComp 
            }
            // else if (oldComp isDescendant of comp) skip 
        }
        // newQueue contains only those components not a descendant of comp 
 
        // to get here, comp must not be a child of anything already in the queue, so it 
        // needs to be added along with its "options": 
        newQueue.push({ item: item, options: options });
 
        me.invalidQueue = newQueue;
    },
 
    queueItemLayouts: function (item) {
        var comp = item.isComponent ? item : item.target,
            layout = comp.componentLayout;
 
        if (!layout.pending && !layout.invalid && !layout.done) {
            this.queueLayout(layout);
        }
 
        layout = comp.layout;
        if (layout && !layout.pending && !layout.invalid && !layout.done && !comp.collapsed) {
            this.queueLayout(layout);
        }
    },
 
    /**
     * Queues a layout for the next calculation cycle. This should not be called if the
     * layout is done, blocked or already in the queue. The only classes that should call
     * this method are this class and {@link Ext.layout.ContextItem}.
     *
     * @param {Ext.layout.Layout} layout The layout to add to the queue.
     * @private
     */
    queueLayout: function (layout) {
        this.layoutQueue.add(layout);
        layout.pending = true;
    },
 
    /**
     * Removes the ContextItem for an element from the cache and from the parent's
     * "children" array.
     * @param {Ext.dom.Element} el
     * @param {Ext.layout.ContextItem} parent
     */
    removeEl: function (el, parent) {
        var id = el.id,
            children = parent ? parent.children : null,
            items = this.items;
 
        if (children) {
            Ext.Array.remove(children, items[id]);
        }
        delete items[id];
    },
 
    /**
     * Resets the given layout object. This is called at the start of the run and can also
     * be called during the run by calling {@link #invalidate}.
     */
    resetLayout: function (layout, ownerContext, firstTime) {
        var me = this;
 
        me.currentLayout = layout;
 
        layout.done = false;
        layout.pending = true;
        layout.firedTriggers = 0;
 
        me.layoutQueue.add(layout);
 
        if (firstTime) {
            me.layouts[layout.id] = layout; // track the layout for this run by its id 
            layout.running = true;
 
            if (layout.finishedLayout) {
                me.finishQueue.add(layout);
            }
 
            // reset or update per-run counters: 
 
            ++me.remainingLayouts;
            ++layout.layoutCount; // the number of whole layouts run for the layout 
 
            layout.ownerContext = ownerContext;
            layout.beginCount = 0; // the number of beginLayout calls 
            layout.blockCount = 0; // the number of blocks set for the layout 
            layout.calcCount = 0; // the number of times calculate is called 
            layout.triggerCount = 0; // the number of triggers set for the layout 
 
            if (!layout.initialized) {
                layout.initLayout();
            }
 
            layout.beginLayout(ownerContext);
        } else {
            ++layout.beginCount;
 
            if (!layout.running) {
                // back into the mahem with this one: 
                ++me.remainingLayouts;
                layout.running = true;
                layout.ownerContext = ownerContext;
 
                if (layout.isComponentLayout) {
                    // this one is fun... if we call setProp('done', false) that would still 
                    // trigger/unblock layouts, but what layouts are really looking for with 
                    // this property is for it to go to true, not just be set to a value... 
                    ownerContext.unsetProp('done');
                }
 
                // and it needs to be removed from the completion and/or finalize queues... 
                me.completionQueue.remove(layout);
                me.finalizeQueue.remove(layout);
            }
        }
 
        layout.beginLayoutCycle(ownerContext, firstTime);
    },
 
    /**
     * Runs the layout calculations. This can be called only once on this object.
     * @return {Boolean} True if all layouts were completed, false if not.
     */
    run: function () {
        var me = this,
            flushed = false,
            watchDog = me.cycleWatchDog;
        
        me.purgeInvalidates();
        me.flushInvalidates();
 
        me.state = 1;
        me.totalCount = me.layoutQueue.getCount();
 
        // We may start with unflushed data placed by beginLayout calls. Since layouts may 
        // use setProp as a convenience, even in a write phase, we don't want to transition 
        // to a read phase with unflushed data since we can write it now "cheaply". Also, 
        // these value could easily be needed in the DOM in order to really get going with 
        // the calculations. In particular, fixed (configured) dimensions fall into this 
        // category. 
        me.flush();
 
        // While we have layouts that have not completed... 
        while ((me.remainingLayouts || me.invalidQueue.length) && watchDog--) {
            if (me.invalidQueue.length) {
                me.flushInvalidates();
            }
 
            // if any of them can run right now, run them 
            if (me.runCycle()) {
                flushed = false; // progress means we probably need to flush something 
                // but not all progress appears in the flushQueue (e.g. 'contentHeight') 
            } else if (!flushed) {
                // as long as we are making progress, flush updates to the DOM and see if 
                // that triggers or unblocks any layouts... 
                me.flush();
                flushed = true; // all flushed now, so more progress is required 
 
                me.flushLayouts('completionQueue', 'completeLayout');
            } else if (!me.invalidQueue.length) {
                // after a flush, we must make progress or something is WRONG 
                me.state = 2;
                break;
            }
 
            if (!(me.remainingLayouts || me.invalidQueue.length)) {
                me.flush();
                me.flushLayouts('completionQueue', 'completeLayout');
                me.flushLayouts('finalizeQueue', 'finalizeLayout');
            }
        }
 
        return me.runComplete();
    },
 
    runComplete: function () {
        var me = this;
 
        me.state = 2;
 
        if (me.remainingLayouts) {
            me.handleFailure();
            return false;
        }
 
        me.flush();
 
        // Call finishedLayout on all layouts, but do not clear the queue. 
        me.flushLayouts('finishQueue', 'finishedLayout', true);
 
        // Call notifyOwner on all layouts and then clear the queue. 
        me.flushLayouts('finishQueue', 'notifyOwner');
 
        me.flush(); // in case any setProp calls were made 
 
        me.flushAnimations();
 
        return true;
    },
 
    /**
     * Performs one layout cycle by calling each layout in the layout queue.
     * @return {Boolean} True if some progress was made, false if not.
     * @protected
     */
    runCycle: function () {
        var me = this,
            layouts = me.layoutQueue.clear(),
            length = layouts.length,
            i;
 
        ++me.cycleCount;
 
        // This value is incremented by ContextItem#setProp whenever new values are set 
        // (thereby detecting forward progress): 
        me.progressCount = 0;
 
        for (= 0; i < length; ++i) {
            me.runLayout(me.currentLayout = layouts[i]);
        }
 
        me.currentLayout = null;
 
        return me.progressCount > 0;
    },
 
    /**
     * Runs one layout as part of a cycle.
     * @private
     */
    runLayout: function (layout) {
        var me = this,
            ownerContext = me.getCmp(layout.owner);
 
        layout.pending = false;
 
        if (ownerContext.state.blocks) {
            return;
        }
 
        // We start with the assumption that the layout will finish and if it does not, it 
        // must clear this flag. It turns out this is much simpler than knowing when a layout 
        // is done (100% correctly) when base classes and derived classes are collaborating. 
        // Knowing that some part of the layout is not done is much more obvious. 
        layout.done = true;
 
        ++layout.calcCount;
        ++me.calcCount;
 
        layout.calculate(ownerContext);
 
        if (layout.done) {
            me.layoutDone(layout);
 
            if (layout.completeLayout) {
                me.queueCompletion(layout);
            }
            if (layout.finalizeLayout) {
                me.queueFinalize(layout);
            }
        } else if (!layout.pending && !layout.invalid &&
                  !(layout.blockCount + layout.triggerCount - layout.firedTriggers)) { // jshint ignore:line 
            // A layout that is not done and has no blocks or triggers that will queue it 
            // automatically, must be queued now: 
            me.queueLayout(layout);
        }
    },
 
    /**
     * Set the size of a component, element or composite or an array of components or elements.
     * @param {Ext.Component/Ext.Component[]/Ext.dom.Element/Ext.dom.Element[]/Ext.dom.CompositeElement} item
     * The item(s) to size.
     * @param {Number} width The new width to set (ignored if undefined or NaN).
     * @param {Number} height The new height to set (ignored if undefined or NaN).
     */
    setItemSize: function(item, width, height) {
        var items = item,
            len = 1,
            contextItem, i;
 
        // NOTE: we don't pre-check for validity because: 
        //  - maybe only one dimension is valid 
        //  - the diagnostics layer will track the setProp call to help find who is trying 
        //      (but failing) to set a property 
        //  - setProp already checks this anyway 
 
        if (item.isComposite) {
            items = item.elements;
            len = items.length;
            item = items[0];
        } else if (!item.dom && !item.el) { // array by process of elimination 
            len = items.length;
            item = items[0];
        }
        // else len = 1 and items = item (to avoid error on "items[++i]") 
 
        for (= 0; i < len; ) {
            contextItem = this.get(item);
            contextItem.setSize(width, height);
 
            item = items[++i]; // this accomodation avoids making an array of 1 
        }
    },
 
    //------------------------------------------------------------------------- 
    // Diagnostics 
 
    debugHooks: {
        $enabled: false, // off by default 
 
        pageAnalyzerMode: true,
 
        logOn: {
            //boxParent: true, 
            //calculate: true, 
            //cancelComponent: true, 
            //cancelLayout: true, 
            //doInvalidate: true, 
            //flush: true, 
            //flushInvalidate: true, 
            //invalidate: true, 
            //initItem: true, 
            //layoutDone: true, 
            //queueLayout: true, 
            //resetLayout: true, 
            //runCycle: true, 
            //setProp: true, 
            0:0
        },
 
        //profileLayoutsByType: true, 
 
        //reportOnSuccess: true, 
 
        cancelComponent: function (comp) {
            if (this.logOn.cancelComponent) {
                Ext.log('cancelCmp: ', comp.id);
            }
            this.callParent(arguments);
        },
 
        cancelLayout: function (layout) {
            if (this.logOn.cancelLayout) {
                Ext.log('cancelLayout: ', this.getLayoutName(layout));
            }
            this.callParent(arguments);
        },
 
        callLayout: function (layout, methodName) {
            var accum = this.accumByType[layout.type],
                frame = accum && accum.enter();
 
            this.callParent(arguments);
 
            if (accum) {
                frame.leave();
            }
        },
 
        checkRemainingLayouts: function () {
            var me       = this,
                expected = 0,
                key, layout;
 
            for (key in me.layouts) {
                layout = me.layouts[key];
 
                if (me.layouts.hasOwnProperty(key) && layout.running) {
                    ++expected;
                }
            }
 
            if (me.remainingLayouts !== expected) {
                Ext.raise({
                    msg: 'Bookkeeping error me.remainingLayouts'
                });
            }
        },
 
        flush: function () {
            if (this.logOn.flush) {
                var items = this.flushQueue;
                Ext.log('--- Flush ', items && items.getCount());
            }
 
            return this.callParent(arguments);
        },
 
        flushInvalidates: function () {
            if (this.logOn.flushInvalidate) {
                Ext.log('>> flushInvalidates');
            }
 
            var ret = this.callParent(arguments);
 
            if (this.logOn.flushInvalidate) {
                Ext.log('<< flushInvalidates');
            }
 
            return ret;
        },
 
        getCmp: function (target) {
            var ret = this.callParent(arguments);
            if (!ret.wrapsComponent) {
                Ext.raise({
                    msg: target.id + ' is not a component'
                });
            }
            return ret;
        },
 
        getEl: function (parent, target) {
            var ret = this.callParent(arguments);
            if (ret && ret.wrapsComponent) {
                Ext.raise({
                    msg: parent.id + '/' + target.id + ' is a component (expected element)'
                });
            }
            return ret;
        },
 
        getLayoutName: function (layout) {
            return layout.owner.id + '<' + layout.type + '>';
        },
 
        layoutDone: function (layout) {
            var me = this,
                name = me.getLayoutName(layout);
 
            if (me.logOn.layoutDone) {
                Ext.log('layoutDone: ', name, ' ( ', me.remainingLayouts, ' running)');
            }
 
            if (!layout.running) {
                Ext.raise({
                    msg: name + ' is already done'
                });
            }
            if (!me.remainingLayouts) {
                Ext.raise({
                    msg: name + ' finished but no layouts are running'
                });
            }
 
            me.callParent(arguments);
        },
 
        layoutTreeHasFailures: function (layout, reported) {
            var me = this;
 
            function hasFailure (lo) {
                var failure = !lo.done,
                    key, childLayout;
 
                if (lo.done) {
                    for (key in me.layouts) {
                        if (me.layouts.hasOwnProperty(key)) {
                            childLayout = me.layouts[key];
 
                            if (childLayout.owner.ownerLayout === lo) {
                                if (hasFailure(childLayout)) {
                                    failure = true;
                                }
                            }
                        }
                    }
                }
 
                return failure;
            }
 
            if (hasFailure(layout)) {
                return true;
            }
 
            function markReported (lo) {
                var key, childLayout;
 
                reported[lo.id] = 1;
 
                for (key in me.layouts) {
                    if (me.layouts.hasOwnProperty(key)) {
                        childLayout = me.layouts[key];
 
                        if (childLayout.owner.ownerLayout === lo) {
                            markReported(childLayout);
                        }
                    }
                }
            }
 
            markReported(layout);
            return false;
        },
 
        queueLayout: function (layout) {
            if (layout.done || layout.blockCount || layout.pending) {
                Ext.raise({
                    msg: this.getLayoutName(layout) + ' should not be queued for layout'
                });
            }
            if (this.logOn.queueLayout) {
                Ext.log('Queue ', this.getLayoutName(layout));
            }
            return this.callParent(arguments);
        },
 
        reportLayoutResult: function (layout, reported) {
            var me           = this,
                owner        = layout.owner,
                ownerContext = me.getCmp(owner),
                blockedBy    = [],
                triggeredBy  = [],
                key, value, i, length, childLayout,
                item, setBy, info;
 
            reported[layout.id] = 1;
 
            for (key in layout.blockedBy) {
                if (layout.blockedBy.hasOwnProperty(key)) {
                    blockedBy.push(layout.blockedBy[key]);
                }
            }
            blockedBy.sort();
 
            for (key in me.triggersByLayoutId[layout.id]) {
                if (me.triggersByLayoutId[layout.id].hasOwnProperty(key)) {
                    value = me.triggersByLayoutId[layout.id][key];
                    triggeredBy.push({ name: key, info: value });
                }
            }
            triggeredBy.sort(function (a, b) {
                return a.name < b.name ? -1 : (b.name < a.name ? 1 : 0);
            });
 
            Ext.log({indent: 1}, (layout.done ? '++' : '--'), me.getLayoutName(layout),
                (ownerContext.isBoxParent ? ' [isBoxParent]' : ''),
                (ownerContext.boxChildren ? ' - boxChildren: ' + ownerContext.state.boxesMeasured + '/' + ownerContext.boxChildren.length : ''),
                ownerContext.boxParent ? (' - boxParent: ' + ownerContext.boxParent.id) : '',
                ' - size: ', ownerContext.widthModel.name, '/', ownerContext.heightModel.name);
 
            if (!layout.done || me.reportOnSuccess) {
                if (blockedBy.length) {
                    ++Ext.log.indent;
                    Ext.log({indent: 1}, 'blockedBy:  count=',layout.blockCount);
 
                    length = blockedBy.length;
                    for (= 0; i < length; i++) {
                        Ext.log(blockedBy[i]);
                    }
 
                    Ext.log.indent -= 2;
                }
                if (triggeredBy.length) {
                    ++Ext.log.indent;
                    Ext.log({indent: 1}, 'triggeredBy: count='+layout.triggerCount);
 
                    length = triggeredBy.length;
                    for (= 0; i < length; i++) {
                        info = value.info || value;
                        item  = info.item;
                        setBy = (item.setBy && item.setBy[info.name]) || '?';
 
                        value = triggeredBy[i];
 
                        Ext.log(
                            value.name,
                            ' (',
                            item.props[info.name],
                            ') dirty: ',
                            (item.dirty ? !!item.dirty[info.name] : false),
                            ', setBy: ',
                            setBy
                        );
                    }
 
                    Ext.log.indent -= 2;
                }
            }
 
            for (key in me.layouts) {
                if (me.layouts.hasOwnProperty(key)) {
                    childLayout = me.layouts[key];
 
                    if (!childLayout.done && childLayout.owner.ownerLayout === layout) {
                        me.reportLayoutResult(childLayout, reported);
                    }
                }
            }
 
            for (key in me.layouts) {
                if (me.layouts.hasOwnProperty(key)) {
                    childLayout = me.layouts[key];
 
                    if (childLayout.done && childLayout.owner.ownerLayout === layout) {
                        me.reportLayoutResult(childLayout, reported);
                    }
                }
            }
 
            --Ext.log.indent;
        },
 
        resetLayout: function (layout) {
            var me = this,
                type = layout.type,
                name = me.getLayoutName(layout),
                accum = me.accumByType[type],
                frame;
 
            if (me.logOn.resetLayout) {
                Ext.log('resetLayout: ', name, ' ( ', me.remainingLayouts, ' running)');
            }
 
            if (!me.state) { // if (first time ... before run) 
                if (!accum && me.profileLayoutsByType) {
                    me.accumByType[type] = accum = Ext.Perf.get('layout_' + layout.type);
                }
                me.numByType[type] = (me.numByType[type] || 0) + 1;
            }
 
            frame = accum && accum.enter();
            me.callParent(arguments);
            if (accum) {
                frame.leave();
            }
 
            me.checkRemainingLayouts();
        },
 
        round: function (t) {
            return Math.round(* 1000) / 1000;
        },
 
        run: function () {
            var me = this,
                ret, time, key, i, layout,
                boxParent, children, n,
                reported, unreported,
                calcs, total,
                calcsLength, calc;
 
            me.accumByType = {};
            me.calcsByType = {};
            me.numByType = {};
            me.timesByType = {};
            me.triggersByLayoutId = {};
 
            Ext.log.indentSize = 3;
            Ext.log('==================== LAYOUT ====================');
 
            time = Ext.perf.getTimestamp();
            ret = me.callParent(arguments);
            time = Ext.perf.getTimestamp() - time;
 
            if (me.logOn.boxParent && me.boxParents) {
                for (key in me.boxParents) {
                    if (me.boxParents.hasOwnProperty(key)) {
                        boxParent = me.boxParents[key];
                        children  = boxParent.boxChildren;
                        n         = children.length;
 
                        Ext.log('boxParent: ', boxParent.id);
                        for (= 0; i < n; ++i) {
                            Ext.log(' --> ', children[i].id);
                        }
                    }
                }
            }
 
            if (ret) {
                Ext.log('----------------- SUCCESS -----------------');
            } else {
                Ext.log(
                    {level: 'error' },
                    '----------------- FAILURE -----------------'
                );
            }
 
            for (key in me.layouts) {
                if (me.layouts.hasOwnProperty(key)) {
                    layout = me.layouts[key];
 
                    if (layout.running) {
                        Ext.log.error('Layout left running: ', me.getLayoutName(layout));
                    }
                    if (layout.ownerContext) {
                        Ext.log.error('Layout left connected: ', me.getLayoutName(layout));
                    }
                }
            }
 
            if (!ret || me.reportOnSuccess) {
                reported = {};
                unreported = 0;
 
                for (key in me.layouts) {
                    if (me.layouts.hasOwnProperty(key)) {
                        layout = me.layouts[key];
 
                        if (me.items[layout.owner.el.id].isTopLevel) {
                            if (me.reportOnSuccess || me.layoutTreeHasFailures(layout, reported)) {
                                me.reportLayoutResult(layout, reported);
                            }
                        }
                    }
                }
 
                // Just in case we missed any layouts... 
                for (key in me.layouts) {
                    if (me.layouts.hasOwnProperty(key)) {
                        layout = me.layouts[key];
 
                        if (!reported[layout.id]) {
                            if (!unreported) {
                                Ext.log('----- Unreported!! -----');
                            }
                            ++unreported;
                            me.reportLayoutResult(layout, reported);
                        }
                    }
                }
            }
 
            Ext.log('Cycles: ', me.cycleCount, ', Flushes: ', me.flushCount,
                ', Calculates: ', me.calcCount, ' in ', me.round(time), ' msec');
 
            Ext.log('Calculates by type:');
            /*Ext.Object.each(me.numByType, function (type, total) {
                Ext.log(type, ': ', total, ' in ', me.calcsByType[type], ' tries (',
                    Math.round(me.calcsByType[type] / total * 10) / 10, 'x) at ',
                    me.round(me.timesByType[type]), ' msec (avg ',
                    me.round(me.timesByType[type] / me.calcsByType[type]), ' msec)');
            });*/
            calcs = [];
            for (key in me.numByType) {
                if (me.numByType.hasOwnProperty(key)) {
                    total = me.numByType[key];
 
                    calcs.push({
                            type       : key,
                            total      : total,
                            calcs      : me.calcsByType[key],
                            multiple   : Math.round(me.calcsByType[key] / total * 10) / 10,
                            calcTime   : me.round(me.timesByType[key]),
                            avgCalcTime: me.round(me.timesByType[key] / me.calcsByType[key])
                        });
                }
            }
 
 
            calcs.sort(function (a,b) {
                return b.calcTime - a.calcTime;
            });
 
            calcsLength = calcs.length;
            for (i=0; i<calcsLength; i++) {
                calc = calcs[i];
 
                Ext.log(
                    calc.type,
                    '',
                    calc.total,
                    ' in ',
                    calc.calcs,
                    ' tries (',
                    calc.multiple,
                    'x) at ',
                    calc.calcTime,
                    ' msec (avg ',
                    calc.avgCalcTime,
                    ' msec)'
                );
            }
 
            return ret;
        },
 
        runCycle: function () {
            if (this.logOn.runCycle) {
                Ext.log('>>> Cycle ', this.cycleCount, ' (queue length: ', this.layoutQueue.length, ')');
            }
 
            return this.callParent(arguments);
        },
 
        runLayout: function (layout) {
            var me = this,
                type = layout.type,
                accum = me.accumByType[type],
                frame, ret, time;
 
            if (me.logOn.calculate) {
                Ext.log('-- calculate ', this.getLayoutName(layout));
            }
 
            frame = accum && accum.enter();
 
            time = Ext.perf.getTimestamp();
            ret = me.callParent(arguments);
            time = Ext.perf.getTimestamp() - time;
            if (accum) {
                frame.leave();
            }
 
            me.calcsByType[type] = (me.calcsByType[type] || 0) + 1;
            me.timesByType[type] = (me.timesByType[type] || 0) + time;
 
            /*  add a / to the front of this line to enable layout completion logging
            if (layout.done) {
                var ownerContext = me.getCmp(layout.owner),
                    props = ownerContext.props;
 
                if (layout.isComponentLayout) {
                    Ext.log('complete ', layout.owner.id, ':', type, ' w=',props.width, ' h=', props.height);
                } else {
                    Ext.log('complete ', layout.owner.id, ':', type, ' cw=',props.contentWidth, ' ch=', props.contentHeight);
                }
            }**/
 
            return ret;
        }
    } // End Diagnostics 
});