/*

This file is part of Ext JS 4

Copyright (c) 2011 Sencha Inc

Contact:  http://www.sencha.com/contact

GNU General Public License Usage
This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file.  Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.

If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.

*/
/**
 * @class Ext.layout.component.AbstractDock
 * @extends Ext.layout.component.Component
 * @private
 * This ComponentLayout handles docking for Panels. It takes care of panels that are
 * part of a ContainerLayout that sets this Panel's size and Panels that are part of
 * an AutoContainerLayout in which this panel get his height based of the CSS or
 * or its content.
 */

Ext.define('Ext.layout.component.AbstractDock', {

    /* Begin Definitions */

    extend: 'Ext.layout.component.Component',

    /* End Definitions */

    type: 'dock',

    /**
     * @private
     * @property autoSizing
     * @type Boolean
     * This flag is set to indicate this layout may have an autoHeight/autoWidth.
     */
    autoSizing: true,

    beforeLayout: function() {
        var returnValue = this.callParent(arguments);
        if (returnValue !== false && (!this.initializedBorders || this.childrenChanged) && (!this.owner.border || this.owner.manageBodyBorders)) {
            this.handleItemBorders();
            this.initializedBorders = true;
        }
        return returnValue;
    },
    
    handleItemBorders: function() {
        var owner = this.owner,
            body = owner.body,
            docked = this.getLayoutItems(),
            borders = {
                top: [],
                right: [],
                bottom: [],
                left: []
            },
            oldBorders = this.borders,
            opposites = {
                top: 'bottom',
                right: 'left',
                bottom: 'top',
                left: 'right'
            },
            i, ln, item, dock, side;

        for (i = 0, ln = docked.length; i < ln; i++) {
            item = docked[i];
            dock = item.dock;
            
            if (item.ignoreBorderManagement) {
                continue;
            }
            
            if (!borders[dock].satisfied) {
                borders[dock].push(item);
                borders[dock].satisfied = true;
            }
            
            if (!borders.top.satisfied && opposites[dock] !== 'top') {
                borders.top.push(item);
            }
            if (!borders.right.satisfied && opposites[dock] !== 'right') {
                borders.right.push(item);
            }            
            if (!borders.bottom.satisfied && opposites[dock] !== 'bottom') {
                borders.bottom.push(item);
            }            
            if (!borders.left.satisfied && opposites[dock] !== 'left') {
                borders.left.push(item);
            }
        }

        if (oldBorders) {
            for (side in oldBorders) {
                if (oldBorders.hasOwnProperty(side)) {
                    ln = oldBorders[side].length;
                    if (!owner.manageBodyBorders) {
                        for (i = 0; i < ln; i++) {
                            oldBorders[side][i].removeCls(Ext.baseCSSPrefix + 'docked-noborder-' + side);
                        }
                        if (!oldBorders[side].satisfied && !owner.bodyBorder) {
                            body.removeCls(Ext.baseCSSPrefix + 'docked-noborder-' + side);                   
                        }                    
                    }
                    else if (oldBorders[side].satisfied) {
                        body.setStyle('border-' + side + '-width', '');
                    }
                }
            }
        }
                
        for (side in borders) {
            if (borders.hasOwnProperty(side)) {
                ln = borders[side].length;
                if (!owner.manageBodyBorders) {
                    for (i = 0; i < ln; i++) {
                        borders[side][i].addCls(Ext.baseCSSPrefix + 'docked-noborder-' + side);
                    }
                    if ((!borders[side].satisfied && !owner.bodyBorder) || owner.bodyBorder === false) {
                        body.addCls(Ext.baseCSSPrefix + 'docked-noborder-' + side);                   
                    }                    
                }
                else if (borders[side].satisfied) {
                    body.setStyle('border-' + side + '-width', '1px');
                }
            }
        }
        
        this.borders = borders;
    },
    
    /**
     * @protected
     * @param {Ext.Component} owner The Panel that owns this DockLayout
     * @param {Ext.Element} target The target in which we are going to render the docked items
     * @param {Array} args The arguments passed to the ComponentLayout.layout method
     */
    onLayout: function(width, height) {
        if (this.onLayout_running) {
            return;
        }
        this.onLayout_running = true;
        var me = this,
            owner = me.owner,
            body = owner.body,
            layout = owner.layout,
            target = me.getTarget(),
            autoWidth = false,
            autoHeight = false,
            padding, border, frameSize;

        // We start of by resetting all the layouts info
        var info = me.info = {
            boxes: [],
            size: {
                width: width,
                height: height
            },
            bodyBox: {}
        };
        // Clear isAutoDock flag
        delete layout.isAutoDock;

        Ext.applyIf(info, me.getTargetInfo());

        // We need to bind to the ownerCt whenever we do not have a user set height or width.
        if (owner && owner.ownerCt && owner.ownerCt.layout && owner.ownerCt.layout.isLayout) {
            if (!Ext.isNumber(owner.height) || !Ext.isNumber(owner.width)) {
                owner.ownerCt.layout.bindToOwnerCtComponent = true;
            }
            else {
                owner.ownerCt.layout.bindToOwnerCtComponent = false;
            }
        }

        // Determine if we have an autoHeight or autoWidth.
        if (height == null || width == null) {
            padding = info.padding;
            border = info.border;
            frameSize = me.frameSize;

            // Auto-everything, clear out any style height/width and read from css
            if ((height == null) && (width == null)) {
                autoHeight = true;
                autoWidth = true;
                me.setTargetSize(null);
                me.setBodyBox({width: null, height: null});
            }
            // Auto-height
            else if (height == null) {
                autoHeight = true;
                // Clear any sizing that we already set in a previous layout
                me.setTargetSize(width);
                me.setBodyBox({width: width - padding.left - border.left - padding.right - border.right - frameSize.left - frameSize.right, height: null});
            // Auto-width
            }
            else {
                autoWidth = true;
                // Clear any sizing that we already set in a previous layout
                me.setTargetSize(null, height);
                me.setBodyBox({width: null, height: height - padding.top - padding.bottom - border.top - border.bottom - frameSize.top - frameSize.bottom});
            }

            // Run the container
            if (layout && layout.isLayout) {
                // Auto-Sized so have the container layout notify the component layout.
                layout.bindToOwnerCtComponent = true;
                // Set flag so we don't do a redundant container layout
                layout.isAutoDock = layout.autoSize !== true;
                layout.layout();

                // If this is an autosized container layout, then we must compensate for a
                // body that is being autosized.  We do not want to adjust the body's size
                // to accommodate the dock items, but rather we will want to adjust the
                // target's size.
                //
                // This is necessary because, particularly in a Box layout, all child items
                // are set with absolute dimensions that are not flexible to the size of its
                // innerCt/target.  So once they are laid out, they are sized for good. By
                // shrinking the body box to accommodate dock items, we're merely cutting off
                // parts of the body.  Not good.  Instead, the target's size should expand
                // to fit the dock items in.  This is valid because the target container is
                // suppose to be autosized to fit everything accordingly.
                info.autoSizedCtLayout = layout.autoSize === true;
                info.autoHeight = autoHeight;
                info.autoWidth = autoWidth;
            }

            // The dockItems method will add all the top and bottom docked items height
            // to the info.panelSize height. That's why we have to call setSize after
            // we dock all the items to actually set the panel's width and height.
            // We have to do this because the panel body and docked items will be position
            // absolute which doesn't stretch the panel.
            me.dockItems();
            me.setTargetSize(info.size.width, info.size.height);
        }
        else {
            me.setTargetSize(width, height);
            me.dockItems();
        }
        me.callParent(arguments);
        this.onLayout_running = false;
    },

    /**
     * @protected
     * This method will first update all the information about the docked items,
     * body dimensions and position, the panel's total size. It will then
     * set all these values on the docked items and panel body.
     * @param {Array} items Array containing all the docked items
     * @param {Boolean} autoBoxes Set this to true if the Panel is part of an
     * AutoContainerLayout
     */
    dockItems : function() {
        this.calculateDockBoxes();

        // Both calculateAutoBoxes and calculateSizedBoxes are changing the
        // information about the body, panel size, and boxes for docked items
        // inside a property called info.
        var info = this.info,
            autoWidth = info.autoWidth,
            autoHeight = info.autoHeight,
            boxes = info.boxes,
            ln = boxes.length,
            dock, i, item;

        // We are going to loop over all the boxes that were calculated
        // and set the position of each item the box belongs to.
        for (i = 0; i < ln; i++) {
            dock = boxes[i];
            item = dock.item;
            item.setPosition(dock.x, dock.y);
            if ((autoWidth || autoHeight) && item.layout && item.layout.isLayout) {
                // Auto-Sized so have the container layout notify the component layout.
                item.layout.bindToOwnerCtComponent = true;
            }
        }

        // Don't adjust body width/height if the target is using an auto container layout.
        // But, we do want to adjust the body size if the container layout is auto sized.
        if (!info.autoSizedCtLayout) {
            if (autoWidth) {
                info.bodyBox.width = null;
            }
            if (autoHeight) {
                info.bodyBox.height = null;
            }
        }

        // If the bodyBox has been adjusted because of the docked items
        // we will update the dimensions and position of the panel's body.
        this.setBodyBox(info.bodyBox);
    },

    /**
     * @protected
     * This method will set up some initial information about the panel size and bodybox
     * and then loop over all the items you pass it to take care of stretching, aligning,
     * dock position and all calculations involved with adjusting the body box.
     * @param {Array} items Array containing all the docked items we have to layout
     */
    calculateDockBoxes : function() {
        if (this.calculateDockBoxes_running) {
            // [AbstractDock#calculateDockBoxes] attempted to run again while it was already running
            return;
        }
        this.calculateDockBoxes_running = true;
        // We want to use the Panel's el width, and the Panel's body height as the initial
        // size we are going to use in calculateDockBoxes. We also want to account for
        // the border of the panel.
        var me = this,
            target = me.getTarget(),
            items = me.getLayoutItems(),
            owner = me.owner,
            bodyEl = owner.body,
            info = me.info,
            autoWidth = info.autoWidth,
            autoHeight = info.autoHeight,
            size = info.size,
            ln = items.length,
            padding = info.padding,
            border = info.border,
            frameSize = me.frameSize,
            item, i, box, rect;

        // If this Panel is inside an AutoContainerLayout, we will base all the calculations
        // around the height of the body and the width of the panel.
        if (autoHeight) {
            size.height = bodyEl.getHeight() + padding.top + border.top + padding.bottom + border.bottom + frameSize.top + frameSize.bottom;
        }
        else {
            size.height = target.getHeight();
        }
        if (autoWidth) {
            size.width = bodyEl.getWidth() + padding.left + border.left + padding.right + border.right + frameSize.left + frameSize.right;
        }
        else {
            size.width = target.getWidth();
        }

        info.bodyBox = {
            x: padding.left + frameSize.left,
            y: padding.top + frameSize.top,
            width: size.width - padding.left - border.left - padding.right - border.right - frameSize.left - frameSize.right,
            height: size.height - border.top - padding.top - border.bottom - padding.bottom - frameSize.top - frameSize.bottom
        };

        // Loop over all the docked items
        for (i = 0; i < ln; i++) {
            item = items[i];
            // The initBox method will take care of stretching and alignment
            // In some cases it will also layout the dock items to be able to
            // get a width or height measurement
            box = me.initBox(item);

            if (autoHeight === true) {
                box = me.adjustAutoBox(box, i);
            }
            else {
                box = me.adjustSizedBox(box, i);
            }

            // Save our box. This allows us to loop over all docked items and do all
            // calculations first. Then in one loop we will actually size and position
            // all the docked items that have changed.
            info.boxes.push(box);
        }
        this.calculateDockBoxes_running = false;
    },

    /**
     * @protected
     * This method will adjust the position of the docked item and adjust the body box
     * accordingly.
     * @param {Object} box The box containing information about the width and height
     * of this docked item
     * @param {Number} index The index position of this docked item
     * @return {Object} The adjusted box
     */
    adjustSizedBox : function(box, index) {
        var bodyBox = this.info.bodyBox,
            frameSize = this.frameSize,
            info = this.info,
            padding = info.padding,
            pos = box.type,
            border = info.border;

        switch (pos) {
            case 'top':
                box.y = bodyBox.y;
                break;

            case 'left':
                box.x = bodyBox.x;
                break;

            case 'bottom':
                box.y = (bodyBox.y + bodyBox.height) - box.height;
                break;

            case 'right':
                box.x = (bodyBox.x + bodyBox.width) - box.width;
                break;
        }

        if (box.ignoreFrame) {
            if (pos == 'bottom') {
                box.y += (frameSize.bottom + padding.bottom + border.bottom);
            }
            else {
                box.y -= (frameSize.top + padding.top + border.top);
            }
            if (pos == 'right') {
                box.x += (frameSize.right + padding.right + border.right);
            }
            else {
                box.x -= (frameSize.left + padding.left + border.left);
            }
        }

        // If this is not an overlaying docked item, we have to adjust the body box
        if (!box.overlay) {
            switch (pos) {
                case 'top':
                    bodyBox.y += box.height;
                    bodyBox.height -= box.height;
                    break;

                case 'left':
                    bodyBox.x += box.width;
                    bodyBox.width -= box.width;
                    break;

                case 'bottom':
                    bodyBox.height -= box.height;
                    break;

                case 'right':
                    bodyBox.width -= box.width;
                    break;
            }
        }
        return box;
    },

    /**
     * @protected
     * This method will adjust the position of the docked item inside an AutoContainerLayout
     * and adjust the body box accordingly.
     * @param {Object} box The box containing information about the width and height
     * of this docked item
     * @param {Number} index The index position of this docked item
     * @return {Object} The adjusted box
     */
    adjustAutoBox : function (box, index) {
        var info = this.info,
            owner = this.owner,
            bodyBox = info.bodyBox,
            size = info.size,
            boxes = info.boxes,
            boxesLn = boxes.length,
            pos = box.type,
            frameSize = this.frameSize,
            padding = info.padding,
            border = info.border,
            autoSizedCtLayout = info.autoSizedCtLayout,
            ln = (boxesLn < index) ? boxesLn : index,
            i, adjustBox;

        if (pos == 'top' || pos == 'bottom') {
            // This can affect the previously set left and right and bottom docked items
            for (i = 0; i < ln; i++) {
                adjustBox = boxes[i];
                if (adjustBox.stretched && adjustBox.type == 'left' || adjustBox.type == 'right') {
                    adjustBox.height += box.height;
                }
                else if (adjustBox.type == 'bottom') {
                    adjustBox.y += box.height;
                }
            }
        }

        switch (pos) {
            case 'top':
                box.y = bodyBox.y;
                if (!box.overlay) {
                    bodyBox.y += box.height;
                    if (info.autoHeight) {
                        size.height += box.height;
                    } else {
                        bodyBox.height -= box.height;
                    }
                }
                break;

            case 'bottom':
                if (!box.overlay) {
                    if (info.autoHeight) {
                        size.height += box.height;
                    } else {
                        bodyBox.height -= box.height;
                    }
                }
                box.y = (bodyBox.y + bodyBox.height);
                break;

            case 'left':
                box.x = bodyBox.x;
                if (!box.overlay) {
                    bodyBox.x += box.width;
                    if (info.autoWidth) {
                        size.width += box.width;
                    } else {
                        bodyBox.width -= box.width;
                    }
                }
                break;

            case 'right':
                if (!box.overlay) {
                    if (info.autoWidth) {
                        size.width += box.width;
                    } else {
                        bodyBox.width -= box.width;
                    }
                }
                box.x = (bodyBox.x + bodyBox.width);
                break;
        }

        if (box.ignoreFrame) {
            if (pos == 'bottom') {
                box.y += (frameSize.bottom + padding.bottom + border.bottom);
            }
            else {
                box.y -= (frameSize.top + padding.top + border.top);
            }
            if (pos == 'right') {
                box.x += (frameSize.right + padding.right + border.right);
            }
            else {
                box.x -= (frameSize.left + padding.left + border.left);
            }
        }
        return box;
    },

    /**
     * @protected
     * This method will create a box object, with a reference to the item, the type of dock
     * (top, left, bottom, right). It will also take care of stretching and aligning of the
     * docked items.
     * @param {Ext.Component} item The docked item we want to initialize the box for
     * @return {Object} The initial box containing width and height and other useful information
     */
    initBox : function(item) {
        var me = this,
            bodyBox = me.info.bodyBox,
            horizontal = (item.dock == 'top' || item.dock == 'bottom'),
            owner = me.owner,
            frameSize = me.frameSize,
            info = me.info,
            padding = info.padding,
            border = info.border,
            box = {
                item: item,
                overlay: item.overlay,
                type: item.dock,
                offsets: Ext.Element.parseBox(item.offsets || {}),
                ignoreFrame: item.ignoreParentFrame
            };
        // First we are going to take care of stretch and align properties for all four dock scenarios.
        if (item.stretch !== false) {
            box.stretched = true;
            if (horizontal) {
                box.x = bodyBox.x + box.offsets.left;
                box.width = bodyBox.width - (box.offsets.left + box.offsets.right);
                if (box.ignoreFrame) {
                    box.width += (frameSize.left + frameSize.right + border.left + border.right + padding.left + padding.right);
                }
                item.setCalculatedSize(box.width - item.el.getMargin('lr'), undefined, owner);
            }
            else {
                box.y = bodyBox.y + box.offsets.top;
                box.height = bodyBox.height - (box.offsets.bottom + box.offsets.top);
                if (box.ignoreFrame) {
                    box.height += (frameSize.top + frameSize.bottom + border.top + border.bottom + padding.top + padding.bottom);
                }
                item.setCalculatedSize(undefined, box.height - item.el.getMargin('tb'), owner);

                // At this point IE will report the left/right-docked toolbar as having a width equal to the
                // container's full width. Forcing a repaint kicks it into shape so it reports the correct width.
                if (!Ext.supports.ComputedStyle) {
                    item.el.repaint();
                }
            }
        }
        else {
            item.doComponentLayout();
            box.width = item.getWidth() - (box.offsets.left + box.offsets.right);
            box.height = item.getHeight() - (box.offsets.bottom + box.offsets.top);
            box.y += box.offsets.top;
            if (horizontal) {
                box.x = (item.align == 'right') ? bodyBox.width - box.width : bodyBox.x;
                box.x += box.offsets.left;
            }
        }

        // If we haven't calculated the width or height of the docked item yet
        // do so, since we need this for our upcoming calculations
        if (box.width === undefined) {
            box.width = item.getWidth() + item.el.getMargin('lr');
        }
        if (box.height === undefined) {
            box.height = item.getHeight() + item.el.getMargin('tb');
        }

        return box;
    },

    /**
     * @protected
     * Returns an array containing all the <b>visible</b> docked items inside this layout's owner Panel
     * @return {Array} An array containing all the <b>visible</b> docked items of the Panel
     */
    getLayoutItems : function() {
        var it = this.owner.getDockedItems(),
            ln = it.length,
            i = 0,
            result = [];
        for (; i < ln; i++) {
            if (it[i].isVisible(true)) {
                result.push(it[i]);
            }
        }
        return result;
    },

    /**
     * @protected
     * Render the top and left docked items before any existing DOM nodes in our render target,
     * and then render the right and bottom docked items after. This is important, for such things
     * as tab stops and ARIA readers, that the DOM nodes are in a meaningful order.
     * Our collection of docked items will already be ordered via Panel.getDockedItems().
     */
    renderItems: function(items, target) {
        var cns = target.dom.childNodes,
            cnsLn = cns.length,
            ln = items.length,
            domLn = 0,
            i, j, cn, item;

        // Calculate the number of DOM nodes in our target that are not our docked items
        for (i = 0; i < cnsLn; i++) {
            cn = Ext.get(cns[i]);
            for (j = 0; j < ln; j++) {
                item = items[j];
                if (item.rendered && (cn.id == item.el.id || cn.contains(item.el.id))) {
                    break;
                }
            }

            if (j === ln) {
                domLn++;
            }
        }

        // Now we go through our docked items and render/move them
        for (i = 0, j = 0; i < ln; i++, j++) {
            item = items[i];

            // If we're now at the right/bottom docked item, we jump ahead in our
            // DOM position, just past the existing DOM nodes.
            //
            // TODO: This is affected if users provide custom weight values to their
            // docked items, which puts it out of (t,l,r,b) order. Avoiding a second
            // sort operation here, for now, in the name of performance. getDockedItems()
            // needs the sort operation not just for this layout-time rendering, but
            // also for getRefItems() to return a logical ordering (FocusManager, CQ, et al).
            if (i === j && (item.dock === 'right' || item.dock === 'bottom')) {
                j += domLn;
            }

            // Same logic as Layout.renderItems()
            if (item && !item.rendered) {
                this.renderItem(item, target, j);
            }
            else if (!this.isValidParent(item, target, j)) {
                this.moveItem(item, target, j);
            }
        }
    },

    /**
     * @protected
     * This function will be called by the dockItems method. Since the body is positioned absolute,
     * we need to give it dimensions and a position so that it is in the middle surrounded by
     * docked items
     * @param {Object} box An object containing new x, y, width and height values for the
     * Panel's body
     */
    setBodyBox : function(box) {
        var me = this,
            owner = me.owner,
            body = owner.body,
            info = me.info,
            bodyMargin = info.bodyMargin,
            padding = info.padding,
            border = info.border,
            frameSize = me.frameSize;
        
        // Panel collapse effectively hides the Panel's body, so this is a no-op.
        if (owner.collapsed) {
            return;
        }
        
        if (Ext.isNumber(box.width)) {
            box.width -= bodyMargin.left + bodyMargin.right;
        }
        
        if (Ext.isNumber(box.height)) {
            box.height -= bodyMargin.top + bodyMargin.bottom;
        }
        
        me.setElementSize(body, box.width, box.height);
        if (Ext.isNumber(box.x)) {
            body.setLeft(box.x - padding.left - frameSize.left);
        }
        if (Ext.isNumber(box.y)) {
            body.setTop(box.y - padding.top - frameSize.top);
        }
    },

    /**
     * @protected
     * We are overriding the Ext.layout.Layout configureItem method to also add a class that
     * indicates the position of the docked item. We use the itemCls (x-docked) as a prefix.
     * An example of a class added to a dock: right item is x-docked-right
     * @param {Ext.Component} item The item we are configuring
     */
    configureItem : function(item, pos) {
        this.callParent(arguments);
        if (item.dock == 'top' || item.dock == 'bottom') {
            item.layoutManagedWidth = 1;
            item.layoutManagedHeight = 2;
        } else {
            item.layoutManagedWidth = 2;
            item.layoutManagedHeight = 1;
        }
        
        item.addCls(Ext.baseCSSPrefix + 'docked');
        item.addClsWithUI('docked-' + item.dock);
    },

    afterRemove : function(item) {
        this.callParent(arguments);
        if (this.itemCls) {
            item.el.removeCls(this.itemCls + '-' + item.dock);
        }
        var dom = item.el.dom;

        if (!item.destroying && dom) {
            dom.parentNode.removeChild(dom);
        }
        this.childrenChanged = true;
    }
});
