(function($){
	$.fn.pagedColumns = function(options){
		var opts = $.extend({}, $.fn.pagedColumns.defaults, options);

		return this.each(function() {
			var $this = $(this);
			//metadata
			var o = $.metadata ? $.extend({}, opts, $this.metadata()) : opts;

			$this.data('filteredMode', true);
			$this.data('variantCount', $('tbody tr:first td').length);

			var navigation = $(o.navigation, $this);
			$(o.navigationFirst, navigation)
				.bind('click', function() { first($this, o); });
			$(o.navigationPrevious, navigation)
				.bind('click', function() { previous($this, o); });
			$(o.navigationNext, navigation)
				.bind('click', function() { next($this, o); })
			$(o.navigationLast, navigation)
				.bind('click', function() { last($this, o); });
			$(o.navigationShowAll)
				.bind(
					'click',
					function() {
						$this.data('filteredMode', false);
						var visible = parseInt($(o.visibleVariantCount, navigation).text());
						var variantCount = $this.data('variantCount');
						$(o.visibleVariantCount, navigation).text(variantCount);
						while (visible < o.MAX_COLUMNS && visible < variantCount) {
							nextLastElements($this, o);
							visible++;
						}
					}
				);
		});
	};

	var first =
		function ($this, o) {
			var lastElement;
			var firstElement;
			var actualFirstElement;
			var currentElement;

			// table body
			currentElement = (firstElement = $('tbody td.first', $this));
			actualCurrentElement = (actualFirstElement = $('tbody tr', $this).find('td:first'));

			firstElement
				.removeClass('first')
				.addClass('hidden');
			actualFirstElement
				.addClass('first')
				.removeClass('hidden');

			while (!(currentElement = currentElement.next('td')).hasClass('last')) {
				actualCurrentElement = actualCurrentElement.next('td');
				currentElement.addClass('hidden');
				actualCurrentElement.removeClass('hidden');
			}

			currentElement
				.addClass('hidden')
				.removeClass('last');
			actualCurrentElement.next('td')
				.removeClass('hidden')
				.addClass('last');

			// table header
			currentElement = (firstElement = $('thead th.first', $this));
			actualCurrentElement = (actualFirstElement = $('thead tr', $this).find('th:first'));
			firstElement
				.removeClass('first')
				.addClass('hidden');
			actualFirstElement
				.addClass('first')
				.removeClass('hidden');

			while (!(currentElement = currentElement.next('th')).hasClass('last')) {
				actualCurrentElement = actualCurrentElement.next('th');
				currentElement.addClass('hidden');
				actualCurrentElement.removeClass('hidden');
			}

			currentElement
				.addClass('hidden')
				.removeClass('last');
			actualCurrentElement.next('th')
				.removeClass('hidden')
				.addClass('last');

			// table footer
			currentElement = (firstElement = $('tfoot td.first', $this));
			actualCurrentElement = (actualFirstElement = $('tfoot tr', $this).find('td.data:first, td.link:first'));
			firstElement
				.removeClass('first')
				.addClass('hidden');
			actualFirstElement
				.addClass('first')
				.removeClass('hidden');

			while (!(currentElement = currentElement.next('td.data, td.link')).hasClass('last')) {
				actualCurrentElement = actualCurrentElement.next('td.data, td.link');
				currentElement.addClass('hidden');
				actualCurrentElement.removeClass('hidden');
			}

			currentElement
				.addClass('hidden')
				.removeClass('last');
			actualCurrentElement.next('td.data, td.link')
				.removeClass('hidden')
				.addClass('last');

			var from = parseInt($(o.fromDisplay, $this).text());
			var to = parseInt($(o.toDisplay, $this).text());

			$(o.fromDisplay, $this).text('1');
			$(o.toDisplay, $this).text(1 + to-from)
		};

	var last =
		function ($this, o) {
			var lastElement;
			var firstElement;
			var actualLastElement;
			var currentElement;
			var filteredMode = $this.data('filteredMode');

			// table body
			currentElement = (lastElement = $('tbody td.last', $this));
			actualCurrentElement = (actualLastElement = $('tbody tr', $this).find(filteredMode ? 'td.unfiltered:last' : 'td:last'));

			lastElement
				.removeClass('last')
				.addClass('hidden');
			actualLastElement
				.addClass('last')
				.removeClass('filtered hidden');

			while (!(currentElement = currentElement.prev('td')).hasClass('first')) {
				actualCurrentElement = actualCurrentElement.prev('td');
				currentElement.addClass('hidden');
				actualCurrentElement.removeClass('filtered hidden');
			}

			currentElement
				.addClass('hidden')
				.removeClass('first');
			actualCurrentElement.prev('td')
				.removeClass('filtered hidden')
				.addClass('first');

			// table header
			currentElement = (lastElement = $('thead th.last', $this));
			actualCurrentElement = (actualLastElement = $('thead tr', $this).find(filteredMode ? 'th.unfiltered:last' : 'th:last'));
			lastElement
				.removeClass('last')
				.addClass('hidden');
			actualLastElement
				.addClass('last')
				.removeClass('filtered hidden');

			while (!(currentElement = currentElement.prev('th')).hasClass('first')) {
				actualCurrentElement = actualCurrentElement.prev('th');
				currentElement.addClass('hidden');
				actualCurrentElement.removeClass('filtered hidden');
			}

			currentElement
				.addClass('hidden')
				.removeClass('first');
			actualCurrentElement.prev('th')
				.removeClass('filtered hidden')
				.addClass('first');

			// table footer
			currentElement = (lastElement = $('tfoot td.last', $this));
			actualCurrentElement = (actualLastElement = $('tfoot tr', $this).find(filteredMode ? 'td.data.unfiltered:last, td.link.unfiltered:last' : 'td.data:last, td.link:last'));
			lastElement
				.removeClass('last')
				.addClass('hidden');
			actualLastElement
				.addClass('last')
				.removeClass('filtered hidden');

			while (!(currentElement = currentElement.prev('td.data, td.link')).hasClass('first')) {
				actualCurrentElement = actualCurrentElement.prev('td.data, td.link');
				currentElement.addClass('hidden');
				actualCurrentElement.removeClass('filtered hidden');
			}

			currentElement
				.addClass('hidden')
				.removeClass('first');
			actualCurrentElement.prev('td.data, td.link')
				.removeClass('filtered hidden')
				.addClass('first');

			var from = parseInt($(o.fromDisplay, $this).text());
			var to = parseInt($(o.toDisplay, $this).text());
			var variantCount = parseInt($(o.visibleVariantCount, $this).text());

			$(o.fromDisplay, $this).text(variantCount - (to-from));
			$(o.toDisplay, $this).text(variantCount)
		};

	var pagePrevious =
		function (last, previousLast, first, previousFirst) {
			last
				.removeClass('last')
				.addClass('hidden');
			previousLast.addClass('last');
			first.removeClass('first');
			previousFirst
				.addClass('first')
				.removeClass('hidden')
				.removeClass('filtered');
		};

	var previous =
		function ($this, o) {
			var lastElement;
			var previousLastElement;
			var firstElement;
			var previousFirstElement;
			var filteredMode = $this.data('filteredMode');

			// table body
			firstElement = $('tbody td.first', $this);
			previousFirstElement = firstElement.prev(filteredMode ? 'td.unfiltered' : 'td');
			if (previousFirstElement.length > 0) {
				lastElement = $('tbody td.last', $this);
				previousLastElement = lastElement.prev(filteredMode ? 'td.unfiltered' : 'td');
				pagePrevious(
					lastElement,
					previousLastElement,
					firstElement,
					previousFirstElement
				);
			}

			// table header
			firstElement = $('thead th.first', $this);
			previousFirstElement = firstElement.prev(filteredMode ? 'th.unfiltered' : 'th');
			if (previousFirstElement.length > 0) {
				lastElement = $('thead th.last', $this);
				previousLastElement = lastElement.prev(filteredMode ? 'th.unfiltered' : 'th');
				pagePrevious(
					lastElement,
					previousLastElement,
					firstElement,
					previousFirstElement
				);
				var $fromDisplay = $(o.fromDisplay, $this);
				var from = parseInt($fromDisplay.text()) - 1;
				var $toDisplay = $(o.toDisplay, $this);
				var to = parseInt($toDisplay.text()) - 1;
				$fromDisplay.text(from);
				$toDisplay.text(to);
			}

			// table footer
			firstElement = $('tfoot td.first', $this);
			previousFirstElement = firstElement.prev(filteredMode ? 'td.data.unfiltered, td.link.unfiltered' : 'td.data, td.link');
			if (previousFirstElement.length > 0) {
				lastElement = $('tfoot td.last', $this);
				previousLastElement = lastElement.prev(filteredMode ? 'td.data.unfiltered, td.link.unfiltered' : 'td.data, td.link');
				pagePrevious(
					lastElement,
					previousLastElement,
					firstElement,
					previousFirstElement
				);
			}
		};

	var pageNextLastElements =
		function (last, nextLast) {
			last.removeClass('last');
			nextLast
				.addClass('last')
				.removeClass('hidden')
				.removeClass('filtered');
		};

	var pageNext =
		function (first, nextFirst, last, nextLast) {
			first
				.removeClass('first')
				.addClass('hidden');
			nextFirst.addClass('first');
			last.removeClass('last');
			nextLast
				.addClass('last')
				.removeClass('hidden')
				.removeClass('filtered');
		};

	var nextLastElements =
		function ($this, o) {
			var lastElement;
			var nextLastElement;
			var filteredMode = $this.data('filteredMode');

			// table body
			lastElement = $('tbody td.last', $this);
			nextLastElement = lastElement.next(filteredMode ? 'td.unfiltered' : 'td');
			if (nextLastElement.length > 0) {
				pageNextLastElements(
					lastElement,
					nextLastElement
				);
			}

			// table header
			lastElement = $('thead th.last', $this);
			nextLastElement = lastElement.next(filteredMode ? 'th.unfiltered' : 'th');
			if (nextLastElement.length > 0) {
				pageNextLastElements(
						lastElement,
						nextLastElement
					);
				var $toDisplay = $(o.toDisplay, $this);
				var to = parseInt($toDisplay.text()) + 1;
				$toDisplay.text(to);
			}

			// table footer
			lastElement = $('tfoot td.last', $this);
			nextLastElement = lastElement.next(filteredMode ? 'td.data.unfiltered, td.link.unfiltered' : 'td.data, td.link');
			if (nextLastElement.length > 0) {
				pageNextLastElements(
						lastElement,
						nextLastElement
					);
			}
		};

	var next =
		function ($this, o) {
			var lastElement;
			var nextLastElement;
			var firstElement;
			var nextFirstElement;
			var filteredMode = $this.data('filteredMode');

			// table body
			lastElement = $('tbody td.last', $this);
			nextLastElement = lastElement.next(filteredMode ? 'td.unfiltered' : 'td');
			if (nextLastElement.length > 0) {
				firstElement = $('tbody td.first', $this);
				nextFirstElement = firstElement.next(filteredMode ? 'td.unfiltered' : 'td');
				pageNext(
					firstElement,
					nextFirstElement,
					lastElement,
					nextLastElement
				);
			}

			// table header
			lastElement = $('thead th.last', $this);
			nextLastElement = lastElement.next(filteredMode ? 'th.unfiltered' : 'th');
			if (nextLastElement.length > 0) {
				firstElement = $('thead th.first', $this);
				nextFirstElement = firstElement.next(filteredMode ? 'th.unfiltered' : 'th');
				pageNext(
						firstElement,
						nextFirstElement,
						lastElement,
						nextLastElement
					);
				var $fromDisplay = $(o.fromDisplay, $this);
				var from = parseInt($fromDisplay.text()) + 1;
				var $toDisplay = $(o.toDisplay, $this);
				var to = parseInt($toDisplay.text()) + 1;
				$fromDisplay.text(from);
				$toDisplay.text(to);
			}

			// table footer
			lastElement = $('tfoot td.last', $this);
			nextLastElement = lastElement.next(filteredMode ? 'td.data.unfiltered, td.link.unfiltered' : 'td.data, td.link');
			if (nextLastElement.length > 0) {
				firstElement = $('tfoot td.first', $this);
				nextFirstElement = firstElement.next(filteredMode ? 'td.data.unfiltered, td.link.unfiltered' : 'td.data, td.link');
				pageNext(
						firstElement,
						nextFirstElement,
						lastElement,
						nextLastElement
					);
			}
		};

	$.fn.pagedColumns.defaults = {
		navigation:				'#navigation',
		navigationFirst:		'.first',
		navigationPrevious:		'.previous',
		navigationNext:			'.next',
		navigationLast:			'.last',
		fromDisplay:			'#fromDisplay',
		toDisplay:				'#toDisplay',
		visibleVariantCount:	'#visibleVariantCount',
		navigationShowAll:		'.showAll',
		MAX_COLUMNS:		4
	}
})(jQuery);

jQuery(function($) {
	$("table:has('thead #navigation')")
		.pagedColumns();
});

