/* ===========================================================================
 *
 * Ext.ux.ScrollPane
 * Version 1.0
 * Parses URLs and provides easy access to information within them.
 *
 * Author: Nanotron
 *
 * ---------------------------------------------------------------------------
 *
 * CREDITS:
 *
 * Based on jScrollPane Plugin by Kelvin Luck (http://www.kelvinluck.com/assets/jquery/jScrollPane/jScrollPane.html).
 *
 * ---------------------------------------------------------------------------
 *
 * LICENCE:
 *
 * Released under the MIT Licence (http://www.opensource.org/licenses/mit-license.php).
 *
 * ---------------------------------------------------------------------------
 *
 * EXAMPLES OF USE:
 *
 * new Ext.ux.ScrollPane('pane1', {scrollbarWidth:9});
 *
 *
 * ---------------------------------------------------------------------------
 *
 * ToDo:
 *
 * Scroll-Animation
 *
 */


Ext.ns('Ext.ux');

Ext.ux.ScrollPane = Ext.extend(Ext.util.Observable, {
    isScrollPane: false,
    dragPosition: 0,

    scrollbarWidth : 10,
	scrollbarMargin : 5,
	wheelSpeed : 18,
	showArrows : true,
	arrowSize : 0,
	animateTo : false,
	dragMinHeight : 1,
	dragMaxHeight : 99999,
	animateInterval : 100,
	animateStep: 3,
	maintainPosition: false,
	scrollbarOnLeft: false,
	reinitialiseOnImageLoad: false,
	tabIndex : 0,
	enableKeyboardNavigation: true,
	animateToInternalLinks: false,
	topCapHeight: 0,
	bottomCapHeight: 0,
    monitorZoom: false,
    monitorDelay:200,
    zoomLevel: 1,

	// Our class constructor
    constructor : function(element, config) {
        Ext.apply(this, config);
        Ext.ux.ScrollPane.superclass.constructor.call(this);

        this.addEvents(
            'update'
        );

        this.el = Ext.get(element);
        this.init();
        if(this.monitorZoom)
        {
            this.z1 = Ext.getBody().createChild({
                style: 'left:-100%;width:10px;height:10px;position:absolute;',
                id: 'zoom1'
            }, null, true);
            this.z2 = Ext.getBody().createChild({
                style: 'width:10px;height:10px;position:absolute;',
                id: 'zoom2'
            }, null, true);
            this.z2.style.left = this.z1.offsetLeft + 'px';
            Ext.TaskMgr.start({
                run: this.monitorTask,
                interval: this.monitorDelay || 200,
                scope: this
            });
        }
    },

    init: function()
    {
        var el = this.el, currentScrollPosition = 0;
        el.setVisible(true);
        if (!this.isScrollPane) {
            this.originalStyleTag = el.getAttribute('style');
            this.originalPadding = el.getPadding('t') + 'px ' + el.getPadding('r') + 'px ' + el.getPadding('b') + 'px ' + el.getPadding('l') + 'px';
            this.originalSidePaddingTotal = el.getPadding('lr');
            this.paneWidth = el.getWidth();// - this.originalSidePaddingTotal;
            this.paneHeight = el.getHeight();// - el.getPadding('tb');
            this.container = el.wrap({
                cls: 'jScrollPaneContainer',
                style: 'height:' + this.paneHeight + 'px;width:' + this.paneWidth + 'px',
                tabindex: this.tabIndex
            });
        }
        else
        {
            var d = this.offsetParent(el);
            currentScrollPosition = this.maintainPosition ? el.getOffsetsTo(d)[1] : 0;
        }
        this.getDimensions();

        if (this.percentInView < .99) {
            this.setScrollElementsDisplayed(true);
            this.initMarkup();
            this.el.setStyle({'position':'absolute', 'overflow':'visible'});
            var dragH = Math.max(Math.min(this.percentInView*(this.paneHeight-this.arrowSize*2), this.dragMaxHeight), this.dragMinHeight);
            this.drag.setHeight(dragH);
            this.initDrag();
            this.registerHandler();
        }
        else
        {
            this.el.setStyle(
                {
                    'height': this.paneHeight+'px',
                    'width': this.paneWidth - this.originalSidePaddingTotal + 'px',
                    'padding': this.originalPadding
                }
            );
            this.setScrollElementsDisplayed(false);
        }
        this.scrollTo(-currentScrollPosition);
        this.isScrollPane = true;
    },

    setScrollElementsDisplayed: function(display)
    {
        if(this.track)
        {
            this.track.setDisplayed(display);
            this.scrollCapTop.setDisplayed(display);
            this.scrollCapBottom.setDisplayed(display);
        }
        if(this.upArrow)
        {
            this.upArrow.setDisplayed(display);
            this.downArrow.setDisplayed(display);
        }
    },

    getDimensions: function()
    {
        var el =  this.el;

        var p = this.originalSidePaddingTotal;
        var realPaneWidth = this.paneWidth - this.scrollbarWidth - this.scrollbarMargin - p;

        var cssToApply = {
            'height':'auto',
            'width': realPaneWidth + 'px'
        }
        if(this.scrollbarOnLeft) {
            cssToApply.paddingLeft = this.scrollbarMargin + this.scrollbarWidth + 'px';
        } else {
            cssToApply.paddingRight = this.scrollbarMargin + 'px';
        }

        el.setStyle(cssToApply);

        this.contentHeight = el.getHeight();
        this.percentInView = this.paneHeight / this.contentHeight;
        this.dragMiddle = this.percentInView * this.paneHeight/2;
    },

    initMarkup: function()
    {
        var trackOffset = this.topCapHeight;
        this.scrollCapTop = this.scrollCapTop || this.container.createChild({
            tag: 'div',
            cls: 'jScrollCap jScrollCapTop',
            style: 'height: ' + this.topCapHeight + 'px'
        });
        this.track = this.track || this.container.createChild({
            tag: 'div',
            cls: 'jScrollPaneTrack',
            style: 'width: ' + this.scrollbarWidth + 'px',
            cn: [{
                tag: 'div',
                cls: 'jScrollPaneDrag',
                style: 'width: ' + this.scrollbarWidth + 'px',
                cn: [{
                    tag: 'div',
                    cls: 'jScrollPaneDragTop',
                    style: 'width: ' + this.scrollbarWidth + 'px'
                },{
                    tag: 'div',
                    cls: 'jScrollPaneDragBottom',
                    style: 'width: ' + this.scrollbarWidth + 'px'
                }]
            }
            ]
        });
        this.scrollCapBottom = this.scrollCapBottom || this.container.createChild({
            tag: 'div',
            cls: 'jScrollCap jScrollCapBottom',
            style: 'height: ' + this.bottomCapHeight + 'px'
        });

        this.drag = this.track.child('.jScrollPaneDrag');

        if (this.showArrows) {
            this.upArrow = this.upArrow || this.container.createChild({
                tag: 'a',
                cls: 'jScrollArrowUp',
                href: 'javascript:;',
                tabindex:-1,
                style: 'width:' + this.scrollbarWidth+'px;top:' +this.topCapHeight + 'px',
                html: 'Scroll up'
            });
            this.downArrow = this.downArrow || this.container.createChild({
                tag: 'a',
                cls: 'jScrollArrowDown',
                href: 'javascript:;',
                tabindex:-1,
                style: 'width:' + this.scrollbarWidth+'px;bottom:' +this.bottomCapHeight + 'px',
                html: 'Scroll up'
            });
        }

        if (this.arrowSize) {
            this.trackHeight = this.paneHeight - this.arrowSize - this.arrowSize;
            trackOffset += this.arrowSize;
        } else if (this.upArrow) {
            var topArrowHeight = this.upArrow.getHeight();
            this.arrowSize = topArrowHeight;
            this.trackHeight = this.paneHeight - topArrowHeight - this.downArrow.getHeight();
            trackOffset += topArrowHeight;
        }
        this.trackHeight -= this.topCapHeight + this.bottomCapHeight;
        this.track.setStyle({'height': this.trackHeight+'px', top:trackOffset+'px'})
    },

    registerHandler: function()
    {
        var dir = 1, scrollTask;
        var toPos;

        var posFn = function()
        {
            return this.dragPosition + dir * this.mouseWheelMultiplier;
        }

        var fn = function(fn){
            fn = fn || posFn;
            toPos = fn.call(this);
            //console.log(toPos)
            this.positionDrag(toPos);
        };
        var fnTask = function(pFn, intervall)
        {
            fn.call(this, pFn );
            scrollTask = Ext.TaskMgr.start({
                run: fn,
                interval: intervall || 100,
                args: [pFn],
                scope: this
            });
        }

        this.downArrow.on({
            'mousedown': {
                fn: function(ev){
                    dir = 1;
                    fnTask.call(this);
                },
                stopEvent: true,
                scope: this
            },
            'click' : function() {return false;}
        });

        this.upArrow.on({
            'mousedown': {
                fn: function(ev){
                    dir = -1;
                    fnTask.call(this);
                },
                stopEvent: true,
                scope: this
            },
            'click' : function() {return false;}
        });

        var onDragMouseMove = function(ev)
        {
            this.dragPos = ev.getPageY() - this.currentOffsetTop - this.dragMiddle;
        };

        var posFnDrag = function()
        {
            return this.dragPos || this.dragPosition;
        };

        var ignoreNativeDrag = function() {return false;};

        this.drag.on({
             'mousedown': {
                fn: function(ev)
                {
                    if (Ext.isIE) {
						Ext.select('html').on({
                            'dragstart': ignoreNativeDrag,
                            'selectstart': ignoreNativeDrag
                        });
                    }
                    this.initDrag();
                    this.dragMiddle = ev.getPageY() - this.dragPosition - this.currentOffsetTop;
					Ext.select('html').on('mousemove', onDragMouseMove, this, {stopEvent: true});
                    fnTask.call(this, posFnDrag, 2);
                },
                scope: this
            },
            'click' : function() {return false;}
        });

        var onTrackMouseMove = function(ev)
        {
            this.trackScrollMousePos = ev.getPageY() - this.currentOffsetTop - this.dragMiddle;
        };

        var posFn3 = function()
        {
            return this.dragPosition - ((this.dragPosition - this.trackScrollMousePos) / 2);
        };

        this.track.on({
            'mousedown': {
                fn: function(ev)
                {
                    if(!ev.getTarget('.jScrollPaneDrag') && this.percentInView < 0.99)
                    {
                        this.initDrag();
                        onTrackMouseMove.call(this, ev);
                        fnTask.call(this, posFn3);
                        Ext.select('html').on('mousemove', onTrackMouseMove, this);
                    }
                },
                scope: this,
                stopEvent: true
            },
            'click' : function() {return false;}
        });

        var onTextSelectionScrollMouseMove= function(ev)
        {
            var offset = this.el.parent().getTop();
            var maxOffset = offset + this.paneHeight;
            var mouseOffset = ev.getPageY();
            this.textDragDistanceAway = mouseOffset < offset ? mouseOffset - offset : (mouseOffset > maxOffset ? mouseOffset - maxOffset : 0);
        };

        var textSelectionPos = function()
        {
            var direction = this.textDragDistanceAway < 0 ? -1 : 1;
            var currentPos = -parseInt(this.el.getStyle('top')) || 0;
			var pos = currentPos + this.textDragDistanceAway / 2;
            var maxScroll = this.contentHeight - this.paneHeight;
			pos = pos > maxScroll ? maxScroll : pos;
			return pos/maxScroll * this.maxY;
        }

        this.container.on({
            'keydown': {
                fn: function(ev){
                    var key = ev.getKey();
                    if(key == 38 && this.percentInView < 0.99)
                    {
                        dir = -1;
                        fn.call(this);
                        ev.stopEvent();
                    }
                    else if(key == 40 && this.percentInView < 0.99)
                    {
                        dir = 1;
                        fn.call(this);
                        ev.stopEvent();
                    }
                },
                scope: this
            },
            'mousewheel': {
                fn: function(ev)
                {
                    if(this.percentInView < 0.99)
                    {
                        this.event = ev;
                        dir = ev.getWheelDelta() > 0 ? -1 : 1;
                        fn.call(this);
                    }
                },
                scope: this
                //stopEvent: true
            },
            'mousedown': {
                fn: function(ev){
                    if(!ev.getTarget('.jScrollPaneTrack') && this.percentInView < 0.99){
                        this.mouseScrollType = 'pane';
                        fnTask.call(this, textSelectionPos);
                        Ext.select('html').on('mousemove', onTextSelectionScrollMouseMove, this);
                    }
                },
                scope: this
            }

        });

        Ext.select('html').on({
            'mouseup': {
                fn: function() {
                    Ext.select('html').un('mousemove', onDragMouseMove, this);
                    Ext.select('html').un('mousemove', onTrackMouseMove, this);
                    Ext.select('html').un('mousemove', onTextSelectionScrollMouseMove, this);
                    this.dragMiddle = this.percentInView*this.paneHeight/2;
                    if(scrollTask)
                        Ext.TaskMgr.stop(scrollTask);
                },
                scope: this
            }
        });

        this.el.select('*').on({
            'focus': {
                fn: function(event, el)
                {
                    var e = Ext.get(el), d;

                    var eleTop = 0;

                    while (e.dom != this.el.dom) {
                        d = this.offsetParent(e);
                        eleTop += e.getOffsetsTo(d)[1];
                        e = d;
                    }

                    var viewportTop = -parseInt(this.el.getStyle('top')) || 0;
                    var maxVisibleEleTop = viewportTop + this.paneHeight;
                    var eleInView = eleTop > viewportTop && eleTop < maxVisibleEleTop;
                    if (!eleInView) {
                        var destPos = eleTop - this.scrollbarMargin;
                        if (eleTop > viewportTop) { // element is below viewport - scroll so it is at bottom.
                            destPos += Ext.fly(el).getHeight() + 15 + this.scrollbarMargin - this.paneHeight;
                        }
                        this.scrollTo(destPos);
                    }
                },
                scope: this
            }
        })
    },

    initDrag: function()
    {
        this.currentOffsetTop = this.drag.getTop();
        this.currentOffsetTop -= this.dragPosition;
        this.maxY = this.trackHeight - this.drag.dom.offsetHeight;
        this.mouseWheelMultiplier = 2 * this.wheelSpeed * this.maxY / this.contentHeight;
    },

    positionDrag: function(destY)
    {
        //console.log(destY)
        if(isNaN(destY))
            return;
        this.container.dom.scrollTop = 0;
        destY = destY < 0 ? 0 : (destY > this.maxY ? this.maxY : destY);
        if(this.event && destY > 0 && destY < this.maxY && destY != this.dragPosition)
        {
            this.event.stopEvent();
        }
        if(destY == this.dragPosition)
            return;
        this.dragPosition = destY;
        this.drag.setTop(destY);
        var p = destY / this.maxY;
        this.el.setTop((this.paneHeight - this.contentHeight)*p);
    },

    scrollTo: function(pos, preventAni)
    {
        if(this.contentHeight > this.paneHeight)
        {
            var maxScroll = this.contentHeight - this.paneHeight;
            pos = pos > maxScroll ? maxScroll : pos;
            var destDragPosition = pos/maxScroll * this.maxY;
        }
        else
            var destDragPosition = 0;
        if (preventAni || !this.animateTo) {
            this.positionDrag(destDragPosition);
        } else {

            this.positionDrag(destDragPosition);
        }
    },

    monitorTask: function()
    {
        var zoomLevel = this.z2.offsetLeft / this.z1.offsetLeft;
        if(this.zoomLevel != zoomLevel){
            this.init();
        }
    },

    offsetParent: function(el) {
		var offsetParent = el.dom.offsetParent || document.body;
		while ( offsetParent && (!/^body|html$/i.test(offsetParent.tagName) && Ext.fly(offsetParent).getStyle('position') == 'static') )
			offsetParent = offsetParent.offsetParent;
		return Ext.get(offsetParent);
	}
});