/**
 * This is a layout that will render form Fields, one under the other all stretched to the Container width.
 *
 *     @example
 *     Ext.create('Ext.Panel', {
 *         width: 500,
 *         height: 300,
 *         title: "FormLayout Panel",
 *         layout: 'form',
 *         renderTo: Ext.getBody(),
 *         bodyPadding: 5,
 *         defaultType: 'textfield',
 *         items: [{
 *            fieldLabel: 'First Name',
 *             name: 'first',
 *             allowBlank:false
 *         },{
 *             fieldLabel: 'Last Name',
 *             name: 'last'
 *         },{
 *             fieldLabel: 'Company',
 *             name: 'company'
 *         }, {
 *             fieldLabel: 'Email',
 *             name: 'email',
 *             vtype:'email'
 *         }, {
 *             fieldLabel: 'DOB',
 *             name: 'dob',
 *             xtype: 'datefield'
 *         }, {
 *             fieldLabel: 'Age',
 *             name: 'age',
 *             xtype: 'numberfield',
 *             minValue: 0,
 *             maxValue: 100
 *         }, {
 *             xtype: 'timefield',
 *             fieldLabel: 'Time',
 *             name: 'time',
 *             minValue: '8:00am',
 *             maxValue: '6:00pm'
 *         }]
 *     });
 *
 * Note that any configured {@link Ext.Component#padding padding} will be ignored on items within a Form layout.
 */
