/**
 * 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);
    }
});