/**
 *
 */
Ext.define('Ext.layout.Auto', {
    alias: ['layout.default', 'layout.auto'],
    alternateClassName: 'Ext.layout.Default',
 
    requires: [
        // Require container here so any component styling is included before
        // any layout styling so layouts take precedence
        'Ext.container.Container',
        'Ext.util.Wrapper',
        'Ext.layout.wrapper.BoxDock',
        'Ext.layout.wrapper.Inner'
    ],
 
    mixins: [
        'Ext.mixin.Observable',
        'Ext.mixin.Factoryable'
    ],
 
    factoryConfig: {
        type: 'layout',
        defaultType: 'auto',
        instanceProp: 'isLayout'
    },
 
    isLayout: true,
 
    config: {
        /**
         * @cfg {Ext.layout.card.fx.Abstract} animation Layout animation configuration
         * Controls how layout transitions are animated.  Currently only available for
         * Card Layouts.
         *
         * Possible values are:
         *
         * - cover
         * - cube
         * - fade
         * - flip
         * - pop
         * - reveal
         * - scroll
         * - slide
         * @accessor
         */
        animation: null,
 
        /**
         * @cfg {Ext.Container} container The container that this layout manages
         */
        container: null
    },
 
    centerCls: Ext.baseCSSPrefix + 'center',
 
    cls: Ext.baseCSSPrefix + 'layout-auto',
 
    itemCls: Ext.baseCSSPrefix + 'layout-auto-item',
 
    spaceRe: /\s+/,
 
    positionMap: {
        top: 'start',
        left: 'start',
        middle: 'center',
        bottom: 'end',
        right: 'end'
    },
 
    positionDirectionMap: {
        top: 'vertical',
        bottom: 'vertical',
        left: 'horizontal',
        right: 'horizontal'
    },
 
    constructor: function(config) {
        this.mixins.observable.constructor.call(this, config);
    },
 
    updateContainer: function(container, oldContainer) {
        var me = this;
 
        me.dockedItems = [];
 
        container.getRenderTarget().addCls(me.cls);
 
        if (container.initialized) {
            me.onContainerInitialized(container);
        }
        else {
            container.onInitialized('onContainerInitialized', me);
        }
    },
 
    onContainerInitialized: function(container) {
        var me = this;
 
        me.handleDockedItemBorders();
 
        container.on({
            delegate: '> component',
 
            beforecenteredchange: 'onItemCenteredChange',
            positionedchange: 'onItemPositionedChange',
            afterdockedchange: 'onAfterItemDockedChange', // see Component#updateDocked
 
            scope: me
        });
    },
 
    onItemAdd: function(item) {
        var me = this,
            container = me.getContainer(),
            floated = item.getFloated();
 
        if (item.getDocked() != null) {
            me.dockItem(item);
        }
        else if (item.isCentered()) {
            me.onItemCenteredChange(item, true);
        }
        else if (item.isPositioned()) {
            me.onItemPositionedChange(item, true);
        }
        else if (!floated) {
            me.onItemInnerStateChange(item, true);
        }
 
        if (container.rendered && !floated) {
            if (item.isInnerItem()) {
                me.renderInnerItem(item, true);
            }
            else {
                item.setRendered(true, true);
            }
        }
    },
 
    /**
     * @param {Ext.Component} item 
     * @param {Boolean} isInner 
     * @param {Boolean} [destroying] 
     */
    onItemInnerStateChange: function(item, isInner, destroying) {
        var itemCls = this.itemCls;
 
        if (isInner) {
            this.insertInnerItem(item, this.getContainer().innerIndexOf(item));
            item.addCls(itemCls);
        }
        else {
            this.removeInnerItem(item);
            item.removeCls(itemCls);
        }
    },
 
    insertInnerItem: function(item, index) {
        var itemDom = item.element.dom,
            container = this.getContainer(),
            renderTarget = container.getRenderTarget(item),
            nextSibling = null;
 
        if (index !== -1) {
            if (renderTarget === container.getRenderTarget()) {
                nextSibling = container.getInnerAt(index + 1);
                nextSibling = nextSibling ? nextSibling.element.dom : null;
            }
            else {
                nextSibling = renderTarget.dom.childNodes[index];
            }
        }
 
        renderTarget.dom.insertBefore(itemDom, nextSibling);
    },
 
    insertPositionedItem: function(item) {
        var me = this,
            renderTarget = me.getContainer().getPositionedItemTarget(item).dom;
 
        if (item.getZIndex() === null) {
            item.setZIndex((me.getContainer().indexOf(item) + 1) * 2);
        }
 
        renderTarget.insertBefore(item.element.dom, renderTarget.firstChild);
 
        return me;
    },
 
    removeInnerItem: function(item) {
        item.element.detach();
    },
 
    removePositionedItem: function(item) {
        item.setZIndex(null);
        item.element.detach();
    },
 
    onItemRemove: function(item, index, destroying) {
        var me = this;
 
        if (item.getDocked()) {
            me.undockItem(item);
        }
        else if (item.isCentered()) {
            me.onItemCenteredChange(item, false);
        }
        else if (item.isPositioned()) {
            me.onItemPositionedChange(item, false);
        }
        else if (!item.getFloated()) {
            me.onItemInnerStateChange(item, false, destroying);
        }
    },
 
    onItemMove: function(item, toIndex, fromIndex) {
        if (item.isCentered() || item.isPositioned()) {
            item.setZIndex((toIndex + 1) * 2);
        }
        else if (item.isInnerItem()) {
            this.insertInnerItem(item, this.getContainer().innerIndexOf(item));
        }
        else {
            this.undockItem(item);
            this.dockItem(item);
        }
    },
 
    onItemCenteredChange: function(item, centered) {
        var wrapperName = '$centerWrapper';
 
        if (item.getFloated()) {
            item.center();
        }
        else {
            if (centered) {
                this.insertPositionedItem(item);
                item.link(wrapperName, new Ext.util.Wrapper({
                    className: this.centerCls
                }, item.element));
            }
            else {
                item.unlink([wrapperName]);
                this.removePositionedItem(item);
            }
        }
    },
 
    onItemPositionedChange: function(item, positioned) {
        if (positioned) {
            this.insertPositionedItem(item);
        }
        else {
            this.removePositionedItem(item);
        }
    },
 
    onAfterItemDockedChange: function(item, docked, oldDocked) {
        // Prevent this from being called during initialization of child items, the
        // setting of docked on the component will occur before add to the container
        if (item.initialized) {
            if (oldDocked) {
                this.undockItem(item, oldDocked);
            }
 
            if (docked) {
                this.dockItem(item);
            }
        }
    },
 
    dockItem: function(item) {
        var me = this,
            BoxDock = Ext.layout.wrapper.BoxDock,
            dockedItems = me.dockedItems,
            ln = dockedItems.length,
            container = me.getContainer(),
            itemIndex = container.indexOf(item),
            positionDirectionMap = me.positionDirectionMap,
            direction = positionDirectionMap[item.getDocked()],
            dockInnerWrapper = me.dockInnerWrapper,
            needsInnerWrapper = !dockInnerWrapper,
            referenceDirection, i, dockedItem, index, previousItem, slice,
            referenceItem, referenceDocked, referenceWrapper, newWrapper,
            nestedWrapper, oldInnerWrapper;
 
        if (needsInnerWrapper) {
            dockInnerWrapper = new Ext.layout.wrapper.Inner({
                container: container
            });
        }
 
        if (ln === 0) {
            dockedItems.push(item);
 
            newWrapper = new BoxDock({
                container: container,
                direction: direction,
                manageBorders: container.manageBorders
            });
 
            newWrapper.getElement().replace(dockInnerWrapper.getElement(), false);
            newWrapper.setInnerWrapper(dockInnerWrapper);
            newWrapper.addItem(item);
        }
        else {
            for (= 0; i < ln; i++) {
                dockedItem = dockedItems[i];
                index = container.indexOf(dockedItem);
 
                if (index > itemIndex) {
                    referenceItem = previousItem || dockedItems[0];
                    dockedItems.splice(i, 0, item);
                    break;
                }
 
                previousItem = dockedItem;
            }
 
            if (!referenceItem) {
                referenceItem = dockedItems[ln - 1];
                dockedItems.push(item);
            }
 
            referenceDocked = referenceItem.getDocked();
            referenceWrapper = referenceItem.$dockWrapper;
            referenceDirection = positionDirectionMap[referenceDocked];
 
            if (direction === referenceDirection) {
                referenceWrapper.addItem(item);
            }
            else {
                slice = referenceWrapper.getItemsSlice(itemIndex);
 
                newWrapper = new BoxDock({
                    container: container,
                    direction: direction
                });
 
                if (slice.length > 0) {
                    if (slice.length === referenceWrapper.itemsCount) {
                        nestedWrapper = referenceWrapper;
                        newWrapper.getElement().replace(nestedWrapper.getElement(), false);
                        newWrapper.setInnerWrapper(nestedWrapper);
                    }
                    else {
                        nestedWrapper = new BoxDock({
                            container: container,
                            direction: referenceDirection
                        });
                        oldInnerWrapper = referenceWrapper.getInnerWrapper();
                        newWrapper.setInnerWrapper(nestedWrapper);
                        referenceWrapper.setInnerWrapper(newWrapper);
                        nestedWrapper.setInnerWrapper(oldInnerWrapper);
                        nestedWrapper.addItems(slice);
                    }
                }
                else {
                    oldInnerWrapper = referenceWrapper.getInnerWrapper();
                    referenceWrapper.setInnerWrapper(newWrapper);
                    newWrapper.setInnerWrapper(oldInnerWrapper);
                }
 
                newWrapper.addItem(item);
            }
        }
 
        if (newWrapper) {
            me.link('dockOuterWrapper', newWrapper);
        }
 
        if (needsInnerWrapper) {
            me.link('dockInnerWrapper', dockInnerWrapper);
        }
 
        if (container.initialized) {
            me.handleDockedItemBorders();
        }
    },
 
    getDockWrapper: function() {
        var dockedItems = this.dockedItems;
 
        if (dockedItems.length > 0) {
            return dockedItems[0].$dockWrapper;
        }
 
        return null;
    },
 
    undockItem: function(item, oldDocked) {
        var me = this,
            dockedItems = me.dockedItems,
            lastBorderMask, lastBorderCollapse,
            dockWrapper = item.$dockWrapper;
 
        if (dockWrapper) {
            dockWrapper.removeItem(item, oldDocked);
        }
 
        if (me.getContainer().initialized) {
            lastBorderMask = item.lastBorderMask;
            lastBorderCollapse = item.lastBorderCollapse;
 
            if (lastBorderMask) {
                item.lastBorderMask = 0;
                item.removeCls(me.noBorderClassTable[lastBorderMask]);
            }
 
            if (lastBorderCollapse) {
                item.lastBorderCollapse = 0;
                item.removeCls(me.getBorderCollapseTable()[lastBorderCollapse]);
            }
 
            me.handleDockedItemBorders();
        }
 
        Ext.Array.remove(dockedItems, item);
    },
 
    destroy: function() {
        this.dockedItems = null;
 
        Ext.destroy(this.getAnimation());
 
        this.callParent();
    },
 
    /**
     * This table contains the border removal classes indexed by the sum of the edges to
     * remove. Each edge is assigned a value:
     *
     *  * `left` = 1
     *  * `bottom` = 2
     *  * `right` = 4
     *  * `top` = 8
     *
     * @private
     */
    noBorderClassTable: [
        0,                                      // TRBL
        Ext.baseCSSPrefix + 'noborder-l',       // 0001 = 1
        Ext.baseCSSPrefix + 'noborder-b',       // 0010 = 2
        Ext.baseCSSPrefix + 'noborder-bl',      // 0011 = 3
        Ext.baseCSSPrefix + 'noborder-r',       // 0100 = 4
        Ext.baseCSSPrefix + 'noborder-rl',      // 0101 = 5
        Ext.baseCSSPrefix + 'noborder-rb',      // 0110 = 6
        Ext.baseCSSPrefix + 'noborder-rbl',     // 0111 = 7
        Ext.baseCSSPrefix + 'noborder-t',       // 1000 = 8
        Ext.baseCSSPrefix + 'noborder-tl',      // 1001 = 9
        Ext.baseCSSPrefix + 'noborder-tb',      // 1010 = 10
        Ext.baseCSSPrefix + 'noborder-tbl',     // 1011 = 11
        Ext.baseCSSPrefix + 'noborder-tr',      // 1100 = 12
        Ext.baseCSSPrefix + 'noborder-trl',     // 1101 = 13
        Ext.baseCSSPrefix + 'noborder-trb',     // 1110 = 14
        Ext.baseCSSPrefix + 'noborder-trbl'     // 1111 = 15
    ],
 
    /**
     * The numeric values assigned to each edge indexed by the `dock` config value.
     * @private
     */
    edgeMasks: {
        top: 8,
        right: 4,
        bottom: 2,
        left: 1
    },
 
    handleDockedItemBorders: function(force) {
        var me = this,
            edges = 0,
            maskT = 8,
            maskR = 4,
            maskB = 2,
            maskL = 1,
            container = me.getContainer(),
            bodyBorder = container.getBodyBorder && container.getBodyBorder(),
            containerBorder = container.getBorder(),
            collapsed = me.collapsed,
            edgeMasks = me.edgeMasks,
            noBorderCls = me.noBorderClassTable,
            dockedItemsGen = container.items.generation,
            bodyClsEl = container.boxScrollerElement || container.bodyElement,
            b, borderCls, docked, edgesTouched, i, ln, item, dock, lastValue, mask,
            addCls, removeCls, header;
 
        if ((!force && (me.initializedBorders === dockedItemsGen)) || !container.manageBorders) {
            return;
        }
 
        addCls = [];
        removeCls = [];
 
        borderCls = me.getBorderCollapseTable();
        noBorderCls = me.getBorderClassTable ? me.getBorderClassTable() : noBorderCls;
 
        me.initializedBorders = dockedItemsGen;
 
        // Borders have to be calculated using expanded docked item collection.
        me.collapsed = false;
        docked = container.getDockedItems();
        me.collapsed = collapsed;
        header = container.getHeader && container.getHeader();
 
        if (header) {
            docked = ([header]).concat(docked);
        }
 
        for (= 0, ln = docked.length; i < ln; i++) {
            item = docked[i];
 
            if (item.getHidden()) {
                continue;
            }
 
            dock = item.isPanelHeader ? item.getPosition() : item.getDocked();
            mask = edgesTouched = 0;
            addCls.length = 0;
            removeCls.length = 0;
 
            if (dock !== 'bottom') {
                if (edges & maskT) { // if (not touching the top edge)
                    b = item.border;
                }
                else {
                    b = containerBorder;
 
                    if (!== false) {
                        edgesTouched += maskT;
                    }
                }
 
                if (=== false) {
                    mask += maskT;
                }
            }
 
            if (dock !== 'left') {
                if (edges & maskR) { // if (not touching the right edge)
                    b = item.border;
                }
                else {
                    b = containerBorder;
 
                    if (!== false) {
                        edgesTouched += maskR;
                    }
                }
 
                if (=== false) {
                    mask += maskR;
                }
            }
 
            if (dock !== 'top') {
                if (edges & maskB) { // if (not touching the bottom edge)
                    b = item.border;
                }
                else {
                    b = containerBorder;
 
                    if (!== false) {
                        edgesTouched += maskB;
                    }
                }
 
                if (=== false) {
                    mask += maskB;
                }
            }
 
            if (dock !== 'right') {
                if (edges & maskL) { // if (not touching the left edge)
                    b = item.border;
                }
                else {
                    b = containerBorder;
 
                    if (!== false) {
                        edgesTouched += maskL;
                    }
                }
 
                if (=== false) {
                    mask += maskL;
                }
            }
 
            if ((lastValue = item.lastBorderMask) !== mask) {
                item.lastBorderMask = mask;
 
                if (lastValue) {
                    removeCls[0] = noBorderCls[lastValue];
                }
 
                if (mask) {
                    addCls[0] = noBorderCls[mask];
                }
            }
 
            if ((lastValue = item.lastBorderCollapse) !== edgesTouched) {
                item.lastBorderCollapse = edgesTouched;
 
                if (lastValue) {
                    removeCls.push.apply(removeCls, borderCls[lastValue]);
                }
 
                if (edgesTouched) {
                    addCls.push.apply(addCls, borderCls[edgesTouched]);
                }
            }
 
            if (removeCls.length) {
                item.removeCls(removeCls);
            }
 
            if (addCls.length) {
                item.addCls(addCls);
            }
 
            // mask can use += but edges must use |= because there can be multiple items
            // on an edge but the mask is reset per item
 
            edges |= edgeMasks[dock]; // = T, R, B or L (8, 4, 2 or 1)
        }
 
        mask = edgesTouched = 0;
        addCls.length = 0;
        removeCls.length = 0;
 
        if (edges & maskT) { // if (not touching the top edge)
            b = bodyBorder;
        }
        else {
            b = containerBorder;
 
            if (!== false) {
                edgesTouched += maskT;
            }
        }
 
        if (=== false) {
            mask += maskT;
        }
 
        if (edges & maskR) { // if (not touching the right edge)
            b = bodyBorder;
        }
        else {
            b = containerBorder;
 
            if (!== false) {
                edgesTouched += maskR;
            }
        }
 
        if (=== false) {
            mask += maskR;
        }
 
        if (edges & maskB) { // if (not touching the bottom edge)
            b = bodyBorder;
        }
        else {
            b = containerBorder;
 
            if (!== false) {
                edgesTouched += maskB;
            }
        }
 
        if (=== false) {
            mask += maskB;
        }
 
        if (edges & maskL) { // if (not touching the left edge)
            b = bodyBorder;
        }
        else {
            b = containerBorder;
 
            if (!== false) {
                edgesTouched += maskL;
            }
        }
 
        if (=== false) {
            mask += maskL;
        }
 
        if ((lastValue = me.lastBodyBorderMask) !== mask) {
            me.lastBodyBorderMask = mask;
 
            if (lastValue) {
                removeCls[0] = noBorderCls[lastValue];
            }
 
            if (mask) {
                addCls[0] = noBorderCls[mask];
            }
        }
 
        if ((lastValue = me.lastBodyBorderCollapse) !== edgesTouched) {
            me.lastBodyBorderCollapse = edgesTouched;
 
            if (lastValue) {
                removeCls.push.apply(removeCls, borderCls[lastValue]);
            }
 
            if (edgesTouched) {
                addCls.push.apply(addCls, borderCls[edgesTouched]);
            }
        }
 
        if (removeCls.length) {
            bodyClsEl.removeCls(removeCls);
        }
 
        if (addCls.length) {
            bodyClsEl.addCls(addCls);
        }
    },
 
    /**
     * This object is indexed by a component's `classCls` to yield another object which
     * is then indexed by the component's `ui` to produce an array of CSS class names.
     * This array is indexed in the same manner as the `noBorderClassTable` and indicates
     * the a particular edge of a docked item or the body element is actually "collapsed"
     * with the component's outer border.
     * @private
     */
    borderCollapseMap: {
        /*
         'x-panel': {
         'default': []
         }
         */
    },
 
    /**
     * Returns the array of class names to add to a docked item or body element when for
     * the edges that should collapse with the outer component border. Basically, the
     * panel's outer border must look visually like a contiguous border but may need to
     * be realized by using the border of docked items and/or the body. This class name
     * allows the border color and width to be controlled accordingly and distinctly from
     * the border of the docked item or body element when it is not having its border
     * collapsed.
     * @private
     */
    getBorderCollapseTable: function() {
        var me = this,
            map = me.borderCollapseMap,
            container = me.getContainer(),
            classCls = container.classCls,
            ui = container.getUi(),
            uiKey = ui || 'default',
            uiList, table, classClsList, baseCls, uiCls, i, ln, j, uiLen;
 
        map = map[classCls] || (map[classCls] = {});
        table = map[uiKey];
 
        if (!table) {
            classClsList = container.classClsList;
            map[uiKey] = table = [[], [], [], [], [], [], [], [], [], [], [], [], [], [], [], []];
 
            uiList = [0];
 
            if (ui) {
                uiList = uiList.concat(ui.split(me.spaceRe));
            }
 
            uiLen = uiList.length;
 
            for (= 0, ln = classClsList.length; i < ln; i++) {
                classCls = classClsList[i];
 
                for (= 0; j < uiLen; j++) {
                    ui = uiList[j];
                    uiCls = (ui ? ('-' + ui) : '');
                    baseCls = classCls + uiCls + '-outer-border-';
 
                    table[1].push(baseCls + 'l');       // 0001 = 1
                    table[2].push(baseCls + 'b');       // 0010 = 2
                    table[3].push(baseCls + 'bl');      // 0011 = 3
                    table[4].push(baseCls + 'r');       // 0100 = 4
                    table[5].push(baseCls + 'rl');      // 0101 = 5
                    table[6].push(baseCls + 'rb');      // 0110 = 6
                    table[7].push(baseCls + 'rbl');     // 0111 = 7
                    table[8].push(baseCls + 't');       // 1000 = 8
                    table[9].push(baseCls + 'tl');      // 1001 = 9
                    table[10].push(baseCls + 'tb');    // 1010 = 10
                    table[11].push(baseCls + 'tbl');   // 1011 = 11
                    table[12].push(baseCls + 'tr');    // 1100 = 12
                    table[13].push(baseCls + 'trl');   // 1101 = 13
                    table[14].push(baseCls + 'trb');   // 1110 = 14
                    table[15].push(baseCls + 'trbl');  // 1111 = 15
                }
            }
        }
 
        return table;
    },
 
    setConfig: function(name, value, options) {
        var config = name,
            alias = this.alias,
            type = config.type;
 
        if (name) {
            if (typeof name === 'string') {
                config = {};
                config[name] = value;
            }
            else {
                options = value;
            }
 
            if (!type || (alias && alias.indexOf('layout.' + type) > -1)) {
                this.callParent([ config, options ]);
            }
            //<debug>
            else {
                Ext.raise('Cannot change layout from ' + this.$className + ' to "' + type + '"');
            }
            //</debug>
        }
 
        return this;
    },
 
    privates: {
        renderInnerItem: function(item, asRoot) {
            item.setRendered(true, asRoot);
        }
    }
});