/**
 * Docked in an Ext.grid.Panel, controls virtualized scrolling and synchronization
 * across different sections.
 */
Ext.define('Ext.grid.Scroller', {
    extend: 'Ext.Component',
    alias: 'widget.gridscroller',
    weight: 110,
    baseCls: Ext.baseCSSPrefix + 'scroller',
    focusable: false,
    reservedSpace: 0,

    renderTpl: [
        '<div class="' + Ext.baseCSSPrefix + 'scroller-ct" id="{baseId}_ct">',
            '<div class="' + Ext.baseCSSPrefix + 'stretcher" id="{baseId}_stretch"></div>',
        '</div>'
    ],

    initComponent: function() {
        var me       = this,
            dock     = me.dock,
            cls      = Ext.baseCSSPrefix + 'scroller-vertical';

        me.offsets = {bottom: 0};
        me.scrollProp = 'scrollTop';
        me.vertical = true;
        me.sizeProp = 'width';

        if (dock === 'top' || dock === 'bottom') {
            cls = Ext.baseCSSPrefix + 'scroller-horizontal';
            me.sizeProp = 'height';
            me.scrollProp = 'scrollLeft';
            me.vertical = false;
            me.weight += 5;
        }

        me.cls += (' ' + cls);

        Ext.applyIf(me.renderSelectors, {
            stretchEl: '.' + Ext.baseCSSPrefix + 'stretcher',
            scrollEl: '.' + Ext.baseCSSPrefix + 'scroller-ct'
        });
        me.callParent();
    },
    
    ensureDimension: function(){
        var me = this,
            sizeProp = me.sizeProp;
            
        me[sizeProp] = me.scrollerSize = Ext.getScrollbarSize()[sizeProp];  
    },

    initRenderData: function () {
        var me = this,
            ret = me.callParent(arguments) || {};

        ret.baseId = me.id;

        return ret;
    },

    afterRender: function() {
        var me = this;
        me.callParent();
        
        me.mon(me.scrollEl, 'scroll', me.onElScroll, me);
        Ext.cache[me.el.id].skipGarbageCollection = true;
    },

    onAdded: function(container) {
        // Capture the controlling grid Panel so that we can use it even when we are undocked, and don't have an ownerCt
        this.ownerGrid = container;
        this.callParent(arguments);
    },

    getSizeCalculation: function() {
        var me     = this,
            owner  = me.getPanel(),
            width  = 1,
            height = 1,
            view, tbl;

        if (!me.vertical) {
            // TODO: Must gravitate to a single region..
            // Horizontal scrolling only scrolls virtualized region
            var items  = owner.query('tableview'),
                center = items[1] || items[0];

            if (!center) {
                return false;
            }
            // center is not guaranteed to have content, such as when there
            // are zero rows in the grid/tree. We read the width from the
            // headerCt instead.
            width = center.headerCt.getFullWidth();

            if (Ext.isIEQuirks) {
                width--;
            }
        } else {
            view = owner.down('tableview:not([lockableInjected])');
            if (!view || !view.el) {
                return false;
            }
            tbl = view.el.child('table', true);
            if (!tbl) {
                return false;
            }

            // needs to also account for header and scroller (if still in picture)
            // should calculate from headerCt.
            height = tbl.offsetHeight;
        }
        if (isNaN(width)) {
            width = 1;
        }
        if (isNaN(height)) {
            height = 1;
        }
        return {
            width: width,
            height: height
        };
    },

    invalidate: function(firstPass) {
        var me = this,
            stretchEl = me.stretchEl;

        if (!stretchEl || !me.ownerCt) {
            return;
        }

        var size  = me.getSizeCalculation(),
            scrollEl = me.scrollEl,
            elDom = scrollEl.dom,
            reservedSpace = me.reservedSpace,
            pos,
            extra = 5;

        if (size) {
            stretchEl.setSize(size);

            size = me.el.getSize(true);

            if (me.vertical) {
                size.width += extra;
                size.height -= reservedSpace;
                pos = 'left';
            } else {
                size.width -= reservedSpace;
                size.height += extra;
                pos = 'top';
            }

            scrollEl.setSize(size);
            elDom.style[pos] = (-extra) + 'px';

            // BrowserBug: IE7
            // This makes the scroller enabled, when initially rendering.
            elDom.scrollTop = elDom.scrollTop;
        }
    },

    afterComponentLayout: function() {
        this.callParent(arguments);
        this.invalidate();
    },

    restoreScrollPos: function () {
        var me = this,
            el = this.scrollEl,
            elDom = el && el.dom;

        if (me._scrollPos !== null && elDom) {
            elDom[me.scrollProp] = me._scrollPos;
            me._scrollPos = null;
        }
    },

    setReservedSpace: function (reservedSpace) {
        var me = this;
        if (me.reservedSpace !== reservedSpace) {
            me.reservedSpace = reservedSpace;
            me.invalidate();
        }
    },

    saveScrollPos: function () {
        var me = this,
            el = this.scrollEl,
            elDom = el && el.dom;

        me._scrollPos = elDom ? elDom[me.scrollProp] : null;
    },

    /**
     * Sets the scrollTop and constrains the value between 0 and max.
     * @param {Number} scrollTop
     * @return {Number} The resulting scrollTop value after being constrained
     */
    setScrollTop: function(scrollTop) {
        var el = this.scrollEl,
            elDom = el && el.dom;

        if (elDom) {
            return elDom.scrollTop = Ext.Number.constrain(scrollTop, 0, elDom.scrollHeight - elDom.clientHeight);
        }
    },

    /**
     * Sets the scrollLeft and constrains the value between 0 and max.
     * @param {Number} scrollLeft
     * @return {Number} The resulting scrollLeft value after being constrained
     */
    setScrollLeft: function(scrollLeft) {
        var el = this.scrollEl,
            elDom = el && el.dom;

        if (elDom) {
            return elDom.scrollLeft = Ext.Number.constrain(scrollLeft, 0, elDom.scrollWidth - elDom.clientWidth);
        }
    },

    /**
     * Scroll by deltaY
     * @param {Number} delta
     * @return {Number} The resulting scrollTop value
     */
    scrollByDeltaY: function(delta) {
        var el = this.scrollEl,
            elDom = el && el.dom;

        if (elDom) {
            return this.setScrollTop(elDom.scrollTop + delta);
        }
    },

    /**
     * Scroll by deltaX
     * @param {Number} delta
     * @return {Number} The resulting scrollLeft value
     */
    scrollByDeltaX: function(delta) {
        var el = this.scrollEl,
            elDom = el && el.dom;

        if (elDom) {
            return this.setScrollLeft(elDom.scrollLeft + delta);
        }
    },


    /**
     * Scroll to the top.
     */
    scrollToTop : function(){
        this.setScrollTop(0);
    },

    // synchronize the scroller with the bound gridviews
    onElScroll: function(event, target) {
        this.fireEvent('bodyscroll', event, target);
    },

    getPanel: function() {
        var me = this;
        if (!me.panel) {
            me.panel = this.up('[scrollerOwner]');
        }
        return me.panel;
    }
});