(function($) {

    $.fn.onScreen = function(options) {
        
        var params = {
            direction: 'vertical',
            toggleClass: null,
            doOnce: false,
            doIn: null,
            doOut: null,
            tolerance: 0,
            tolerance_out: 0,
            throttle: null
        };

        if (options !== 'remove') {
            $.extend(params, options);
        }

        return this.each(function() {

            var self = this;
            var isOnScreen = false; // Initialize boolean
            var scrollTop; // Initialize Vertical Scroll Position
            var scrollLeft; // Initialize Horizontal Scroll Position
            var $el = $(this); // Matched element

            // Initialize Viewport dimensions
            var $container;
            var containerHeight;
            var containerWidth;
            var containerBottom;
            var containerRight;

            // Initialize element dimensions & position
            var elHeight;
            var elWidth;
            var elTop;
            var elLeft;

            // Remove bindings from onScreen container
            function remove() {
                $(self).off('scroll.onScreen resize.onScreen');
                $(window).off('resize.onScreen');
            }

            function verticalIn() {
                return (elTop - params.tolerance < scrollTop + (window.innerHeight || document.documentElement.clientHeight))
                    && (elTop + elHeight > scrollTop - params.tolerance );
            }

            function verticalOut() {
                return (elTop + params.tolerance > scrollTop + (window.innerHeight || document.documentElement.clientHeight))
                    || (elTop + elHeight < scrollTop - params.tolerance);
            }

            function horizontalIn() {
                return (elLeft - params.tolerance < scrollLeft + (window.innerWidth || document.documentElement.clientWidth))
                && (elLeft + elWidth > scrollLeft - params.tolerance );
            }

            function horizontalOut() {
                return (elLeft + params.tolerance > scrollLeft + (window.innerWidth || document.documentElement.clientWidth))
                || (elLeft + elWidth < scrollLeft - params.tolerance );
            }

            function directionIn() {
                if (isOnScreen) {
                    return false;
                }

                if (params.direction === 'horizontal') {
                    return horizontalIn();
                } else {
                    return verticalIn();
                }
            }

            function directionOut() {
                if (!isOnScreen) {
                    return false;
                }
                if (params.direction === 'horizontal') {
                    return horizontalOut();
                } else {
                    return verticalOut();
                }
            }

            function throttle(fn, timeout, ctx) {
                var timer, args, needInvoke;
                return function() {
                    args = arguments;
                    needInvoke = true;
                    ctx = ctx || this;
                    if (!timer) {
                        (function() {
                            if (needInvoke) {
                                fn.apply(ctx, args);
                                needInvoke = false;
                                timer = setTimeout(arguments.callee, timeout);
                            } else {
                                timer = null;
                            }
                        })();
                    }

                };

            }

            if (options === 'remove') {
                remove();
                return;
            }

            var checkPos = function( event ) {
                // Make container relative
                if (event){
                    if (event.type === 'resize'){
                        $el.loaded = false;
                    } else if ((params.doOnce === true) && (event.type === 'scroll') && ($el.loaded)){
                        return false;
                    }
                }
                // Update Viewport dimensions
                $container = $(params.container);
                containerHeight = $container.height();
                containerWidth = $container.width();
                containerBottom = $container.scrollTop() + containerHeight;
                containerRight = $container.scrollLeft() + containerWidth;

                // Update scroll position
                scrollTop = $container.scrollTop();
                scrollLeft = $container.scrollLeft();

                // Update element dimensions & position
                elHeight = $el.outerHeight(true);
                elWidth = $el.outerWidth(true);

                var position = $el.position();
                elTop = position.top;
                elLeft = position.left;

                $elpp=$el; $elp=$el.offsetParent();

                while ( ($elp[0].nodeName!=='HTML') && ($elpp!==$elp) ) {
                var elppos = $elp.position();
                    elTop = elTop + elppos.top;
                    elLeft = elLeft + elppos.left;
                    $elpp = $elp; $elp=$elp.offsetParent();
                }

                // This will spam A LOT of messages in your console

                if (directionIn()) {
                    if (params.toggleClass) {
                        $el.addClass(params.toggleClass);
                    }
                    if ($.isFunction(params.doIn)) {
                        params.doIn.call($el[0]);
                    }
                    isOnScreen = true;
                    $el.loaded = true;
                } else if (directionOut()) {
                    if (params.toggleClass) {
                        $el.removeClass(params.toggleClass);
                    }
                    if ($.isFunction(params.doOut)) {
                        params.doOut.call($el[0]);
                    }
                    isOnScreen = false;
                }
            };
            
            if (window.location.hash) {
                throttle(checkPos, 50);
            } else {
                checkPos();
            }

            if (params.throttle) {
                checkPos = throttle(checkPos, params.throttle);
            }

            // Attach checkPos
            $(params.container).on('scroll.onScreen resize.onScreen',{reset:false}, checkPos);
            $(window).on('resize.onScreen',{reset:true},checkPos);

            // Module support
            if (typeof module === 'object' && module && typeof module.exports === 'object') {
                // Node.js module pattern
                module.exports = jQuery;
            } else {
                // AMD
                if (typeof define === 'function' && define.amd) {
                    define('jquery-onscreen', [], function() {
                        return jQuery;
                    });
                }
            }

        });
    };

})(jQuery);