Ext.define('Ext.rtl.dom.Element', {
    override: 'Ext.dom.Element',
 
    requires: [
        "Ext.CompositeElementLite"
    ],
    
    rtlXAnchors: {
        l: 'r',
        r: 'l'
    },
 
    _positionTopRight: ['position', 'top', 'right'],
 
    pxRe: /^\d+(?:\.\d*)?px$/i,
   
    statics: { 
        rtlParseBox: function (box) {
            var ret = Ext.Element.parseBox(box),
                temp;
               
            temp = ret.left;
            ret.left = ret.right;
            ret.right = temp;
            
            return ret;
        },
 
        rtlUnitizeBox: function(box, units){
            var Element = Ext.Element,
                a = Element.addUnits,
                b = Element.parseBox(box);
 
            // Usual order is trbl, so reverse it 
            // to return tlbr 
            return a(b.top, units) + ' ' +
                   a(b.left, units) + ' ' +
                   a(b.bottom, units) + ' ' +
                   a(b.right, units);
        }
    },
 
    anchorAnimX: function(anchor) {
        if (Ext.rootInheritedState.rtl) {
            anchor = this.rtlXAnchors[anchor];
        }
        this.callParent(arguments);
    },
 
    getPositioning: function(autoPx){
        var xStyle = Ext.rootInheritedState.rtl ? 'right' : 'left',
            styles = this.getStyle([xStyle, 'top', 'position', 'z-index']),
            dom = this.dom;
 
        if(autoPx) {
            if(styles[xStyle] === 'auto') {
                styles[xStyle] = (xStyle === 'left') ? (dom.offsetLeft + 'px') :
                    (dom.offsetParent.offsetWidth - dom.offsetLeft - dom.offsetWidth);
            }
            if(styles.top === 'auto') {
                styles.top = dom.offsetTop + 'px';
            }
        }
 
        return styles;
    },
 
    getXY: function() {
        var doc = document,
            round = Math.round,
            dom = this.dom,
            body = doc.body,
            x = 0,
            y = 0,
            bodyRect, rect;
 
        if(dom !== doc && dom !== body){
            // IE (including IE10) throws an error when getBoundingClientRect 
            // is called on an element not attached to dom 
            try {
                bodyRect = body.getBoundingClientRect();
                rect = dom.getBoundingClientRect();
 
                x = rect.left - bodyRect.left;
                y = rect.top - bodyRect.top;
 
                if (Ext.rootInheritedState.rtl) {
                    x = body.scrollWidth - rect.right;
                }
            } catch (ex) {}
        }
 
        return [round(x), round(y)];
    },
 
    rtlGetLocalX: function() {
        var me = this,
            offsetParent = me.dom.offsetParent,
            x = me.getStyle('right');
 
        if (!|| x === 'auto') {
            x = 0;
        } else if (me.pxRe.test(x)) {
            x = parseFloat(x);
        } else {
            x = me.getX();
            if (offsetParent) {
                x -= Ext.fly(offsetParent, '_internal').getX();
            }
        }
 
        return x;
    },
 
    rtlGetLocalXY: function() {
        var me = this,
            offsetParent = me.dom.offsetParent,
            style = me.getStyle(['right', 'top']),
            x = style.right,
            y = style.top;
 
        if (!|| x === 'auto') {
            x = 0;
        } else if (me.pxRe.test(x)) {
            x = parseFloat(x);
        } else {
            x = me.getX();
            if (offsetParent) {
                x -= Ext.fly(offsetParent, '_internal').getX();
            }
        }
 
        if (!|| y === 'auto') {
            y = 0;
        } else if (me.pxRe.test(y)) {
            y = parseFloat(y);
        } else {
            y = me.getY();
            if (offsetParent) {
                y -= Ext.fly(offsetParent, '_internal').getY();
            }
        }
 
        return [x, y];
    },
 
    rtlGetScroll: function() {
        var me = this,
            dom = me.dom,
            doc = document,
            body = doc.body,
            scroll = me.getScroll(),
            left = scroll.left,
            isDocOrBody = (dom === doc || dom === body);
 
        if (isDocOrBody ? (3 & me._rtlDocScrollFlag) : (me._rtlScrollFlag === 1)) { // jshint ignore:line 
            // If the browser reports scrollLeft as the number of pixels from left 
            // (same as ltr) we need to convert it to a rtl position by subtracting it 
            // from scrollWidth 
            if (isDocOrBody) {
                dom = body;
            }
            
            left = dom.scrollWidth - left -
                (isDocOrBody ? Ext.Element.getViewportWidth() : dom.clientWidth);
        }
        scroll.left = left;
 
        return scroll;
    },
    
    rtlGetScrollLeft: function() {
        return this.rtlGetScroll().left;
    },
 
    rtlNormalizeScrollLeft: function(left){
        var dom = this.dom,
            flag = this._rtlScrollFlag;
            
        if (flag === 0) {
            left = -left;
        } else if (flag === 1) {
            left = dom.scrollWidth - left - dom.clientWidth;
        }
        return left;
    },
 
    rtlScrollBy: function(deltaX, deltaY, animate) {
        var me = this,
            dom = me.dom,
            left;
 
        // Extract args if deltas were passed as an Array. 
        if (deltaX.length) {
            animate = deltaY;
            deltaY = deltaX[1];
            deltaX = deltaX[0];
        } else if (typeof deltaX !== 'number') { // or an object 
            animate = deltaY;
            deltaY = deltaX.y;
            deltaX = deltaX.x;
        }
       
        if (deltaX) {
            left = me.rtlNormalizeScrollLeft(
                me.constrainScrollLeft(me.rtlGetScrollLeft() + deltaX)
            ); 
 
            me.scrollTo('left', left, animate);
        }
        if (deltaY) {
            me.scrollTo('top', me.constrainScrollTop(dom.scrollTop + deltaY), animate);
        }
 
        return me;
    },
 
    rtlScrollIntoView: function(container, hscroll, animate, highlight) {
        container = Ext.getDom(container) || Ext.getBody().dom;
 
        return this.doScrollIntoView(
            container,
            hscroll,
            animate,
            highlight,
            'rtlGetScrollLeft',
            'rtlScrollTo'
        );
    },
 
    rtlScrollTo: function(side, value, animate) {
        if (side === 'left') {
            value = this.rtlNormalizeScrollLeft(value);
        }
 
        return this.scrollTo(side, value, animate);
    },
 
    rtlSetLocalX: function(x) {
        var me = this,
            style = me.dom.style;
 
        // clear left style just in case it was previously set by setXY/setLocalXY 
        style.left = 'auto';
        style.right = (=== null) ? 'auto' : x + 'px';
 
        if (me.shadow || me.shim) {
            me.syncUnderlays();
        }
 
        return me;
    },
 
    rtlSetLocalXY: function(x, y) {
        var me = this,
            style = me.dom.style;
 
        // clear left style just in case it was previously set by setXY/setLocalXY 
        style.left = 'auto';
 
        if (&& x.length) {
            y = x[1];
            x = x[0];
        }
 
        if (=== null) {
            style.right = 'auto';
        } else if (!== undefined) {
            style.right = x + 'px';
        }
 
        if (=== null) {
            style.top = 'auto';
        } else if (!== undefined) {
            style.top = y + 'px';
        }
 
        if (me.shadow || me.shim) {
            me.syncUnderlays();
        }
 
        return me;
    },
 
    rtlSetScrollLeft: function(left){
        var me = this;
 
        me.dom.scrollLeft = me.rtlNormalizeScrollLeft(left);
        return me;
    },
 
    rtlTranslatePoints: function(x, y) {
        var pos = this.rtlTranslateXY(x, y);
 
        return {
            right: pos.x,
            top: pos.y
        };
    },
 
    rtlTranslateXY: function(x, y) {
        var me = this,
            styles = me.getStyle(me._positionTopRight),
            relative = (styles.position === 'relative'),
            right = parseFloat(styles.right),
            top = parseFloat(styles.top),
            xy = me.getXY(),
            dom = me.dom,
            doc, body, offsetParentWidth, offsetParent;
 
        if (&& x.length) {
             y = x[1];
             x = x[0];
        }
        if (isNaN(right)) {
            doc = document;
            body = doc.body;
            if (dom === body) {
                // translateXY can sometimes be called on the body element. 
                // e.g. in Renderable#afterFirstLayout if the "container" is a viewport 
                right = 0;
            } else {
                offsetParent = dom.offsetParent;
                offsetParentWidth = (offsetParent &&
                    offsetParent !== body && offsetParent !== doc.documentElement) ?
                        offsetParent.scrollWidth : Ext.Element.getViewportWidth();
                right = offsetParentWidth - dom.offsetLeft - me.getWidth();
            }
        }
        if (isNaN(top)) {
            top = relative ? 0 : me.dom.offsetTop;
        }
        right = (typeof x === 'number') ? x - xy[0] + right : undefined;
        top = (typeof y === 'number') ? y - xy[1] + top : undefined;
        return {
            x: right,
            y: top
        };
    },
 
    translatePoints: function(x, y) {
        return Ext.rootInheritedState.rtl ? this.rtlTranslatePoints(x, y) :
            this.callParent(arguments);
    },
 
    translateXY: function(x, y) {
        return Ext.rootInheritedState.rtl ? this.rtlTranslateXY(x, y) :
            this.callParent(arguments);
    },
 
    wrap: function() {
        var wrapFly = (this.wrapFly || (this.wrapFly = new Ext.dom.Fly())).attach(this.dom.parentNode),
            rtlCls = Ext.baseCSSPrefix + 'rtl',
            ltrCls = Ext.baseCSSPrefix + 'ltr',
            wrapEl = this.callParent(arguments),
            cls;
 
        // if the parentNode of the element being wrapped has the "x-rtl" or "x-ltr" css 
        // class, then add that class to the wrapper as well.  This ensures that descendant 
        // and child selectors still apply e.g. ".x-rtl > .x-foo" or ".x-ltr .x-foo" 
        if (wrapFly.hasCls(rtlCls)) {
            cls = rtlCls;
        } else if (wrapFly.hasCls(ltrCls)) {
            cls = ltrCls;
        }
 
        if (cls) {
            // superclass method may return dom, so use fly() to access the wrap el 
            wrapFly.attach(wrapEl).addCls(cls);
        }
 
        return wrapEl;
    }
 
}, function() {
    var Element = this;
 
    // ensure that any methods added by this override are also added to Ext.CompositeElementLite 
    Ext.CompositeElementLite.importElementMethods();
 
    /*
     * Sets a _rtlScrollFlag property on the Element class prototype indicating how the
     * browser reports scrollLeft on overflowing rtl elements.  This method cannot be used
     * reliably on the documentElement or document.body because the behavior of these
     * elements can be different from other elements in some browsers.
     * 
     * 0: offset from right (negative number) - Firefox & Safari
     * 1: offset from left (positive number) - Webkit
     * 2: offset from right (positive number) - IE8 - IE10
     */
    function cacheRtlScrollFlag() {
        var el = Ext.getBody().createChild({
            tag: 'div',
            style: 'direction:rtl;position:absolute;overflow:auto;height:100px;width:100px;background-color:yellow',
            children: [{
                tag: 'div',
                style: 'height:30px;width:150px;background-color:red'
            }]
        }),
        dom = el.dom,
        inner = dom.firstChild,
        flag = 2;
 
        if (dom.scrollLeft === 50) {
            flag = 1;
        } else {
            dom.scrollLeft = -1;
            if (dom.scrollLeft) {
                flag = 0;
            }
        }
 
        // Make content overflow vertically to see where the vertical scsrollbar is 
        inner.style.width = '30px';
        inner.style.height = '150px';
 
        // Scrollbar is erroneously on right in RTL mode if the inner element is not flush against the right egde of the outer. 
        // Safaris suffers from this bug. 
        Element.prototype._rtlScrollbarOnRight = inner.getBoundingClientRect().right < dom.getBoundingClientRect().right;
 
        el.destroy();
        Element.prototype._rtlScrollFlag = flag;
    }
 
    /*
     * scrollLeft on the document/body is reported differently from ordinary
     * overflowing elements in many browsers (see getRtlScrollFlag).There are 2
     * separate things we have to detect:
     * 1. The element that overflows - when the document overflows some browsers
     * set scrollLeft on the document body (webkit), while other browsers set scrollLeft
     * on the documentElement (all other supported browsers at the time of this writing).
     * 2. The scrollLeft of the overflowing document/body can be one of the
     * following:
     *    a. number of pixels offset from right expressed as a negative number
     *       (Webkit, Firefox)
     *    b. number of pixels offset from right expressed as a positive number
     *       (IE8 - IE10)
     *
     * The following logic feture detects the handling of scrollLeft and sets the 
     * _rtlDocScrollFlag property on this class' prototype as a bit flag which has 
     * the following values:
     * 
     * 0 - docEl, negative right
     * 1 - docEl, positive left
     * 2 - docEl, positive right
     * 4 - body, negative right
     */
    function cacheRtlDocScrollFlag() {
        var doc = document,
            docEl = doc.documentElement,
            body = doc.body,
            // flag defaults to body, negative right (webkit) so no detection needed 
            // is needed for this scenario 
            flag = 4,
            bodyStyle = body.style,
            // save the direction property so we can set it back when we are done. 
            direction = bodyStyle.direction,
            el = Ext.getBody().createChild(
                '<div style="height:20000px;width:20000px;"></div>'
            );
 
        bodyStyle.direction = 'rtl';
 
        // First, check if scrollLeft is a non-zero value on the documentElement or 
        // body. This means scrollLeft is a positive number offset from the left. 
        if (docEl.scrollLeft > 0) {
            flag = 1;
        } else {
            // The next step is to attempt to set scrollLeft values, This allows us to 
            // test for non-zero values to see if the value was valid (scrollLeft 
            // resets to 0 when a non-valid value is set). 
            // attempt to set the documentElement's scrollLeft to a negative number 
            docEl.scrollLeft = -1;
            if (docEl.scrollLeft) {
                // it worked! we were able to set a negative scroll left on the 
                // documentElement (firefox) 
                flag = 0;
            } else {
                // attempt to set the documentElement's scrollLeft to a positive number 
                docEl.scrollLeft = 1;
                if (docEl.scrollLeft) {
                    // success setting scroll left to a positive number on 
                    // documentElement (IE8 & IE9) 
                    flag = 2;
                }
            }
        }
 
        el.destroy();
        if (!direction) {
            // if direction is an empty string, we set it back to "ltr", because once 
            // the direction style on the body element is changed to "rtl" in webkit, 
            // it becomes permanent, even after it is set back to "", unless it is first 
            // explicitly set back to "ltr" 
            bodyStyle.direction = 'ltr';
            // read the scroll width before setting the direction back to "". 
            // This forces webkit to update its computed direction style to ltr 
            body.scrollWidth; // jshint ignore:line 
        }
        // set direction back to its original value 
        bodyStyle.direction = direction;
        Element.prototype._rtlDocScrollFlag = flag;
    }
 
    Ext.onInternalReady(function () {
        // This function attaches to onReady with a priority of 1000 so that we can 
        // detect how the browser reports scrollLeft by manipulating the document/body 
        // before any components have been rendered to the page.   
        cacheRtlDocScrollFlag();
        cacheRtlScrollFlag();
    });
});