// @tag dom,core
/**
 */
Ext.define('Ext.dom.Element_scroll', {
    override: 'Ext.dom.Element',

    /**
     * Returns true if this element is scrollable.
     * @return {Boolean}
     */
    isScrollable: function() {
        var dom = this.dom;
        return dom.scrollHeight > dom.clientHeight || dom.scrollWidth > dom.clientWidth;
    },

    /**
     * Returns the current scroll position of the element.
     * @return {Object} An object containing the scroll position in the format
     * `{left: (scrollLeft), top: (scrollTop)}`
     */
    getScroll: function() {
        var me = this,
            dom = me.dom,
            doc = document,
            body = doc.body,
            docElement = doc.documentElement,
            left, top;

        if (dom === doc || dom === body) {
            // the scrollLeft/scrollTop may be either on the body or documentElement,
            // depending on browser. It is possible to use window.pageXOffset/pageYOffset
            // in most modern browsers but this complicates things when in rtl mode because
            // pageXOffset does not always behave the same as scrollLeft when direction is
            // rtl. (e.g. pageXOffset can be an offset from the right, while scrollLeft
            // is offset from the left, one can be positive and the other negative, etc.)
            // To avoid adding an extra layer of feature detection in rtl mode to deal with
            // these differences, it's best just to always use scrollLeft/scrollTop
            left = docElement.scrollLeft || (body ? body.scrollLeft : 0);
            top = docElement.scrollTop || (body ? body.scrollTop : 0);
        } else {
            left = dom.scrollLeft;
            top = dom.scrollTop;
        }

        return {
            left: left,
            top: top
        };
    },
    
    /**
     * Gets the left scroll position
     * @return {Number} The left scroll position
     */
    getScrollLeft: function() {
        var dom = this.dom,
            doc = document;
            
        if (dom === doc || dom === doc.body) {
            return this.getScroll().left;
        } else {
            return dom.scrollLeft;
        }
    },
    
    /**
     * Gets the top scroll position
     * @return {Number} The top scroll position
     */
    getScrollTop: function(){
        var dom = this.dom,
            doc = document;
            
        if (dom === doc || dom === doc.body) {
            return this.getScroll().top;
        } else {
            return dom.scrollTop;
        }
    },
    
    /**
     * Sets the left scroll position
     * @param {Number} left The left scroll position
     * @return {Ext.dom.Element} this
     */
    setScrollLeft: function(left){
        this.dom.scrollLeft = left;
        return this;
    },
    
    /**
     * Sets the top scroll position
     * @param {Number} top The top scroll position
     * @return {Ext.dom.Element} this
     */
    setScrollTop: function(top) {
        this.dom.scrollTop = top;
        return this;
    },

    /**
     * Scrolls this element by the passed delta values, optionally animating.
     * 
     * All of the following are equivalent:
     *
     *      el.scrollBy(10, 10, true);
     *      el.scrollBy([10, 10], true);
     *      el.scrollBy({ x: 10, y: 10 }, true);
     * 
     * @param {Number/Number[]/Object} deltaX Either the x delta, an Array specifying x and y deltas or
     * an object with "x" and "y" properties.
     * @param {Number/Boolean/Object} deltaY Either the y delta, or an animate flag or config object.
     * @param {Boolean/Object} animate Animate flag/config object if the delta values were passed separately.
     * @return {Ext.Element} this
     */
    scrollBy: function(deltaX, deltaY, animate) {
        var me = this,
            dom = me.dom;

        // 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) {
            me.scrollTo('left', me.constrainScrollLeft(dom.scrollLeft + deltaX), animate);
        }
        if (deltaY) {
            me.scrollTo('top', me.constrainScrollTop(dom.scrollTop + deltaY), animate);
        }

        return me;
    },

    /**
     * Scrolls this element the specified scroll point. It does NOT do bounds checking so
     * if you scroll to a weird value it will try to do it. For auto bounds checking, use #scroll.
     * @param {String} side Either "left" for scrollLeft values or "top" for scrollTop values.
     * @param {Number} value The new scroll value
     * @param {Boolean/Object} [animate] true for the default animation or a standard Element
     * animation config object
     * @return {Ext.Element} this
     */
    scrollTo: function(side, value, animate) {
        //check if we're scrolling top or left
        var top = /top/i.test(side),
            me = this,
            prop = top ? 'scrollTop' : 'scrollLeft',
            dom = me.dom,
            animCfg;

        if (!animate || !me.anim) {
            // just setting the value, so grab the direction
            dom[prop] = value;
            // corrects IE, other browsers will ignore
            dom[prop] = value;
        }
        else {
            animCfg = {
                to: {}
            };
            animCfg.to[prop] = value;
            if (Ext.isObject(animate)) {
                Ext.applyIf(animCfg, animate);
            }
            me.animate(animCfg);
        }
        return me;
    },

    /**
     * Scrolls this element into view within the passed container.
     * @param {String/HTMLElement/Ext.Element} [container=document.body] The container element
     * to scroll.  Should be a string (id), dom node, or Ext.Element.
     * @param {Boolean} [hscroll=true] False to disable horizontal scroll.
     * @param {Boolean/Object} [animate] true for the default animation or a standard Element
     * @param {Boolean} [highlight=false] true to {@link #highlight} the element when it is in view.
     * animation config object
     * @return {Ext.dom.Element} this
     */
    scrollIntoView: function(container, hscroll, animate, highlight) {
        var me = this,
            dom = me.dom,
            offsets = me.getOffsetsTo(container = Ext.getDom(container) || Ext.getBody().dom),
        // el's box
            left = offsets[0] + container.scrollLeft,
            top = offsets[1] + container.scrollTop,
            bottom = top + dom.offsetHeight,
            right = left + dom.offsetWidth,
        // ct's box
            ctClientHeight = container.clientHeight,
            ctScrollTop = parseInt(container.scrollTop, 10),
            ctScrollLeft = parseInt(container.scrollLeft, 10),
            ctBottom = ctScrollTop + ctClientHeight,
            ctRight = ctScrollLeft + container.clientWidth,
            newPos;

        // Highlight upon end of scroll
        if (highlight) {
            if (animate) {
                animate = Ext.apply({
                    listeners: {
                        afteranimate: function() {
                            me.scrollChildFly.attach(dom).highlight();
                        }
                    }
                }, animate);
            } else {
                me.scrollChildFly.attach(dom).highlight();
            }
        }

        if (dom.offsetHeight > ctClientHeight || top < ctScrollTop) {
            newPos = top;
        } else if (bottom > ctBottom) {
            newPos = bottom - ctClientHeight;
        }
        if (newPos != null) {
            me.scrollChildFly.attach(container).scrollTo('top', newPos, animate);
        }

        if (hscroll !== false) {
            newPos = null;
            if (dom.offsetWidth > container.clientWidth || left < ctScrollLeft) {
                newPos = left;
            } else if (right > ctRight) {
                newPos = right - container.clientWidth;
            }
            if (newPos != null) {
                me.scrollChildFly.attach(container).scrollTo('left', newPos, animate);
            }
        }
        return me;
    },

    // @private
    scrollChildIntoView: function(child, hscroll) {
        this.scrollChildFly.attach(Ext.getDom(child)).scrollIntoView(this, hscroll);
    },

    /**
     * Scrolls this element the specified direction. Does bounds checking to make sure the scroll is
     * within this element's scrollable range.
     * @param {String} direction Possible values are:
     *
     * - `"l"` (or `"left"`)
     * - `"r"` (or `"right"`)
     * - `"t"` (or `"top"`, or `"up"`)
     * - `"b"` (or `"bottom"`, or `"down"`)
     *
     * @param {Number} distance How far to scroll the element in pixels
     * @param {Boolean/Object} [animate] true for the default animation or a standard Element
     * animation config object
     * @return {Boolean} Returns true if a scroll was triggered or false if the element
     * was scrolled as far as it could go.
     */
    scroll: function(direction, distance, animate) {
        if (!this.isScrollable()) {
            return false;
        }
        
        // Allow full word, or initial to be sent.
        // (Ext.dd package uses full word)
        direction = direction.charAt(0);
        var me = this,
            dom = me.dom,
            side = direction === 'r' || direction === 'l' ? 'left' : 'top',
            scrolled = false,
            currentScroll, constrainedScroll;

        if (direction === 'l' || direction === 't' || direction === 'u') {
            distance = -distance;
        }

        if (side === 'left') {
            currentScroll = dom.scrollLeft;
            constrainedScroll = me.constrainScrollLeft(currentScroll + distance);
        } else {
            currentScroll = dom.scrollTop;
            constrainedScroll = me.constrainScrollTop(currentScroll + distance);
        }

        if (constrainedScroll !== currentScroll) {
            this.scrollTo(side, constrainedScroll, animate);
            scrolled = true;
        }

        return scrolled;
    },

    constrainScrollLeft: function(left) {
        var dom = this.dom;
        return Math.max(Math.min(left, dom.scrollWidth - dom.clientWidth), 0);
    },

    constrainScrollTop: function(top) {
        var dom = this.dom;
        return Math.max(Math.min(top, dom.scrollHeight - dom.clientHeight), 0);
    }
}, function() {
    this.prototype.scrollChildFly = new this.Fly();
    this.prototype.scrolltoFly = new this.Fly();
});