/**
 * This class is the base for all layout types: component and container.
 * @protected
 */
Ext.define('Ext.layout.Layout', {
    mixins: [
        'Ext.mixin.Factoryable'
    ],
 
    requires: [
        'Ext.XTemplate',
        'Ext.layout.SizeModel'
    ],
 
    uses: [ 'Ext.layout.Context' ],
 
    factoryConfig: {
        type: 'layout',
        defaultType: 'autocontainer',
        instanceProp: 'isLayout'
    },
 
    /**
     * @property {Boolean} isLayout
     * `true` in this class to identify an object as an instantiated Layout, or subclass thereof.
     * @readonly
     */
    isLayout: true,
    initialized: false,
    running: false,
 
    /**
     * @private
     * `true` if this layout may need to incorporate the dimensions of individual child
     * items into its layout calculations.  Layouts that handle the size of their children
     * as a group (autocontainer, form) can set this to false for an additional performance
     * optimization.  When `false` the layout system will not recurse into the child
     * items if {@link Ext.layout.container.Container#activeItemCount} is `0`, which will be
     * the case if all child items use "liquid" CSS layout, e.g. form fields.
     * (See Ext.Component#liquidLayout)
     */
    needsItemSize: true,
 
    /**
     * @private
     * `true` if this layout may set the size of its child items.  Layouts that do not
     * set the size of their child items (autocontainer, form) can set this to false
     * for an additional performance optimization.  When `true` the layout system will
     * not create a context item for children that use liquid layout, because there is
     * no need for a context item if item size is neither read nor set by the owning layout.
     */
    setsItemSize: true,
 
    autoSizePolicy: {
        readsWidth: 1,
        readsHeight: 1,
        setsWidth: 0,
        setsHeight: 0
    },
 
    $configPrefixed: false,
    $configStrict: false,
 
    constructor: function(config) {
        var me = this;
 
        me.id = Ext.id(null, me.type + '-');
 
        me.initConfig(config);
 
        // prevent any "type" that was passed as the alias from overriding the "type"
        // property from the prototype
        delete me.type;
        me.layoutCount = 0;
    },
 
    /**
     * @property {Boolean} done Used only during a layout run, this value indicates that a
     * layout has finished its calculations. This flag is set to true prior to the call to
     * {@link #calculate} and should be set to false if this layout has more work to do.
     */
 
    /**
     * Called before any calculation cycles to prepare for layout.
     * 
     * This is a write phase and DOM reads should be strictly avoided when overridding
     * this method.
     * 
     * @param {Ext.layout.ContextItem} ownerContext The context item for the layout's owner
     * component.
     * @method beginLayout
     */
    beginLayout: Ext.emptyFn,
 
    /**
     * Called before any calculation cycles to reset DOM values and prepare for calculation.
     * 
     * This is a write phase and DOM reads should be strictly avoided when overridding
     * this method.
     * 
     * @param {Ext.layout.ContextItem} ownerContext The context item for the layout's owner
     * component.
     * @method beginLayoutCycle
     */
    beginLayoutCycle: function(ownerContext) {
        var me = this,
            context = me.context,
            changed;
 
        if (me.lastWidthModel !== ownerContext.widthModel) {
            if (me.lastWidthModel) {
                changed = true;
            }
 
            me.lastWidthModel = ownerContext.widthModel;
        }
 
        if (me.lastHeightModel !== ownerContext.heightModel) {
            if (me.lastWidthModel) {
                changed = true;
            }
 
            me.lastHeightModel = ownerContext.heightModel;
        }
 
        if (changed) {
            (context = ownerContext.context).clearTriggers(me, false);
            context.clearTriggers(me, true);
            me.triggerCount = 0;
        }
    },
 
    /**
     * Called to perform the calculations for this layout. This method will be called at
     * least once and may be called repeatedly if the {@link #done} property is cleared
     * before return to indicate that this layout is not yet done. The {@link #done} property
     * is always set to `true` before entering this method.
     * 
     * This is a read phase and DOM writes should be strictly avoided in derived classes.
     * Instead, DOM writes need to be written to {@link Ext.layout.ContextItem} objects to
     *  be flushed at the next opportunity.
     * 
     * @param {Ext.layout.ContextItem} ownerContext The context item for the layout's owner
     * component.
     * @method calculate
     * @abstract
     */
 
    /**
     * This method (if implemented) is called at the end of the cycle in which this layout
     * completes (by not setting {@link #done} to `false` in {@link #calculate}). It is
     * possible for the layout to complete and yet become invalid before the end of the cycle,
     * in which case, this method will not be called. It is also possible for this method to
     * be called and then later the layout becomes invalidated. This will result in
     * {@link #calculate} being called again, followed by another call to this method.
     * 
     * This is a read phase and DOM writes should be strictly avoided in derived classes.
     * Instead, DOM writes need to be written to {@link Ext.layout.ContextItem} objects to
     * be flushed at the next opportunity.
     * 
     * This method need not be implemented by derived classes and, in fact, should only be
     * implemented when needed.
     * 
     * @param {Ext.layout.ContextItem} ownerContext The context item for the layout's owner
     * component.
     * @method completeLayout
     */
 
    /**
     * This method (if implemented) is called after all layouts have completed. In most
     * ways this is similar to {@link #completeLayout}. This call can cause this (or any
     * layout) to be become invalid (see {@link Ext.layout.Context#invalidate}), but this
     * is best avoided. This method is intended to be where final reads are made and so it
     * is best to avoid invalidating layouts at this point whenever possible. Even so, this
     * method can be used to perform final checks that may require all other layouts to be
     * complete and then invalidate some results.
     * 
     * This is a read phase and DOM writes should be strictly avoided in derived classes.
     * Instead, DOM writes need to be written to {@link Ext.layout.ContextItem} objects to
     * be flushed at the next opportunity.
     * 
     * This method need not be implemented by derived classes and, in fact, should only be
     * implemented when needed.
     * 
     * @param {Ext.layout.ContextItem} ownerContext The context item for the layout's owner
     * component.
     * @method finalizeLayout
     */
 
    /**
     * This method is called after all layouts are complete and their calculations flushed
     * to the DOM. No further layouts will be run and this method is only called once per
     * layout run. The base component layout caches `lastComponentSize`.
     * 
     * This is a write phase and DOM reads should be avoided if possible when overridding
     * this method.
     * 
     * This method need not be implemented by derived classes and, in fact, should only be
     * implemented when needed.
     * 
     * @param {Ext.layout.ContextItem} ownerContext The context item for the layout's owner
     * component.
     */
    finishedLayout: function(ownerContext) {
        this.lastWidthModel = ownerContext.widthModel;
        this.lastHeightModel = ownerContext.heightModel;
        this.ownerContext = null;
    },
 
    /**
     * This method (if implemented) is called after all layouts are finished, and all have
     * a `lastComponentSize` cached. No further layouts will be run and this method is only
     * called once per layout run. It is the bookend to {@link #beginLayout}.
     * 
     * This is a write phase and DOM reads should be avoided if possible when overridding
     * this method. This is the catch-all tail method to a layout and so the rules are more
     * relaxed. Even so, for performance reasons, it is best to avoid reading the DOM. If
     * a read is necessary, consider implementing a {@link #finalizeLayout} method to do the
     * required reads.
     * 
     * This method need not be implemented by derived classes and, in fact, should only be
     * implemented when needed.
     * 
     * @param {Ext.layout.ContextItem} ownerContext The context item for the layout's owner
     * component.
     * @method notifyOwner
     */
 
    redoLayout: Ext.emptyFn,
    undoLayout: Ext.emptyFn,
 
    /**
     * @cfg {Object} animatePolicy
     * An object that contains as keys the names of the properties that can be animated
     * by child items as a consequence of a layout. This config is used internally by the
     * {@link Ext.layout.container.Accordion accordion} layout to cause the child panels
     * to animate to their proper size and position after a collapse/expand event.
     * @protected
     * @since 4.1.0
     */
 
    getAnimatePolicy: function() {
        return this.animatePolicy;
    },
 
    /**
     * Returns an object describing how this layout manages the size of the given component.
     * This method must be implemented by any layout that manages components.
     *
     * @param {Ext.Component} item 
     * @return {Ext.layout.SizePolicy} An object describing the sizing done by the layout
     * for this item.
     * @protected
     */
    getItemSizePolicy: function(item) {
        return this.autoSizePolicy;
    },
 
    isItemBoxParent: function(itemContext) {
        return false;
    },
 
    isItemLayoutRoot: function(item) {
        var sizeModel = item.getSizeModel(),
            width = sizeModel.width,
            height = sizeModel.height;
 
        // If this component has never had a layout and some of its dimensions are set by
        // its ownerLayout, we cannot be the layoutRoot...
        if (!item.componentLayout.lastComponentSize && (width.calculated || height.calculated)) {
            return false;
        }
 
        // otherwise an ownerCt whose size is not effected by its content is a root
        return !width.shrinkWrap && !height.shrinkWrap;
    },
 
    isItemShrinkWrap: function(item) {
        return item.shrinkWrap;
    },
 
    isRunning: function() {
        return !!this.ownerContext;
    },
 
    //-----------------------------------------------------
    /*
     * Clears any styles which must be cleared before layout can take place.
     * Only DOM WRITES must be performed at this stage.
     *
     * An entry for the owner's element ID must be created in the layoutContext containing
     * a reference to the target which must be sized/positioned/styled by the layout at
     * the flush stage:
     *
     *     {
     *         target: me.owner
     *     }
     *
     * Component layouts should iterate through managed Elements,
     * pushing an entry for each element:
     *
     *     {
     *         target: childElement
     *     }
     */
    //-----------------------------------------------------
 
    getItemsRenderTree: function(items, renderCfgs) {
        var length = items.length,
            i, item, itemConfig, result;
 
        if (length) {
            result = [];
 
            for (= 0; i < length; ++i) {
                item = items[i];
 
                // If we are being asked to move an already rendered Component, we must not
                // recalculate its renderTree and rerun its render process.
                // The Layout's isValidParent check will ensure that the DOM is moved into place.
                if (!item.rendered) {
 
                    // If we've already calculated the item's element config, don't calculate it
                    // again. This may happen if the rendering process mutates the owning
                    // Container's items collection, and Ext.layout.Container#getRenderTree runs
                    // through the collection again.
                    // Note that the config may be null if a beforerender listener vetoed
                    // the operation, so we must compare to undefined.
                    if (renderCfgs && (renderCfgs[item.id] !== undefined)) {
                        itemConfig = renderCfgs[item.id];
                    }
                    else {
                        // Perform layout preprocessing in the bulk render path
                        this.configureItem(item);
                        itemConfig = item.getRenderTree();
 
                        if (renderCfgs) {
                            renderCfgs[item.id] = itemConfig;
                        }
                    }
 
                    // itemConfig mey be null if a beforerender listener vetoed the operation.
                    if (itemConfig) {
                        result.push(itemConfig);
                    }
                }
            }
        }
 
        return result;
    },
 
    finishRender: Ext.emptyFn,
 
    finishRenderItems: function(target, items) {
        var length = items.length,
            i, item;
 
        for (= 0; i < length; i++) {
            item = items[i];
 
            // Only postprocess items which are being rendered. deferredRender may mean
            // that only one has been rendered.
            if (item.rendering) {
 
                // Tell the item at which index in the Container it is
                item.finishRender(i);
            }
        }
    },
 
    renderChildren: function() {
        var me = this,
            items = me.getLayoutItems(),
            target = me.getRenderTarget();
 
        me.renderItems(items, target);
    },
 
    /**
     * Iterates over all passed items, ensuring they are rendered.  If the items
     * are already rendered, also determines if the items are in the proper place in the dom.
     * @protected
     */
    renderItems: function(items, target) {
        var me = this,
            ln = items.length,
            i = 0,
            pos = 0,
            item;
 
        if (ln) {
            Ext.suspendLayouts();
 
            for (; i < ln; i++, pos++) {
                item = items[i];
 
                if (item && !item.rendered) {
                    me.renderItem(item, target, pos);
                }
                else if (item.ignoreDomPosition) {
                    --pos;
                }
                else if (!me.isValidParent(item, target, pos)) {
                    me.moveItem(item, target, pos);
                }
                else {
                    // still need to configure the item, it may have moved in the container.
                    me.configureItem(item);
                }
            }
 
            Ext.resumeLayouts(true);
        }
    },
 
    /**
     * Validates item is in the proper place in the dom.
     * @protected
     */
    isValidParent: function(item, target, position) {
        var targetDom = (target && target.dom) || target,
            itemDom = this.getItemLayoutEl(item);
 
        // Test DOM nodes for equality using "===" : http://jsperf.com/dom-equality-test
        if (itemDom && targetDom) {
            if (typeof position === 'number') {
                position = this.getPositionOffset(position);
 
                return itemDom === targetDom.childNodes[position];
            }
 
            return itemDom.parentNode === targetDom;
        }
 
        return false;
    },
 
    /**
     * For a given item, returns the element that participates in the childNodes array
     * of the layout's target element.  This is usually the component's "el", but can
     * also be a wrapper
     * @private
     * @param {Ext.Component} item 
     * @return {HTMLElement} 
     */
    getItemLayoutEl: function(item) {
        var dom = item.el ? item.el.dom : Ext.getDom(item),
            parentNode = dom.parentNode,
            className;
 
        if (parentNode) {
            className = parentNode.className;
 
            if (className && className.indexOf(Ext.baseCSSPrefix + 'resizable-wrap') !== -1) {
                dom = dom.parentNode;
            }
        }
 
        return dom;
    },
 
    getPositionOffset: function(position) {
        return position;
    },
 
    /**
     * Called before an item is rendered to allow the layout to configure the item.
     * @param {Ext.Component} item The item to be configured
     * @protected
     */
    configureItem: function(item) {
        item.ownerLayout = this;
    },
 
    /**
     * Renders the given Component into the target Element.
     * @param {Ext.Component} item The Component to render
     * @param {Ext.dom.Element} target The target Element
     * @param {Number} position The position within the target to render the item to
     * @private
     */
    renderItem: function(item, target, position) {
        var me = this;
 
        if (!item.rendered) {
            me.configureItem(item);
 
            item.render(target, position);
        }
    },
 
    /**
     * Moves Component to the provided target instead.
     * @private
     */
    moveItem: function(item, target, position) {
        var activeEl = Ext.fly(document.activeElement);
 
        target = target.dom || target;
 
        if (typeof position === 'number') {
            position = target.childNodes[position];
        }
 
        // If the element we are about to move contains focus, ensure we don't
        // disturb application state when it blurs on remove.
        // 
        // That's if it blurs on remove! Some browsers don't, which leaves
        // focus "stranded" with framework and app state set, and rendition
        // set on an element while the element is in fact not focused.
        // By actively restoring focus afterwards we avoid an inconsistent state.
        // Specifically: https://sencha.jira.com/browse/EXTJS-20609
        if (item.el.contains(activeEl)) {
            activeEl.suspendFocusEvents();
        }
        else {
            activeEl = null;
        }
 
        target.insertBefore(item.el.dom, position || null);
        item.container = Ext.get(target);
        this.configureItem(item);
 
        // If we moved the element that contained focus, silently restore it.
        if (activeEl) {
            activeEl.focus();
            activeEl.resumeFocusEvents();
        }
    },
 
    /**
     * This method is called when a child item changes in some way. By default this calls
     * {@link Ext.Component#method!updateLayout} on this layout's owner.
     * 
     * @param {Ext.Component} child The child item that has changed.
     * @return {Boolean} True if this layout has handled the content change.
     */
    onContentChange: function() {
        this.owner.updateLayout();
 
        return true;
    },
 
    /**
     * A one-time initialization method called just before rendering.
     * @protected
     */
    initLayout: function() {
        this.initialized = true;
    },
 
    /**
     * @private
     * Sets the layout owner
     */
    setOwner: function(owner) {
        this.owner = owner;
    },
 
    /**
     * Returns the set of items to layout (empty by default).
     * @protected
     */
    getLayoutItems: function() {
        return [];
    },
 
    onAdd: function(item) {
        item.ownerLayout = this;
    },
 
    onRemove: Ext.emptyFn,
    onDestroy: Ext.emptyFn,
 
    /**
     * Removes layout's itemCls and owning Container's itemCls.
     * Clears the managed dimensions flags
     * @protected
     */
    afterRemove: function(item) {
        var me = this,
            el = item.el,
            owner = me.owner,
            removeClasses;
 
        if (item.rendered) {
            removeClasses = [].concat(me.itemCls || []);
 
            if (owner.itemCls) {
                removeClasses = Ext.Array.push(removeClasses, owner.itemCls);
            }
 
            if (removeClasses.length) {
                el.removeCls(removeClasses);
            }
        }
 
        delete item.ownerLayout;
    },
 
    /**
     * @private
     * Called by an owning Panel after the Panel finishes its collapse process.
     */
    afterCollapse: function(owner, animated) {
        if (animated) {
            this.onContentChange(owner);
        }
    },
 
    /**
     * @private
     * Called by an owning Panel after the Panel finishes its expand process.
     */
    afterExpand: function(owner, animated) {
        if (animated) {
            this.onContentChange(owner);
        }
    },
 
    /**
     * Destroys this layout. This method removes a `targetCls` from the `target`
     * element and calls `onDestroy`.
     * 
     * A derived class can override either this method or `onDestroy` but in all
     * cases must call the base class versions of these methods to allow the base class to
     * perform its cleanup.
     * 
     * This method (or `onDestroy`) are overridden by subclasses most often to purge
     * event handlers or remove unmanged DOM nodes.
     *
     * @protected
     */
    destroy: function() {
        var me = this,
            target;
 
        if (me.targetCls) {
            target = me.getTarget();
 
            if (target) {
                target.removeCls(me.targetCls);
            }
        }
 
        if (!me.onDestroy.$emptyFn) {
            me.onDestroy();
        }
 
        me.callParent();
    },
 
    sortWeightedItems: function(items, reverseProp) {
        var i, length;
 
        for (= 0, length = items.length; i < length; ++i) {
            items[i].$i = i;
        }
 
        Ext.Array.sort(items, function(item1, item2) {
            var ret = item2.weight - item1.weight;
 
            if (!ret) {
                ret = item1.$i - item2.$i;
 
                if (item1[reverseProp]) {
                    ret = -ret;
                }
            }
 
            return ret;
        });
 
        for (= 0; i < length; ++i) {
            delete items[i].$i;
        }
    }
}, function() {
    var Layout = this;
 
    Layout.prototype.sizeModels = Layout.sizeModels = Ext.layout.SizeModel.sizeModels;
});