Ext.define('Ext.layout.container.Form', {

    /* Begin Definitions */

    alias: 'layout.form',
    extend: 'Ext.layout.container.Container',
    alternateClassName: 'Ext.layout.FormLayout',

    /* End Definitions */
   
    tableCls: Ext.baseCSSPrefix + 'form-layout-table',

    type: 'form',
    
    createsInnerCt: true,

    manageOverflow: true,

    // Begin with no previous adjustments
    lastOverflowAdjust: {
        width: 0,
        height: 0
    },

    childEls: ['formTable'],
    
    padRow: '<tr><td class="' + Ext.baseCSSPrefix + 'form-item-pad" colspan="3"></td></tr>',

    renderTpl: [
        '<table id="{ownerId}-formTable" class="{tableCls}" style="width:100%" cellpadding="0">',
            '{%this.renderBody(out,values)%}',
        '</table>',
        '{%this.renderPadder(out,values)%}'
    ],

    getRenderData: function(){
        var data = this.callParent();
        data.tableCls = this.tableCls;
        return data;    
    },

    calculate : function (ownerContext) {
        var me = this,
            containerSize = me.getContainerSize(ownerContext, true),
            tableWidth,
            childItems,
            i = 0, length,
            shrinkwrapHeight = ownerContext.sizeModel.height.shrinkWrap;

        if (shrinkwrapHeight) {
            if (ownerContext.hasDomProp('containerChildrenSizeDone')) {
                ownerContext.setProp('contentHeight', me.formTable.dom.offsetHeight + ownerContext.targetContext.getPaddingInfo().height);
            } else {
                me.done = false;
            }
        }

        // Once we have been widthed, we can impose that width (in a non-dirty setting) upon all children at once
        if (containerSize.gotWidth) {
            tableWidth = me.formTable.dom.offsetWidth;
            childItems = ownerContext.childItems;

            for (length = childItems.length; i < length; ++i) {
                childItems[i].setWidth(tableWidth, false);
            }
        } else {
            me.done = false;
        }
    },

    getRenderTarget: function() {
        return this.formTable;
    },

    getRenderTree: function() {
        var me = this,
            result = me.callParent(arguments),
            i, len;

        for (i = 0, len = result.length; i < len; i++) {
            result[i] = me.transformItemRenderTree(result[i]);
        }
        return result;
    },

    transformItemRenderTree: function(item) {

        if (item.tag && item.tag == 'table') {
            item.tag = 'tbody';
            delete item.cellspacing;
            delete item.cellpadding;

            // IE6 doesn't separate cells nicely to provide input field
            // vertical separation. It also does not support transparent borders
            // which is how the extra 1px is added to the 2px each side cell spacing.
            // So it needs a 5px high pad row.
            if (Ext.isIE6) {
                item.cn = this.padRow;
            }

            return item;
        }

        return {
            tag: 'tbody',
            cn: {
                tag: 'tr',
                cn: {
                    tag: 'td',
                    colspan: 3,
                    style: 'width:100%',
                    cn: item
                }
            }
        };

    },

    isValidParent: function(item, target, position) {
        return true;
    },

    isItemShrinkWrap: function(item) {
        return ((item.shrinkWrap === true) ? 3 : item.shrinkWrap||0) & 2;
    },

    getItemSizePolicy: function(item) {
        return {
            setsWidth: 1,
            setsHeight: 0
        };
    },


// All of the below methods are old methods moved here from Container layout
// TODO: remove these methods once Form layout extends Auto layout

    beginLayoutCycle: function (ownerContext, firstCycle) {
        var padEl = this.overflowPadderEl;

        if (padEl) {
            padEl.setStyle('display', 'none');
        }

        // Begin with the scrollbar adjustment that we used last time - this is more likely to be correct
        // than beginning with no adjustment at all
        if (!ownerContext.state.overflowAdjust) {
            ownerContext.state.overflowAdjust = this.lastOverflowAdjust;
        }
    },

    /**
     * Handles overflow processing for a container. This should be called once the layout
     * has determined contentWidth/Height. In addition to the ownerContext passed to the
     * {@link #calculate} method, this method also needs the containerSize (the object
     * returned by {@link #getContainerSize}).
     * 
     * @param {Ext.layout.ContextItem} ownerContext
     * @param {Object} containerSize
     * @param {Number} dimensions A bit mask for the overflow managed dimensions. The 0-bit
     * is for `width` and the 1-bit is for `height`. In other words, a value of 1 would be
     * only `width`, 2 would be only `height` and 3 would be both.
     */
    calculateOverflow: function (ownerContext, containerSize, dimensions) {
        var me = this,
            targetContext = ownerContext.targetContext,
            manageOverflow = me.manageOverflow,
            state = ownerContext.state,
            overflowAdjust = state.overflowAdjust,
            padWidth, padHeight, padElContext, padding, scrollRangeFlags,
            scrollbarSize, contentW, contentH, ownerW, ownerH, scrollbars,
            xauto, yauto;

        if (manageOverflow && !state.secondPass && !me.reserveScrollbar) {
            // Determine the dimensions that have overflow:auto applied. If these come by
            // way of component config, this does not require a DOM read:
            xauto = (me.getOverflowXStyle(ownerContext) === 'auto');
            yauto = (me.getOverflowYStyle(ownerContext) === 'auto');

            // If the container layout is not using width, we don't need to adjust for the
            // vscroll (likewise for height). Perhaps we don't even need to run the layout
            // again if the adjustments won't have any effect on the result!
            if (!containerSize.gotWidth) {
                xauto = false;
            }
            if (!containerSize.gotHeight) {
                yauto = false;
            }

            if (xauto || yauto) {
                scrollbarSize = Ext.getScrollbarSize();

                // as a container we calculate contentWidth/Height, so we don't want
                // to use getProp and make it look like we are triggered by them...
                contentW = ownerContext.peek('contentWidth');
                contentH = ownerContext.peek('contentHeight');
                // The content size includes the target element's padding.  Since
                // the containerSize excludes the target element's padding, we  need
                // to subtract this padding from the content size before checking
                // to see if scrollbars are needed.
                padding = targetContext.getPaddingInfo();
                contentW -= padding.width;
                contentH -= padding.height;
                ownerW = containerSize.width;
                ownerH = containerSize.height;

                scrollbars = me.getScrollbarsNeeded(ownerW, ownerH, contentW, contentH);
                state.overflowState = scrollbars;

                if (typeof dimensions == 'number') {
                    scrollbars &= ~dimensions; // ignore dimensions that have no effect
                }

                overflowAdjust = {
                    width:  (xauto && (scrollbars & 2)) ? scrollbarSize.width : 0,
                    height: (yauto && (scrollbars & 1)) ? scrollbarSize.height : 0
                };

                // We can have 0-sized scrollbars (new Mac OS) and so don't invalidate
                // the layout unless this will change something...
                if (overflowAdjust.width !== me.lastOverflowAdjust.width || overflowAdjust.height !== me.lastOverflowAdjust.height) {
                    me.done = false;

                    // we pass overflowAdjust and overflowState in as state for the next
                    // cycle (these are discarded if one of our ownerCt's invalidates):
                    ownerContext.invalidate({
                        state: {
                            overflowAdjust: overflowAdjust,
                            overflowState: state.overflowState,
                            secondPass: true
                        }
                    });
                }
            }
        }

        if (!me.done) {
            return;
        }

        padElContext = ownerContext.padElContext ||
                      (ownerContext.padElContext = ownerContext.getEl('overflowPadderEl', me));

        // Even if overflow does not effect the layout, we still do need the padEl to be
        // sized or hidden appropriately...
        if (padElContext) {
            scrollbars = state.overflowState; // the true overflow state
            padWidth = ownerContext.peek('contentWidth');
            // the padder element must have a height of at least 1px, or its width will be ignored by the container.
            // The extra height is adjusted for by adding a -1px top margin to the padder element
            padHeight = 1;

            if (scrollbars) {
                padding = targetContext.getPaddingInfo();
                scrollRangeFlags = me.scrollRangeFlags;

                if ((scrollbars & 2) && (scrollRangeFlags & 1)) { // if (vscroll and loses bottom)
                    padHeight += padding.bottom;
                }

                if ((scrollbars & 1) && (scrollRangeFlags & 4)) { // if (hscroll and loses right)
                    padWidth += padding.right;
                }
                padElContext.setProp('display', '');
                padElContext.setSize(padWidth, padHeight);
            } else {
                padElContext.setProp('display', 'none');
            }
        }
    },

    completeLayout: function (ownerContext) {
        // Cache the scrollbar adjustment
        this.lastOverflowAdjust = ownerContext.state.overflowAdjust;
    },

    /**
     * Creates an element that makes bottom/right body padding consistent across browsers.
     * This element is sized based on the need for scrollbars in {@link #calculateOverflow}.
     * If the {@link #manageOverflow} option is false, this element is not created.
     *
     * See {@link #getScrollRangeFlags} for more details.
     */
    doRenderPadder: function (out, renderData) {
        // Careful! This method is bolted on to the renderTpl so all we get for context is
        // the renderData! The "this" pointer is the renderTpl instance!

        var me = renderData.$layout,
            owner = me.owner,
            scrollRangeFlags = me.getScrollRangeFlags();

        if (me.manageOverflow) {
            if (scrollRangeFlags & 5) { // if (loses parent bottom and/or right padding)
                out.push('<div id="',owner.id,'-overflowPadderEl" ',
                    // the padder element must have a height of at least 1px, or its width will be ignored by the container.
                    // The extra height is adjusted for by adding a -1px top margin to the padder element
                    // A side effect of this approach is that click events will not register on bottom 1px of the container's
                    // last child item, unless that item has a bottom margin.  To fix this we make the padder element
                    // relatively positioned and give it a large negative z-index.  -99999 seems sufficient.
                    'style="font-size: 1px; height: 1px; margin-top: -1px; position: relative; z-index: -99999');

                out.push('"></div>');

                me.scrollRangeFlags = scrollRangeFlags; // remember for calculateOverflow
            }
        }
    },

    /**
     * Returns the container size (that of the target). Only the fixed-sized dimensions can
     * be returned because the shrinkWrap dimensions are based on the contentWidth/Height
     * as determined by the container layout.
     *
     * If the {@link #calculateOverflow} method is used and if {@link #manageOverflow} is
     * true, this may adjust the width/height by the size of scrollbars.
     * 
     * @param {Ext.layout.ContextItem} ownerContext The owner's context item.
     * @param {Boolean} [inDom=false] True if the container size must be in the DOM.
     * @param {Boolean} [ignoreOverflow=true] if true scrollbar size will not be 
     * subtracted from container size.
     * @return {Object} The size
     * @return {Number} return.width The width
     * @return {Number} return.height The height
     * @protected
     */
    getContainerSize : function(ownerContext, inDom, ignoreOverflow) {
        // Subtle But Important:
        // 
        // We don't want to call getProp/hasProp et.al. unless we in fact need that value
        // for our results! If we call it and don't need it, the layout manager will think
        // we depend on it and will schedule us again should it change.

        var targetContext = ownerContext.targetContext,
            frameInfo = targetContext.getFrameInfo(),
            // if we're managing padding the padding is on the innerCt instead of the targetEl
            padding = targetContext.getPaddingInfo(),
            got = 0,
            needed = 0,
            overflowAdjust = ignoreOverflow ?  null : ownerContext.state.overflowAdjust,
            gotWidth, gotHeight, width, height;

        // In an shrinkWrap width/height case, we must not ask for any of these dimensions
        // because they will be determined by contentWidth/Height which is calculated by
        // this layout...

        // Fit/Card layouts are able to set just the width of children, allowing child's
        // resulting height to autosize the Container.
        // See examples/tabs/tabs.html for an example of this.

        if (!ownerContext.widthModel.shrinkWrap) {
            ++needed;
            width = inDom ? targetContext.getDomProp('width') : targetContext.getProp('width');
            gotWidth = (typeof width == 'number');
            if (gotWidth) {
                ++got;
                width -= frameInfo.width + padding.width;
                if (overflowAdjust) {
                    width -= overflowAdjust.width;
                }
            }
        }

        if (!ownerContext.heightModel.shrinkWrap) {
            ++needed;
            height = inDom ? targetContext.getDomProp('height') : targetContext.getProp('height');
            gotHeight = (typeof height == 'number');
            if (gotHeight) {
                ++got;
                height -= frameInfo.height + padding.height;
                if (overflowAdjust) {
                    height -= overflowAdjust.height;
                }
            }
        }

        return {
            width: width,
            height: height,
            needed: needed,
            got: got,
            gotAll: got == needed,
            gotWidth: gotWidth,
            gotHeight: gotHeight
        };
    },

    /**
     * returns the overflow-x style of the render target
     * @protected
     * @param {Ext.layout.ContextItem} ownerContext
     * @return {String}
     */
    getOverflowXStyle: function(ownerContext) {
        var me = this;

        return me.overflowXStyle ||
            (me.overflowXStyle = me.owner.scrollFlags.overflowX || ownerContext.targetContext.getStyle('overflow-x'));
    },

    /**
     * returns the overflow-y style of the render target
     * @protected
     * @param {Ext.layout.ContextItem} ownerContext
     * @return {String}
     */
    getOverflowYStyle: function(ownerContext) {
        var me = this;

        return me.overflowYStyle || 
            (me.overflowYStyle = me.owner.scrollFlags.overflowY || ownerContext.targetContext.getStyle('overflow-y'));
    },

    /**
     * Returns flags indicating cross-browser handling of scrollHeight/Width. In particular,
     * IE has issues with padding-bottom in a scrolling element (it does not include that
     * padding in the scrollHeight). Also, margin-bottom on a child in a scrolling element
     * can be lost.
     * 
     * All browsers seem to ignore margin-right on children and padding-right on the parent
     * element (the one with the overflow)
     * 
     * This method returns a number with the follow bit positions set based on things not
     * accounted for in scrollHeight and scrollWidth:
     *
     *  - 1: Scrolling element's padding-bottom is not included in scrollHeight.
     *  - 2: Last child's margin-bottom is not included in scrollHeight.
     *  - 4: Scrolling element's padding-right is not included in scrollWidth.
     *  - 8: Child's margin-right is not included in scrollWidth.
     *
     * To work around the margin-bottom issue, it is sufficient to create a 0px tall last
     * child that will "hide" the margin. This can also be handled by wrapping the children
     * in an element, again "hiding" the margin. Wrapping the elements is about the only
     * way to preserve their right margins. This is the strategy used by Column layout.
     *
     * To work around the padding-bottom problem, since it is comes from a style on the
     * parent element, about the only simple fix is to create a last child with height
     * equal to padding-bottom. To preserve the right padding, the sizing element needs to
     * have a width that includes the right padding.
     */
    getScrollRangeFlags: (function () {
        var flags = -1;

        return function () {
            if (flags < 0) {
                var div = Ext.getBody().createChild({
                        //cls: 'x-border-box x-hide-offsets',
                        cls: Ext.baseCSSPrefix + 'border-box',
                        style: {
                            width: '100px', height: '100px', padding: '10px',
                            overflow: 'auto'
                        },
                        children: [{
                            style: {
                                border: '1px solid red',
                                width: '150px', height: '150px',
                                margin: '0 5px 5px 0' // TRBL
                            }
                        }]
                    }),
                    scrollHeight = div.dom.scrollHeight,
                    scrollWidth = div.dom.scrollWidth,
                    heightFlags = {
                        // right answer, nothing missing:
                        175: 0,
                        // missing parent padding-bottom:
                        165: 1,
                        // missing child margin-bottom:
                        170: 2,
                        // missing both
                        160: 3
                    },
                    widthFlags = {
                        // right answer, nothing missing:
                        175: 0,
                        // missing parent padding-right:
                        165: 4,
                        // missing child margin-right:
                        170: 8,
                        // missing both
                        160: 12
                    };

                flags = (heightFlags[scrollHeight] || 0) | (widthFlags[scrollWidth] || 0);
                //Ext.log('flags=',flags.toString(2));
                div.remove();
            }

            return flags;
        };
    }()),

    initLayout: function() {
        var me = this,
            scrollbarWidth = Ext.getScrollbarSize().width;

        me.callParent();

        // Create a default lastOverflowAdjust based upon scrolling configuration.
        // If the Container is to overflow, or we *always* reserve space for a scrollbar
        // then reserve space for a vertical scrollbar
        if (scrollbarWidth && me.manageOverflow && !me.hasOwnProperty('lastOverflowAdjust')) {
            if (me.owner.scrollFlags.y || me.reserveScrollbar) {
                me.lastOverflowAdjust = {
                    width: scrollbarWidth,
                    height: 0
                };
            }
        }
    },

    setupRenderTpl: function (renderTpl) {
        this.callParent(arguments);

        renderTpl.renderPadder = this.doRenderPadder;
    }
});