/**
 * @preserve jQuery Lazy Scroll Loading Plugin %Revision%
 * 
 * http://plugins.jquery.com/jQueryLazyScrollLoading/
 * https://code.google.com/p/jquerylazyscrollloading/
 * 
 * Apache License 2.0 - http://www.apache.org/licenses/LICENSE-2.0
 * 
 * @author Dreamltf
 * @date %BuiltDate%
 * 
 * Depends: jquery.js (1.2+)
 */
(function($) {
	/* static variables */
	var PLUGIN_NAMESPACE = "LazyScrollLoading";
	var PLUGIN_LAZYIMAGE_ATTR = "data-lazyimg";

	/* default options */
	var defaultOptions = {
		"isDefaultLazyImageMode" : false,
		"lazyItemSelector" : null,
		"delay" : 500,
		"resetScrollBarPositionAfterEventTriggered" : true,
		/* callback function */
		"onCreate" : null,
		"onScroll" : null,
		"onLazyItemFirstVisible" : null,
		"onLazyItemVisible" : null,
		"onLazyItemInvisible" : null,
		"onScrollVertically" : null,
		"onScrollHorizontally" : null,
		"onScrollUp" : null,
		"onScrollDown" : null,
		"onScrollLeft" : null,
		"onScrollRight" : null,
		"onScrollToTop" : null,
		"onScrollToBottom" : null,
		"onScrollToLeftmost" : null,
		"onScrollToRightmost" : null
	};

	/* others variables */
	var userAgentStr = navigator.userAgent.toLowerCase();
	var isIE = /msie/.test(userAgentStr);
	var IEVersion = (isIE ? parseFloat((userAgentStr.match(/.*(?:rv|ie)[\/: ](.+?)([ \);]|$)/) || [])[1]) : -1);
	var scrollBarSize = 16;

	/**
	 * Public Method
	 */
	$.extend($.fn, {
		/**
		 * Public : Main method
		 * 
		 * @param options
		 *            Object
		 * @return jQuery
		 */
		"lazyScrollLoading" : function(options) {
			options = $.extend({}, defaultOptions, options);
			/* correct options */
			if (options.isDefaultLazyImageMode && options.lazyItemSelector == null) {
				options.lazyItemSelector = "img[" + PLUGIN_LAZYIMAGE_ATTR + "]:not([src])";
			}
			/* starting */
			return this.each(function() {
				var $container = $(this);
				/* destroy */
				$container.destroyLazyScrollLoading();
				/* prepare options */
				$container.data("options." + PLUGIN_NAMESPACE, options);
				/* initialize */
				initializeLazyScrollLoading($container, options);
				/* trigger event */
				if (options.onCreate != null) {
					options.onCreate.apply($container[0]);
				}
			});
		},

		/**
		 * Public : Get container's options
		 * 
		 * @return Object
		 */
		"getLazyScrollLoadingOptions" : function() {
			return this.data("options." + PLUGIN_NAMESPACE);
		},

		/**
		 * Public : Get container's scroll history
		 * 
		 * @return Object
		 */
		"getLazyScrollLoadingScrollHistory" : function() {
			return this.data("scrollHistory." + PLUGIN_NAMESPACE);
		},

		/**
		 * Public : Clear container's scroll history
		 * 
		 * @return jQuery
		 */
		"clearLazyScrollLoadingScrollHistory" : function() {
			return this.removeData("scrollHistory." + PLUGIN_NAMESPACE);
		},

		/**
		 * Public : Get container or lazyItem's viewport
		 * 
		 * @return Object
		 */
		"getLazyScrollLoadingViewport" : function() {
			var $container = this;
			var containerViewport = $container.data("viewport." + PLUGIN_NAMESPACE);
			if (containerViewport == null) {
				var container = $container[0];
				var isRoot = isRootContainer(container);
				var $window = $(window);
				var $document = $(document);
				var $body = $(document.body);
				containerViewport = {
					"getOffset" : function() {
						return (isRoot ? $body.offset() : $container.offset());
					},
					"getScrollLeft" : function() {
						return (isRoot ? $window : $container).scrollLeft();
					},
					"getScrollTop" : function() {
						return (isRoot ? $window : $container).scrollTop();
					},
					"getScrollBindTarget" : function() {
						return (isRoot ? $window : $container);
					},
					"getWidth" : function(isOuter) {
						return (isRoot ? $window.width() : (isOuter ? $container.outerWidth() : $container.innerWidth()));
					},
					"getHeight" : function(isOuter) {
						return (isRoot ? $window.height() : (isOuter ? $container.outerHeight() : $container.innerHeight()));
					},
					"getScrollWidth" : function(includeScrollBarSize) {
						return (isRoot ? $document.width() : container.scrollWidth) + (includeScrollBarSize && this.isVerticalScrollBarVisible() ? scrollBarSize : 0);
					},
					"getScrollHeight" : function(includeScrollBarSize) {
						return (isRoot ? $document.height() : container.scrollHeight) + (includeScrollBarSize && this.isHorizontalScrollBarVisible() ? scrollBarSize : 0);
					},
					"getLeftPos" : function() {
						return (isRoot ? this.getScrollLeft() : this.getOffset().left);
					},
					"getTopPos" : function() {
						return (isRoot ? this.getScrollTop() : this.getOffset().top);
					},
					"getRightPos" : function() {
						return this.getLeftPos() + this.getWidth(true);
					},
					"getBottomPos" : function() {
						return this.getTopPos() + this.getHeight(true);
					},
					"isVerticalScrollBarVisible" : function() {
						return (this.getHeight(false) < this.getScrollHeight(false));
					},
					"isHorizontalScrollBarVisible" : function() {
						return (this.getWidth(false) < this.getScrollWidth(false));
					},
					"isVerticalScrollBarScrolling" : function() {
						if (!this.isVerticalScrollBarVisible()) {
							return false;
						}
						var containerScrollHistory = $container.getLazyScrollLoadingScrollHistory();
						return (containerScrollHistory != null && containerScrollHistory.scrollTop != this.getScrollTop());
					},
					"isHorizontalScrollBarScrolling" : function() {
						if (!this.isHorizontalScrollBarVisible()) {
							return false;
						}
						var containerScrollHistory = $container.getLazyScrollLoadingScrollHistory();
						return (containerScrollHistory != null && containerScrollHistory.scrollLeft != this.getScrollLeft());
					},
					"isScrollUp" : function() {
						if (!this.isVerticalScrollBarVisible()) {
							return false;
						}
						var containerScrollHistory = $container.getLazyScrollLoadingScrollHistory();
						return (containerScrollHistory != null && containerScrollHistory.scrollTop > this.getScrollTop());
					},
					"isScrollDown" : function() {
						if (!this.isVerticalScrollBarVisible()) {
							return false;
						}
						var containerScrollHistory = $container.getLazyScrollLoadingScrollHistory();
						return (containerScrollHistory != null && containerScrollHistory.scrollTop < this.getScrollTop());
					},
					"isScrollLeft" : function() {
						if (!this.isHorizontalScrollBarVisible()) {
							return false;
						}
						var containerScrollHistory = $container.getLazyScrollLoadingScrollHistory();
						return (containerScrollHistory != null && containerScrollHistory.scrollLeft > this.getScrollLeft());
					},
					"isScrollRight" : function() {
						if (!this.isHorizontalScrollBarVisible()) {
							return false;
						}
						var containerScrollHistory = $container.getLazyScrollLoadingScrollHistory();
						return (containerScrollHistory != null && containerScrollHistory.scrollLeft < this.getScrollLeft());
					},
					"isScrollToTop" : function() {
						if (!this.isVerticalScrollBarVisible()) {
							return false;
						}
						return (this.getScrollTop() <= 0);
					},
					"isScrollToBottom" : function() {
						if (!this.isVerticalScrollBarVisible()) {
							return false;
						}
						return (this.getScrollTop() + this.getHeight(true) >= this.getScrollHeight(true));
					},
					"isScrollToLeftmost" : function() {
						if (!this.isHorizontalScrollBarVisible()) {
							return false;
						}
						return (this.getScrollLeft() <= 0);
					},
					"isScrollToRightmost" : function() {
						if (!this.isHorizontalScrollBarVisible()) {
							return false;
						}
						return (this.getScrollLeft() + this.getWidth(true) >= this.getScrollWidth(true));
					}
				};
			}
			$container.data("viewport." + PLUGIN_NAMESPACE, containerViewport)
			return containerViewport;
		},

		/**
		 * Public : Get container's cached lazy items
		 * 
		 * @param selector
		 *            String
		 * @return jQuery
		 */
		"getLazyScrollLoadingCachedLazyItems" : function(selector) {
			return this.pushStack($.map(this, function(container) {
				var $container = $(container);
				var options = $container.getLazyScrollLoadingOptions();
				var $lazyItems = $container.data("items." + PLUGIN_NAMESPACE);
				if (options != null && options.lazyItemSelector != null && $lazyItems == null) {
					/* cache lazy items if necessary */
					$lazyItems = $(options.lazyItemSelector, (isRootContainer(container) ? undefined : $container));
					$container.data("items." + PLUGIN_NAMESPACE, $lazyItems);
				}
				if ($lazyItems != null && selector != null) {
					$lazyItems = $lazyItems.filter(selector);
				}
				return ($lazyItems != null ? $lazyItems.get() : null);
			}));
		},

		/**
		 * Public : Clear container's cached lazy items
		 * 
		 * @return jQuery
		 */
		"clearLazyScrollLoadingCachedLazyItems" : function() {
			return this.removeData("items." + PLUGIN_NAMESPACE);
		},

		/**
		 * Public : Refresh container
		 * 
		 * @return jQuery
		 */
		"refreshLazyScrollLoading" : function() {
			return this.clearLazyScrollLoadingCachedLazyItems();
		},

		/**
		 * Public : Destroy LazyScrollLoading
		 * 
		 * @return jQuery
		 */
		"destroyLazyScrollLoading" : function() {
			/* yield event handler */
			return this.each(function() {
				var $container = $(this);
				/* reset event handler */
				$container.getLazyScrollLoadingViewport().getScrollBindTarget().unbind("scroll." + PLUGIN_NAMESPACE);
				/* clear cache */
				$container.getLazyScrollLoadingCachedLazyItems().removeData("isLoaded." + PLUGIN_NAMESPACE);
				$container.clearLazyScrollLoadingCachedLazyItems().clearLazyScrollLoadingScrollHistory().removeData("viewport." + PLUGIN_NAMESPACE).removeData("options." + PLUGIN_NAMESPACE);
			});
		},

		/**
		 * Public : Is lazy item loaded
		 * 
		 * @return boolean
		 */
		"isLazyScrollLoadingLazyItemLoaded" : function() {
			return this.data("isLoaded." + PLUGIN_NAMESPACE);
		},

		/**
		 * Public : Is lazy item loading
		 * 
		 * @return boolean
		 */
		"isLazyScrollLoadingLazyItemLoading" : function() {
			return this.data("isLoading." + PLUGIN_NAMESPACE);
		},

		/**
		 * Public : Is lazy item visible
		 * 
		 * @param $container
		 *            jQuery
		 * @return boolean
		 */
		"isLazyScrollLoadingLazyItemVisible" : function($container) {
			var lazyItemViewport = this.getLazyScrollLoadingViewport();
			var containerViewport = $container.getLazyScrollLoadingViewport();
			/* calculate isVisible by position */
			return (lazyItemViewport.getBottomPos() > containerViewport.getTopPos() && lazyItemViewport.getLeftPos() < containerViewport.getRightPos() && lazyItemViewport.getTopPos() < containerViewport.getBottomPos() && lazyItemViewport.getRightPos() > containerViewport.getLeftPos());
		}
	});

	/**
	 * Private : Is Root Container
	 */
	function isRootContainer(container) {
		return (container == window || container == document || container == document.body);
	}

	/**
	 * Private : Initialize LazyScrollLoading
	 */
	function initializeLazyScrollLoading($container, options) {
		var containerViewport = $container.getLazyScrollLoadingViewport();
		var $scrollBindTarget = containerViewport.getScrollBindTarget();
		/* starting */
		var isTimerOn = false;
		var timer = null;
		$scrollBindTarget.bind("scroll." + PLUGIN_NAMESPACE, function(e) {
			if (options.delay <= 0) {
				fireOnScrollEvent(e, $container, options);
			} else if (!isTimerOn) {
				isTimerOn = true;
				if (timer != null) {
					clearTimeout(timer);
				}
				timer = setTimeout(function() {
					fireOnScrollEvent(e, $container, options);
					/* clear timer */
					clearTimeout(timer);
					isTimerOn = false;
				}, options.delay);
			}
		});
		/* on first window loaded, for visible element only */
		/* IE version < 9 would not be triggered the onscroll event */
		if ((containerViewport.getScrollTop() <= 0 && containerViewport.getScrollLeft() <= 0) || (isIE && IEVersion < 9)) {
			$scrollBindTarget.trigger("scroll." + PLUGIN_NAMESPACE);
		}
	}

	/**
	 * Private : Fire OnScroll Event
	 */
	function fireOnScrollEvent(e, $container, options) {
		var $lazyItems = $container.getLazyScrollLoadingCachedLazyItems();
		var lazyItemVisibleArray = [];
		var lazyItemFirstVisibleArray = [];
		var lazyItemInvisibleArray = [];
		if (options.lazyItemSelector != null && (options.isDefaultLazyImageMode || options.onLazyItemFirstVisible != null || options.onLazyItemVisible != null || options.onLazyItemInvisible != null)) {
			$lazyItems.each(function() {
				var $lazyItem = $(this);
				/* is lazy item visible */
				if ($lazyItem.isLazyScrollLoadingLazyItemVisible($container)) {
					$lazyItem.data("isLoading." + PLUGIN_NAMESPACE, true);
					lazyItemVisibleArray.push(this);
					if (!$lazyItem.isLazyScrollLoadingLazyItemLoaded()) {
						$lazyItem.data("isLoaded." + PLUGIN_NAMESPACE, true);
						lazyItemFirstVisibleArray.push(this);
					}
				} else if ($lazyItem.isLazyScrollLoadingLazyItemLoading()) {
					$lazyItem.removeData("isLoading." + PLUGIN_NAMESPACE);
					lazyItemInvisibleArray.push(this);
				}
			});
			/* lazy image mode */
			if (options.isDefaultLazyImageMode) {
				for (var i = 0, lazyItemFirstVisibleArraySize = lazyItemFirstVisibleArray.length; i < lazyItemFirstVisibleArraySize; i++) {
					var lazyImageItem = lazyItemFirstVisibleArray[i];
					lazyImageItem.src = lazyImageItem.getAttribute(PLUGIN_LAZYIMAGE_ATTR);
				}
			}
		}
		/* keep the old scrollTop and scrollLeft */
		var containerViewport = $container.getLazyScrollLoadingViewport();
		var scrollTop = containerViewport.getScrollTop();
		var scrollLeft = containerViewport.getScrollLeft();
		var newScrollHistory = {
			"scrollTop" : scrollTop,
			"scrollLeft" : scrollLeft
		};
		triggerCallbackFunctions(e, $container, options, $lazyItems, lazyItemVisibleArray, lazyItemFirstVisibleArray, lazyItemInvisibleArray);
		/* reset the scrollbar after event triggered */
		if (options.resetScrollBarPositionAfterEventTriggered) {
			$container.scrollTop(scrollTop);
			$container.scrollLeft(scrollLeft);
		}
		/* reset history to record previous scroll position */
		$container.data("scrollHistory." + PLUGIN_NAMESPACE, newScrollHistory);
	}

	/**
	 * Private : Trigger Customized OnScroll Event
	 */
	function triggerCallbackFunctions(e, $container, options, $lazyItems, lazyItemVisibleArray, lazyItemFirstVisibleArray, lazyItemInvisibleArray) {
		var container = $container[0];
		/* trigger callback */
		if (options.onScroll != null) {
			options.onScroll.apply(container, [ e ]);
		}
		if (options.onScrollVertically != null || options.onScrollUp != null || options.onScrollDown != null || options.onScrollToTop != null || options.onScrollToBottom != null || options.onScrollHorizontally != null || options.onScrollLeft != null || options.onScrollRight != null
				|| options.onScrollToLeftmost != null || options.onScrollToRightmost != null) {
			var containerViewport = $container.getLazyScrollLoadingViewport();
			var defaultApplyParameter = [ e, $lazyItems ];
			if (containerViewport.isVerticalScrollBarScrolling()) {
				if (options.onScrollVertically != null) {
					options.onScrollVertically.apply(container, defaultApplyParameter);
				}
				if (options.onScrollUp != null && containerViewport.isScrollUp()) {
					options.onScrollUp.apply(container, defaultApplyParameter);
				}
				if (options.onScrollDown != null && containerViewport.isScrollDown()) {
					options.onScrollDown.apply(container, defaultApplyParameter);
				}
				if (options.onScrollToTop != null && containerViewport.isScrollToTop()) {
					options.onScrollToTop.apply(container, defaultApplyParameter);
				}
				if (options.onScrollToBottom != null && containerViewport.isScrollToBottom()) {
					options.onScrollToBottom.apply(container, defaultApplyParameter);
				}
			}
			if (containerViewport.isHorizontalScrollBarScrolling()) {
				if (options.onScrollHorizontally != null) {
					options.onScrollHorizontally.apply(container, defaultApplyParameter);
				}
				if (options.onScrollLeft != null && containerViewport.isScrollLeft()) {
					options.onScrollLeft.apply(container, defaultApplyParameter);
				}
				if (options.onScrollRight != null && containerViewport.isScrollRight()) {
					options.onScrollRight.apply(container, defaultApplyParameter);
				}
				if (options.onScrollToLeftmost != null && containerViewport.isScrollToLeftmost()) {
					options.onScrollToLeftmost.apply(container, defaultApplyParameter);
				}
				if (options.onScrollToRightmost != null && containerViewport.isScrollToRightmost()) {
					options.onScrollToRightmost.apply(container, defaultApplyParameter);
				}
			}
		}
		if (options.onLazyItemFirstVisible != null && lazyItemFirstVisibleArray.length > 0) {
			options.onLazyItemFirstVisible.apply(container, [ e, $lazyItems, $container.pushStack(lazyItemFirstVisibleArray) ]);
		}
		if (options.onLazyItemVisible != null && lazyItemVisibleArray.length > 0) {
			options.onLazyItemVisible.apply(container, [ e, $lazyItems, $container.pushStack(lazyItemVisibleArray) ]);
		}
		if (options.onLazyItemInvisible != null && lazyItemInvisibleArray.length > 0) {
			options.onLazyItemInvisible.apply(container, [ e, $lazyItems, $container.pushStack(lazyItemInvisibleArray) ]);
		}
	}

})(jQuery);