/*! Copyright © 2011 Piotr Rochala (rocha.la)

* Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php)
* and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses.
*
* Version: 1.3.0
*
*/

(function ($) {

jQuery.fn.extend({
    slimScroll: function (options) {

        var defaults = {

            // width in pixels of the visible scroll area
            width: 'auto',

            // height in pixels of the visible scroll area
            height: '250px',

            // width in pixels of the scrollbar and rail
            size: '7px',

            // scrollbar color, accepts any hex/color value
            color: '#000',

            // scrollbar position - left/right
            position: 'right',

            // distance in pixels between the side edge and the scrollbar
            distance: '1px',

            // default scroll position on load - top / bottom / $('selector')
            start: 'top',

            // sets scrollbar opacity
            opacity: .4,

            // enables always-on mode for the scrollbar
            alwaysVisible: false,

            // check if we should hide the scrollbar when user is hovering over
            disableFadeOut: false,

            // sets visibility of the rail
            railVisible: false,

            // sets rail color
            railColor: '#333',

            // sets rail opacity
            railOpacity: .2,

            // whether  we should use jQuery UI Draggable to enable bar dragging
            railDraggable: true,

            // defautlt CSS class of the slimscroll rail
            railClass: 'slimScrollRail',

            // defautlt CSS class of the slimscroll bar
            barClass: 'slimScrollBar',

            // defautlt CSS class of the slimscroll wrapper
            wrapperClass: 'slimScrollDiv',

            // check if mousewheel should scroll the window if we reach top/bottom
            allowPageScroll: false,

            // scroll amount applied to each mouse wheel step
            wheelStep: 20,

            // scroll amount applied when user is using gestures
            touchScrollStep: 200,

            // sets border radius
            borderRadius: '7px',

            // sets border radius of the rail
            railBorderRadius: '7px'
        };

        var o = $.extend(defaults, options);

        // do it for every element that matches selector
        this.each(function () {

            var isOverPanel, isOverBar, isDragg, queueHide, touchDif,
              barHeight, percentScroll, lastScroll,
              divS = '<div></div>',
              minBarHeight = 30,
              releaseScroll = false;

            // used in event handlers and for better minification
            var me = $(this);

            // ensure we are not binding it again
            if (me.parent().hasClass(o.wrapperClass)) {
                // start from last bar position
                var offset = me.scrollTop();

                // find bar and rail
                bar = me.parent().find('.' + o.barClass);
                rail = me.parent().find('.' + o.railClass);

                getBarHeight();

                // check if we should scroll existing instance
                if ($.isPlainObject(options)) {
                    // Pass height: auto to an existing slimscroll object to force a resize after contents have changed
                    if ('height' in options && options.height == 'auto') {
                        me.parent().css('height', 'auto');
                        me.css('height', 'auto');
                        var height = me.parent().parent().height();
                        me.parent().css('height', height);
                        me.css('height', height);
                    }

                    if ('scrollTo' in options) {
                        // jump to a static point
                        offset = parseInt(o.scrollTo);
                    }
                    else if ('scrollBy' in options) {
                        // jump by value pixels
                        offset += parseInt(o.scrollBy);
                    }
                    else if ('destroy' in options) {
                        // remove slimscroll elements
                        bar.remove();
                        rail.remove();
                        me.unwrap();
                        return;
                    }

                    // scroll content by the given offset
                    scrollContent(offset, false, true);
                }

                return;
            }

            // optionally set height to the parent's height
            o.height = (o.height == 'auto') ? me.parent().height() : o.height;

            // wrap content
            var wrapper = $(divS)
              .addClass(o.wrapperClass)
              .css({
                  position: 'relative',
                  overflow: 'hidden',
                  width: o.width,
                  height: o.height
              });

            // update style for the div
            me.css({
                overflow: 'hidden',
                width: o.width,
                height: o.height
            });

            // create scrollbar rail
            var rail = $(divS)
              .addClass(o.railClass)
              .css({
                  width: o.size,
                  height: '100%',
                  position: 'absolute',
                  top: 0,
                  display: (o.alwaysVisible && o.railVisible) ? 'block' : 'none',
                  'border-radius': o.railBorderRadius,
                  background: o.railColor,
                  opacity: o.railOpacity,
                  zIndex: 90
              });

            // create scrollbar
            var bar = $(divS)
              .addClass(o.barClass)
              .css({
                  background: o.color,
                  width: o.size,
                  position: 'absolute',
                  top: 0,
                  opacity: o.opacity,
                  display: o.alwaysVisible ? 'block' : 'none',
                  'border-radius': o.borderRadius,
                  BorderRadius: o.borderRadius,
                  MozBorderRadius: o.borderRadius,
                  WebkitBorderRadius: o.borderRadius,
                  zIndex: 99
              });

            // set position
            var posCss = (o.position == 'right') ? { right: o.distance } : { left: o.distance };
            rail.css(posCss);
            bar.css(posCss);

            // wrap it
            me.wrap(wrapper);

            // append to parent div
            me.parent().append(bar);
            me.parent().append(rail);

            // make it draggable and no longer dependent on the jqueryUI
            if (o.railDraggable) {
                bar.bind("mousedown", function (e) {
                    var $doc = $(document);
                    isDragg = true;
                    t = parseFloat(bar.css('top'));
                    pageY = e.pageY;

                    $doc.bind("mousemove.slimscroll", function (e) {
                        currTop = t + e.pageY - pageY;
                        bar.css('top', currTop);
                        scrollContent(0, bar.position().top, false);// scroll content
                    });

                    $doc.bind("mouseup.slimscroll", function (e) {
                        isDragg = false; hideBar();
                        $doc.unbind('.slimscroll');
                    });
                    return false;
                }).bind("selectstart.slimscroll", function (e) {
                    e.stopPropagation();
                    e.preventDefault();
                    return false;
                });
            }

            // on rail over
            rail.hover(function () {
                showBar();
            }, function () {
                hideBar();
            });

            // on bar over
            bar.hover(function () {
                isOverBar = true;
            }, function () {
                isOverBar = false;
            });

            // show on parent mouseover
            me.hover(function () {
                isOverPanel = true;
                showBar();
                hideBar();
            }, function () {
                isOverPanel = false;
                hideBar();
            });

            // support for mobile
            me.bind('touchstart', function (e, b) {
                if (e.originalEvent.touches.length) {
                    // record where touch started
                    touchDif = e.originalEvent.touches[0].pageY;
                }
            });

            me.bind('touchmove', function (e) {
                // prevent scrolling the page if necessary
                if (!releaseScroll) {
                    e.originalEvent.preventDefault();
                }
                if (e.originalEvent.touches.length) {
                    // see how far user swiped
                    var diff = (touchDif - e.originalEvent.touches[0].pageY) / o.touchScrollStep;
                    // scroll content
                    scrollContent(diff, true);
                    touchDif = e.originalEvent.touches[0].pageY;
                }
            });

            // set up initial height
            getBarHeight();

            // check start position
            if (o.start === 'bottom') {
                // scroll content to bottom
                bar.css({ top: me.outerHeight() - bar.outerHeight() });
                scrollContent(0, true);
            }
            else if (o.start !== 'top') {
                // assume jQuery selector
                scrollContent($(o.start).position().top, null, true);

                // make sure bar stays hidden
                if (!o.alwaysVisible) { bar.hide(); }
            }

            // attach scroll events
            attachWheel();

            function _onWheel(e) {
                // use mouse wheel only when mouse is over
                if (!isOverPanel) { return; }

                var e = e || window.event;

                var delta = 0;
                if (e.wheelDelta) { delta = -e.wheelDelta / 120; }
                if (e.detail) { delta = e.detail / 3; }

                var target = e.target || e.srcTarget || e.srcElement;
                /* console.log($(target).closest('.' + o.wrapperClass).attr("class"));
                 console.log(me.parent().attr("class"));*/
                if ($(target).closest('.' + o.wrapperClass).is(me.parent())) {
                    // scroll content

                    scrollContent(delta, true);
                }

                // stop window scroll
                if (e.preventDefault && !releaseScroll) { e.preventDefault(); }
                if (!releaseScroll) { e.returnValue = false; }
            }

            function scrollContent(y, isWheel, isJump) {

                releaseScroll = false;
                var delta = y;
                var maxTop = me.outerHeight() - bar.outerHeight();

                if (isWheel) {
                    // move bar with mouse wheel
                    delta = parseInt(bar.css('top')) + y * parseInt(o.wheelStep) / 100 * bar.outerHeight();

                    // move bar, make sure it doesn't go out
                    delta = Math.min(Math.max(delta, 0), maxTop);

                    // if scrolling down, make sure a fractional change to the
                    // scroll position isn't rounded away when the scrollbar's CSS is set
                    // this flooring of delta would happened automatically when
                    // bar.css is set below, but we floor here for clarity
                    delta = (y > 0) ? Math.ceil(delta) : Math.floor(delta);

                    // scroll the scrollbar
                    bar.css({ top: delta + 'px' });
                }

                // calculate actual scroll amount
                percentScroll = parseInt(bar.css('top')) / (me.outerHeight() - bar.outerHeight());
                delta = percentScroll * (me[0].scrollHeight - me.outerHeight());

                if (isJump) {
                    delta = y;
                    var offsetTop = delta / me[0].scrollHeight * me.outerHeight();
                    offsetTop = Math.min(Math.max(offsetTop, 0), maxTop);
                    bar.css({ top: offsetTop + 'px' });
                }

                // scroll content
                me.scrollTop(delta);

                // fire scrolling event
                me.trigger('slimscrolling', ~~delta);

                // ensure bar is visible
                showBar();

                // trigger hide when scroll is stopped
                hideBar();
            }

            function attachWheel() {
                if (window.addEventListener) {
                    this.addEventListener('DOMMouseScroll', _onWheel, false);
                    this.addEventListener('mousewheel', _onWheel, false);
                    //this.addEventListener('MozMousePixelScroll', _onWheel, false );
                }
                else {
                    document.attachEvent("onmousewheel", _onWheel)
                }
            }

            function getBarHeight() {
                // calculate scrollbar height and make sure it is not too small
                barHeight = Math.max((me.outerHeight() / me[0].scrollHeight) * me.outerHeight(), minBarHeight);
                bar.css({ height: barHeight + 'px' });

                // hide scrollbar if content is not long enough
                var display = barHeight == me.outerHeight() ? 'none' : 'block';
                bar.css({ display: display });
            }

            function showBar() {
                // recalculate bar height
                getBarHeight();
                clearTimeout(queueHide);

                // when bar reached top or bottom
                if (percentScroll == ~~percentScroll) {
                    //release wheel
                    releaseScroll = o.allowPageScroll;

                    // publish approporiate event
                    if (lastScroll != percentScroll) {
                        var msg = (~~percentScroll == 0) ? 'top' : 'bottom';
                        me.trigger('slimscroll', msg);
                    }
                }
                else {
                    releaseScroll = false;
                }
                lastScroll = percentScroll;

                // show only when required
                if (barHeight >= me.outerHeight()) {
                    //allow window scroll
                    releaseScroll = true;
                    return;
                }
                bar.stop(true, true).fadeIn('fast');
                if (o.railVisible) { rail.stop(true, true).fadeIn('fast'); }
            }

            function hideBar() {
                // only hide when options allow it
                if (!o.alwaysVisible) {
                    queueHide = setTimeout(function () {
                        if (!(o.disableFadeOut && isOverPanel) && !isOverBar && !isDragg) {
                            bar.fadeOut('slow');
                            rail.fadeOut('slow');
                        }
                    }, 1000);
                }
            }

        });

        // maintain chainability
        return this;
    }
});

jQuery.fn.extend({
    slimscroll: jQuery.fn.slimScroll
});

})(jQuery);