(function ($) {
	
$.fn.paginator = function(options) {
	var defaults = {
		innerWindow: 1, /*How many links are shown around the current page. */
		
		outerWindow: 0, /*How many links are around the first and the last page. */
		prevLabel: '<img src="template/icon_prev.png" alt="Previous Page" title="Previous Page" />',
		nextLabel: '<img src="template/icon_next.png" alt="Next Page" title="Next Page" />',
		separator: '&nbsp;',
		clickHandler: function(pageNum, event){} ,
		infoContainer: '.pageinfo',
		pagerContainer: '.pagination',
		currentPageClass: 'current',
		gap: '<span class="pager-gap">&hellip;</span>',
		currentPage: 1,
		totalPages: null,
		totalRecords:null,
		perPage: 2,
		objName: 'result',
		objNamePlural: 'results'
	}
	
	var self = this;
	options =  $.extend(defaults, options);

	var currentPage = options.currentPage;
	var totalPages = options.totalPages;

	function previousPage() {
		return currentPage > 1 ? (currentPage - 1) : null;
	}
	

	function nextPage() {
		return currentPage < totalPages ? (currentPage + 1) : null;
	}
	
	
	function firstPage() {
		 return 1;
	}
	
	function lastPage() {
		return totalPages;
	}
	
	function relValue(page) {
		switch (page) {
			case previousPage():
				return 'prev' + (page == 1 ? 'start' : '');
			case nextPage():
				return 'next';
			case 1:
				return 'start';
			default: 
				return '';
		}
	}
	
	return this.each(function() {
		var obj = $(this);
		/**
		* @returns {Array} The links for the visible page numbers.
		*/
		function windowedLinks() {
			var links = [];
			
			var prev = null;
			
			visible = visiblePageNumbers();
			for (var i = 0, l = visible.length; i < l; i++) {
				if (prev && visible[i] > prev + 1) 
					links.push(options.gap);
				if(visible[i] == currentPage) {
					links.push(pageLinkOrSpan(visible[i], ['',options.currentPageClass]));
				} else {
					links.push(pageLinkOrSpan(visible[i],[]));
				}
				prev = visible[i];
			}
			
			return links;
		}
		
		
		
		/**
		* @returns {Array} The visible page numbers according to the window options.
		*/ 
		function visiblePageNumbers() {
			var windowFrom = currentPage - options.innerWindow;
			var windowTo = currentPage + options.innerWindow;
			
			// If the window is truncated on one side, make the other side longer
			if (windowTo > totalPages) {
			  windowFrom = Math.max(0, windowFrom - (windowTo - totalPages));
			  windowTo = totalPages;
			}
			if (windowFrom < 1) {
			  windowTo = Math.min(totalPages, windowTo + (1 - windowFrom));
			  windowFrom = 1;
			}
			
			var visible = [];
			
			// Always show the first page
			visible.push(1);
			// Don't add inner window pages twice
			for (var i = 2; i <= Math.min(1 + options.outerWindow, windowFrom - 1); i++) {
			  visible.push(i);
			}
			// If the gap is just one page, close the gap
			if (1 + this.outerWindow == windowFrom - 2) {
			  visible.push(windowFrom - 1);
			}
			// Don't add the first or last page twice
			for (var i = Math.max(2, windowFrom); i <= Math.min(windowTo, totalPages - 1); i++) {
			  visible.push(i);
			}
			// If the gap is just one page, close the gap
			if (totalPages - options.outerWindow == windowTo + 2) {
			  visible.push(windowTo + 1);
			}
			// Don't add inner window pages twice
			for (var i = Math.max(totalPages - options.outerWindow, windowTo + 1); i < totalPages; i++) {
			  visible.push(i);
			}
			// Always show the last page, unless it's the first page
			if (totalPages > 1) {
			  visible.push(totalPages);
			}
			
			return visible;
		}
		
		/**
		* @param {Number} page A page number.
		* @param {String} classnames CSS classes to add to the page link.
		* @param {String} text The inner HTML of the page link (optional).
		* @returns The link or span for the given page.
		*/
		function pageLinkOrSpan(page, classnames, text, item) {
			text = text || page;
			var self = this;
			if (page && page != this.currentPage) {
				return $('<a />').html(text)
					.attr('rel', relValue(page))
					.addClass(classnames[1])
					.click(function(event) {
						options.clickHandler(page, event);
						this.currentPage = page;
						renderLinks(windowedLinks());
						renderInfo();
						
					});
			}
			else {
			  return $('<span/>').html(text).addClass(classnames.join(' '));
			}
		}
		
			
		function renderLinks(links) {
			if (totalPages) {
				links.unshift(pageLinkOrSpan(previousPage(), [ 'pager-disabled', 'pager-prev' ], options.prevLabel));
				//links.unshift(this.pageLinkOrSpan(this.firstPage(), [ 'pager-disabled', 'pager-first' ], this.firstLabel, 'first'));
				links.push(pageLinkOrSpan(nextPage(), [ 'pager-disabled', 'pager-next' ], options.nextLabel));
				//links.push(this.pageLinkOrSpan(this.lastPage(), [ 'pager-disabled', 'pager-last' ], this.lastLabel, 'last'));
				$(options.pagerContainer, obj).empty();
				for (var i = 0; i< links.length; i++) {
					$(options.pagerContainer, obj).append(links[i]);
				}
			}
		}
		
		
		function renderInfo() {
			var firstItem = (currentPage -1) * options.perPage +1;
			var lastItem = Math.min(options.totalRecords,currentPage  * options.perPage);
			if(options.totalRecords > 0){
				$(options.infoContainer, obj).empty().text(firstItem + " - " + lastItem + " of " + options.totalRecords + " "
					+ (options.totalRecords ==1 ? options.objName : options.objNamePlural) );		
			}else{
				$(options.infoContainer, obj).empty().text("No "+options.objNamePlural + " found");
			}
		}
		
		renderLinks(windowedLinks());
		renderInfo();
		
		$(this).bind('updatePagination', function(e, totalPages, currentPage) {
				self.currentPage = currentPage;
				self.totalPages= totalPages;
				renderLinks(windowedLinks());
				renderInfo();
		});
	
	});		
}

})(jQuery);
