/**
 * This class is intended to be extended or created via the
 * {@link Ext.container.Container#layout layout} configuration property.
 * See {@link Ext.container.Container#layout} for additional details.
 */
Ext.define('Ext.layout.container.Container', {
    extend: 'Ext.layout.Layout',
    alternateClassName: 'Ext.layout.ContainerLayout',
    alias: 'layout.container',
 
    mixins: [
        'Ext.util.ElementContainer'
    ],
 
    requires: [
        'Ext.XTemplate'
    ],
 
    type: 'container',
 
    /**
     * @cfg {String} itemCls
     * An optional extra CSS class that will be added to the container. This can be useful for
     * adding customized styles to the container or any of its children using standard CSS
     * rules. See {@link Ext.Component}.{@link Ext.Component#componentCls componentCls} also.
     */
 
    /**
     * @private
     * Called by an owning Panel before the Panel begins its collapse process.
     * Most layouts will not need to override the default Ext.emptyFn implementation.
     */
    beginCollapse: Ext.emptyFn,
 
    /**
     * @private
     * Called by an owning Panel before the Panel begins its expand process.
     * Most layouts will not need to override the default Ext.emptyFn implementation.
     */
    beginExpand: Ext.emptyFn,
 
    /**
     * An object which contains boolean properties specifying which properties are to be
     * animated upon flush of child Component ContextItems. For example, Accordion would
     * have:
     *
     *      {
     *          y: true,
     *          height: true
     *      }
     *
     * @private
     */
    animatePolicy: null,
 
    /**
     * @private
     * tracks the number of child items that do not use "liquid" CSS layout
     */
    activeItemCount: 0,
 
    renderTpl: [
        '{%this.renderBody(out,values)%}'
    ],
 
    usesContainerHeight: true,
    usesContainerWidth: true,
    usesHeight: true,
    usesWidth: true,
 
    constructor: function() {
        this.callParent(arguments);
        this.mixins.elementCt.constructor.call(this);
    },
 
    destroy: function() {
        this.mixins.elementCt.destroy.call(this);
        this.callParent();
    },
 
    /**
     * In addition to work done by our base classes, containers benefit from some extra
     * cached data. The following properties are added to the ownerContext:
     *
     *  - visibleItems: the result of {@link #getVisibleItems}
     *  - childItems: the ContextItem[] for each visible item
     *  - targetContext: the ContextItem for the {@link #getTarget} element
     */
    beginLayout: function(ownerContext) {
        this.callParent(arguments);
 
        ownerContext.targetContext = ownerContext.paddingContext =
            ownerContext.getEl('getTarget', this);
 
        this.cacheChildItems(ownerContext);
    },
 
    beginLayoutCycle: function(ownerContext, firstCycle) {
        var me = this;
 
        me.callParent(arguments);
 
        if (firstCycle) {
            if (me.usesContainerHeight) {
                ++ownerContext.consumersContainerHeight;
            }
 
            if (me.usesContainerWidth) {
                ++ownerContext.consumersContainerWidth;
            }
        }
    },
 
    cacheChildItems: function(ownerContext) {
        var me = this,
            context, childItems, items, length, i;
 
        // if we neither read nor set the size of our items, we can skip creation of
        // the childItems array
        if (me.needsItemSize || me.setsItemSize) {
            context = ownerContext.context;
            childItems = ownerContext.childItems = [];
            items = ownerContext.visibleItems = me.getVisibleItems();
            length = items.length;
 
            for (= 0; i < length; ++i) {
                childItems.push(context.getCmp(items[i]));
            }
        }
    },
 
    cacheElements: function() {
        var owner = this.owner;
 
        this.attachChildEls(owner.el, owner); // from ElementContainer mixin
    },
 
    calculate: function(ownerContext) {
        var props = ownerContext.props,
            el = ownerContext.el;
 
        if (ownerContext.widthModel.shrinkWrap && isNaN(props.width)) {
            ownerContext.setContentWidth(el.getWidth());
        }
 
        if (ownerContext.heightModel.shrinkWrap && isNaN(props.height)) {
            ownerContext.setContentHeight(el.getHeight());
        }
    },
 
    /**
     * Adds layout's itemCls and owning Container's itemCls
     * @protected
     */
    configureItem: function(item) {
        var me = this,
            itemCls = me.itemCls,
            ownerItemCls = me.owner.itemCls,
            needsCopy,
            addClasses;
 
        // Effectively callParent but without the function overhead
        item.ownerLayout = me;
 
        if (itemCls) {
            // itemCls can be a single class or an array
            if (typeof itemCls === 'string') {
                addClasses = [itemCls];
            }
            else {
                addClasses = itemCls;
                needsCopy = !!addClasses;
            }
        }
 
        if (ownerItemCls) {
            // Add some extra logic so we don't clone the array unnecessarily
            if (needsCopy) {
                addClasses = Ext.Array.clone(addClasses);
            }
 
            addClasses = Ext.Array.push(addClasses || [], ownerItemCls);
        }
 
        if (addClasses) {
            item.addCls(addClasses);
        }
    },
 
    doRenderBody: 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!
 
        this.renderItems(out, renderData);
        this.renderContent(out, renderData);
    },
 
    doRenderContainer: 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.$comp.layout,
            tpl = me.getRenderTpl(),
            data = me.getRenderData();
 
        tpl.applyOut(data, out);
    },
 
    doRenderItems: 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,
            tree = me.getRenderTree();
 
        if (tree) {
            Ext.DomHelper.generateMarkup(tree, out);
        }
    },
    
    doRenderTabGuard: function(out, renderData, position) {
        // 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 cmp = renderData.$comp,
            tabGuardTpl;
        
        // Due to framing, we will be called in two different ways: in the frameTpl or in
        // the renderTpl. The frameTpl version enters via doRenderFramingTabGuard which
        // sets "$skipTabGuards" on the renderTpl's renderData.
        //
        if (cmp.tabGuard && !renderData.$skipTabGuards) {
            tabGuardTpl = cmp.lookupTpl('tabGuardTpl');
            
            if (tabGuardTpl) {
                renderData.tabGuard = position;
                renderData.tabGuardEl = cmp.tabGuardElements[position];
                
                cmp.addChildEl(renderData.tabGuardEl);
                tabGuardTpl.applyOut(renderData, out);
                
                delete renderData.tabGuard;
                delete renderData.tabGuardEl;
            }
        }
    },
 
    finishRender: function() {
        var me = this,
            target, items;
 
        me.callParent();
 
        me.cacheElements();
 
        target = me.getRenderTarget();
        items = me.getLayoutItems();
 
        me.finishRenderItems(target, items);
    },
 
    /**
     * @private
     * Called for every layout in the layout context after all the layouts have been finally flushed
     */
    notifyOwner: function() {
        //<debug>
        if (!this._hasTargetWarning && this.targetCls && !this.getTarget().hasCls(this.targetCls)) {
            this._hasTargetWarning = true;
            Ext.log.warn('targetCls is missing. This may mean that getTargetEl() ' +
                         'is being overridden but not applyTargetCls(). ' + this.owner.id);
        }
        //</debug>
        
        this.owner.afterLayout(this);
    },
 
    /**
     * 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.
     *
     * @param {Ext.layout.ContextItem} ownerContext The owner's context item.
     * @param {Boolean} [inDom=false] True if the container size must be in the DOM.
     * @return {Object} The size
     * @return {Number} return.width The width
     * @return {Number} return.height The height
     * @protected
     */
    getContainerSize: function(ownerContext, inDom) {
        // 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(),
            padding = ownerContext.paddingContext.getPaddingInfo(),
            got = 0,
            needed = 0,
            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 (width < 0) {
                    width = 0;
                }
            }
        }
 
        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 (height < 0) {
                    height = 0;
                }
            }
        }
 
        return {
            width: width,
            height: height,
            needed: needed,
            got: got,
            gotAll: got === needed,
            gotWidth: gotWidth,
            gotHeight: gotHeight
        };
    },
 
    // This method is used to offset the DOM position when checking
    // whether the element is a certain child of the target. This is
    // required in cases where the extra elements prepended to the target
    // before any of the items. An example of this is when using labelAlign: 'top'
    // on a field. The label appears first in the DOM before any child items are
    // created, so when we check the position we need to add an extra offset.
    // Containers that create an innerCt are exempt because this new element
    // preserves the order
    getPositionOffset: function(position) {
        var offset;
        
        if (!this.createsInnerCt) {
            offset = this.owner.itemNodeOffset;
 
            if (offset) {
                position += offset;
            }
        }
 
        return position;
    },
 
    /**
     * Returns an array of child components either for a render phase (Performed in the beforeLayout
     * method of the layout's base class), or the layout phase (onLayout).
     * @return {Ext.Component[]} of child components
     */
    getLayoutItems: function() {
        var owner = this.owner,
            items = owner && owner.items;
 
        return (items && items.items) || [];
    },
 
    getRenderData: function() {
        var comp = this.owner;
 
        return {
            $comp: comp,
            $layout: this,
            ownerId: comp.id
        };
    },
 
    /**
     * @protected
     * Returns all items that are rendered
     * @return {Array} All matching items
     */
    getRenderedItems: function() {
        var me = this,
            target = me.getRenderTarget(),
            items = me.getLayoutItems(),
            ln = items.length,
            renderedItems = [],
            i, pos, item;
 
        for (= 0, pos = 0; i < ln; i++, pos++) {
            item = items[i];
 
            if (item.rendered) {
                if (item.ignoreDomPosition) {
                    --pos;
                }
                else if (!this.isValidParent(item, target, pos)) {
                    continue;
                }
 
                renderedItems.push(item);
            }
        }
 
        return renderedItems;
    },
 
    /**
     * Returns the element into which rendering must take place. Defaults to the owner Container's
     * target element.
     *
     * May be overridden in layout managers which implement an inner element.
     *
     * @return {Ext.dom.Element} 
     */
    getRenderTarget: function() {
        return this.owner.getTargetEl();
    },
 
    /**
     * Returns the element into which extra functional DOM elements can be inserted.
     * Defaults to the owner Component's encapsulating element.
     *
     * May be overridden in Component layout managers which implement a
     * {@link #getRenderTarget component render target} which must only contain child components.
     * @return {Ext.dom.Element} 
     */
    getElementTarget: function() {
        return this.getRenderTarget();
    },
 
    getRenderTpl: function() {
        var me = this,
            renderTpl = Ext.XTemplate.getTpl(this, 'renderTpl');
 
        // Make sure all standard callout methods for the owner component are placed on the
        // XTemplate instance (but only once please):
        if (!renderTpl.renderContent) {
            me.owner.setupRenderTpl(renderTpl);
        }
 
        return renderTpl;
    },
 
    getRenderTree: function() {
        var result,
            items = this.owner.items,
            itemsGen,
            renderCfgs = {};
 
        do {
            itemsGen = items.generation;
            result = this.getItemsRenderTree(this.getLayoutItems(), renderCfgs);
        } while (items.generation !== itemsGen);
 
        return result;
    },
 
    renderChildren: function() {
        var me = this,
            ownerItems = me.owner.items,
            target = me.getRenderTarget(),
            itemsGen, items;
 
        // During the render phase, new items may be added. Specifically, a panel will
        // create a placeholder component during render if required, so we need to catch
        // it here so we can render it.
        do {
            itemsGen = ownerItems.generation;
            items = me.getLayoutItems();
            me.renderItems(items, target);
        } while (ownerItems.generation !== itemsGen);
    },
 
    getScrollbarsNeeded: function(width, height, contentWidth, contentHeight) {
        var scrollbarSize = Ext.getScrollbarSize(),
            hasWidth = typeof width === 'number',
            hasHeight = typeof height === 'number',
            needHorz = 0,
            needVert = 0;
 
        // No space-consuming scrollbars.
        if (!scrollbarSize.width) {
            return 0;
        }
 
        if (hasHeight && height < contentHeight) {
            needVert = 2;
            width -= scrollbarSize.width;
        }
 
        if (hasWidth && width < contentWidth) {
            needHorz = 1;
 
            if (!needVert && hasHeight) {
                height -= scrollbarSize.height;
 
                if (height < contentHeight) {
                    needVert = 2;
                }
            }
        }
 
        return needVert + needHorz;
    },
 
    /**
     * Returns the owner component's resize element.
     * @return {Ext.dom.Element} 
     */
    getTarget: function() {
        return this.owner.getTargetEl();
    },
 
    /**
     * @protected
     * Returns all items that are both rendered and visible
     * @return {Array} All matching items
     */
    getVisibleItems: function() {
        var target = this.getRenderTarget(),
            items = this.getLayoutItems(),
            ln = items.length,
            visibleItems = [],
            i, pos, item;
 
        for (= 0, pos = 0; i < ln; i++, pos++) {
            item = items[i];
 
            if (item.rendered && item.hidden !== true && !item.floated) {
                if (item.ignoreDomPosition) {
                    --pos;
                }
                else if (!this.isValidParent(item, target, pos)) {
                    continue;
                }
 
                visibleItems.push(item);
            }
        }
 
        return visibleItems;
    },
 
    getMoveAfterIndex: function(after) {
        return this.owner.items.indexOf(after) + 1;
    },
 
    moveItemBefore: function(item, before) {
        var owner = this.owner,
            items = owner.items,
            index = items.indexOf(item),
            toIndex;
 
        if (item === before) {
            return item;
        }
 
        if (before) {
            toIndex = items.indexOf(before);
 
            if (index > -1 && index < toIndex) {
                --toIndex;
            }
        }
        else {
            toIndex = items.length;
        }
 
        return owner.insert(toIndex, item);
    },
 
    setupRenderTpl: function(renderTpl) {
        renderTpl.renderBody = this.doRenderBody;
        renderTpl.renderContainer = this.doRenderContainer;
        renderTpl.renderItems = this.doRenderItems;
        renderTpl.renderTabGuard = this.doRenderTabGuard;
    },
 
    getContentTarget: function() {
        return this.owner.getDefaultContentTarget();
    },
 
    onAdd: function(item) {
        if (!item.liquidLayout) {
            ++this.activeItemCount;
        }
 
        this.callParent([item]);
    },
 
    onRemove: function(item, isDestroying) {
        if (!item.liquidLayout) {
            --this.activeItemCount;
        }
 
        this.callParent([item, isDestroying]);
    }
});