/**
 * This ComponentLayout handles docking for Panels. It takes care of panels that are
 * part of a ContainerLayout that sets this Panel's size and Panels that are part of
 * an AutoContainerLayout in which this panel get his height based of the CSS or
 * its content.
 * @private
 */
Ext.define('Ext.layout.component.Dock', {
    extend: 'Ext.layout.component.Component',
    alias: 'layout.dock',
    alternateClassName: 'Ext.layout.component.AbstractDock',
 
    type: 'dock',
    
    horzAxisProps: {
        name: 'horz',
        oppositeName: 'vert',
        dockBegin: 'left',
        dockEnd: 'right',
        horizontal: true,
        marginBegin: 'margin-left',
        maxSize: 'maxWidth',
        minSize: 'minWidth',
        pos: 'x',
        setSize: 'setWidth',
        shrinkWrapDock: 'shrinkWrapDockWidth',
        size: 'width',
        sizeModel: 'widthModel'
    },
 
    vertAxisProps: {
        name: 'vert',
        oppositeName: 'horz',
        dockBegin: 'top',
        dockEnd: 'bottom',
        horizontal: false,
        marginBegin: 'margin-top',
        maxSize: 'maxHeight',
        minSize: 'minHeight',
        pos: 'y',
        setSize: 'setHeight',
        shrinkWrapDock: 'shrinkWrapDockHeight',
        size: 'height',
        sizeModel: 'heightModel'
    },
 
    initializedBorders: -1,
 
    horizontalCollapsePolicy: { width: true, x: true },
 
    verticalCollapsePolicy: { height: true, y: true },
 
    finishRender: function() {
        var me = this,
            target, items;
 
        me.callParent();
 
        target = me.getRenderTarget();
        items = me.getDockedItems();
 
        me.finishRenderItems(target, items);
    },
 
    isItemBoxParent: function(itemContext) {
        return true;
    },
 
    isItemShrinkWrap: function(item) {
        return true;
    },
 
    noBorderClasses: [
        Ext.baseCSSPrefix + 'docked-noborder-top',
        Ext.baseCSSPrefix + 'docked-noborder-right',
        Ext.baseCSSPrefix + 'docked-noborder-bottom',
        Ext.baseCSSPrefix + 'docked-noborder-left'
    ],
 
    noBorderClassesSides: {
        top: Ext.baseCSSPrefix + 'docked-noborder-top',
        right: Ext.baseCSSPrefix + 'docked-noborder-right',
        bottom: Ext.baseCSSPrefix + 'docked-noborder-bottom',
        left: Ext.baseCSSPrefix + 'docked-noborder-left'
    },
 
    borderWidthProps: {
        top: 'border-top-width',
        right: 'border-right-width',
        bottom: 'border-bottom-width',
        left: 'border-left-width'
    },
 
    _itemCls: Ext.baseCSSPrefix + 'docked',
 
    handleItemBorders: function() {
        var me = this,
            owner = me.owner,
            borders, docked,
            lastItems = me.lastDockedItems,
            oldBorders = me.borders,
            currentGeneration = owner.dockedItems.generation,
            noBorderClassesSides = me.noBorderClassesSides,
            borderWidthProps = me.borderWidthProps,
            i, ln, item, dock, side,
            collapsed = me.collapsed;
 
        if (me.initializedBorders === currentGeneration ||
            (owner.border && !owner.manageBodyBorders) ||
            (owner.collapsed && owner.collapseMode === 'mini')) {
            return;
        }
 
        me.initializedBorders = currentGeneration;
 
        // Borders have to be calculated using expanded docked item collection.
        me.collapsed = false;
        me.lastDockedItems = docked = me.getLayoutItems();
        me.collapsed = collapsed;
 
        borders = { top: [], right: [], bottom: [], left: [] };
 
        for (= 0, ln = docked.length; i < ln; i++) {
            item = docked[i];
            dock = item.dock;
 
            if (item.ignoreBorderManagement) {
                continue;
            }
 
            if (!borders[dock].satisfied) {
                borders[dock].push(item);
                borders[dock].satisfied = true;
            }
 
            if (!borders.top.satisfied && dock !== 'bottom') {
                borders.top.push(item);
            }
 
            if (!borders.right.satisfied && dock !== 'left') {
                borders.right.push(item);
            }
 
            if (!borders.bottom.satisfied && dock !== 'top') {
                borders.bottom.push(item);
            }
 
            if (!borders.left.satisfied && dock !== 'right') {
                borders.left.push(item);
            }
        }
 
        if (lastItems) {
            for (= 0, ln = lastItems.length; i < ln; i++) {
                item = lastItems[i];
 
                if (!item.destroyed && !item.ignoreBorderManagement && !owner.manageBodyBorders) {
                    item.removeCls(me.noBorderClasses);
                }
            }
        }
 
        if (oldBorders) {
            for (side in oldBorders) {
                if (owner.manageBodyBorders && oldBorders[side].satisfied) {
                    owner.setBodyStyle(borderWidthProps[side], '');
                }
            }
        }
 
        for (side in borders) {
            ln = borders[side].length;
 
            if (!owner.manageBodyBorders) {
                for (= 0; i < ln; i++) {
                    borders[side][i].addCls(noBorderClassesSides[side]);
                }
 
                if ((!borders[side].satisfied && !owner.bodyBorder) || owner.bodyBorder === false) {
                    owner.addBodyCls(noBorderClassesSides[side]);
                }
                else {
                    owner.removeBodyCls(noBorderClassesSides[side]);
                }
            }
            else if (borders[side].satisfied) {
                owner.setBodyStyle(borderWidthProps[side], '1px');
            }
        }
 
        me.borders = borders;
    },
 
    beforeLayoutCycle: function(ownerContext) {
        var me = this,
            owner = me.owner,
            shrinkWrap = me.sizeModels.shrinkWrap,
            shrinkWrapDock = owner.shrinkWrapDock,
            collapsedHorz, collapsedVert;
 
        if (owner.collapsed) {
            if (owner.collapsedVertical()) {
                collapsedVert = true;
                ownerContext.measureDimensions = 1;
            }
            else {
                collapsedHorz = true;
                ownerContext.measureDimensions = 2;
            }
        }
 
        ownerContext.collapsedVert = collapsedVert;
        ownerContext.collapsedHorz = collapsedHorz;
 
        // If we are collapsed, we want to auto-layout using the placeholder/expander
        // instead of the normal items/dockedItems. This must be done here since we could
        // be in a box layout w/stretchmax which sets the width/heightModel to allow it to
        // control the size.
        if (collapsedVert) {
            ownerContext.heightModel = shrinkWrap;
        }
        else if (collapsedHorz) {
            ownerContext.widthModel = shrinkWrap;
        }
        
        shrinkWrapDock = shrinkWrapDock === true ? 3 : (shrinkWrapDock || 0);
        ownerContext.shrinkWrapDockHeight = (shrinkWrapDock & 1) &&
                                            ownerContext.heightModel.shrinkWrap;
        ownerContext.shrinkWrapDockWidth = (shrinkWrapDock & 2) &&
                                           ownerContext.widthModel.shrinkWrap;
    },
 
    beginLayout: function(ownerContext) {
        var me = this,
            owner = me.owner,
            docked = me.getLayoutItems(),
            layoutContext = ownerContext.context,
            dockedItemCount = docked.length,
            lastCollapsedState = me.lastCollapsedState,
            dockedItems, i, item, itemContext, offsets,
            collapsed, dock;
 
        me.callParent(arguments);
 
        // Cache the children as ContextItems (like a Container). Also setup to handle
        // collapsed state:
        collapsed = owner.getCollapsed();
 
        if (collapsed !== lastCollapsedState && lastCollapsedState !== undefined) {
            // If we are collapsing...
            if (me.owner.collapsed) {
                ownerContext.isCollapsingOrExpanding = 1;
                // Add the collapsed class now, so that collapsed CSS rules
                // are applied before measurements are taken by the layout.
                owner.addClsWithUI(owner.collapsedCls);
            }
            else {
                ownerContext.isCollapsingOrExpanding = 2;
                // Remove the collapsed class now, before layout calculations are done.
                owner.removeClsWithUI(owner.collapsedCls);
                ownerContext.lastCollapsedState = me.lastCollapsedState;
            }
        }
 
        me.lastCollapsedState = collapsed;
 
        ownerContext.dockedItems = dockedItems = [];
 
        for (= 0; i < dockedItemCount; i++) {
            item = docked[i];
 
            if (item.rendered) {
                dock = item.dock;
                itemContext = layoutContext.getCmp(item);
                itemContext.dockedAt = { x: 0, y: 0 };
                itemContext.offsets = offsets = Ext.Element.parseBox(item.offsets || 0);
                itemContext.horizontal = dock === 'top' || dock === 'bottom';
                offsets.width = offsets.left + offsets.right;
                offsets.height = offsets.top + offsets.bottom;
                dockedItems.push(itemContext);
            }
        }
 
        ownerContext.bodyContext = ownerContext.getEl('body');
    },
 
    beginLayoutCycle: function(ownerContext) {
        var me = this,
            docked = ownerContext.dockedItems,
            len = docked.length,
            owner = me.owner,
            frameBody = owner.frameBody,
            lastHeightModel = me.lastHeightModel,
            i, item, dock;
 
        me.callParent(arguments);
 
        if (me.owner.manageHeight) {
            // Reset in case manageHeight gets turned on during lifecycle.
            // See below for why display could be set to non-default value.
            if (me.lastBodyDisplay) {
                owner.body.dom.style.display = me.lastBodyDisplay = '';
            }
        }
        else {
            // When manageHeight is false, the body stretches the outer el by using wide margins
            // to force it to accommodate the docked items. When overflow is visible
            // (when panel is resizable and has embedded handles), the body must be inline-block
            // so as not to collapse its margins
            if (me.lastBodyDisplay !== 'inline-block') {
                owner.body.dom.style.display = me.lastBodyDisplay = 'inline-block';
            }
 
            if (lastHeightModel && lastHeightModel.shrinkWrap &&
                        !ownerContext.heightModel.shrinkWrap) {
                owner.body.dom.style.marginBottom = '';
            }
        }
 
        if (ownerContext.widthModel.auto) {
            if (ownerContext.widthModel.shrinkWrap) {
                owner.el.setWidth(null);
            }
 
            owner.body.setWidth(null);
 
            if (frameBody) {
                frameBody.setWidth(null);
            }
        }
 
        if (ownerContext.heightModel.auto) {
            owner.body.setHeight(null);
 
            // owner.el.setHeight(null); Disable this for now
            if (frameBody) {
                frameBody.setHeight(null);
            }
        }
 
        // Each time we begin (2nd+ would be due to invalidate) we need to publish the
        // known contentWidth/Height if we are collapsed:
        if (ownerContext.collapsedVert) {
            ownerContext.setContentHeight(0);
        }
        else if (ownerContext.collapsedHorz) {
            ownerContext.setContentWidth(0);
        }
 
        // dock: 'right' items, when a panel gets narrower get "squished". Moving them to
        // left:0px avoids this!
        for (= 0; i < len; i++) {
            item = docked[i].target;
            dock = item.dock;
 
            if (dock === 'right') {
                item.setLocalX(0);
            }
            else if (dock !== 'left') {
                continue;
            }
 
            // TODO - clear width/height?
        }
    },
 
    calculate: function(ownerContext) {
        var me = this,
            measure = me.measureAutoDimensions(ownerContext, ownerContext.measureDimensions),
            state = ownerContext.state,
            horzDone = state.horzDone,
            vertDone = state.vertDone,
            bodyContext = ownerContext.bodyContext,
            framing, horz, vert, forward, backward;
 
        // make sure we can use these value w/o calling methods to get them
        ownerContext.borderInfo || ownerContext.getBorderInfo();
        ownerContext.paddingInfo || ownerContext.getPaddingInfo();
        ownerContext.frameInfo || ownerContext.getFrameInfo();
        bodyContext.borderInfo || bodyContext.getBorderInfo();
        bodyContext.paddingInfo || bodyContext.getPaddingInfo();
 
        // On CSS3 browsers, the border and padding frame the outer el. On non-CSS3
        // browsers, the outer el has no border or padding - all that appears on the
        // framing elements as padding and height. In CSS3, the border size effects the
        // origin of the dockedItems but the padding does not (so that must be added in
        // most of the time). In non-CSS3 mode, the dockedItems are outside the framing:
        //
        //      ... top / left dockedItems ...
        //      <div id="...-ml" style="padding-left: border-radius-left;">
        //          <div id="...-mr" style="padding-right: border-radius-right;">
        //              <div id="...-mc" style="padding: extra;">
        //                  ... body ...
        //              </div>
        //          </div>
        //      </div>
        //      ... bottom / right dockedItems ...
        // 
        // For the sake of sanity, we perform all the calculations in CSS3 mode. We test
        // for the presence of non-CSS3 framing only when necessary.
        //
        if (!ownerContext.frameBorder) {
            if (!(framing = ownerContext.framing)) {
                ownerContext.frameBorder = ownerContext.borderInfo;
                ownerContext.framePadding = ownerContext.paddingInfo;
            }
            else {
                // These values match what they would have been in CSS3.
                ownerContext.frameBorder = framing.border;
                ownerContext.framePadding = framing.padding;
            }
        }
 
        // Start the axes so they are ready to proceed inwards (fixed-size) or outwards
        // (shrinkWrap) and stash key property names as well:
        horz = !horzDone &&
               me.createAxis(ownerContext, measure.contentWidth, ownerContext.widthModel,
                             me.horzAxisProps, ownerContext.collapsedHorz);
        vert = !vertDone &&
               me.createAxis(ownerContext, measure.contentHeight, ownerContext.heightModel,
                             me.vertAxisProps, ownerContext.collapsedVert);
 
        // We iterate forward and backward over the dockedItems at the same time based on
        // whether an axis is shrinkWrap or fixed-size. For a fixed-size axis, the outer box
        // axis is allocated to docked items in forward order and is reduced accordingly.
        // To handle a shrinkWrap axis, the box starts at the inner (body) size and is used to
        // size docked items in backwards order. This is because the last docked item shares
        // an edge with the body. The item size is used to adjust the shrinkWrap axis outwards
        // until the first docked item (at the outermost edge) is processed. This backwards
        // order ensures that docked items never get an incorrect size for any dimension.
        for (forward = 0, backward = ownerContext.dockedItems.length; backward--; ++forward) {
            if (horz) {
                me.dockChild(ownerContext, horz, backward, forward);
            }
 
            if (vert) {
                me.dockChild(ownerContext, vert, backward, forward);
            }
        }
        
        if (horz && me.finishAxis(ownerContext, horz)) {
            state.horzDone = horzDone = horz;
        }
        
        if (vert && me.finishAxis(ownerContext, vert)) {
            state.vertDone = vertDone = vert;
        }
 
        // Once all items are docked, the final size of the outer panel or inner body can
        // be determined. If we can determine both width and height, we are done.
        if (horzDone && vertDone && me.finishConstraints(ownerContext, horzDone, vertDone)) {
            // Size information is published as we dock items but position is hard to do
            // that way (while avoiding published multiple times) so we publish all the
            // positions at the end.
            me.finishPositions(ownerContext, horzDone, vertDone);
        }
        else {
            me.done = false;
        }
    },
 
    /**
     * Creates an axis object given the particulars. The process starts by placing the
     * dockedItems in an idealized box where this method is called once for each side.
     * The ideal box is defined by the CSS3 border and padding values (which account for
     * the influence of border-radius). The origin (the (0,0) point) of the ideal box is
     * the top-left edge of the border or the border-box. Normal dockedItems are placed
     * inside this box at an offset to clear the border and padding and sit properly in
     * the panel next to the body.
     * 
     * The origin has to be started differently if the axis is in shrinkWrap mode. When
     * shrink-wrapping an axis, the axis starts at the edge of the body and expands
     * outwards as items are docked. This means the ideal (0,0) for shrinkWrap is on the
     * top-left corner of the body.
     * 
     * The following diagram illustrates this using the vertical axis.
     * 
     *      +---------------------------+ 10px (border)
     *      |                           |
     *      |  xxxxxxxxxxxxxxxxxxxxxxx  | 5px (padding)   shrinkWrap    other
     *      |  +=====================+  |                   -50         15
     *      |  |  Header             |  | 30px
     *      |  |                     |  |
     *      |  +=====================+  |
     *      |  +---------------------+  |                   -20         45
     *      |  |  tbar               |  | 20 px
     *      |  +---------------------+  |
     *      |  +---------------------+  |                   0           65
     *      |  |  Body               |  | 100px
     *      |  |                     |  |
     *      |  |                     |  |
     *      |  +---------------------+  |
     *      |  +---------------------+  |                   100         165
     *      |  |  bbar               |  | 15px
     *      |  +---------------------+  |
     *      |  xxxxxxxxxxxxxxxxxxxxxxx  | 5px
     *      |                           |
     *      +---------------------------+ 10px
     *
     * These are sufficient to determine sizes of things, but to finalize this process
     * and assign proper positions, the tentative coordinates have to be adjusted by an
     * amount appropriate for the item. Because dockedItems are position:absolute, they
     * sit inside the border and so must be adjusted for padding. The body is different
     * because it is position:relative and so it naturally sits inside the padding and
     * the padding must not be included in its position.
     * 
     * Headers and footers that use `ignoreParentFrame` interact with this process by
     * moving themselves outside the border and padding. So in the above diagram, the
     * Header would move up by 15px and *everything else* would move up by 10px. When
     * shrinkWrap is taking place, the 10px of border on the top is removed from the
     * height as well.
     * 
     * The bbar behaves slightly different when it is `ignoreParentFrame`. In shrinkWrap
     * mode, it alone would move down by the padding and the bottom border would not be
     * included in the height. Otherwise, the bbar would be moved down 15px (since the
     * edge is fixed) and the next dockedItem would be placed at, or the body would be
     * stretched down to, 5px (padding) pixels above the bbar.
     *
     * @private
     */
    createAxis: function(ownerContext, contentSize, sizeModel, axisProps, collapsedAxis) {
        var me = this,
            begin = 0,
            owner = me.owner,
            maxSize = owner[axisProps.maxSize],
            minSize = owner[axisProps.minSize] || 0,
            dockBegin = axisProps.dockBegin,
            dockEnd = axisProps.dockEnd,
            posProp = axisProps.pos,
            sizeProp = axisProps.size,
            // exactly the same as "maxSize !== null && maxSize !== undefined"
            hasMaxSize = maxSize != null,
            shrinkWrap = sizeModel.shrinkWrap,
            bodyContext, framing, padding, end;
 
        if (shrinkWrap) {
            // End position before adding docks around the content is content size
            // plus the body borders in this axis.
            // If collapsed in this axis, the body borders will not be shown.
            if (collapsedAxis) {
                end = 0;
            }
            else {
                bodyContext = ownerContext.bodyContext;
                end = contentSize + bodyContext.borderInfo[sizeProp];
            }
        }
        else {
            framing = ownerContext.frameBorder;
            padding = ownerContext.framePadding;
 
            begin = framing[dockBegin] + padding[dockBegin];
            end = ownerContext.getProp(sizeProp) - (framing[dockEnd] + padding[dockEnd]);
        }
 
        return {
            shrinkWrap: sizeModel.shrinkWrap,
            sizeModel: sizeModel,
            // An axis tracks start and end+1 px positions. eg 0 to 10 for 10px high
            initialBegin: begin,
            begin: begin,
            end: end,
            collapsed: collapsedAxis,
            horizontal: axisProps.horizontal,
            ignoreFrameBegin: null,
            ignoreFrameEnd: null,
            initialSize: end - begin,
            maxChildSize: 0,
            hasMinMaxConstraints: (minSize || hasMaxSize) && sizeModel.shrinkWrap,
            minSize: minSize,
            maxSize: hasMaxSize ? maxSize : 1e9,
            bodyPosProp: me.owner.manageHeight ? posProp : axisProps.marginBegin,
            dockBegin: dockBegin,    // 'left' or 'top'
            dockEnd: dockEnd,        // 'right' or 'end'
            posProp: posProp,        // 'x' or 'y'
            sizeProp: sizeProp,      // 'width' or 'height'
            setSize: axisProps.setSize,
            shrinkWrapDock: ownerContext[axisProps.shrinkWrapDock],
            sizeModelName: axisProps.sizeModel,
            dockedPixelsEnd: 0
        };
    },
 
    /**
     * Docks a child item on the specified axis. This boils down to determining if the item
     * is docked at the "beginning" of the axis ("left" if horizontal, "top" if vertical),
     * the "end" of the axis ("right" if horizontal, "bottom" if vertical) or stretches
     * along the axis ("top" or "bottom" if horizontal, "left" or "right" if vertical). It
     * also has to differentiate between fixed and shrinkWrap sized dimensions.
     * @private
     */
    dockChild: function(ownerContext, axis, backward, forward) {
        var me = this,
            itemContext = ownerContext.dockedItems[axis.shrinkWrap ? backward : forward],
            item = itemContext.target,
            dock = item.dock, // left/top/right/bottom
            sizeProp = axis.sizeProp,
            pos, size;
 
        if (item.ignoreParentFrame && ownerContext.isCollapsingOrExpanding) {
            // collapsed window header margins may differ from expanded window header margins
            // so we need to make sure the old cached values are not used in axis calculations
            itemContext.clearMarginCache();
        }
 
        if (!itemContext.marginInfo) {
            itemContext.getMarginInfo(); // get marginInfo ready
        }
 
        if (dock === axis.dockBegin) {
            if (axis.shrinkWrap) {
                pos = me.dockOutwardBegin(ownerContext, itemContext, item, axis);
            }
            else {
                pos = me.dockInwardBegin(ownerContext, itemContext, item, axis);
            }
        }
        else if (dock === axis.dockEnd) {
            if (axis.shrinkWrap) {
                pos = me.dockOutwardEnd(ownerContext, itemContext, item, axis);
            }
            else {
                pos = me.dockInwardEnd(ownerContext, itemContext, item, axis);
            }
        }
        else {
            if (axis.shrinkWrapDock) {
                // we are still shrinkwrapping transversely... so we need to include the
                // size of this item in the max calculation
                size = itemContext.getProp(sizeProp) + itemContext.marginInfo[sizeProp];
                axis.maxChildSize = Math.max(axis.maxChildSize, size);
                pos = 0;
            }
            else {
                pos = me.dockStretch(ownerContext, itemContext, item, axis);
            }
        }
 
        itemContext.dockedAt[axis.posProp] = pos;
    },
 
    /**
     * Docks an item on a fixed-size axis at the "beginning". The "beginning" of the horizontal
     * axis is "left" and the vertical is "top". For a fixed-size axis, the size works from
     * the outer element (the panel) towards the body.
     * @private
     */
    dockInwardBegin: function(ownerContext, itemContext, item, axis) {
        var pos = axis.begin,
            sizeProp = axis.sizeProp,
            ignoreParentFrame = item.ignoreParentFrame,
            delta,
            size,
            dock;
 
        if (ignoreParentFrame) {
            axis.ignoreFrameBegin = itemContext;
            dock = item.dock;
 
            // We need to move everything up by the border-width.
            delta = ownerContext.frameBorder[dock];
 
            // We need to move the header "up" by the padding as well.
            pos -= delta + ownerContext.framePadding[dock];
        }
 
        if (!item.overlay) {
            size = itemContext.getProp(sizeProp) + itemContext.marginInfo[sizeProp];
            axis.begin += size;
 
            if (ignoreParentFrame) {
                axis.begin -= delta;
            }
        }
 
        return pos;
    },
 
    /**
     * Docks an item on a fixed-size axis at the "end". The "end" of the horizontal axis is
     * "right" and the vertical is "bottom".
     * @private
     */
    dockInwardEnd: function(ownerContext, itemContext, item, axis) {
        var sizeProp = axis.sizeProp,
            size = itemContext.getProp(sizeProp) + itemContext.marginInfo[sizeProp],
            pos = axis.end - size,
            frameEnd;
 
        if (!item.overlay) {
            axis.end = pos;
        }
 
        if (item.ignoreParentFrame) {
            axis.ignoreFrameEnd = itemContext;
            frameEnd = ownerContext.frameBorder[item.dock];
            pos += frameEnd + ownerContext.framePadding[item.dock];
            axis.end += frameEnd;
        }
 
        return pos;
    },
 
    /**
     * Docks an item on a shrinkWrap axis at the "beginning". The "beginning" of the horizontal
     * axis is "left" and the vertical is "top". For a shrinkWrap axis, the size works from
     * the body outward to the outermost element (the panel).
     * 
     * During the docking process, coordinates are allowed to be negative. We start with the
     * body at (0,0) so items docked "top" or "left" will simply be assigned negative x/y. In
     * the {@link #finishPositions} method these are corrected and framing is added. This way
     * the correction is applied as a simple translation of delta x/y on all coordinates to
     * bring the origin back to (0,0).
     * @private
     */
    dockOutwardBegin: function(ownerContext, itemContext, item, axis) {
        var pos = axis.begin,
            sizeProp = axis.sizeProp,
            size;
 
        if (axis.collapsed) {
            axis.ignoreFrameBegin = axis.ignoreFrameEnd = itemContext;
        }
        else if (item.ignoreParentFrame) {
            axis.ignoreFrameBegin = itemContext;
        }
        // NOTE - When shrinkWrapping an ignoreParentFrame, this must be the last item
        // on the axis. Since that is so, we let finishAxis take this in to account.
 
        if (!item.overlay) {
            size = itemContext.getProp(sizeProp) + itemContext.marginInfo[sizeProp];
            pos -= size;
            axis.begin = pos;
        }
 
        return pos;
    },
 
    /**
     * Docks an item on a shrinkWrap axis at the "end". The "end" of the horizontal axis is
     * "right" and the vertical is "bottom".
     * @private
     */
    dockOutwardEnd: function(ownerContext, itemContext, item, axis) {
        var pos = axis.end,
            sizeProp = axis.sizeProp,
            size;
 
        size = itemContext.getProp(sizeProp) + itemContext.marginInfo[sizeProp];
 
        if (axis.collapsed) {
            axis.ignoreFrameBegin = axis.ignoreFrameEnd = itemContext;
        }
        else if (item.ignoreParentFrame) {
            axis.ignoreFrameEnd = itemContext;
        }
        // NOTE - When shrinkWrapping an ignoreParentFrame, this must be the last item
        // on the axis. Since that is so, we let finishAxis take this in to account.
 
        if (!item.overlay) {
            axis.end = pos + size;
            axis.dockedPixelsEnd += size;
        }
 
        return pos;
    },
 
    /**
     * Docks an item that might stretch across an axis. This is done for dock "top" and
     * "bottom" items on the horizontal axis and dock "left" and "right" on the vertical.
     * @private
     */
    dockStretch: function(ownerContext, itemContext, item, axis) {
        var dock = item.dock, // left/top/right/bottom (also used to index padding/border)
            sizeProp = axis.sizeProp, // 'width' or 'height'
            horizontal = dock === 'top' || dock === 'bottom',
            border = ownerContext.frameBorder,
            offsets = itemContext.offsets,
            padding = ownerContext.framePadding,
            endProp = horizontal ? 'right' : 'bottom',
            startProp = horizontal ? 'left' : 'top',
            pos = axis.begin + offsets[startProp],
            margin, size;
 
        if (item.stretch !== false) {
            size = axis.end - pos - offsets[endProp];
 
            if (item.ignoreParentFrame) {
                // In CSS3, the border and padding need to be ignored specifically. In
                // non-CSS3 / framing mode, the border and padding will be 0 **but** the
                // header is not rendered inside the framing elements and so we do not
                // want to do anything anyway!
                pos -= padding[startProp] + border[startProp];
                size += padding[sizeProp] + border[sizeProp];
            }
 
            margin = itemContext.marginInfo;
            size -= margin[sizeProp];
 
            itemContext[axis.setSize](size);
        }
 
        return pos;
    },
 
    /**
     * Finishes the calculation of an axis by determining its size. In non-shrink-wrap
     * cases, this is also where we set the body size.
     * @private
     */
    finishAxis: function(ownerContext, axis) {
        // If the maxChildSize is NaN it means at some point we tried to determine
        // The size of a docked item but we couldn't, so just jump out straight
        // away before doing any other processing
        if (isNaN(axis.maxChildSize)) {
            return false;
        }
        
        // eslint-disable-next-line vars-on-top
        var axisBegin = axis.begin,
            size = axis.end - axisBegin,
            collapsed = axis.collapsed,
            setSizeMethod = axis.setSize,
            beginName = axis.dockBegin, // left or top
            endName = axis.dockEnd, // right or bottom
            padding = ownerContext.framePadding,
            border = ownerContext.frameBorder,
            borderBegin = border[beginName],
            framing = ownerContext.framing,
            framingBegin = framing && framing[beginName],
            // The padding is in play unless the axis is collapsed.
            paddingBegin = collapsed ? 0 : padding[beginName],
            sizeProp = axis.sizeProp,
            ignoreFrameBegin = axis.ignoreFrameBegin,
            ignoreFrameEnd = axis.ignoreFrameEnd,
            bodyContext = ownerContext.bodyContext,
            extraPaddingBegin = Math.max(borderBegin + paddingBegin - framingBegin, 0),
            bodyPos, bodySize, delta, dirty;
 
        if (axis.shrinkWrap) {
            // Since items docked left/top on a shrinkWrap axis go into negative coordinates,
            // we apply a delta to all coordinates to adjust their relative origin back to
            // a (0,0) inside the border.
 
            bodySize = axis.initialSize;
 
            if (framing) {
                // In CSS3 mode, things are compartively simple because "framing" is just
                // borders and padding. In non-CSS3 mode, however, the framing elements
                // are given a size equal to the max of the border-width and border-radius
                // and this pushes the body down accordingly. Further, the dockedItems are
                // all rendered outside the framing elements, so their origin equals the
                // ideal box origin. To translate this to match CSS3, we have to add on
                // the border-top.
 
                delta = -axisBegin + borderBegin + paddingBegin;
                bodyPos = delta - framingBegin - extraPaddingBegin;
            }
            else {
                bodyPos = -axisBegin;
                delta = bodyPos + paddingBegin;
            }
 
            if (!collapsed) {
                size += padding[sizeProp];
            }
 
            if (ignoreFrameBegin) {
                // When some component ignores the begin framing, we move everything "up"
                // by that amount of framing. We also do not include that amount of the
                // framing in the shrinkWrap size.
                delta -= borderBegin;
                bodyPos -= borderBegin;
 
                // The item ignoring the framing must also escape the padding. Since the
                // axis.delta includes the padding and we want to apply this to only the
                // one item, we just poke its dockedAt.x/y property so that when we add
                // axis.begin the padding will cancel out. (Note: when we are collapsed
                // paddingBegin will be 0).
                
                ignoreFrameBegin.dockedAt[axis.posProp] -= paddingBegin;
            }
            else {
                size += borderBegin;
            }
 
            if (collapsed) {
                // in this case "ignoreFrameBegin === ignoreFrameEnd" so we can take the
                // special cases out of the mix here...
            }
            else if (ignoreFrameEnd) {
                // When a component ignores the end framing, we simply move it further
                // "down" by the end padding and we do not add the end framing to the
                // shrinkWrap size.
                ignoreFrameEnd.dockedAt[axis.posProp] += padding[endName];
            }
            else {
                size += border[endName];
            }
 
            axis.size = size; // we have to wait for min/maxWidth/Height processing
 
            if (!axis.horizontal && !this.owner.manageHeight) {
                // the height of the bodyEl will give the proper height to the outerEl so
                // we don't need to set heights in the DOM
                dirty = false;
            }
        }
        else {
            // For a fixed-size axis, we started at the outer box and already have the
            // proper origin... almost... except for the owner's border.
            if (framing) {
                // since dockedItems are rendered outside the framing, they have the
                // proper origin already:
                delta = 0;
                bodyPos = axisBegin - framingBegin - extraPaddingBegin;
            }
            else {
                delta = -borderBegin;
                bodyPos = axisBegin - paddingBegin - borderBegin;
            }
 
            // Body size is remaining space between ends of Axis.
            bodySize = size;
        }
 
        axis.delta = delta;
        bodyContext[setSizeMethod](bodySize, dirty);
        bodyContext.setProp(axis.bodyPosProp, bodyPos);
 
        return !isNaN(size);
    },
    
    beforeInvalidateShrinkWrapDock: function(itemContext, options) {
        var sizeModelName = options.axis.sizeModelName;
 
        if (!itemContext[sizeModelName].constrainedMin) {
            // if the child hit a min constraint, it needs to be at its configured size, so
            // we leave the sizeModel alone
            itemContext[sizeModelName] = Ext.layout.SizeModel.calculated;
        }
    },
    
    afterInvalidateShrinkWrapDock: function(itemContext, options) {
        var axis = options.axis,
            me = options.layout,
            pos;
 
        if (itemContext[axis.sizeModelName].calculated) {
            pos = me.dockStretch(options.ownerContext, itemContext, itemContext.target, axis);
            itemContext.setProp(axis.posProp, axis.delta + pos);
        }
    },
    
    /**
     * Finishes processing of each axis by applying the min/max size constraints.
     * @private
     */
    finishConstraints: function(ownerContext, horz, vert) {
        var me = this,
            sizeModels = me.sizeModels,
            publishWidth = horz.shrinkWrap,
            publishHeight = vert.shrinkWrap,
            owner = me.owner,
            dirty, height, width, heightModel, widthModel, size,
            minSize, maxSize, maxChildSize, desiredSize;
 
        // In these calculations, maxChildSize will only be > 0 in the scenario where
        // we are dock shrink wrapping in that direction, otherwise it is not measured.
        // As such, the additions are done to simplify the logic, even though in most
        // cases, it will have no impact on the overall result.
        
        if (publishWidth) {
            size = horz.size;
            minSize = horz.collapsed ? 0 : horz.minSize;
            maxSize = horz.maxSize;
            maxChildSize = horz.maxChildSize;
            desiredSize = Math.max(size, maxChildSize);
 
            if (desiredSize > maxSize) {
                widthModel = sizeModels.constrainedMax;
                width = maxSize;
            }
            else if (desiredSize < minSize) {
                widthModel = sizeModels.constrainedMin;
                width = minSize;
            }
            else if (size < maxChildSize) {
                widthModel = sizeModels.constrainedDock;
                owner.dockConstrainedWidth = width = maxChildSize;
            }
            else {
                width = size;
            }
        }
 
        if (publishHeight) {
            size = vert.size;
            minSize = vert.collapsed ? 0 : vert.minSize;
            maxSize = vert.maxSize;
            maxChildSize = vert.maxChildSize;
            // For vertical docks, their weighting means the height is affected by top/bottom
            // docked items, so we need to subtract them here
            desiredSize = Math.max(size, maxChildSize + size - vert.initialSize);
 
            if (desiredSize > maxSize) {
                heightModel = sizeModels.constrainedMax;
                height = maxSize;
            }
            else if (desiredSize < minSize) {
                heightModel = sizeModels.constrainedMin;
                height = minSize;
            }
            else if (size < maxChildSize) {
                heightModel = sizeModels.constrainedDock;
                owner.dockConstrainedHeight = height = maxChildSize;
            }
            else {
                if (!ownerContext.collapsedVert && !owner.manageHeight) {
                    // height of the outerEl is provided by the height (including margins)
                    // of the bodyEl, so this value does not need to be written to the DOM
                    dirty = false;
 
                    // so long as we set top and bottom margins on the bodyEl!
                    ownerContext.bodyContext.setProp('margin-bottom', vert.dockedPixelsEnd);
                }
 
                height = size;
            }
        }
 
        // Handle the constraints...
 
        if (widthModel || heightModel) {
            // See ContextItem#init for an analysis of why this case is special. Basically,
            // in this case, we only know the width and the height could be anything.
            if (widthModel && heightModel &&
                        widthModel.constrainedMax && heightModel.constrainedByMin) {
                ownerContext.invalidate({ widthModel: widthModel });
 
                return false;
            }
 
            // To process a width or height other than that to which we have shrinkWrapped,
            // we need to invalidate our component and carry forward w/these constrains...
            // unless the ownerLayout wants these results and will invalidate us anyway.
            if (!ownerContext.widthModel.calculatedFromShrinkWrap &&
                        !ownerContext.heightModel.calculatedFromShrinkWrap) {
                // nope, just us to handle the constraint...
                ownerContext.invalidate({ widthModel: widthModel, heightModel: heightModel });
 
                return false;
            }
 
            // We have a constraint to deal with, so we just adjust the size models and
            // allow the ownerLayout to invalidate us with its contribution to our final
            // size...
        }
        else {
            // We're not invalidating, the ownerContext, so if we're shrink wrapping we'll need to
            // tell any docked items to invalidate themselves if necessary.'
            me.invalidateAxes(ownerContext, horz, vert);
            
        }
 
        // we only publish the sizes if we are not invalidating the result...
 
        if (publishWidth) {
            ownerContext.setWidth(width);
 
            if (widthModel) {
                ownerContext.widthModel = widthModel; // important to the ownerLayout
            }
        }
 
        if (publishHeight) {
            ownerContext.setHeight(height, dirty);
 
            if (heightModel) {
                ownerContext.heightModel = heightModel; // important to the ownerLayout
            }
        }
 
        return true;
    },
    
    /**
     * 
     * The default weighting of docked items produces this arrangement:
     * 
     *      +--------------------------------------------+
     *      |                    Top 1                   |
     *      +--------------------------------------------+
     *      |                    Top 2                   |
     *      +-----+-----+--------------------+-----+-----+
     *      |     |     |                    |     |     |
     *      |     |     |                    |     |     |
     *      |     |     |                    |  R  |  R  |
     *      |  L  |  L  |                    |  I  |  I  |
     *      |  E  |  E  |                    |  G  |  G  |
     *      |  F  |  F  |                    |  H  |  H  |
     *      |  T  |  T  |                    |  T  |  T  |
     *      |     |     |                    |     |     |
     *      |  2  |  1  |                    |  1  |  2  |
     *      |     |     |                    |     |     |
     *      |     |     |                    |     |     |
     *      +-----+-----+--------------------+-----+-----+
     *      |                  Bottom 1                  |
     *      +--------------------------------------------+
     *      |                  Bottom 2                  |
     *      +--------------------------------------------+
     * 
     * So when we are shrinkWrapDock on the horizontal, the stretch size for top/bottom
     * docked items is the final axis size. For the vertical axis, however, the stretch
     *
     */ 
    invalidateAxes: function(ownerContext, horz, vert) {
        var before = this.beforeInvalidateShrinkWrapDock,
            after = this.afterInvalidateShrinkWrapDock,
            horzSize = horz.end - horz.begin,
            vertSize = vert.initialSize,
            invalidateHorz = horz.shrinkWrapDock && horz.maxChildSize <= horzSize,
            invalidateVert = vert.shrinkWrapDock && vert.maxChildSize <= vertSize,
            dockedItems, len, i, itemContext, itemSize, isHorz, axis, sizeProp;
 
        if (invalidateHorz || invalidateVert) {
            if (invalidateVert) {
                // For vertical, we need to reset the initial position because they are affected
                // by the horizontally docked items
                vert.begin = vert.initialBegin;
                vert.end = vert.begin + vert.initialSize;
            }
 
            dockedItems = ownerContext.dockedItems;
 
            for (= 0, len = dockedItems.length; i < len; ++i) {
                itemContext = dockedItems[i];
                isHorz = itemContext.horizontal;
                axis = null;
 
                if (invalidateHorz && isHorz) {
                    sizeProp = horz.sizeProp;
                    itemSize = horzSize;
                    axis = horz;
                }
                else if (invalidateVert && !isHorz) {
                    sizeProp = vert.sizeProp;
                    itemSize = vertSize;
                    axis = vert;
                }
                
                if (axis) {
                    // subtract any margins
                    itemSize -= itemContext.getMarginInfo()[sizeProp];
 
                    if (itemSize !== itemContext.props[sizeProp]) {
                        itemContext.invalidate({
                            before: before,
                            after: after,
                            axis: axis,
                            ownerContext: ownerContext,
                            layout: this
                        });
                    }
                }
            }
        }
    },
 
    /**
     * Finishes the calculation by setting positions on the body and all of the items.
     * @private
     */
    finishPositions: function(ownerContext, horz, vert) {
        var dockedItems = ownerContext.dockedItems,
            length = dockedItems.length,
            deltaX = horz.delta,
            deltaY = vert.delta,
            index, itemContext;
 
        for (index = 0; index < length; ++index) {
            itemContext = dockedItems[index];
 
            itemContext.setProp('x', deltaX + itemContext.dockedAt.x);
            itemContext.setProp('y', deltaY + itemContext.dockedAt.y);
        }
    },
 
    finishedLayout: function(ownerContext) {
        var me = this,
            target = ownerContext.target;
 
        me.callParent(arguments);
 
        if (!ownerContext.animatePolicy) {
            if (ownerContext.isCollapsingOrExpanding === 1) {
                target.afterCollapse(false);
            }
            else if (ownerContext.isCollapsingOrExpanding === 2) {
                target.afterExpand(false);
            }
        }
    },
 
    getAnimatePolicy: function(ownerContext) {
        var me = this,
            lastCollapsedState, policy;
 
        if (ownerContext.isCollapsingOrExpanding === 1) {
            lastCollapsedState = me.lastCollapsedState;
        }
        else if (ownerContext.isCollapsingOrExpanding === 2) {
            lastCollapsedState = ownerContext.lastCollapsedState;
        }
 
        if (lastCollapsedState === 'left' || lastCollapsedState === 'right') {
            policy = me.horizontalCollapsePolicy;
        }
        else if (lastCollapsedState === 'top' || lastCollapsedState === 'bottom') {
            policy = me.verticalCollapsePolicy;
        }
 
        return policy;
    },
 
    /**
     * Retrieve an ordered and/or filtered array of all docked Components.
     * @param {String} [order='render'] The desired ordering of the items ('render' or 'visual').
     * @param {Boolean} [beforeBody] An optional flag to limit the set of items to only those
     *  before the body (true) or after the body (false). All components are returned by
     *  default.
     * @return {Ext.Component[]} An array of components.
     * @protected
     */
    getDockedItems: function(order, beforeBody) {
        var me = this,
            renderedOnly = (order === 'visual'),
            all = renderedOnly
                ? Ext.ComponentQuery.query('[rendered]', me.owner.dockedItems.items)
                : me.owner.dockedItems.items,
            sort = all && all.length && order !== false,
            renderOrder,
            dock, dockedItems, i, isBefore, length;
 
        if (beforeBody == null) {
            dockedItems = sort && !renderedOnly ? all.slice() : all;
        }
        else {
            dockedItems = [];
 
            for (= 0, length = all.length; i < length; ++i) {
                dock = all[i].dock;
                isBefore = (dock === 'top' || dock === 'left');
 
                if (beforeBody ? isBefore : !isBefore) {
                    dockedItems.push(all[i]);
                }
            }
 
            sort = sort && dockedItems.length;
        }
 
        if (sort) {
            renderOrder = (order = order || 'render') === 'render';
            Ext.Array.sort(dockedItems, function(a, b) {
                var aw, bw;
 
                // If the two items are on opposite sides of the body, they must not be sorted
                // by any weight value:
                // For rendering purposes, left/top *always* sorts before right/bottom
                // eslint-disable-next-line max-len
                if (renderOrder && ((aw = me.owner.dockOrder[a.dock]) !== (bw = me.owner.dockOrder[b.dock]))) {
                    // The two dockOrder values cancel out when two items are on opposite sides.
                    if (!(aw + bw)) {
                        return aw - bw;
                    }
                }
 
                aw = me.getItemWeight(a, order);
                bw = me.getItemWeight(b, order);
 
                if ((aw !== undefined) && (bw !== undefined)) {
                    return aw - bw;
                }
 
                return 0;
            });
        }
 
        return dockedItems || [];
    },
 
    getItemWeight: function(item, order) {
        var weight = item.weight || this.owner.defaultDockWeights[item.dock];
 
        return weight[order] || weight;
    },
 
    /**
     * @protected
     * Returns an array containing all the **visible** docked items inside this layout's owner Panel
     * @return {Array} An array containing all the **visible** docked items of the Panel
     */
    getLayoutItems: function() {
        var me = this,
            items,
            itemCount,
            item,
            i,
            result;
 
        if (me.owner.collapsed) {
            result = me.owner.getCollapsedDockedItems();
        }
        else {
            items = me.getDockedItems('visual');
            itemCount = items.length;
            result = [];
 
            for (= 0; i < itemCount; i++) {
                item = items[i];
 
                if (!item.hidden) {
                    result.push(item);
                }
            }
        }
 
        return result;
    },
 
    // Content size includes padding but not borders, so subtract them off
    measureContentWidth: function(ownerContext) {
        var bodyContext = ownerContext.bodyContext;
 
        return bodyContext.el.getWidth() - bodyContext.getBorderInfo().width;
    },
 
    measureContentHeight: function(ownerContext) {
        var bodyContext = ownerContext.bodyContext;
 
        return bodyContext.el.getHeight() - bodyContext.getBorderInfo().height;
    },
    
    redoLayout: function(ownerContext) {
        var me = this,
            owner = me.owner;
        
        // If we are collapsing...
        if (ownerContext.isCollapsingOrExpanding === 1) {
            if (owner.reExpander) {
                owner.reExpander.el.show();
            }
 
            // Add the collapsed class now, so that collapsed CSS rules
            // are applied before measurements are taken by the layout.
            owner.addClsWithUI(owner.collapsedCls);
            ownerContext.redo(true);
        }
        else if (ownerContext.isCollapsingOrExpanding === 2) {
            // Remove the collapsed class now, before layout calculations are done.
            owner.removeClsWithUI(owner.collapsedCls);
            ownerContext.bodyContext.redo();
        }
    },
    
    getRenderTarget: function() {
        return this.owner.bodyWrap;
    },
 
    /**
     * @private
     * Used to render in the correct order, top/left before bottom/right
     */
    renderChildren: function() {
        var me = this,
            items = me.getDockedItems(),
            target = me.getRenderTarget();
 
        me.handleItemBorders();
 
        me.renderItems(items, target);
    },
 
    /**
     * Render the top and left docked items before any existing DOM nodes in our render
     * target and then render the right and bottom docked items after. This is important,
     * for such things as tab stops and ARIA readers, that the DOM nodes are in a
     * meaningful order.
     *
     * Our collection of docked items will already be ordered via Panel.getDockedItems().
     * @protected
     */
    renderItems: function(items, target) {
        var me = this,
            owner = me.owner,
            dockedItemIds = {},
            dockedItemCount = items.length,
            bodyDom = owner.body.dom,
            bodyWrapDom = owner.bodyWrap.dom,
            hasFrame = !!owner.frameSize,
            bodyContainer = owner.bodyContainer,
            childNodes, childNodeCount, hasDockedToEl, item, dom, elFound, gap,
            bodyBaseIndex, bodyDockIndex, wrapBaseIndex, wrapDockIndex, i,
            insertPosition, insertTarget;
 
        // TODO: This is affected if users provide custom weight values to their
        // docked items, which puts it out of (t,l,r,b) order. Avoiding a second
        // sort operation here, for now, in the name of performance. getDockedItems()
        // needs the sort operation not just for this layout-time rendering, but
        // also for getRefItems() to return a logical ordering (CQ, et al).
 
        if (dockedItemCount) {
 
            // Build a correct map of docked item ids to match with the ID of found DOM elements.
            // The "map" property of the dockedItems Collection uses the *itemId* as the key, so 
            // that will never match, and will cause isValidParent to return false, resulting
            // in DOM motion.
            for (= 0; i < dockedItemCount; i++) {
                item = items[i];
                dockedItemIds[item.id] = item;
                
                if (item.dockToEl) {
                    hasDockedToEl = true;
                }
            }
 
            childNodes = me.getRenderTarget().dom.childNodes;
            childNodeCount = childNodes.length;
            gap = 0;
 
            // Our childNodes may contain non-item elements as well as the bodyEl. We
            // want to find the bodyEl's index and count the number of dockedItems that
            // are already rendered before it.
            //
            for (= 0; i < childNodeCount; ++i) {
                dom = childNodes[i];
                
                // Regardless of whether framing is used, bodyEl will be contained
                // in the bodyWrap element.
                elFound = dom === bodyDom;
                
                if (elFound) {
                    // We want top/left dockedItems to be inserted before the body, so
                    // use the bodyEl's index as the base.
                    bodyBaseIndex = i;
                    break;
                }
 
                if (dockedItemIds[dom.id]) {
                    ++gap;
                }
            }
            
            //<debug>
            if (!elFound) {
                Ext.log.error('Dock layout error for ' + owner.id + ': bodyEl not found!');
            }
            //</debug>
 
            // Subtract the number of rendered dockedItems from the bodyEl's index to get
            // the actual base.
            //
            bodyBaseIndex -= gap;
 
            // If we have any items docked to the main el (presently Panel header only),
            // we want to find bodyWrap element base index the same way as we did for
            // the body element.
            //
            if (hasDockedToEl) {
                elFound = false;
                gap = 0;
                
                childNodes = owner.el.dom.childNodes;
                childNodeCount = childNodes.length;
                
                for (= 0; i < childNodeCount; i++) {
                    dom = childNodes[i];
                    
                    // When extra framing elements are used, bodyWrap will be contained
                    // in the frameBody, which in turn might be contained in other
                    // framing elements.
                    if (hasFrame) {
                        elFound = dom === bodyWrapDom || dom === bodyContainer;
                        
                        // Cache the found body container to speed up subsequent layouts
                        if (!elFound && Ext.fly(dom).contains(bodyWrapDom)) {
                            elFound = true;
                            owner.bodyContainer = dom;
                        }
                    }
                    else {
                        elFound = dom === bodyWrapDom;
                    }
                    
                    if (elFound) {
                        wrapBaseIndex = i;
                        break;
                    }
                    
                    if (dockedItemIds[dom.id]) {
                        ++gap;
                    }
                }
 
                //<debug>
                if (!elFound) {
                    Ext.log.error('Dock layout error for ' + owner.id + ': bodyWrapEl not found!');
                }
                //</debug>
                
                // We need to adjust wrapBaseIndex the same way as bodyBaseIndex above,
                // because docked-to-el header may already exist when this method is called.
                wrapBaseIndex -= gap;
            }
            
            bodyDockIndex = wrapDockIndex = 0;
            
            // Finally loop over the dockedItems again and ensure that the top/left and
            // bottom/right items are at the proper DOM offset, immediately surrounding
            // the bodyEl or bodyWrap if they're docked to the main el.
            // Presently only Panel header does that.
            //
            for (= 0; i < dockedItemCount; i++) {
                item = items[i];
                
                // Header is rendered to the el instead of bodyWrap
                if (item.dockToEl) {
                    insertTarget = owner.el;
                    insertPosition = wrapBaseIndex + wrapDockIndex++;
                    
                    if (item.dock === 'right' || item.dock === 'bottom') {
                        insertPosition++; // skip over the bodyWrap
                        
                        // frameBody consists of 3 elements, wrapBaseIndex points
                        // to the middle one. Skip the bottom one, too.
                        if (hasFrame) {
                            insertPosition++;
                        }
                    }
                    else if (hasFrame && insertPosition > 0) {
                        // Skip top frameBody element
                        insertPosition--;
                    }
                }
                else {
                    insertTarget = target;
                    insertPosition = bodyBaseIndex + bodyDockIndex++;
                    
                    if (item.dock === 'right' || item.dock === 'bottom') {
                        insertPosition++; // skip over the bodyEl or frameBody
                    }
                }
 
                // Same logic as Layout.renderItems()
                if (!item.rendered) {
                    me.renderItem(item, insertTarget, insertPosition);
                }
                else if (!me.isValidParent(item, insertTarget, insertPosition)) {
                    me.moveItem(item, insertTarget, insertPosition);
                }
            }
        }
    },
    
    undoLayout: function(ownerContext) {
        var me = this,
            owner = me.owner;
        
        // If we are collapsing...
        if (ownerContext.isCollapsingOrExpanding === 1) {
 
            // We do not want to see the re-expander header until the final collapse is complete
            if (owner.reExpander) {
                owner.reExpander.el.hide();
            }
 
            // Add the collapsed class now, so that collapsed CSS rules are applied
            // before measurements are taken by the layout.
            owner.removeClsWithUI(owner.collapsedCls);
            ownerContext.undo(true);
        }
        else if (ownerContext.isCollapsingOrExpanding === 2) {
            // Remove the collapsed class now, before layout calculations are done.
            owner.addClsWithUI(owner.collapsedCls);
            ownerContext.bodyContext.undo();
        }
    },
 
    sizePolicy: {
        nostretch: {
            setsWidth: 0,
            setsHeight: 0
        },
 
        horz: { // item goes horizontally (top or bottom docked)
            shrinkWrap: {
                // This is how we manage the width of a top/bottom docked item when its
                // shrinkWrapWidth and ours need to be maxed (calculatedFromShrinkWrap)
                setsWidth: 1,
                setsHeight: 0,
                readsWidth: 1
            },
            stretch: {
                setsWidth: 1,
                setsHeight: 0
            }
        },
 
        vert: { // item goes vertically (left or right docked)
            shrinkWrap: {
                setsWidth: 0,
                setsHeight: 1,
                readsHeight: 1
            },
            stretch: {
                setsWidth: 0,
                setsHeight: 1
            }
        },
 
        stretchV: {
            setsWidth: 0,
            setsHeight: 1
        },
 
        // Circular dependency with partial auto-sized panels:
        //
        // If we have an autoHeight docked item being stretched horizontally (top/bottom),
        // that stretching will determine its width and its width must be set before its
        // autoHeight can be determined. If that item is docked in an autoWidth panel, the
        // body will need its height set before it can determine its width, but the height
        // of the docked item is needed to subtract from the panel height in order to set
        // the body height.
        //
        // This same pattern occurs with autoHeight panels with autoWidth docked items on
        // left or right. If the panel is fully auto or fully fixed, these problems don't
        // come up because there is no dependency between the dimensions.
        //
        // Cutting the Gordian Knot: In these cases, we have to allow something to measure
        // itself without full context. This is OK as long as the managed dimension doesn't
        // effect the auto-dimension, which is often the case for things like toolbars. The
        // managed dimension only effects overflow handlers and such and does not change the
        // auto-dimension. To encourage the item to measure itself without waiting for the
        // managed dimension, we have to tell it that the layout will also be reading that
        // dimension. This is similar to how stretchmax works.
 
        autoStretchH: {
            readsWidth: 1,
            setsWidth: 1,
            setsHeight: 0
        },
        autoStretchV: {
            readsHeight: 1,
            setsWidth: 0,
            setsHeight: 1
        }
    },
 
    getItemSizePolicy: function(item, ownerSizeModel) {
        var me = this,
            policy = me.sizePolicy,
            shrinkWrapDock = me.owner.shrinkWrapDock,
            dock, vertical;
 
        if (item.stretch === false) {
            return policy.nostretch;
        }
 
        dock = item.dock;
        vertical = (dock === 'left' || dock === 'right');
 
        shrinkWrapDock = shrinkWrapDock === true ? 3 : (shrinkWrapDock || 0);
 
        if (vertical) {
            policy = policy.vert;
            shrinkWrapDock = shrinkWrapDock & 1;
        }
        else {
            policy = policy.horz;
            shrinkWrapDock = shrinkWrapDock & 2;
        }
 
        if (shrinkWrapDock) {
            // Getting the size model is expensive, so only do so if we really need it
            if (!ownerSizeModel) {
                ownerSizeModel = me.owner.getSizeModel();
            }
 
            if (ownerSizeModel[vertical ? 'height' : 'width'].shrinkWrap) {
                return policy.shrinkWrap;
            }
        }
 
        return policy.stretch;
    },
 
    /**
     * @protected
     * We are overriding the Ext.layout.Layout configureItem method to also add a class that
     * indicates the position of the docked item. We use the itemCls (x-docked) as a prefix.
     * An example of a class added to a dock: right item is x-docked-right
     * @param {Ext.Component} item The item we are configuring
     * @param pos
     */
    configureItem: function(item, pos) {
        this.callParent(arguments);
 
        item.addCls(this._itemCls);
 
        if (!item.ignoreBorderManagement) {
            item.addClsWithUI(this.getDockCls(item.dock));
        }
    },
 
    /**
     * Get's the css class name for a given dock position.
     * @param {String} dock `top`, `right`, `bottom`, or `left`
     * @return {String} 
     * @private 
     */
    getDockCls: function(dock) {
        return 'docked-' + dock;
    },
 
    afterRemove: function(item) {
        var dom;
 
        this.callParent(arguments);
 
        item.removeCls(this._itemCls);
 
        if (!item.ignoreBorderManagement) {
            item.removeClsWithUI(this.getDockCls(item.dock));
        }
 
        dom = item.el && item.el.dom;
 
        if (dom && !item.destroying) {
            dom.parentNode.removeChild(dom);
        }
 
        this.childrenChanged = true;
    },
 
    /**
     * This object is indexed by a component's `baseCls` to yield another object which
     * is then indexed by the component's `ui` to produce an array of CSS class names.
     * This array is indexed in the same manner as the `noBorderClassTable` and indicates
     * the a particular edge of a docked item or the body element is actually "collapsed"
     * with the component's outer border.
     * @private
     */
    borderCollapseMap: {
        /*
        'x-panel': {
            'default': []
        }
        */
    },
 
    /**
     * Returns the array of class names to add to a docked item or body element when for
     * the edges that should collapse with the outer component border. Basically, the
     * panel's outer border must look visually like a contiguous border but may need to
     * be realized by using the border of docked items and/or the body. This class name
     * allows the border color and width to be controlled accordingly and distinctly from
     * the border of the docked item or body element when it is not having its border
     * collapsed.
     * @private
     */
    getBorderCollapseTable: function() {
        var me = this,
            map = me.borderCollapseMap,
            owner = me.owner,
            baseCls = owner.baseCls,
            ui = owner.ui,
            table;
 
        map = map[baseCls] || (map[baseCls] = {});
        table = map[ui];
 
        if (!table) {
            baseCls += '-' + ui + '-outer-border-';
            map[ui] = table = [
                0,                  // TRBL
                baseCls + 'l',      // 0001 = 1
                baseCls + 'b',      // 0010 = 2
                baseCls + 'bl',     // 0011 = 3
                baseCls + 'r',      // 0100 = 4
                baseCls + 'rl',     // 0101 = 5
                baseCls + 'rb',     // 0110 = 6
                baseCls + 'rbl',    // 0111 = 7
                baseCls + 't',      // 1000 = 8
                baseCls + 'tl',     // 1001 = 9
                baseCls + 'tb',     // 1010 = 10
                baseCls + 'tbl',    // 1011 = 11
                baseCls + 'tr',     // 1100 = 12
                baseCls + 'trl',    // 1101 = 13
                baseCls + 'trb',    // 1110 = 14
                baseCls + 'trbl'    // 1111 = 15
            ];
        }
 
        return table;
    }
});