(function() {
    'use strict';
    var addInViewItem, checkInView, checkInViewDebounced, debounce, getViewportHeight, offsetTop, removeInViewItem, _checkInViewItems;

    angular.module('angular-inview', []).directive('inViewContainer', function() {
        return {
            restrict: 'AC',
            controller: function() {
                this.items = [];
                this.addItem = function(item) {
                    return this.items.push(item);
                };
                this.removeItem = function(item) {
                    var i;

                    return this.items = (function() {
                        var _i, _len, _ref, _results;

                        _ref = this.items;
                        _results = [];
                        for (_i = 0, _len = _ref.length; _i < _len; _i++) {
                            i = _ref[_i];
                            if (i !== item) {
                                _results.push(i);
                            }
                        }
                        return _results;
                    }).call(this);
                };
                return this;
            },
            link: function(scope, element, attrs, controller) {
                var check;

                check = debounce(function() {
                    return checkInView(controller.items);
                });
                element.bind('scroll', check);
                return scope.$on('$destroy', function() {
                    return element.unbind('scroll', check);
                });
            }
        };
    }).directive('inView', [
        '$parse', function($parse) {
            return {
                restrict: 'A',
                require: '?^inViewContainer',
                link: function(scope, element, attrs, container) {
                    var inViewFunc, item;

                    if (!attrs.inView) {
                        return;
                    }
                    inViewFunc = $parse(attrs.inView);
                    item = {
                        element: element,
                        wasInView: false,
                        offset: 0,
                        callback: function($inview, $inviewpart) {
                            var _this = this;

                            return scope.$apply(function() {
                                return inViewFunc(scope, {
                                    '$element': element[0],
                                    '$inview': $inview,
                                    '$inviewpart': $inviewpart
                                });
                            });
                        }
                    };
                    if (container != null) {
                        container.addItem(item);
                    }
                    if (attrs.inViewOffset != null) {
                        attrs.$observe('inViewOffset', function(offset) {
                            if (!angular.isNumber(offset)) {
                                offset = parseInt(offset);
                                if (isNaN(offset)) {
                                    offset = 0;
                                }
                            }
                            item.offset = offset;
                            return checkInViewDebounced();
                        });
                    }
                    addInViewItem(item);
                    checkInViewDebounced();
                    return scope.$on('$destroy', function() {
                        if (container != null) {
                            container.removeItem(item);
                        }
                        return removeInViewItem(item);
                    });
                }
            };
        }
    ]);

    getViewportHeight = function() {
        var height, mode, _ref;

        height = window.innerHeight;
        if (height) {
            return height;
        }
        mode = document.compatMode;
        if (mode || !(typeof $ !== "undefined" && $ !== null ? (_ref = $.support) != null ? _ref.boxModel : void 0 : void 0)) {
            height = mode === 'CSS1Compat' ? document.documentElement.clientHeight : document.body.clientHeight;
        }
        return height;
    };

    offsetTop = function(el) {
        var parent, result;

        result = 0;
        parent = el.parentElement;
        while (el) {
            result += el.offsetTop;
            el = el.offsetParent;
        }
        while (parent) {
            if (parent.scrollTop != null) {
                result -= parent.scrollTop;
            }
            parent = parent.parentElement;
        }
        return result;
    };

    _checkInViewItems = [];

    addInViewItem = function(item) {
        if (_checkInViewItems.length === 0) {
            angular.element(window).bind('checkInView click ready scroll resize', checkInViewDebounced);
        }
        return _checkInViewItems.push(item);
    };

    removeInViewItem = function(item) {
        var i;

        _checkInViewItems = (function() {
            var _i, _len, _results;

            _results = [];
            for (_i = 0, _len = _checkInViewItems.length; _i < _len; _i++) {
                i = _checkInViewItems[_i];
                if (i !== item) {
                    _results.push(i);
                }
            }
            return _results;
        })();
        if (_checkInViewItems.length === 0) {
            return angular.element(window).unbind('checkInView click ready scroll resize', checkInViewDebounced);
        }
    };

    checkInView = function(items) {
        var element, elementBottom, elementHeight, elementTop, inView, inViewWithOffset, inviewpart, isBottomVisible, isTopVisible, item, viewportBottom, viewportTop, _i, _len, _ref, _results;

        viewportTop = 0;
        viewportBottom = viewportTop + getViewportHeight();
        _results = [];
        for (_i = 0, _len = items.length; _i < _len; _i++) {
            item = items[_i];
            element = item.element[0];
            elementTop = offsetTop(element) + ((_ref = item.offset) != null ? _ref : 0);
            elementHeight = element.offsetHeight;
            elementBottom = elementTop + elementHeight;
            inView = elementTop > viewportTop && elementBottom < viewportBottom;
            isBottomVisible = elementBottom > viewportTop && elementTop < viewportTop;
            isTopVisible = elementTop < viewportBottom && elementBottom > viewportBottom;
            inViewWithOffset = inView || isBottomVisible || isTopVisible || (elementTop < viewportTop && elementBottom > viewportBottom);
            if (inViewWithOffset) {
                inviewpart = (isTopVisible && 'top') || (isBottomVisible && 'bottom') || 'both';
                if (!(item.wasInView && item.wasInView === inviewpart && element.offsetTop === item.lastOffsetTop)) {
                    item.lastOffsetTop = element.offsetTop;
                    item.wasInView = inviewpart;
                    _results.push(item.callback(true, inviewpart));
                } else {
                    _results.push(void 0);
                }
            } else if (!inView && item.wasInView) {
                item.wasInView = false;
                _results.push(item.callback(false));
            } else {
                _results.push(void 0);
            }
        }
        return _results;
    };

    debounce = function(f, t) {
        var timer;

        timer = null;
        return function() {
            if (timer != null) {
                clearTimeout(timer);
            }
            return timer = setTimeout(f, t != null ? t : 100);
        };
    };

    checkInViewDebounced = debounce(function() {
        return checkInView(_checkInViewItems);
    });

}).call(this);