/**
 * @class Ext.panel.AbstractPanel
 * @extends Ext.container.Container
 * A base class which provides methods common to Panel classes across the Sencha product range.
 * @private
 */
Ext.define('Ext.panel.AbstractPanel', {

    /* Begin Definitions */

    extend: 'Ext.container.Container',

    requires: ['Ext.util.MixedCollection', 'Ext.Element', 'Ext.toolbar.Toolbar'],

    /* End Definitions */

    /**
     * @cfg {String} [baseCls='x-panel']
     * The base CSS class to apply to this panel's element.
     */
    baseCls : Ext.baseCSSPrefix + 'panel',

    /**
     * @cfg {Number/String} bodyPadding
     * A shortcut for setting a padding style on the body element. The value can either be
     * a number to be applied to all sides, or a normal css string describing padding.
     */

    /**
     * @cfg {Boolean} bodyBorder
     * A shortcut to add or remove the border on the body of a panel. This only applies to a panel
     * which has the {@link #frame} configuration set to `true`.
     */

    /**
     * @cfg {String/Object/Function} bodyStyle
     * Custom CSS styles to be applied to the panel's body element, which can be supplied as a valid CSS style string,
     * an object containing style property name/value pairs or a function that returns such a string or object.
     * For example, these two formats are interpreted to be equivalent:<pre><code>
bodyStyle: 'background:#ffc; padding:10px;'

bodyStyle: {
    background: '#ffc',
    padding: '10px'
}
     * </code></pre>
     */

    /**
     * @cfg {String/String[]} bodyCls
     * A CSS class, space-delimited string of classes, or array of classes to be applied to the panel's body element.
     * The following examples are all valid:<pre><code>
bodyCls: 'foo'
bodyCls: 'foo bar'
bodyCls: ['foo', 'bar']
     * </code></pre>
     */

    isPanel: true,

    componentLayout: 'dock',

    /**
     * @cfg {Object} defaultDockWeights
     * This object holds the default weights applied to dockedItems that have no weight. These start with a
     * weight of 1, to allow negative weights to insert before top items and are odd numbers
     * so that even weights can be used to get between different dock orders.
     *
     * To make default docking order match border layout, do this:
     * <pre><code>
Ext.panel.AbstractPanel.prototype.defaultDockWeights = { top: 1, bottom: 3, left: 5, right: 7 };</code></pre>
     * Changing these defaults as above or individually on this object will effect all Panels.
     * To change the defaults on a single panel, you should replace the entire object:
     * <pre><code>
initComponent: function () {
    // NOTE: Don't change members of defaultDockWeights since the object is shared.
    this.defaultDockWeights = { top: 1, bottom: 3, left: 5, right: 7 };

    this.callParent();
}</code></pre>
     *
     * To change only one of the default values, you do this:
     * <pre><code>
initComponent: function () {
    // NOTE: Don't change members of defaultDockWeights since the object is shared.
    this.defaultDockWeights = Ext.applyIf({ top: 10 }, this.defaultDockWeights);

    this.callParent();
}</code></pre>
     */
    defaultDockWeights: { top: 1, left: 3, right: 5, bottom: 7 },

    renderTpl: [
        '<div id="{id}-body" class="{baseCls}-body<tpl if="bodyCls"> {bodyCls}</tpl>',
            ' {baseCls}-body-{ui}<tpl if="uiCls">',
                '<tpl for="uiCls"> {parent.baseCls}-body-{parent.ui}-{.}</tpl>',
            '</tpl>"<tpl if="bodyStyle"> style="{bodyStyle}"</tpl>>',
        '</div>'
    ],

    // TODO: Move code examples into product-specific files. The code snippet below is Touch only.
    /**
     * @cfg {Object/Object[]} dockedItems
     * A component or series of components to be added as docked items to this panel.
     * The docked items can be docked to either the top, right, left or bottom of a panel.
     * This is typically used for things like toolbars or tab bars:
     * <pre><code>
var panel = new Ext.panel.Panel({
    fullscreen: true,
    dockedItems: [{
        xtype: 'toolbar',
        dock: 'top',
        items: [{
            text: 'Docked to the top'
        }]
    }]
});</code></pre>
     */

    border: true,

    initComponent : function() {
        var me = this;

        me.addEvents(
            /**
             * @event bodyresize
             * Fires after the Panel has been resized.
             * @param {Ext.panel.Panel} p the Panel which has been resized.
             * @param {Number} width The Panel body's new width.
             * @param {Number} height The Panel body's new height.
             */
            'bodyresize'
            // // inherited
            // 'activate',
            // // inherited
            // 'deactivate'
        );

        me.addChildEls('body');

        //!frame
        //!border

        if (me.frame && me.border && me.bodyBorder === undefined) {
            me.bodyBorder = false;
        }
        if (me.frame && me.border && (me.bodyBorder === false || me.bodyBorder === 0)) {
            me.manageBodyBorders = true;
        }

        me.callParent();
    },

    // @private
    initItems : function() {
        var me = this,
            items = me.dockedItems;

        me.callParent();
        me.dockedItems = Ext.create('Ext.util.MixedCollection', false, me.getComponentId);
        if (items) {
            me.addDocked(items);
        }
    },

    /**
     * Finds a docked component by id, itemId or position. Also see {@link #getDockedItems}
     * @param {String/Number} comp The id, itemId or position of the docked component (see {@link #getComponent} for details)
     * @return {Ext.Component} The docked component (if found)
     */
    getDockedComponent: function(comp) {
        if (Ext.isObject(comp)) {
            comp = comp.getItemId();
        }
        return this.dockedItems.get(comp);
    },

    /**
     * Attempts a default component lookup (see {@link Ext.container.Container#getComponent}). If the component is not found in the normal
     * items, the dockedItems are searched and the matched component (if any) returned (see {@link #getDockedComponent}). Note that docked
     * items will only be matched by component id or itemId -- if you pass a numeric index only non-docked child components will be searched.
     * @param {String/Number} comp The component id, itemId or position to find
     * @return {Ext.Component} The component (if found)
     */
    getComponent: function(comp) {
        var component = this.callParent(arguments);
        if (component === undefined && !Ext.isNumber(comp)) {
            // If the arg is a numeric index skip docked items
            component = this.getDockedComponent(comp);
        }
        return component;
    },

    /**
     * Parses the {@link bodyStyle} config if available to create a style string that will be applied to the body element.
     * This also includes {@link bodyPadding} and {@link bodyBorder} if available.
     * @return {String} A CSS style string with body styles, padding and border.
     * @private
     */
    initBodyStyles: function() {
        var me = this,
            bodyStyle = me.bodyStyle,
            styles = [],
            Element = Ext.Element,
            prop;

        if (Ext.isFunction(bodyStyle)) {
            bodyStyle = bodyStyle();
        }
        if (Ext.isString(bodyStyle)) {
            styles = bodyStyle.split(';');
        } else {
            for (prop in bodyStyle) {
                if (bodyStyle.hasOwnProperty(prop)) {
                    styles.push(prop + ':' + bodyStyle[prop]);
                }
            }
        }

        if (me.bodyPadding !== undefined) {
            styles.push('padding: ' + Element.unitizeBox((me.bodyPadding === true) ? 5 : me.bodyPadding));
        }
        if (me.frame && me.bodyBorder) {
            if (!Ext.isNumber(me.bodyBorder)) {
                me.bodyBorder = 1;
            }
            styles.push('border-width: ' + Element.unitizeBox(me.bodyBorder));
        }
        delete me.bodyStyle;
        return styles.length ? styles.join(';') : undefined;
    },

    /**
     * Parse the {@link bodyCls} config if available to create a comma-delimited string of
     * CSS classes to be applied to the body element.
     * @return {String} The CSS class(es)
     * @private
     */
    initBodyCls: function() {
        var me = this,
            cls = '',
            bodyCls = me.bodyCls;

        if (bodyCls) {
            Ext.each(bodyCls, function(v) {
                cls += " " + v;
            });
            delete me.bodyCls;
        }
        return cls.length > 0 ? cls : undefined;
    },

    /**
     * Initialized the renderData to be used when rendering the renderTpl.
     * @return {Object} Object with keys and values that are going to be applied to the renderTpl
     * @private
     */
    initRenderData: function() {
        return Ext.applyIf(this.callParent(), {
            bodyStyle: this.initBodyStyles(),
            bodyCls: this.initBodyCls()
        });
    },

    /**
     * Adds docked item(s) to the panel.
     * @param {Object/Object[]} component The Component or array of components to add. The components
     * must include a 'dock' parameter on each component to indicate where it should be docked ('top', 'right',
     * 'bottom', 'left').
     * @param {Number} pos (optional) The index at which the Component will be added
     */
    addDocked : function(items, pos) {
        var me = this,
            i = 0,
            item, length;

        items = me.prepareItems(items);
        length = items.length;

        for (; i < length; i++) {
            item = items[i];
            item.dock = item.dock || 'top';

            // Allow older browsers to target docked items to style without borders
            if (me.border === false) {
                // item.cls = item.cls || '' + ' ' + me.baseCls + '-noborder-docked-' + item.dock;
            }

            if (pos !== undefined) {
                me.dockedItems.insert(pos + i, item);
            }
            else {
                me.dockedItems.add(item);
            }
            item.onAdded(me, i);
            me.onDockedAdd(item);
        }

        // Set flag which means that beforeLayout will not veto the layout due to the size not changing
        me.componentLayout.childrenChanged = true;
        if (me.rendered && !me.suspendLayout) {
            me.doComponentLayout();
        }
        return items;
    },

    // Placeholder empty functions
    onDockedAdd : Ext.emptyFn,
    onDockedRemove : Ext.emptyFn,

    /**
     * Inserts docked item(s) to the panel at the indicated position.
     * @param {Number} pos The index at which the Component will be inserted
     * @param {Object/Object[]} component. The Component or array of components to add. The components
     * must include a 'dock' paramater on each component to indicate where it should be docked ('top', 'right',
     * 'bottom', 'left').
     */
    insertDocked : function(pos, items) {
        this.addDocked(items, pos);
    },

    /**
     * Removes the docked item from the panel.
     * @param {Ext.Component} item. The Component to remove.
     * @param {Boolean} autoDestroy (optional) Destroy the component after removal.
     */
    removeDocked : function(item, autoDestroy) {
        var me = this,
            layout,
            hasLayout;

        if (!me.dockedItems.contains(item)) {
            return item;
        }

        layout = me.componentLayout;
        hasLayout = layout && me.rendered;

        if (hasLayout) {
            layout.onRemove(item);
        }

        me.dockedItems.remove(item);
        item.onRemoved();
        me.onDockedRemove(item);

        if (autoDestroy === true || (autoDestroy !== false && me.autoDestroy)) {
            item.destroy();
        } else if (hasLayout) {
            // not destroying, make any layout related removals
            layout.afterRemove(item);    
        }


        // Set flag which means that beforeLayout will not veto the layout due to the size not changing
        me.componentLayout.childrenChanged = true;
        if (!me.destroying && !me.suspendLayout) {
            me.doComponentLayout();
        }

        return item;
    },

    /**
     * Retrieve an array of all currently docked Components.
     * @param {String} cqSelector A {@link Ext.ComponentQuery ComponentQuery} selector string to filter the returned items.
     * @return {Ext.Component[]} An array of components.
     */
    getDockedItems : function(cqSelector) {
        var me = this,
            defaultWeight = me.defaultDockWeights,
            dockedItems;

        if (me.dockedItems && me.dockedItems.items.length) {
            // Allow filtering of returned docked items by CQ selector.
            if (cqSelector) {
                dockedItems = Ext.ComponentQuery.query(cqSelector, me.dockedItems.items);
            } else {
                dockedItems = me.dockedItems.items.slice();
            }

            Ext.Array.sort(dockedItems, function(a, b) {
                // Docked items are ordered by their visual representation by default (t,l,r,b)
                var aw = a.weight || defaultWeight[a.dock],
                    bw = b.weight || defaultWeight[b.dock];
                if (Ext.isNumber(aw) && Ext.isNumber(bw)) {
                    return aw - bw;
                }
                return 0;
            });

            return dockedItems;
        }
        return [];
    },

    // inherit docs
    addUIClsToElement: function(cls, force) {
        var me = this,
            result = me.callParent(arguments),
            classes = [Ext.baseCSSPrefix + cls, me.baseCls + '-body-' + cls, me.baseCls + '-body-' + me.ui + '-' + cls],
            array, i;

        if (!force && me.rendered) {
            if (me.bodyCls) {
                me.body.addCls(me.bodyCls);
            } else {
                me.body.addCls(classes);
            }
        } else {
            if (me.bodyCls) {
                array = me.bodyCls.split(' ');

                for (i = 0; i < classes.length; i++) {
                    if (!Ext.Array.contains(array, classes[i])) {
                        array.push(classes[i]);
                    }
                }

                me.bodyCls = array.join(' ');
            } else {
                me.bodyCls = classes.join(' ');
            }
        }

        return result;
    },

    // inherit docs
    removeUIClsFromElement: function(cls, force) {
        var me = this,
            result = me.callParent(arguments),
            classes = [Ext.baseCSSPrefix + cls, me.baseCls + '-body-' + cls, me.baseCls + '-body-' + me.ui + '-' + cls],
            array, i;

        if (!force && me.rendered) {
            if (me.bodyCls) {
                me.body.removeCls(me.bodyCls);
            } else {
                me.body.removeCls(classes);
            }
        } else {
            if (me.bodyCls) {
                array = me.bodyCls.split(' ');

                for (i = 0; i < classes.length; i++) {
                    Ext.Array.remove(array, classes[i]);
                }

                me.bodyCls = array.join(' ');
            }
        }

        return result;
    },

    // inherit docs
    addUIToElement: function(force) {
        var me = this,
            cls = me.baseCls + '-body-' + me.ui,
            array;

        me.callParent(arguments);

        if (!force && me.rendered) {
            if (me.bodyCls) {
                me.body.addCls(me.bodyCls);
            } else {
                me.body.addCls(cls);
            }
        } else {
            if (me.bodyCls) {
                array = me.bodyCls.split(' ');

                if (!Ext.Array.contains(array, cls)) {
                    array.push(cls);
                }

                me.bodyCls = array.join(' ');
            } else {
                me.bodyCls = cls;
            }
        }
    },

    // inherit docs
    removeUIFromElement: function() {
        var me = this,
            cls = me.baseCls + '-body-' + me.ui,
            array;

        me.callParent(arguments);

        if (me.rendered) {
            if (me.bodyCls) {
                me.body.removeCls(me.bodyCls);
            } else {
                me.body.removeCls(cls);
            }
        } else {
            if (me.bodyCls) {
                array = me.bodyCls.split(' ');
                Ext.Array.remove(array, cls);
                me.bodyCls = array.join(' ');
            } else {
                me.bodyCls = cls;
            }
        }
    },

    // @private
    getTargetEl : function() {
        return this.body;
    },

    getRefItems: function(deep) {
        var items = this.callParent(arguments),
            // deep fetches all docked items, and their descendants using '*' selector and then '* *'
            dockedItems = this.getDockedItems(deep ? '*,* *' : undefined),
            ln = dockedItems.length,
            i = 0,
            item;

        // Find the index where we go from top/left docked items to right/bottom docked items
        for (; i < ln; i++) {
            item = dockedItems[i];
            if (item.dock === 'right' || item.dock === 'bottom') {
                break;
            }
        }

        // Return docked items in the top/left position before our container items, and
        // return right/bottom positioned items after our container items.
        // See AbstractDock.renderItems() for more information.
        return Ext.Array.splice(dockedItems, 0, i).concat(items).concat(dockedItems);
    },

    beforeDestroy: function(){
        var docked = this.dockedItems,
            c;

        if (docked) {
            while ((c = docked.first())) {
                this.removeDocked(c, true);
            }
        }
        this.callParent();
    },

    setBorder: function(border) {
        var me = this;
        me.border = (border !== undefined) ? border : true;
        if (me.rendered) {
            me.doComponentLayout();
        }
    }
});