/**
 * 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 (!comp.destroying) {
                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, parent);
 
        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;
    },
 
    /**
     * Get a context item, if a cached item already exists it will
     * be returned.
     * @param {Ext.Component/Ext.dom.Element} target The target.
     * @param {Ext.dom.Element} el The element for the context item. If `target`,
     * is an element, these should be the same.
     * @param {Ext.layout.ContextItem} [componentContext] The owning component
     * context. This is used for element contexts.
     * @return {Ext.layout.ContextItem} The context item.
     */
    getItem: function(target, el, componentContext) {
        var id = el.id,
            items = this.items,
            item;
 
        item = items[id] || (items[id] = new Ext.layout.ContextItem({
            context: this,
            target: target,
            el: el,
            componentContext: componentContext
        }));
 
        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) {
        var c;
 
        if (ancestor.isContainer) {
            for (= 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) {
        var me = this,
            currentLayout;
 
        // When calling a callback, the currentLayout needs to be adjusted so
        // that whichever layout caused the invalidate is the currentLayout...
        if (options[name]) {
            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 
     * @param {Boolean} [replace=false] If an item by that ID is already queued, replace it.
     * @private
     */
    queueFlush: function(item, replace) {
        this.flushQueue.add(item, replace);
    },
 
    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();
 
        Ext.layoutUpdates = (Ext.layoutUpdates || 0) + 1;
        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)) {
            // A layout that is not done and has no blocks or triggers that will queue it
            // automatically, must be queued now:
            me.queueLayout(layout);
        }
    },
 
    /* eslint-disable max-len */
    /**
     * 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
        }
    },
    /* eslint-enable max-len */
 
    //-------------------------------------------------------------------------
    // 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() {
            var items;
 
            if (this.logOn.flush) {
                items = this.flushQueue;
                Ext.log('--- Flush ', items && items.getCount());
            }
 
            return this.callParent(arguments);
        },
 
        flushInvalidates: function() {
            var ret;
 
            if (this.logOn.flushInvalidate) {
                Ext.log('>> flushInvalidates');
            }
 
            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]' : ''),
                // eslint-disable-next-line max-len
                (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 (= 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
});