/**
 * Panels are most useful as Overlays - containers that float over your application. They contain extra styling such
 * that when you {@link #showBy} another component, the container will appear in a rounded black box with a 'tip'
 * pointing to a reference component.
 *
 * If you don't need this extra functionality, you should use {@link Ext.Container} instead. See the
 * [Overlays example](#!/example/overlays) for more use cases.
 *
 *      @example miniphone preview
 *
 *      var button = Ext.create('Ext.Button', {
 *           text: 'Button',
 *           id: 'rightButton'
 *      });
 *
 *      Ext.create('Ext.Container', {
 *          fullscreen: true,
 *          items: [
 *              {
 *                   docked: 'top',
 *                   xtype: 'titlebar',
 *                   items: [
 *                       button
 *                   ]
 *               }
 *          ]
 *      });
 *
 *      Ext.create('Ext.Panel', {
 *          html: 'Floating Panel',
 *          left: 0,
 *          padding: 10
 *      }).showBy(button);
 *
 */
Ext.define('Ext.Panel', {
    extend: 'Ext.Container',
    xtype: 'panel',

    requires: [
        'Ext.util.LineSegment'
    ],

    alternateClassName: 'Ext.panel.Panel',

    defaultBindProperty: 'title',

    isPanel: true,

    config: {
        baseCls: Ext.baseCSSPrefix + 'panel',

        /**
         * @cfg border
         * @inheritdoc
         */
        border: false,

        /**
         * @cfg {Number/Boolean/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.
         */
        bodyPadding: null,

        /**
         * @cfg {Boolean} bodyBorder
         * - `true` to enable the border around the panel body (as defined by the theme)
         * Note that even when enabled, the bodyBorder is only visible when there are docked
         * items around the edges of the panel.  Where the bodyBorder touches the panel's outer
         * border it is automatically collapsed into a single border.
         *
         * - `false` to disable the body border
         *
         * - `null` - use the value of {@link #border} as the value for bodyBorder
         */
        bodyBorder: null,

        header: null,

        icon: null,

        iconCls: null,

        title: null,

        tools: null
    },

    manageBorders: true,

    getElementConfig: function() {
        return {
            reference: 'element',
            classList: ['x-container', 'x-unsized'],
            children: [
                {
                    reference: 'innerElement',
                    className: 'x-inner'
                },
                {
                    reference: 'tipElement',
                    className: 'x-anchor',
                    hidden: true
                }
            ]
        };
    },

    /**
     * Adds a CSS class to the body element. If not rendered, the class will
     * be added when the panel is rendered.
     * @param {String} cls The class to add
     * @return {Ext.panel.Panel} this
     */
    addBodyCls: function(cls) {
        this.innerElement.addCls(cls);
        return this;
    },

    /**
     * Removes a CSS class from the body element.
     * @param {String} cls The class to remove
     * @return {Ext.panel.Panel} this
     */
    removeBodyCls: function(cls) {
        this.innerElement.removeCls(cls);
        return this;
    },

    applyBodyPadding: function(bodyPadding) {
        if (bodyPadding === true) {
            bodyPadding = 5;
        }

        if (bodyPadding) {
            bodyPadding = Ext.dom.Element.unitizeBox(bodyPadding);
        }

        return bodyPadding;
    },

    addTool: function (tool) {
        var header = this.ensureHeader(),  // creates if header !== false
            items;

        if (header) {
            items = header.createTools(Ext.Array.from(tool), this);

            if (items && items.length) {
                items = header.add(items);
            }
        }

        return items;
    },

    applyHeader: function (newHeader, oldHeader) {
        var me = this,
            header = oldHeader;

        if (newHeader === false) {
            if (header) {
                me.remove(header);
                header = null;
            }
        } else if (newHeader) {
            if (header) {
                if (newHeader !== true) {
                    header.setConfig(newHeader);
                }
            } else {
                // add() will ensure we sort the header to the front by its "weight"

                header = me.add(me.createHeader(newHeader));
            }
        }

        return header || null;
    },

    applyTools: function (tools) {
        var header = this.ensureHeader(),  // creates if header !== false
            items;

        if (header) {
            // Remove all tools (since we are the impl of a setTools([...]) call)
            header.clearTools();

            items = header.createTools(tools, this);

            if (items && items.length) {
                header.add(items);
            }
        }

        // we don't return anything since the tools are "stored" on the Header
    },

    createHeader: function (config) {
        var me = this,
            ret = {
                xtype: 'panelheader',
                docked: 'top',
                ui: me.getUi()
            },
            icon, title;

        if (config && config !== true) {
            Ext.merge(ret, config);
        }

        if (me.initialized) {
            // Only attempt to configure title if we are not currently initializing.
            // During initialization the updater for title will run if present and apply
            // it to the header so there is no work to be done here.
            title = me.getTitle();

            if (title != null) {
                if (typeof title === 'string') {
                    title = {
                        text: title
                    };
                }

                Ext.merge(ret, {
                    title: title
                });
            }

            icon = me.getIconCls();
            if (icon != null) {
                ret.iconCls = icon;
            } else {
                icon = me.getIcon();
                if (icon != null) {
                    ret.icon = icon;
                }
            }
        }

        return ret;
    },

    updateBorder: function(border, oldBorder) {
        this.callParent([ border, oldBorder ]);

        if (this.getBodyBorder() === null) {
            this.setBodyBorderEnabled(border !== false);
        }
    },

    updateBodyPadding: function(newBodyPadding) {
        this.innerElement.setStyle('padding', newBodyPadding);
    },

    updateBodyBorder: function(bodyBorder) {
        var border = (bodyBorder === null) ? this.getBorder() : bodyBorder;

        this.setBodyBorderEnabled(bodyBorder !== false);
    },

    updateIcon: function (icon) {
        var header = this.ensureHeader();  // creates if header !== false

        if (header) {
            header.setIcon(icon);
        }
    },

    updateIconCls: function (icon) {
        var header = this.ensureHeader();  // creates if header !== false

        if (header) {
            header.setIconCls(icon);
        }
    },

    updateTitle: function (title) {
        var header = this.ensureHeader();  // creates if header !== false

        if (header) {
            header.setTitle(title);
        }
    },

    updateUi: function(ui, oldUi) {
        var me = this,
            suffix = 'x-panel-inner-',
            innerElement = me.innerElement,
            // Let the header initter get the ui since ui is a cached config and
            // should not pull in non-cached cfgs at this early stage
            header = !me.isConfiguring && me.ensureHeader();

        if (oldUi) {
            innerElement.removeCls(suffix + oldUi);
        }

        if (ui) {
            innerElement.addCls(suffix + ui);
        }

        if (header) {
            me.getTitle();
            header.setUi(ui);
        }

        me.callParent([ui, oldUi]);
    },

    alignTo: function(component, alignment) {
        var alignmentInfo = this.getAlignmentInfo(component, alignment);
        if(alignmentInfo.isAligned) return;
        var tipElement = this.tipElement;

        tipElement.hide();

        if (this.currentTipPosition) {
            tipElement.removeCls('x-anchor-' + this.currentTipPosition);
        }

        this.callParent(arguments);

        var LineSegment = Ext.util.LineSegment,
            alignToElement = component.isComponent ? component.renderElement : component,
            element = this.renderElement,
            alignToBox = alignToElement.getBox(),
            box = element.getBox(),
            left = box.left,
            top = box.top,
            right = box.right,
            bottom = box.bottom,
            centerX = left + (box.width / 2),
            centerY = top + (box.height / 2),
            leftTopPoint = { x: left, y: top },
            rightTopPoint = { x: right, y: top },
            leftBottomPoint = { x: left, y: bottom },
            rightBottomPoint = { x: right, y: bottom },
            boxCenterPoint = { x: centerX, y: centerY },
            alignToCenterX = alignToBox.left + (alignToBox.width / 2),
            alignToCenterY = alignToBox.top + (alignToBox.height / 2),
            alignToBoxCenterPoint = { x: alignToCenterX, y: alignToCenterY },
            centerLineSegment = new LineSegment(boxCenterPoint, alignToBoxCenterPoint),
            offsetLeft = 0,
            offsetTop = 0,
            tipSize, tipWidth, tipHeight, tipPosition, tipX, tipY;

        tipElement.setVisibility(false);
        tipElement.show();
        tipSize = tipElement.getSize();
        tipWidth = tipSize.width;
        tipHeight = tipSize.height;

        if (centerLineSegment.intersects(new LineSegment(leftTopPoint, rightTopPoint))) {
            tipX = Math.min(Math.max(alignToCenterX, left + tipWidth), right - (tipWidth));
            tipY = top;
            offsetTop = tipHeight + 10;
            tipPosition = 'top';
        }
        else if (centerLineSegment.intersects(new LineSegment(leftTopPoint, leftBottomPoint))) {
            tipX = left;
            tipY = Math.min(Math.max(alignToCenterY + (tipWidth / 2), tipWidth * 1.6), bottom - (tipWidth / 2.2));
            offsetLeft = tipHeight + 10;
            tipPosition = 'left';
        }
        else if (centerLineSegment.intersects(new LineSegment(leftBottomPoint, rightBottomPoint))) {
            tipX = Math.min(Math.max(alignToCenterX, left + tipWidth), right - tipWidth);
            tipY = bottom;
            offsetTop = -tipHeight - 10;
            tipPosition = 'bottom';
        }
        else if (centerLineSegment.intersects(new LineSegment(rightTopPoint, rightBottomPoint))) {
            tipX = right;
            tipY = Math.max(Math.min(alignToCenterY - tipHeight, bottom - tipWidth * 1.3), tipWidth / 2);
            offsetLeft = -tipHeight - 10;
            tipPosition = 'right';
        }

        if (tipX || tipY) {
            this.currentTipPosition = tipPosition;
            tipElement.addCls('x-anchor-' + tipPosition);
            tipElement.setLeft(tipX - left);
            tipElement.setTop(tipY - top);
            tipElement.setVisibility(true);

            this.setLeft(this.getLeft() + offsetLeft);
            this.setTop(this.getTop() + offsetTop);
        }
    },

    privates: {
        ensureHeader: function () {
            var me = this,
                header;

            me.getViewModel();
            me.getItems();

            header = me.getHeader();

            if (!header && header !== false) {
                me.setHeader(true);
                header = me.getHeader();
            }

            return header;
        },

        setBodyBorderEnabled: function(enabled) {
            this.innerElement.setStyle('border-width', enabled ? '' : '0');
        }
    }
});
