/*!
 * jQuery scrollintoview() plugin and :scrollable selector filter
 *
 * Version 1.9.4 (06 April 2016)
 * Requires jQuery 1.4 or newer
 *
 * Copyright (c) 2011 Robert Koritnik
 * Licensed under the terms of the MIT license
 * http://www.opensource.org/licenses/mit-license.php
 */

!function(root, factory) {
    if (typeof define === 'function' && define.amd) {
        define(['jquery'], factory);
    } else if (typeof exports === 'object') {
        factory(require('jquery'));
    } else {
        factory(root.jQuery);
    }
}
(this, function($) {
    var converter = {
        vertical: { x: false, y: true },
        horizontal: { x: true, y: false },
        both: { x: true, y: true },
        x: { x: true, y: false },
        y: { x: false, y: true }
    };

    var settings = {
        duration: "fast",
        direction: "both",
        viewPadding: 0
    };

    var rootrx = /^(?:html)$/i;

    // gets border dimensions
    var borders = function(domElement, styles) {
        styles = styles || (document.defaultView && document.defaultView.getComputedStyle ? document.defaultView.getComputedStyle(domElement, null) : domElement.currentStyle);
        var px = document.defaultView && document.defaultView.getComputedStyle ? true : false;
        var b = {
            top: (parseFloat(px ? styles.borderTopWidth : $.css(domElement, "borderTopWidth")) || 0),
            left: (parseFloat(px ? styles.borderLeftWidth : $.css(domElement, "borderLeftWidth")) || 0),
            bottom: (parseFloat(px ? styles.borderBottomWidth : $.css(domElement, "borderBottomWidth")) || 0),
            right: (parseFloat(px ? styles.borderRightWidth : $.css(domElement, "borderRightWidth")) || 0)
        };
        return {
            top: b.top,
            left: b.left,
            bottom: b.bottom,
            right: b.right,
            vertical: b.top + b.bottom,
            horizontal: b.left + b.right
        };
    };

    var dimensions = function($element) {
        var elem = $element[0],
            isRoot = rootrx.test(elem.nodeName),
            $elem = isRoot ? $(window) : $element;
        return {
            border: isRoot ? { top: 0, left: 0, bottom: 0, right: 0 } : borders(elem),
            scroll: {
                top: $elem.scrollTop(),
                left: $elem.scrollLeft(),
                maxtop: elem.scrollHeight - elem.clientHeight,
                maxleft: elem.scrollWidth - elem.clientWidth
            },
            scrollbar: isRoot
                ? { right: 0, bottom: 0 }
                : {
                    right: $elem.innerWidth() - elem.clientWidth,
                    bottom: $elem.innerHeight() - elem.clientHeight
                },
            rect: isRoot ? { top: 0, left: 0, bottom: elem.clientHeight, right: elem.clientWidth } : elem.getBoundingClientRect()
        };
    };

    $.fn.extend({
        scrollintoview: function(options) {
            /// <summary>Scrolls the first element in the set into view by scrolling its closest scrollable parent.</summary>
            /// <param name="options" type="Object">Additional options that can configure scrolling:
            ///        duration (default: "fast") - jQuery animation speed (can be a duration string or number of milliseconds)
            ///        direction (default: "both") - select possible scrollings ("vertical" or "y", "horizontal" or "x", "both")
            ///        complete (default: none) - a function to call when scrolling completes (called in context of the DOM element being scrolled)
            /// </param>
            /// <return type="jQuery">Returns the same jQuery set that this function was run on.</return>

            options = $.extend({}, settings, options);
            options.direction = converter[typeof (options.direction) === "string" && options.direction.toLowerCase()] || converter.both;

            if (typeof options.viewPadding == "number") {
                options.viewPadding = { x: options.viewPadding, y: options.viewPadding };
            } else if (typeof options.viewPadding == "object") {
                if (options.viewPadding.x == undefined) {
                    options.viewPadding.x = 0;
                }
                if (options.viewPadding.y == undefined) {
                    options.viewPadding.y = 0;
                }
            }

            var dirStr = "";
            if (options.direction.x === true) dirStr = "horizontal";
            if (options.direction.y === true) dirStr = dirStr ? "both" : "vertical";

            var el = this.eq(0);
            var scroller = el.parent().closest(":scrollable(" + dirStr + ")");

            // check if there's anything to scroll in the first place
            if (scroller.length > 0) {
                scroller = scroller.eq(0);

                var dim = {
                    e: dimensions(el),
                    s: dimensions(scroller)
                };

                var rel = {
                    top: dim.e.rect.top - (dim.s.rect.top + dim.s.border.top),
                    bottom: dim.s.rect.bottom - dim.s.border.bottom - dim.s.scrollbar.bottom - dim.e.rect.bottom,
                    left: dim.e.rect.left - (dim.s.rect.left + dim.s.border.left),
                    right: dim.s.rect.right - dim.s.border.right - dim.s.scrollbar.right - dim.e.rect.right
                };

                var animProperties = {};

                // vertical scroll
                if (options.direction.y === true) {
                    if (rel.top < 0) {
                        animProperties.scrollTop = Math.max(0, dim.s.scroll.top + rel.top - options.viewPadding.y);
                    } else if (rel.top > 0 && rel.bottom < 0) {
                        animProperties.scrollTop = Math.min(dim.s.scroll.top + Math.min(rel.top, -rel.bottom) + options.viewPadding.y, dim.s.scroll.maxtop);
                    }
                }

                // horizontal scroll
                if (options.direction.x === true) {
                    if (rel.left < 0) {
                        animProperties.scrollLeft = Math.max(0, dim.s.scroll.left + rel.left - options.viewPadding.x);
                    } else if (rel.left > 0 && rel.right < 0) {
                        animProperties.scrollLeft = Math.min(dim.s.scroll.left + Math.min(rel.left, -rel.right) + options.viewPadding.x, dim.s.scroll.maxleft);
                    }
                }

                // scroll if needed
                if (!$.isEmptyObject(animProperties)) {
                    var scrollExpect = {},
                        scrollListener = scroller;

                    if (rootrx.test(scroller[0].nodeName)) {
                        scroller = $("html,body");
                        scrollListener = $(window);
                    }

                    function animateStep(now, tween) {
                        scrollExpect[tween.prop] = Math.floor(now);
                    };

                    function onscroll(event) {
                        $.each(scrollExpect, function(key, value) {
                            if (Math.floor(scrollListener[key]()) != Math.floor(value)) {
                                options.complete = null; // don't run complete function if the scrolling was interrupted
                                scroller.stop('scrollintoview');
                            }
                        });
                    }

                    scrollListener.on('scroll', onscroll);

                    scroller
                        .stop('scrollintoview')
                        .animate(animProperties, { duration: options.duration, step: animateStep, queue: 'scrollintoview' })
                        .eq(0) // we want function to be called just once (ref. "html,body")
                        .queue('scrollintoview', function(next) {
                            scrollListener.off('scroll', onscroll);
                            $.isFunction(options.complete) && options.complete.call(scroller[0]);
                            next();
                        })

                    scroller.dequeue('scrollintoview');
                } else {
                    // when there's nothing to scroll, just call the "complete" function
                    $.isFunction(options.complete) && options.complete.call(scroller[0]);
                }
            }

            // return set back
            return this;
        }
    });

    var scrollValue = {
        auto: true,
        scroll: true,
        visible: false,
        hidden: false
    };

    var scroll = function(element, direction) {
        direction = converter[typeof (direction) === "string" && direction.toLowerCase()] || converter.both;
        var styles = (document.defaultView && document.defaultView.getComputedStyle ? document.defaultView.getComputedStyle(element, null) : element.currentStyle);
        var overflow = {
            x: scrollValue[styles.overflowX.toLowerCase()] || false,
            y: scrollValue[styles.overflowY.toLowerCase()] || false,
            isRoot: rootrx.test(element.nodeName)
        };

        // check if completely unscrollable (exclude HTML element because it's special)
        if (!overflow.x && !overflow.y && !overflow.isRoot) {
            return false;
        }

        var size = {
            height: {
                scroll: element.scrollHeight,
                client: element.clientHeight
            },
            width: {
                scroll: element.scrollWidth,
                client: element.clientWidth
            },
            // check overflow.x/y because iPad (and possibly other tablets) don't dislay scrollbars
            scrollableX: function() {
                return (overflow.x || overflow.isRoot) && this.width.scroll > this.width.client;
            },
            scrollableY: function() {
                return (overflow.y || overflow.isRoot) && this.height.scroll > this.height.client;
            }
        };
        return direction.y && size.scrollableY() || direction.x && size.scrollableX();
    };

    $.expr[":"].scrollable = $.expr.createPseudo(function(direction) {
        return function(element) {
            return scroll(element, direction);
        };
    });
});
