/**
 * Copyright 2009,2010 Cleveratom Limited
 * 
 * 
 * This file is part of CleverTable.
 * 
 * CleverTable is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * CleverTable is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with CleverTable.  If not, see <http://www.gnu.org/licenses/>.
 * 
 */

jQuery.fn.clevertable = function(options) {
	
	if( options ) {

		// actually setup class
		return this.each(function() {
			var element = jQuery(this);
			
			// create object
			var table = new CleverTable(options);
			
			// register with table node
			element.data('clevertable', table);
			
		});
		
	} else {
		// just return existing table
		return this.data('clevertable');
	}
	
	
	
	// ########################################################################
	
	// declare class
	function CleverTable(options) {
		
		
		// support defaults
		var defaults = {
			callbackSort: [],
			callbackPage: [],
			callbackUpdate: [],
			callbackSearch: []
		};
		var opts = jQuery.extend(defaults, options);
	
		this.id = opts.id;
		this.baseUrl = opts.baseUrl;
		this.rowsPerPage = opts.rowsPerPage;
		this.sortBy = opts.sortBy;
		this.sortAsc = opts.sortAsc;
		this.searchFor = opts.searchFor;
		this.searchIn = opts.searchIn;
		this.total = opts.total;
		this.offset = opts.offset;
		
		this.callbackSort = opts.callbackSort;
		this.callbackPage = opts.callbackPage;
		this.callbackUpdate = opts.callbackUpdate;
		this.callbackSearch = opts.callbackSearch;

		this.columnIds = new Array();
		
		this.waitingSpinner = null;
		
		
		this.addCallbackUpdate = function(func)
		{
			$.isFunction(func)
				this.callbackUpdate.push(func);
		}
		
		this.addCallbackSort = function(func)
		{
			$.isFunction(func)
				this.callbackSort.push(func);
		}
		
		this.addCallbackPage = function(func)
		{
			$.isFunction(func)
				this.callbackPage.push(func);
		}
		this.addCallbackSearch = function(func)
		{
			$.isFunction(func)
				this.callbackSearch.push(func);
		}
		
		
		this.generateUrl = function(table) {
			var url = table.baseUrl + "/sort_by=" + $.URLEncode(table.sortBy) + "&sort_asc=" + (table.sortAsc ? 'true' : 'false') + "&offset=" + table.offset;
			var searchFor = $('#' + this.id + ' #search_for').val();
			if(searchFor && searchFor != '') {
				url += "&search_for=" + $.URLEncode(searchFor);
				var searchIn = $('#' + this.id + ' #search_in').val();
				if(searchIn)  url += "&search_in=" + $.URLEncode(searchIn);
			}
			return url;
		};
		
		this.refresh = function() {
//			alert("request table data");
			var table = this;
			
			// build url
			var url = this.generateUrl(this);
//			alert(url);
			
			this.waitingSpinner.show();
			
			// request data
			$.getJSON(url, function(data) {
				table.receivedData(data);
			});
		};

		this.receivedData = function(data) {
			
//			alert( "rx table data");
//			alert('Total=' + data.total);
			var table = this;
			
			this.waitingSpinner.hide();
			
			
			// grab stats
			this.total = parseInt(data.total);
			this.offset = parseInt(data.offset);
			
			// row content
			$('#' + this.id + ' tbody').empty();
			$.each(data.rows, function(rowIndex, row) {
				var html = '<tr>';
				
				$.each(table.columnIds, function(index, id) {
					var value = row[id];
					if( value == null ) value = '';
					html += '<td>' + value + '</td>';
				});
				
				html += '</tr>';
				$('#' + table.id + ' tbody').append(html);
			});
			
			
			// colour rows
			$('#' + this.id + ' tbody tr:even').addClass('odd');
			
			
			// page buttons
			this.updatePageButtons();
			
			// trigger the update callback if set
			if (this.callbackUpdate)
				for (var cb = 0; cb < this.callbackUpdate.length; cb++)
					this.callbackUpdate[cb](table);	
		
		};



		this.updatePageButtons = function() {
			
//			alert("update page buttons");
			var table = this;
			
			var numPages = Math.ceil(this.total / this.rowsPerPage);
			if( numPages > 1 ) {
			
		    	var html = '<p>';
		    	
		    	var start = this.offset + 1;
				var end = this.offset+this.rowsPerPage;
				
				if (end > this.total)
					end = this.total;
				
				html += '<span class="display_range"><span class="displaying">Displaying </span><span class="range">'+start+'-'+end+'</span><span class="total"> of '+this.total+'</span></span>';
		    	
		    	
		    	var currentPage = Math.floor(this.offset / this.rowsPerPage);
		    	
		    	
		    	// num either side
		    	// first/last
		    	// forward/backwards
		    	var numEitherSide = 2;
		    	var start = Math.max(currentPage - numEitherSide, 0);
		    	var end = Math.min(currentPage + numEitherSide, numPages - 1);

		    	
//		    	html += "numPages=" + numPages + " currentPage=" + currentPage + " start=" + start + " end=" + end + "<br />";
		    	

		    	// first
		    	if(currentPage > 0)
		    		html += '<a class="pfirst" href="#">&lt;&lt;</a> ';
		    	else
		    		html += '<span class="pfirst">&lt;&lt;</span> ';
		    	
		    	// page prev
		    	if(currentPage > 0) 
		    		html += '<a class="p' + (currentPage - 1) + ' page_prev" href="#">&lt;</a> ';
		    	else
		    		html += '<span class="p' + (currentPage + 1) + ' page_prev current_page">&lt;</span> ';
		    		
		    	
		    	// direct
		    	for(var p = start; p <= end; p++) {
		    		if(p != currentPage)
		    			html += '<a class="p' + p + ' pdigit" href="#">' + (p + 1) + '</a> ';
	    			else 
	    				html += '<span class="current_page">'+ (p + 1) + '</span> ';
		    	}
		    	
		    	
		    	// page next
		   		if(currentPage < numPages - 1) 
		   			html += '<a class="p' + (currentPage + 1) + ' page_next" href="#">&gt;</a> ';
		   		else
		   			html += '<span class="p' + (currentPage + 1) + 'page_next current_page">&gt;</span> ';
		   		
		    	// last
		    	if(currentPage < numPages - 1)
		    		html += '<a class="plast" href="#">&gt;&gt;</a>';
		    	else
		    		html += '<span class="plast">&gt;&gt;</span>';
		    	
		    	
		    	html += "</p>";
		//alert(html);
				
				
		    	// apply html
		    	$('#' + this.id + ' .page_buttons').html(html);
		    	
		    	
		    	
		    	// bind events
		    	
		    	
		    	// first
		    	if(start > 0) {
		    		$('#' + this.id + ' .page_buttons .pfirst').click(function() {
		    			table.pageButtonPressed(0);
						return false;
		    		});
		     	}
		    	
		    	// direct
		   		// inner function to fix scope... there must be a better way.
		    	function bindPageButton(p) {
		    		$('#' + table.id + ' .page_buttons .p' + p).click(function() {
		    			table.pageButtonPressed(p);
						return false;
		    		});	
		    	}
		    	for(var p = start; p <= end; p++) {
		    		if(p != currentPage) {
		    			bindPageButton(p);
		    		}
		    	}
		    	
		    	// last
		    	if(currentPage < numPages - 1) {
		    		$('#' + this.id + ' .page_buttons .plast').click(function() {
		    			table.pageButtonPressed(numPages - 1);
						return false;
		    		});
		    	}    	
		    	
		    	
			} else {
				// just empty it
				$('#' + this.id + ' .page_buttons').empty();
			}



			
		};



		this.pageButtonPressed = function(p) {
//			alert("page button pressed: " + p);
			this.offset = p * this.rowsPerPage;
			
			// trigger the page callback if set
			if (this.callbackPage)
				for (var cb = 0; cb < this.callbackPage.length; cb++)
					this.callbackPage[cb](table);	
			
			this.refresh();
		};


		this.bindColumnHeadings = function() {
			
//			alert("bind column headings");
			var table = this;
			
			// remove link url
			$('#' + this.id + ' thead th a')
				.attr('href', '#');
			
			// get column headings
			$('#' + this.id + ' thead th').each(function() {
					var columnId = this.id;
					columnId = columnId.substring(0, columnId.length - '_heading'.length);
					table.columnIds.push(columnId);
			});
			
			// bind event
			$('#' + this.id + ' thead th a')
				.click(function(event) {
					var columnId = $(event.target).parent().attr('id');
					columnId = columnId.substring(0, columnId.length - '_heading'.length);
					
					
					
					
//					alert('sort by: ' + columnId );
					
					// apply changes to table
					if( table.sortBy == columnId ) {
						// reverse order
						table.sortAsc = !table.sortAsc;
					} else {
						// change column
						table.sortAsc = true;
						table.sortBy = columnId;
					}
					
					
					// remove offset
					table.offset = 0;
					

					// remove all the classes from the headings
					$('#' + table.id + ' thead th').removeClass('sort_column').removeClass('asc').removeClass('desc');



					// apply classes to sort column
					$('#' + table.id + ' thead #' + columnId + '_heading')
						.addClass('sort_column')
						.addClass(table.sortAsc ? 'asc' : 'desc');
					
					// trigger the sort callback if set
					if (this.callbackSort)
						for (var cb = 0; cb < this.callbackSort.length; cb++)
							this.callbackSort[cb](table);	
					
					// request data
					table.refresh();
				
					return false;
				});
		};


		
		
		
		// constructor code
		
//		alert("baseUrl=" + this.baseUrl);
		// bind column headings
		this.bindColumnHeadings();
		// page buttons
		this.updatePageButtons();
		// bind search box & menu
		var table = this;
		$('#' + this.id + ' #search_for').keyup(function(event) {
			table.offset = 0;
			$(this).stopTime('search_delay');
			$(this).oneTime(500, 'search_delay', function() {
				
				if (this.callbackSearch)
					for (var cb = 0; cb < this.callbackSearch.length; cb++)
						this.callbackSearch[cb](table);	
			
				table.refresh();
			});
		});
		
		$('#' + this.id + ' #search_for').parents('form').submit(function() {
			table.offset = 0;
			table.refresh();
			return false;
		});
		
		$('#' + this.id + ' #search_in').change(function(event) {
			table.offset = 0;
			table.refresh();
		});
		
		// add loading spinner then hide
		this.waitingSpinner = $("<div>").addClass("loading_spinner").append($("<span>").text("Loading..."));//.hide();
		
		$('#' + table.id).prepend(this.waitingSpinner.hide());
		
	}
	
	
	// ########################################################################
	

	
};




