/**
 * AbstractBox is a superclass for the two box layouts:
 *
 * * {@link Ext.layout.HBox hbox}
 * * {@link Ext.layout.VBox vbox}
 *
 * FlexBox itself is never used directly, but its subclasses provide flexible arrangement of child components
 * inside a {@link Ext.Container Container}.
 *
 * ## Horizontal Box
 *
 * HBox allows you to easily lay out child components horizontally. It can size items based on a fixed width or a
 * fraction of the total width available, enabling you to achieve flexible layouts that expand or contract to fill the
 * space available.
 *
 * {@img ../guides/layouts/hbox.jpg}
 *
 * See the {@link Ext.layout.HBox HBox layout docs} for more information on using hboxes.
 *
 * ## Vertical Box
 *
 * VBox allows you to easily lay out child components verticaly. It can size items based on a fixed height or a
 * fraction of the total height available, enabling you to achieve flexible layouts that expand or contract to fill the
 * space available.
 *
 * See the {@link Ext.layout.VBox VBox layout docs} for more information on using vboxes.
 */
Ext.define('Ext.layout.FlexBox', {
    extend: 'Ext.layout.Box',
 
    alias: 'layout.box',
 
    config: {
        align: 'stretch'
    },
 
    cls: Ext.baseCSSPrefix + 'layout-box',
    baseItemCls: Ext.baseCSSPrefix + 'layout-box-item',
 
    orientMap: {
        horizontal: {
            sizeProp: 'width',
            event: 'widthchange',
            containerCls: [
                Ext.baseCSSPrefix + 'layout-hbox', 
                Ext.baseCSSPrefix + 'horizontal'
            ],
            itemCls: Ext.baseCSSPrefix + 'layout-hbox-item'
        },
        vertical: {
            sizeProp: 'height',
            event: 'heightchange',
            containerCls: [
                Ext.baseCSSPrefix + 'layout-vbox', 
                Ext.baseCSSPrefix + 'vertical'
            ],
            itemCls: Ext.baseCSSPrefix + 'layout-vbox-item'
        }
    },
 
    setContainer: function(container) {
        this.callParent([container]);
 
        this.monitorSizeFlagsChange();
    },
 
    applyOrient: function(orient) {
        //<debug>
        if (orient !== 'horizontal' && orient !== 'vertical') {
            Ext.Logger.error("Invalid box orient of: '" + orient + "', must be either 'horizontal' or 'vertical'");
        }
        //</debug>
 
        return orient;
    },
 
    updateOrient: function(orient, oldOrient) {
        var me = this,
            container = me.container,
            innerElement = container.innerElement,
            innerItems = container.innerItems,
            len = innerItems.length,
            map = me.orientMap,
            newMap = map[orient],
            oldMap = map[oldOrient],
            delegation = {
                delegate: '> component'
            }, i, itemCls, item;
 
        me.sizePropertyName = newMap.sizeProp;
 
        if (oldOrient) {
            innerElement.removeCls(oldMap.containerCls);
            container.un(oldMap.event, 'onItemSizeChange', me, delegation);
            for (= 0; i < len; ++i) {
                innerItems[i].removeCls(oldMap.itemCls);
            }
        }
 
        innerElement.addCls(newMap.containerCls);
 
        container.on(newMap.event, 'onItemSizeChange', me, delegation);
        me.itemCls = itemCls = [me.baseItemCls, newMap.itemCls];
        for (= 0; i < len; ++i) {
            item = innerItems[i];
            item.addCls(itemCls);
            me.refreshItemSizeState(item);
        }
 
        if (oldOrient) {
            me.redrawContainer();
        }
    },
 
    onItemInnerStateChange: function(item, isInner) {
        this.callParent(arguments);
 
        var flex, size;
 
        if (isInner) {
            flex = item.getFlex();
            size = item.getConfig(this.sizePropertyName);
 
            if (flex) {
                this.doItemFlexChange(item, flex);
            }
            else if (size) {
                this.doItemSizeChange(item, size);
            }
        }
 
        this.refreshItemSizeState(item);
    },
 
    refreshItemSizeState: function(item) {
        var isInner = item.isInnerItem(),
            container = this.container,
            LAYOUT_HEIGHT = container.LAYOUT_HEIGHT,
            LAYOUT_WIDTH = container.LAYOUT_WIDTH,
            dimension = this.sizePropertyName,
            layoutSizeFlags = 0,
            containerSizeFlags = container.getSizeFlags();
 
        if (isInner) {
            layoutSizeFlags |= container.LAYOUT_STRETCHED;
 
            if (this.getAlign() === 'stretch') {
                layoutSizeFlags |= containerSizeFlags & (dimension === 'width' ? LAYOUT_HEIGHT : LAYOUT_WIDTH);
            }
 
            if (item.getFlex()) {
                layoutSizeFlags |= containerSizeFlags & (dimension === 'width' ? LAYOUT_WIDTH : LAYOUT_HEIGHT);
            }
        }
 
        item.setLayoutSizeFlags(layoutSizeFlags);
    },
 
    refreshAllItemSizedStates: function() {
        var innerItems = this.container.innerItems,
            i, ln, item;
 
        for (= 0,ln = innerItems.length; i < ln; i++) {
            item = innerItems[i];
            this.refreshItemSizeState(item);
        }
    },
 
    onContainerSizeFlagsChange: function() {
        this.refreshAllItemSizedStates();
 
        this.callParent(arguments);
    },
 
    onItemSizeChange: function(item, size) {
        if (item.isInnerItem()) {
            this.doItemSizeChange(item, size);
        }
    },
 
    doItemSizeChange: function(item, size) {
        if (size) {
            item.setFlex(null);
            this.redrawContainer();
        }
    },
 
    onItemFlexChange: function(item, flex) {
        if (item.isInnerItem()) {
            this.doItemFlexChange(item, flex);
            this.refreshItemSizeState(item);
        }
    },
 
    doItemFlexChange: function(item, flex) {
        this.setItemFlex(item, flex);
 
        if (flex) {
            item.setConfig(this.sizePropertyName, null);
        }
        else {
            this.redrawContainer();
        }
    },
 
    redrawContainer: function() {
        var container = this.container,
            renderedTo = container.element.dom.parentNode;
 
        if (renderedTo && renderedTo.nodeType !== 11) {
            container.innerElement.redraw();
        }
    },
 
    /**
     * Sets the flex of an item in this box layout.
     * @param {Ext.Component} item The item of this layout which you want to update the flex of.
     * @param {Number} flex The flex to set on this method
     */
    setItemFlex: function(item, flex) {
        var element = item.element,
            style = element.dom.style;
 
        element.toggleCls(Ext.baseCSSPrefix + 'flexed', !!flex);
 
        if (Ext.isWebKit) {
            style.setProperty('-webkit-box-flex', flex ? flex : '', null);
        } else if (Ext.isIE) {
            style.setProperty('-ms-flex', (flex ? flex + ' ' + flex + ' 0px' : ''), null);
        }
 
        style.setProperty('flex', (flex ? flex + ' ' + flex + ' 0px' : ''), null);
    },
 
    convertPosition: function(position) {
        var positionMap = this.positionMap;
 
        if (positionMap.hasOwnProperty(position)) {
            return positionMap[position];
        }
 
        return position;
    },
 
    applyAlign: function(align) {
        return this.convertPosition(align);
    },
 
    updateAlign: function(align, oldAlign) {
        var container = this.container;
 
        container.innerElement.swapCls(align, oldAlign, true, 'x-align');
 
        if (oldAlign !== undefined) {
            this.refreshAllItemSizedStates();
        }
    },
 
    applyPack: function(pack) {
        return this.convertPosition(pack);
    },
 
    updatePack: function(pack, oldPack) {
        this.container.innerElement.swapCls(pack, oldPack, true, 'x-pack');
    }
});