/**
 * Table sorting class
 * 
 * Applies click events to the <th> elements which carry the functions
 * to perform the Array.sort() methods
 * 
 * @requires Mootools-1.2
 * 
 * @author Lee Hoang
 * @author Ollie Maitland
 * @copyright Byng Systems LLP
 * 
 * @see http://www.phatfusion.net/sortabletable/
 */

var ByngSort = new Class(
{
	Implements : [Events, Options],
	
	/**
	 * Set sort options
	 *
	 * @param Object 
	 */
	options : 
	{
		overClass: false,
		onClick: false,
		sortOn: 0,
		sortBy: 'ASC'
	},
	
 	/**
 	 * Initialise source table and request
 	 * define sort algorithm for table
 	 * 
 	 * @param DomElement sourceTable
 	 * @param Object options 
 	 */
 	initialize : function (sourceTable, options) 
 	{		
 		this.setOptions(options);
  		this.sourceTable = $(sourceTable);
		// load the <td> elements
		this.loadElements(options);
	},
	
	/**
	 * Attach behaviour to the header rows
	 * 
	 * @param DomElement el
	 * @param Integer i Cell index
	 */
	attachBehaviour : function(el,i)
	{
		// condition: axis attribute set
		if(el.axis) {
			
			// remove any previous event assignment
			el.removeEvents();
			
			// add the click event
			el.addEvent('click', this.orderRows.bind(this,i));
			// mouse over method
			el.addEvent('mouseover', function(){
				el.addClass('table_header_over');
			});
			// mouseout event
			el.addEvent('mouseout', function(){
				el.removeClass('table_header_over');
			});
			
			// attach date utility method by reference
			el.getDate = this.getDate;
			
			/**
			 * Locate the deepest text node
			 * 
			 * @param DomElement element
			 * @return String
			 */
			el.findData = function(elem) 
			{
				// check if there is a child ement
				if(elem.firstChild) {
					return this.findData(elem.firstChild);
				}else{
					return (elem.textContent ? elem.textContent.trim() : (elem.data ? elem.data.trim() : ''));
				}
			}.bind(el);
			
			/**
			 * Compare function for element sort
			 * 
			 * @param DomElement a
			 * @param DomElement b
			 * @returns Integer
			 */
			el.compare = function(a,b) 
			{
				// get the data of the two elements to compare
				try {
					var1 = this.findData(a.getElementsByTagName('td')[i]);
					var2 = this.findData(b.getElementsByTagName('td')[i]);
				} catch (e) {
					var1 = null;
					var2 = null;
				}
	
				/**
				 * Sort numbers 
				 * 
				 */
				if(this.axis == 'number') {
					
					var1 = (var1 ? parseFloat(var1) : 0);
					var2 = (var2 ? parseFloat(var2) : 0);
					
					if(this.sortBy == 'ASC'){
						return var1-var2;
					}else{
						return var2-var1;
					}
				/**
				 * Sort strings 
				 * 
				 */
				} else if(this.axis == 'string') {
					var1 = var1.toUpperCase();
					var2 = var2.toUpperCase();
					
					if(var1==var2){return 0};
					if(this.sortBy == 'ASC'){
						if(var1<var2){return -1};
					}else{
						if(var1>var2){return -1};
					}
					return 1;					
				/**
				 * Sort dates
				 * 
				 */
				} else if(this.axis == 'date'){
					var1 = parseFloat(this.getDate(var1));
					var2 = parseFloat(this.getDate(var2));
					
					if(this.sortBy == 'ASC'){
						return var1-var2;
					}else{
						return var2-var1;
					}
				/**
				 * Sort currency
				 *  
				 */
				} else if(this.axis == 'currency'){
					var1 = parseFloat(var1.substr(1).replace(',',''));
					var2 = parseFloat(var2.substr(1).replace(',',''));
					
					if(this.sortBy == 'ASC'){
						return var1-var2;
					}else{
						return var2-var1;
					}
				}
			}.bind(el);
			
			if(i == this.options.sortOn){
				el.fireEvent('click');
			}else{
				this.altRow();	
			}
		}
	},
			
	/**
	 * Load the row elements
	 * 
	 * @param Object options
	 */
	loadElements : function (options)
	{
		if (!options) options = this.options;
		
  		// set the class references to the table elements	
		this.tHead = this.sourceTable.getElement('thead');
		this.tBody = this.sourceTable.getElement('tbody');
		this.tFoot = this.sourceTable.getElement('tfoot');
				
		this.elements = this.tBody.getElements('tr');

		// attach the hover events
		this.elements.each(function(el,i){
			if(this.options.overClass){
				el.addEvent('mouseover', function(){
					el.addClass(options.overClass);
				}, this);
				el.addEvent('mouseout', function(){
					el.removeClass(options.overClass);
				});
			}
			if(this.options.onClick){
				el.addEvent('click', options.onClick);
			}
		}, this);
		
		// Attached events to the header rows
		this.tHead.getElements('th').each(this.attachBehaviour.bind(this));		
	},
	
	/**
	 * Get date utility method
	 * 
	 * @param String str
	 */
	getDate : function(str) 
	{
		function fixYear(yr) {
			yr = +yr;
			if (yr<50) { yr += 2000; }
			else if (yr<100) { yr += 1900; }
			return yr;
		};
		var ret;
		
		if (str.length>12){
			strtime = str.substring(str.lastIndexOf(' ')+1);
			strtime = strtime.substring(0,2)+strtime.substr(-2)
		}else{
			strtime = '0000';
		}
		
		if (ret=str.match(/(\d{2,4})-(\d{1,2})-(\d{1,2})/)) {
			return (fixYear(ret[1])*10000) + (ret[2]*100) + (+ret[3]) + strtime;
		}
		
		if (ret=str.match(/(\d{1,2})[\/-](\d{1,2})[\/-](\d{2,4})/)) {
			return (fixYear(ret[3])*10000) + (ret[2]*100) + (+ret[1]) + strtime;
		}
		return 999999990000; // So non-parsed dates will be last, not first
	},
	
	/**
	 * Sort the table ascending/descending
	 * 
	 * @param Int index
	 */
	orderRows : function(index)
	{
		this.options.sortOn = index;
		var header = this.tHead.getElements('th');
		var el = header[index];
		
		// remove the previous classes
		header.each(function(e,i){
			if(i != index){
				e.removeClass('sorted_asc');
				e.removeClass('sorted_desc');
			}
		});
		
		// toggle the sorting order
		if(el.hasClass('sorted_asc')){
			el.removeClass('sorted_asc');
			el.addClass('sorted_desc');
			el.sortBy = 'DESC';
		} else if(el.hasClass('sorted_desc')){
			el.removeClass('sorted_desc');
			el.addClass('sorted_asc');
			el.sortBy = 'ASC';
		} else{
			if(this.options.sortBy == 'ASC'){
				el.addClass('sorted_asc');
				el.sortBy = 'ASC';
			} else if(this.options.sortBy == 'DESC'){
				el.addClass('sorted_asc');
				el.sortBy = 'DESC';
			}
		}

		this.elements.sort(el.compare);
		this.elements.injectInside(this.tBody);
	},
	
	/**
	 * Add/remove class for table rows
	 * 
	 */
	altRow : function() {
		this.elements.each(function(el,i){
			if(i % 2){
				el.removeClass('altRow');
			}else{
				el.addClass('altRow');
			}
		});
	}

});
	
if (typeof(ByngTable) != "undefined") {
	
	// implement ByngTable.sortable( boolean ) method
	ByngTable.implement(
	{
		/**
		 * Make sortable
		 * 
		 * @param Object state
		 */
		setSortable : function ( options ) 
		{
			var state = ($type(options) == 'object' ? options.sortable : options);
			if (state == true) {
				this.__sorter = new ByngSort (this.getTable(), options);
			} else {
				this.__sorter = null;
			}
		},
		
		getSorter : function ()
		{
			return this.__sorter;
		},
		
		/**
		 * Refresh the elements
		 * 
		 * @return void
		 */
		refreshSortables : function ()
		{
			this.__sorter.loadElements();
		}
	});
}

