    /**
     * This is a base class for layouts that contain a single item that automatically expands to fill the layout's
     * container. This class is intended to be extended or created via the layout:'fit'
     * {@link Ext.container.Container#layout} config, and should generally not need to be created directly via the new keyword.
     *
     * Fit layout does not have any direct config options (other than inherited ones). To fit a panel to a container using
     * Fit layout, simply set `layout: 'fit'` on the container and add a single panel to it.
     *
     *     @example
     *     Ext.create('Ext.panel.Panel', {
     *         title: 'Fit Layout',
     *         width: 300,
     *         height: 150,
     *         layout:'fit',
     *         items: {
     *             title: 'Inner Panel',
     *             html: 'This is the inner panel content',
     *             bodyPadding: 20,
     *             border: false
     *         },
     *         renderTo: Ext.getBody()
     *     });
     *
     * If the container has multiple items, all of the items will all be equally sized. This is usually not
     * desired, so to avoid this, place only a **single** item in the container. This sizing of all items
     * can be used to provide a background {@link Ext.Img image} that is "behind" another item
     * such as a {@link Ext.view.View dataview} if you also absolutely position the items.
     */
    Ext.define('Ext.layout.container.Fit', {

        /* Begin Definitions */
        extend: 'Ext.layout.container.Container',
        alternateClassName: 'Ext.layout.FitLayout',

        alias: 'layout.fit',

        /* End Definitions */

        itemCls: Ext.baseCSSPrefix + 'fit-item',
        targetCls: Ext.baseCSSPrefix + 'layout-fit',
        type: 'fit',

        /**
         * @cfg {Object} defaultMargins
         * If the individual contained items do not have a margins property specified or margin specified via CSS, the
         * default margins from this property will be applied to each item.
         *
         * This property may be specified as an object containing margins to apply in the format:
         *
         *     {
         *         top: (top margin),
         *         right: (right margin),
         *         bottom: (bottom margin),
         *         left: (left margin)
         *     }
         *
         * This property may also be specified as a string containing space-separated, numeric margin values. The order of
         * the sides associated with each value matches the way CSS processes margin values:
         *
         *   - If there is only one value, it applies to all sides.
         *   - If there are two values, the top and bottom borders are set to the first value and the right and left are
         *     set to the second.
         *   - If there are three values, the top is set to the first value, the left and right are set to the second,
         *     and the bottom is set to the third.
         *   - If there are four values, they apply to the top, right, bottom, and left, respectively.
         *
         */
        defaultMargins: {
            top: 0,
            right: 0,
            bottom: 0,
            left: 0
        },

        manageMargins: true,

        sizePolicies: {
            0: { setsWidth: 0, setsHeight: 0 },
            1: { setsWidth: 1, setsHeight: 0 },
            2: { setsWidth: 0, setsHeight: 1 },
            3: { setsWidth: 1, setsHeight: 1 }
        },

        getItemSizePolicy: function (item) {
            // this layout's sizePolicy is derived from its owner's sizeModel:
            var sizeModel = this.owner.getSizeModel(),
                mode = (sizeModel.width.shrinkWrap ? 0 : 1) |
                       (sizeModel.height.shrinkWrap ? 0 : 2);

           return this.sizePolicies[mode];
        },

        beginLayoutCycle: function(ownerContext, firstCycle) {
            var me = this,
                widthModel = ownerContext.widthModel,
                heightModel = ownerContext.heightModel,
                childItems = ownerContext.childItems,
                childWidthCalculated = !widthModel.shrinkWrap,
                childHeightCalculated = !heightModel.shrinkWrap,
                length = childItems.length,
                clearItemSizes = (ownerContext.targetContext.el.dom.tagName.toUpperCase() === 'TD'),
                i, invalidateOptions, itemContext, targetEl;

            me.callParent(arguments);

            for (i = 0; i < length; ++i) {
                itemContext = childItems[i];

                if (!firstCycle) {
                    if (itemContext.widthModel.calculated == childWidthCalculated) {
                        invalidateOptions = null;
                    } else {
                        invalidateOptions = {
                            widthModel: childWidthCalculated ? me.sizeModels.calculated
                                                             : itemContext.sizeModel.width
                        };
                    }

                    if (itemContext.heightModel.calculated != childHeightCalculated) {
                        (invalidateOptions || (invalidateOptions = {})).heightModel =
                            childHeightCalculated ? me.sizeModels.calculated
                                                  : itemContext.sizeModel.height
                    }

                    if (invalidateOptions) {
                        invalidateOptions.before = me.onBeforeInvalidateChild;
                        itemContext.invalidate(invalidateOptions);
                    }
                }

                // Clear any dimensions which we set before calculation, in case the current
                // settings affect the available size. This particularly effects self-sizing
                // containers such as fields, in which the target element is naturally sized,
                // and should not be stretched by a sized child item.
                if (clearItemSizes) {
                    targetEl = itemContext.target.el.dom;
                    if (itemContext.heightModel.calculated) {
                        targetEl.style.height = '';
                    }
                    if (itemContext.widthModel.calculated) {
                        targetEl.style.width = '';
                    }
                }
            }
        },

        // @private
        calculate : function (ownerContext) {
            var me = this,
                childItems = ownerContext.childItems,
                length = childItems.length,
                info = {
                    contentWidth: 0,
                    contentHeight: 0,
                    length: length,
                    ownerContext: ownerContext,
                    targetSize: me.getContainerSize(ownerContext)
                },
                calcWidth = ownerContext.widthModel.shrinkWrap,
                calcHeight = ownerContext.heightModel.shrinkWrap,
                padWidth = 0,
                padHeight = 0,
                padding,
                i;

            for (i = 0; i < length; ++i) {
                info.index = i;
                me.fitItem(childItems[i], info);
            }

            if (calcHeight || calcWidth) {
                padding = ownerContext.targetContext.getPaddingInfo();

                if (calcWidth) {
                    padWidth = padding.width;
                }

                if (calcHeight) {
                    padHeight = padding.height;
                }
            }

            // contentWidth and contentHeight include the margins
            if (!ownerContext.setContentSize(info.contentWidth + padWidth, info.contentHeight + padHeight)) {
                me.done = false;
            }
        },

        fitItem: function (itemContext, info) {
            var me = this;

            if (itemContext.invalid) {
                me.done = false;
                return;
            }

            info.margins = itemContext.getMarginInfo();
            info.needed = info.got = 0;

            me.fitItemWidth(itemContext, info);
            me.fitItemHeight(itemContext, info);

            // If not all required dimensions have been satisfied, we're not done.
            if (info.got != info.needed) {
                me.done = false;
            }
        },

        fitItemWidth: function (itemContext, info) {
            // Attempt to set only dimensions that are being controlled, not shrinkWrap dimensions
            if (info.ownerContext.widthModel.shrinkWrap) {
                // contentWidth must include the margins to be consistent with setItemWidth
                info.contentWidth = Math.max(info.contentWidth, itemContext.getProp('width') + info.margins.width);
            } else if (itemContext.widthModel.calculated) {
                ++info.needed;
                if (info.targetSize.gotWidth) {
                    ++info.got;
                    this.setItemWidth(itemContext, info);
                }
            }

            this.positionItemX(itemContext, info);
        },

        fitItemHeight: function (itemContext, info) {
            if (info.ownerContext.heightModel.shrinkWrap) {
                // contentHeight must include the margins to be consistent with setItemHeight
                info.contentHeight = Math.max(info.contentHeight, itemContext.getProp('height') + info.margins.height);
            } else if (itemContext.heightModel.calculated) {
                ++info.needed;
                if (info.targetSize.gotHeight) {
                    ++info.got;
                    this.setItemHeight(itemContext, info);
                }
            }

            this.positionItemY(itemContext, info);
        },

        onBeforeInvalidateChild: function (itemContext, options) {
            ++itemContext.context.progressCount;

            if (options.widthModel) {
                itemContext.widthModel = options.widthModel;
            }
            if (options.heightModel) {
                itemContext.heightModel = options.heightModel;
            }
        },

        positionItemX: function (itemContext, info) {
            var margins = info.margins;

            // Adjust position to account for configured margins or if we have multiple items
            // (all items should overlap):
            if (info.index || margins.left) {
                itemContext.setProp('x', margins.left);
            }

            if (margins.width) {
                // Need the margins for shrink-wrapping but old IE sometimes collapses the left margin into the padding
                itemContext.setProp('margin-right', margins.width);
            }
        },

        positionItemY: function (itemContext, info) {
            var margins = info.margins;

            if (info.index || margins.top) {
                itemContext.setProp('y', margins.top);
            }

            if (margins.height) {
                // Need the margins for shrink-wrapping but old IE sometimes collapses the top margin into the padding
                itemContext.setProp('margin-bottom', margins.height);
            }
        },

        setItemHeight: function (itemContext, info) {
            itemContext.setHeight(info.targetSize.height - info.margins.height);
        },

        setItemWidth: function (itemContext, info) {
            itemContext.setWidth(info.targetSize.width - info.margins.width);
        }
    });