// @tag dom,core
/**
 */
Ext.define('Ext.dom.Element_position', {
    override: 'Ext.dom.Element'
},
function() {

var flyInstance,
    Element = this,
    LEFT = "left",
    RIGHT = "right",
    TOP = "top",
    BOTTOM = "bottom",
    POSITION = "position",
    STATIC = "static",
    RELATIVE = "relative",
    ZINDEX = "z-index",
    BODY = 'BODY',

    PADDING = 'padding',
    BORDER = 'border',
    SLEFT = '-left',
    SRIGHT = '-right',
    STOP = '-top',
    SBOTTOM = '-bottom',
    SWIDTH = '-width',
    // special markup used throughout Ext when box wrapping elements
    borders = {l: BORDER + SLEFT + SWIDTH, r: BORDER + SRIGHT + SWIDTH, t: BORDER + STOP + SWIDTH, b: BORDER + SBOTTOM + SWIDTH},
    paddings = {l: PADDING + SLEFT, r: PADDING + SRIGHT, t: PADDING + STOP, b: PADDING + SBOTTOM},
    paddingsTLRB = [paddings.l, paddings.r, paddings.t, paddings.b],
    bordersTLRB = [borders.l,  borders.r,  borders.t,  borders.b],
    round = Math.round,
    doc = document,
    fly = function (el) {
        if (!flyInstance) {
            flyInstance = new Ext.Element.Fly();
        }
        flyInstance.attach(el);
        return flyInstance;
    };

    Element.override({

        pxRe: /^\d+(?:\.\d*)?px$/i,

        inheritableStatics: {
            getX: function(el) {
                return Element.getXY(el)[0];
            },

            getXY: function(el) {
                var bd = doc.body,
                    docEl = doc.documentElement,
                    leftBorder = 0,
                    topBorder = 0,
                    ret = [0,0],
                    box,
                    scroll;

                el = Ext.getDom(el);

                if(el != doc && el != bd){
                    // IE has the potential to throw when getBoundingClientRect
                    // is called on an element not attached to dom
                    if (Ext.isIE) {
                        try {
                            box = el.getBoundingClientRect();
                            // In some versions of IE, the documentElement (HTML element)
                            // will have a 2px border that gets included, so subtract it off
                            topBorder = docEl.clientTop || bd.clientTop;
                            leftBorder = docEl.clientLeft || bd.clientLeft;
                        } catch (ex) {
                            box = { left: 0, top: 0 };
                        }
                    } else {
                        box = el.getBoundingClientRect();
                    }

                    scroll = fly(doc).getScroll();
                    ret = [
                        round(box.left + scroll.left - leftBorder),
                        round(box.top + scroll.top - topBorder)
                    ];
                }
                return ret;
            },

            getY: function(el) {
                return Element.getXY(el)[1];
            },

            setX: function(el, x) {
                Element.setXY(el, [x, false]);
            },

            setXY: function(el, xy) {
                (el = Ext.fly(el, '_setXY')).position();

                var pts = el.translatePoints(xy),
                    style = el.dom.style,
                    pos;

                // right position may have been previously set by rtlSetXY or
                // rtlSetLocalXY so clear it here just in case.
                style.right = 'auto';
                for (pos in pts) {
                    if (!isNaN(pts[pos])) {
                        style[pos] = pts[pos] + "px";
                    }
                }
            },

            setY: function(el, y) {
                Element.setXY(el, [false, y]);
            }
        },

        /**
         * Centers the Element in either the viewport, or another Element.
         * @param {String/HTMLElement/Ext.dom.Element} centerIn element in
         * which to center the element.
         */
        center: function(centerIn){
            return this.alignTo(centerIn || doc, 'c-c');
        },

        /**
         * Clears positioning back to the default when the document was loaded.
         * @param {String} [value=''] The value to use for the left, right, top, bottom.
         * You could use 'auto'.
         * @return {Ext.dom.Element} this
         */
        clearPositioning: function(value) {
            value = value || '';
            return this.setStyle({
                left : value,
                right : value,
                top : value,
                bottom : value,
                'z-index' : '',
                position : STATIC
            });
        },

        getAnchorToXY: function(el, anchor, local, mySize) {
            return el.getAnchorXY(anchor, local, mySize);
        },

        /**
         * Gets the bottom Y coordinate of the element (element Y position + element height)
         * @param {Boolean} local True to get the local css position instead of page
         * coordinate
         * @return {Number}
         * @deprecated
         */
        getBottom: function(local) {
            return (local ? this.getLocalY() : this.getY()) + this.getHeight();
        },

        getBorderPadding: function() {
            var paddingWidth = this.getStyle(paddingsTLRB),
                bordersWidth = this.getStyle(bordersTLRB);

            return {
                beforeX: (parseFloat(bordersWidth[borders.l]) || 0) + (parseFloat(paddingWidth[paddings.l]) || 0),
                afterX: (parseFloat(bordersWidth[borders.r]) || 0) + (parseFloat(paddingWidth[paddings.r]) || 0),
                beforeY: (parseFloat(bordersWidth[borders.t]) || 0) + (parseFloat(paddingWidth[paddings.t]) || 0),
                afterY: (parseFloat(bordersWidth[borders.b]) || 0) + (parseFloat(paddingWidth[paddings.b]) || 0)
            };
        },

        /**
         * Calculates the x, y to center this element on the screen
         * @return {Number[]} The x, y values [x, y]
         * @deprecated
         */
        getCenterXY: function(){
            return this.getAlignToXY(doc, 'c-c');
        },

        /**
         * Gets the left X coordinate
         * @param {Boolean} local True to get the local css position instead of
         * page coordinate
         * @return {Number}
         * @deprecated Use {@link #getX} or {@link #getLocalX}
         */
        getLeft: function(local) {
            return local ? this.getLocalX() : this.getX();
        },

        /**
         * Gets the local CSS X position for the element
         *
         * @return {Number}
         */
        getLocalX: function() {
            var me = this,
                offsetParent = me.dom.offsetParent,
                x = me.getStyle('left');

            if (!x || x === 'auto') {
                x = 0;
            } else if (me.pxRe.test(x)) {
                x = parseFloat(x);
            } else {
                x = me.getX();
                if (offsetParent) {
                    x -= Element.getX(offsetParent);
                }
            }

            return x;
        },

        /**
         * Gets the local CSS X and Y position for the element
         *
         * @return {Array} [x, y]
         */
        getLocalXY: function() {
            var me = this,
                offsetParent = me.dom.offsetParent,
                style = me.getStyle(['left', 'top']),
                x = style.left,
                y = style.top;

            if (!x || x === 'auto') {
                x = 0;
            } else if (me.pxRe.test(x)) {
                x = parseFloat(x);
            } else {
                x = me.getX();
                if (offsetParent) {
                    x -= Element.getX(offsetParent);
                }
            }

            if (!y || y === 'auto') {
                y = 0;
            } else if (me.pxRe.test(y)) {
                y = parseFloat(y);
            } else {
                y = me.getY();
                if (offsetParent) {
                    y -= Element.getY(offsetParent);
                }
            }

            return [x, y];
        },

        /**
         * Gets the local CSS Y position for the element
         *
         * @return {Number}
         */
        getLocalY: function() {
            var me = this,
                offsetParent = me.dom.offsetParent,
                y = me.getStyle('top');

            if (!y || y === 'auto') {
                y = 0;
            } else if (me.pxRe.test(y)) {
                y = parseFloat(y);
            } else {
                y = me.getY();
                if (offsetParent) {
                    y -= Element.getY(offsetParent);
                }
            }

            return y;
        },

        /**
         * Returns an object defining the area of this Element which can be passed to
         * {@link Ext.util.Positionable#setBox} to set another Element's size/location to match this element.
         *
         * @param {Boolean} [asRegion] If true an Ext.util.Region will be returned
         * @return {Object/Ext.util.Region} box An object in the following format:
         *
         *     {
         *         left: <Element's X position>,
         *         top: <Element's Y position>,
         *         width: <Element's width>,
         *         height: <Element's height>,
         *         bottom: <Element's lower bound>,
         *         right: <Element's rightmost bound>
         *     }
         *
         * The returned object may also be addressed as an Array where index 0 contains
         * the X position and index 1 contains the Y position. So the result may also be
         * used for {@link #setXY}
         * @deprecated use {@link Ext.util.Positionable#getBox} to get a box object, and
         * {@link Ext.util.Positionable#getRegion} to get a {@link Ext.util.Region Region}.
         */
        getPageBox: function(getRegion) {
            var me = this,
                dom = me.dom,
                isDoc = dom.nodeName == BODY,
                w = isDoc ? Ext.Element.getViewWidth() : dom.offsetWidth,
                h = isDoc ? Ext.Element.getViewHeight() : dom.offsetHeight,
                xy = me.getXY(),
                t = xy[1],
                r = xy[0] + w,
                b = xy[1] + h,
                l = xy[0];

            if (getRegion) {
                return new Ext.util.Region(t, r, b, l);
            }
            else {
                return {
                    left: l,
                    top: t,
                    width: w,
                    height: h,
                    right: r,
                    bottom: b
                };
            }
        },

        /**
         * Gets an object with all CSS positioning properties. Useful along with
         * #setPostioning to get snapshot before performing an update and then restoring
         * the element.
         * @param {Boolean} [autoPx=false] true to return pixel values for "auto" styles.
         * @return {Object}
         */
        getPositioning: function(autoPx){
            var styles = this.getStyle(['left', 'top', 'position', 'z-index']),
                dom = this.dom;

            if(autoPx) {
                if(styles.left === 'auto') {
                    styles.left = dom.offsetLeft + 'px';
                }
                if(styles.top === 'auto') {
                    styles.top = dom.offsetTop + 'px';
                }
            }

            return styles;
        },

        /**
         * Gets the right X coordinate of the element (element X position + element width)
         * @param {Boolean} local True to get the local css position instead of page
         * coordinates
         * @return {Number}
         * @deprecated
         */
        getRight: function(local) {
            return (local ? this.getLocalX() : this.getX()) + this.getWidth();
        },

        /**
         * Gets the top Y coordinate
         * @param {Boolean} local True to get the local css position instead of page
         * coordinates
         * @return {Number}
         * @deprecated Use {@link #getY} or {@link #getLocalY}
         */
        getTop: function(local) {
            return local ? this.getLocalY() : this.getY();
        },

        /**
         * Gets element X position in page coordinates
         *
         * @return {Number}
         */
        getX: function() {
            return Element.getX(this.dom);
        },

        /**
         * Gets element X and Y positions in page coordinates
         *
         * @return {Array} [x, y]
         */
        getXY: function() {
            return Element.getXY(this.dom);
        },

        /**
         * Gets element Y position in page coordinates
         *
         * @return {Number}
         */
        getY: function() {
            return Element.getY(this.dom);
        },

        /**
         * Sets the position of the element in page coordinates.
         * @param {Number} x X value for new position (coordinates are page-based)
         * @param {Number} y Y value for new position (coordinates are page-based)
         * @param {Boolean/Object} [animate] True for the default animation, or a standard
         * Element animation config object
         * @return {Ext.dom.Element} this
         * @deprecated Use {@link #setXY} instead.
         */
        moveTo: function(x, y, animate) {
            return this.setXY([x, y], animate);
        },

        /**
         * Initializes positioning on this element. If a desired position is not passed,
         * it will make the the element positioned relative IF it is not already positioned.
         * @param {String} [pos] Positioning to use "relative", "absolute" or "fixed"
         * @param {Number} [zIndex] The zIndex to apply
         * @param {Number} [x] Set the page X position
         * @param {Number} [y] Set the page Y position
         */
        position: function(pos, zIndex, x, y) {
            var me = this;

            if (!pos && me.isStyle(POSITION, STATIC)) {
                me.setStyle(POSITION, RELATIVE);
            } else if (pos) {
                me.setStyle(POSITION, pos);
            }
            if (zIndex) {
                me.setStyle(ZINDEX, zIndex);
            }
            if (x || y) {
                me.setXY([x || false, y || false]);
            }
        },

        /**
         * Sets the element's CSS bottom style.
         * @param {Number/String} bottom Number of pixels or CSS string value to set as
         * the bottom CSS property value
         * @return {Ext.dom.Element} this
         * @deprecated
         */
        setBottom: function(bottom) {
            this.dom.style[BOTTOM] = this.addUnits(bottom);
            return this;
        },

        /**
         * Sets the element's position and size in one shot. If animation is true then
         * width, height, x and y will be animated concurrently.
         *
         * @param {Number} x X value for new position (coordinates are page-based)
         * @param {Number} y Y value for new position (coordinates are page-based)
         * @param {Number/String} width The new width. This may be one of:
         *
         * - A Number specifying the new width in this Element's
         * {@link #defaultUnit}s (by default, pixels)
         * - A String used to set the CSS width style. Animation may **not** be used.
         *
         * @param {Number/String} height The new height. This may be one of:
         *
         * - A Number specifying the new height in this Element's
         * {@link #defaultUnit}s (by default, pixels)
         * - A String used to set the CSS height style. Animation may **not** be used.
         *
         * @param {Boolean/Object} [animate] true for the default animation or
         * a standard Element animation config object
         *
         * @return {Ext.dom.Element} this
         * @deprecated Use {@link Ext.util.Positionable#setBox} instead.
         */
        setBounds: function(x, y, width, height, animate) {
            return this.setBox({
                x: x,
                y: y,
                width: width,
                height: height
            }, animate);
        },

        /**
         * Sets the element's left position directly using CSS style
         * (instead of {@link #setX}).
         * @param {Number/String} left Number of pixels or CSS string value to
         * set as the left CSS property value
         * @return {Ext.dom.Element} this
         * @deprecated
         */
        setLeft: function(left) {
            this.dom.style[LEFT] = this.addUnits(left); 
            return this;
        },

        /**
         * Sets the element's left and top positions directly using CSS style
         * @param {Number/String} left Number of pixels or CSS string value to
         * set as the left CSS property value
         * @param {Number/String} top Number of pixels or CSS string value to
         * set as the top CSS property value
         * @return {Ext.dom.Element} this
         * @deprecated
         */
        setLeftTop: function(left, top) {
            var me = this,
                style = me.dom.style;

            style.left = me.addUnits(left);
            style.top = me.addUnits(top);

            return me;
        },

        setLocalX: function(x) {
            var style = this.dom.style;

            // clear right style just in case it was previously set by rtlSetXY/rtlSetLocalXY
            style.right = 'auto';
            style.left = (x === null) ? 'auto' : x + 'px';
        },

        setLocalXY: function(x, y) {
            var style = this.dom.style;

            // clear right style just in case it was previously set by rtlSetXY/rtlSetLocalXY
            style.right = 'auto';

            if (x && x.length) {
                y = x[1];
                x = x[0];
            }

            if (x === null) {
                style.left = 'auto';
            } else if (x !== undefined) {
                style.left = x + 'px';
            }

            if (y === null) {
                style.top = 'auto';
            } else if (y !== undefined) {
                style.top = y + 'px';
            }
        },

        setLocalY: function(y) {
            this.dom.style.top = (y === null) ? 'auto' : y + 'px';
        },

        /**
         * Sets the position of the element in page coordinates.
         * @param {Number} x X value for new position
         * @param {Number} y Y value for new position
         * @param {Boolean/Object} [animate] True for the default animation, or a standard
         * Element animation config object
         * @return {Ext.dom.Element} this
         * @deprecated Use {@link #setXY} instead.
         */
        setLocation: function(x, y, animate) {
            return this.setXY([x, y], animate);
        },

        /**
         * Set positioning with an object returned by #getPositioning.
         * @param {Object} posCfg
         * @return {Ext.dom.Element} this
         */
        setPositioning: function(pc) {
            return this.setStyle(pc);
        },

        /**
         * Sets the element's CSS right style.
         * @param {Number/String} right Number of pixels or CSS string value to
         * set as the right CSS property value
         * @return {Ext.dom.Element} this
         * @deprecated
         */
        setRight: function(right) {
            this.dom.style[RIGHT] = this.addUnits(right);
            return this;
        },

        /**
         * Sets the element's top position directly using CSS style
         * (instead of {@link #setY}).
         * @param {Number/String} top Number of pixels or CSS string value to
         * set as the top CSS property value
         * @return {Ext.dom.Element} this
         * @deprecated
         */
        setTop: function(top) {
            this.dom.style[TOP] = this.addUnits(top);
            return this;
        },

        setX: function(x, animate) {
            return this.setXY([x, this.getY()], animate);
        },

        setXY: function(xy, animate) {
            var me = this;

            if (!animate || !me.anim) {
                Element.setXY(me.dom, xy);
            } else {
                if (!Ext.isObject(animate)) {
                    animate = {};
                }
                me.animate(Ext.applyIf({ to: { x: xy[0], y: xy[1] } }, animate));
            }
            return this;
        },

        setY: function(y, animate) {
            return this.setXY([this.getX(), y], animate);
        }
    });

    /**
     * @private
     * Returns the `X,Y` position of the passed element in browser document space without regard
     * to any RTL direction settings.
     */
    Element.getTrueXY = Element.getXY;

});