/**
 * Copyright 2006, Internet Broadcasting Systems. All Rights Reserved.
 * Version:   $Name: REL_2_39_0 $
 * ID:        $Id: TableSort.js,v 1.24 2007/04/19 16:27:26 breisinger Exp $
*/
using("ibsys");

/**
* global constant for TableSort.  The "this" keyword gets clobbered inside the scope
* of array.sort(), which is where the custom sort fcts use this variable
**/
ibsys.SORT_COLUMN_INDEX;

/***
* Extract text from el, stripping out html
**/
ibsys.extract = function(el) {
	if (typeof el == "string" || typeof el == "undefined") return el;
	if (el.innerText) return el.innerText;	//Not needed but it is faster
	if (el.textContent) return el.textContent;
	var str = "";
	
	var cs = el.childNodes;
	var l = cs.length;
	for (var i = 0; i < l; i++) {
		if(cs[i].nodeType == 1)
			str += ibsys.extract(cs[i]);
		else if (cs[i].nodeType == 3)
			str += cs[i].nodeValue;
	}
	return str;
}


/*********************************************
* ibsys.TableSort
*  sort any table (using a header row) by each column. 
*  uses Array.sort() with custom sorting algorithms
* 
* Dependent on prototype.js, TableColumnHighlighter.js
*
* Example Usage: 
*	//enable every table on the page with the class name "sortable"
* 	Event.observe(window,"load",function() {
*		var tables = document.getElementsByClassName("sortable");
*		tables.each(function(value,index) {
			var headerRow = value.getElementsByClassName("headerRow")[0];
*			value.tablesort = new ibsys.TableSort(value,headerRow);
*		},false);
*	});
* 
* Adapted from Stuart Langridge (http://www.kryogenix.org/code/browser/sorttable/)
* by breisinger
***/
ibsys.TableSort = function(table,headerRow,ignoreClassName) {
	this.table = table;
	this.headerRow = headerRow;
	this.arrow = document.createElement("span");
	this.ignoreClassName = typeof ignoreClassName != "undefined" ? ignoreClassName : false;
	this.firstSortableRowIndex = 0; 	//first table row that contains sortable data, 0 start
	this.columnHighlighter = new ibsys.TableColumnHighlighter(this.table,0,"columnHighlight");	
	this.ts_makeSortable(this.table,this.headerRow);
	this.reverseFlag = true;		//flag to switch default sort order for a field type
	this.columnSortTypes = new Array();	//array to store column sort functions.  

}


/***
* make table sortable 
****/
ibsys.TableSort.prototype.ts_makeSortable = function(table,headerRow) {
	var self = this;
	if (!headerRow) return;
	
	/* store number of first sortable row */
	this.firstSortableRowIndex = this.findFirstSortableRowIndex();
	
	/* see if a default row should be highlighted */
	if(table.className.match(/column\d+/)) {
		var sortedColumn = table.className.split("column")[1].split(/\s/)[0];
		this.addClassNameToCurrentColumn(sortedColumn);
	}
	
	/* We have a first row: make its cells clickable */
	for (var i=0;i<headerRow.cells.length;i++) {
		var current = i;
		var cell = headerRow.cells[i];
		var txt = ibsys.extract(cell);
		cell.setAttribute("seq",i);
		if(cell.getElementsByTagName("span").length < 1 || cell.getElementsByTagName("span")[0].className != "sortheader") {
			cell.innerHTML = '<span class="sortheader">' + txt + '</span><span class="sortarrow"></span>';
		}
		
		/* add click event to table cell */
		Event.observe(cell,"click",function(e) {
			if(typeof e.srcElement != "undefined") {
				el = e.srcElement;
				while(el.nodeName.toUpperCase() != "TH" && el.nodeName.toUpperCase() != "TD") {
					el = el.parentNode;
				}
			} else { 
				el = this;
			}
			
			self.ts_resortTable(el, el.getAttribute("seq"));
			return false;
		},false);
		
	}
}


ibsys.TableSort.prototype.findFirstSortableRowIndex = function() {
	var self = this;
	//grab first cell in column of first row that is sortable
	var firstSortableRow = $A(this.table.rows).find(function(value) {
		return (!Element.hasClassName(value,self.ignoreClassName) && value != self.headerRow);
	});
	return firstSortableRow.rowIndex;
}
/***
* Driver to sort table and change indicators
* lnk = element that fired event
* clid = column index of element in table
***/
ibsys.TableSort.prototype.ts_resortTable = function(lnk,clid) {
	var self = this;
	//don't sort a column with one row
	if (this.table.rows.length <= 1) return;
	
	//grab first span in table cell, assume it has text content we want.
	var span = false;
	span = lnk.getElementsByTagName("span")[0];
	if(span) var spantext = ibsys.extract(span);
	
	//grab first cell in column of first row that is sortable
	var firstSortableRow = this.table.rows[this.firstSortableRowIndex]; 
		
	//get sample text content from this column
	var itm = ibsys.extract(firstSortableRow.cells[clid]);
	//only choose sort fct once per column. 
	if(!this.columnSortTypes[clid]) {
		//choose which sorting method to use.  default sort alg. is alpha
		this.columnSortTypes[clid] = this.chooseSortFunction(itm);
	}
	
	//this variable must be in scope for array sort functions to use. 
	ibsys.SORT_COLUMN_INDEX = clid;
	
	var sortableRows = new Array(); //rows that don't have nosort class name and not headerRow
	var skeletonRows = new Array(); //will store rows not sortable with null spots where sortable rows can go
	
	//get rows to be sorted
	for (j=0;j<this.table.rows.length;j++) {
		if(Element.hasClassName(this.table.rows[j],this.ignoreClassName) || this.table.rows[j] == this.headerRow) {
			skeletonRows.push(this.table.rows[j]);
		} else {
			skeletonRows.push(null);	//empty slot in skeleton
			sortableRows.push(this.table.rows[j]);
		}
	} 
	
	
	//use native array sort method with custom sorting algorithm
	sortableRows.sort(this.columnSortTypes[clid]);
	
	//REVERSE ARRAY if direction should be up
	if(Element.hasClassName(span.parentNode,"sort_down")) {
		sortableRows.reverse();
	}
	//add a sort down flag if reverse flag is set. 
	if(this.reverseFlag) {
		if(!Element.hasClassName(span.parentNode,"sort_down") && !Element.hasClassName(span.parentNode,"sort_up")) {
			sortableRows.reverse();
			Element.addClassName(span.parentNode,"sort_down");
		} 
	}
	if (Element.hasClassName(span.parentNode,"sort_down")) {
		this.arrow.innerHTML = '&darr;';
		Element.removeClassName(span.parentNode,"sort_down");
		Element.addClassName(span.parentNode,"sort_up");
	} else {
		this.arrow.innerHTML = '&uarr;';
		Element.removeClassName(span.parentNode,"sort_up");
		Element.addClassName(span.parentNode,"sort_down");
	}
	this.moveSortIndicatorTo(span.parentNode.getElementsByTagName("span")[1]);

	
	//insert newly sorted rows into skeleton
	var skeletonCounter = 0;  //position relative to skeleton row
	$A(sortableRows).each(function(value,index) {	//"index" is position relative to sortable row. 
		//if this spot is occupied, go to next unoccupied spot
		while(skeletonRows[skeletonCounter]) {
			skeletonCounter++;
		}
		skeletonRows[skeletonCounter] = value;
		skeletonCounter++;
	});
	
	
	//do most of DOM interaction "offline" to shave ms 
	var rows = document.createDocumentFragment();
	for(var i=0; i<skeletonRows.length; i++) {
		//move row to new spot
		rows.appendChild(skeletonRows[i]);
	}
	this.table.tBodies[0].appendChild(rows);
	
	//highlight current column, kick off in settimeout to increase perceived speed
	window.setTimeout(function() {
		self.addClassNameToCurrentColumn(clid);
	},1);
}


/***
* move html node to currently sorted column's span in headerRow to indicate current
* sorted column
****/
ibsys.TableSort.prototype.moveSortIndicatorTo = function(toSpan) {
	toSpan.appendChild(this.arrow);
}

/***
* choose which function to use as sort method based on string content
***/
ibsys.TableSort.prototype.chooseSortFunction = function(stringToTest) {
	var sortfn = this.ts_sort_caseinsensitive;
	//date fields
	if (stringToTest.match(/\d{1,2}[\/-]\d{1,2}[\/-]\d{2,4}$/)) {
		sortfn = this.ts_sort_date;
		this.reverseFlag = false;
	} else if (stringToTest.match(/^[£$]/)) { 
		sortfn = this.ts_sort_currency;
		this.reverseFlag = true;
	//floating-point fields 
	} else if (stringToTest.match(/^-?\.?\d+[\d,]*\.?\d*$/)) {
		sortfn = this.ts_sort_numeric;
		this.reverseFlag = true;
	//height: #'#"
	} else if (stringToTest.match(/^\d{1}\'\d{1,2}\"/)) {
		sortfn = this.ts_sort_height;
		this.reverseFlag = true;
	//digit-start (with possible alpha afterwards)
	} else if (stringToTest.match(/^-?\.?\d+[\d,]*\.?\d*\D+/)) {
		sortfn = this.ts_sort_numericStart;
		this.reverseFlag = true;
	//Streak
	} else if (stringToTest.match(/^[WL][-\s]*\d+$/)) {
		sortfn = this.ts_sort_streak;
		this.reverseFlag = true;
	//negative numbers	
	} /*else if (stringToTest.match(/^-[\d]+.+/)) {
		sortfn = this.ts_sort_numeric;
		this.reverseFlag = true;
	}*/
	return sortfn;	
}

/***
* highlight the current column that is being sorted, remove highlight from last column
***/
ibsys.TableSort.prototype.addClassNameToCurrentColumn = function(colNum) {
	this.columnHighlighter.highlightColumn(colNum);
}
/***
* Return the first parent node of a specific tag type
**/
ibsys.TableSort.prototype.getParent = function(el, pTagName) {
	if (el == null) 
		return null;
	else if (el.nodeType == 1 && el.tagName.toLowerCase() == pTagName.toLowerCase())	// Gecko bug, supposed to be uppercase
		return el;
	else
		this.getParent(el.parentNode, pTagName);
}


/**************************************************************************
* NOTE: The scope of these sorting functions below is the scope of the 
* Array object (they are called by Array.sort())  tricky tricky
***************************************************************************/

/***
* sort by date
* FIX: (?) two digit years less than 50 are treated as 20XX, greater than 50 are treated as 19XX
***/
ibsys.TableSort.prototype.ts_sort_date = function(a,b) {
	handle_null_date = function(value) {
  if (value=="" || value=="-")
	  return "1/1/0001";
	else
	  return value;		
  }
	aa = handle_null_date(ibsys.extract(a.cells[ibsys.SORT_COLUMN_INDEX]));
	bb = handle_null_date(ibsys.extract(b.cells[ibsys.SORT_COLUMN_INDEX]));
	//find delimiter.  this is faster than a loop, but caution: we are only
	//testing the 2nd and 3rd character of the date string. 
	delimiter = (isNaN(parseInt(aa.charAt(1)))) ? aa.charAt(1) : aa.charAt(2);
	
	aaa = aa.split(delimiter);
	bbb = bb.split(delimiter);
	
	//add trailing zeroes where needed	
	if(aaa[0].toString().length == 1) aa = "0" + aa;
	if(bbb[0].toString().length == 1) bb = "0" + bb;
	
	if(aaa[1].toString().length == 1) aa = aa.substr(0,3) + "0" + aa.substr(3);
	if(bbb[1].toString().length == 1) bb = bb.substr(0,3) + "0" + bb.substr(3);
	
	if (aaa[2].toString().length == 4) {
		dt1 = aa.substr(6,4)+aa.substr(0,2)+aa.substr(3,2);
	} else {
		yr = aa.substr(6,2);
		if (parseInt(yr) < 50) { yr = '20'+yr; } else { yr = '19'+yr; }
		dt1 = yr+aa.substr(0,2)+aa.substr(3,2);
	}
	if (bbb[2].toString().length == 4) {
		dt2 = bb.substr(6,4)+bb.substr(0,2)+bb.substr(3,2);
	} else {
		yr = bb.substr(6,2);
		if (parseInt(yr) < 50) { yr = '20'+yr; } else { yr = '19'+yr; }
		dt2 = yr+bb.substr(0,2)+bb.substr(3,2);
	}
	if (dt1==dt2) return 0;
	if (dt1<dt2) return -1;
	return 1;
}

/***
* sort currency
***/
ibsys.TableSort.prototype.ts_sort_currency = function(a,b) { 
	aa = ibsys.extract(a.cells[ibsys.SORT_COLUMN_INDEX]).replace(/[^0-9.]/g,'');
	bb = ibsys.extract(b.cells[ibsys.SORT_COLUMN_INDEX]).replace(/[^0-9.]/g,'');
	return parseFloat("0"+aa) - parseFloat("0"+bb);
}

/***
* sort numbers
***/
ibsys.TableSort.prototype.ts_sort_numeric = function(a,b) { 
	aa = parseFloat(ibsys.extract(a.cells[ibsys.SORT_COLUMN_INDEX]).replace(/[^0-9.-]/g,''));
	if (isNaN(aa)) aa = 0;
	bb = parseFloat(ibsys.extract(b.cells[ibsys.SORT_COLUMN_INDEX]).replace(/[^0-9.-]/g,'')); 
	if (isNaN(bb)) bb = 0;
	return aa-bb;
}

/***
* sort fields that start with numbers as numbers
***/
ibsys.TableSort.prototype.ts_sort_numericStart = function(a,b) {
	aa = parseFloat(ibsys.extract(a.cells[ibsys.SORT_COLUMN_INDEX]).replace(/[\D]+/g,""));
	bb = parseFloat(ibsys.extract(b.cells[ibsys.SORT_COLUMN_INDEX]).replace(/[\D]+/g,""));
	if(isNaN(aa)) aa = 0;
	if(isNaN(bb)) bb = 0;
	return aa-bb;
}
	

/***
* case insensitive alphabetical sort
***/
ibsys.TableSort.prototype.ts_sort_caseinsensitive = function(a,b) {
	aa = ibsys.extract(a.cells[ibsys.SORT_COLUMN_INDEX]).toLowerCase();
	bb = ibsys.extract(b.cells[ibsys.SORT_COLUMN_INDEX]).toLowerCase();
	if (aa==bb) return 0;
	if (aa<bb) return -1;
	return 1;
}

/***
* alphabetical sort, case sensitive
***/
ibsys.TableSort.prototype.ts_sort_default = function(a,b) {
	aa = ibsys.extract(a.cells[ibsys.SORT_COLUMN_INDEX]);
	bb = ibsys.extract(b.cells[ibsys.SORT_COLUMN_INDEX]);
	if (aa==bb) return 0;
	if (aa<bb) return -1;
	return 1;
}
/***
* sort height ( #'##" )
***/
ibsys.TableSort.prototype.ts_sort_height = function(a,b) {
	aa = ibsys.extract(a.cells[ibsys.SORT_COLUMN_INDEX]);
	bb = ibsys.extract(b.cells[ibsys.SORT_COLUMN_INDEX]);
	aaa = aa.split("\'");
	bbb = bb.split("\'");
	//feet
	if(aaa[0] > bbb[0]) {
		return 1;
	} else if (aaa[0] < bbb[0]) { 
		return -1;
	//inches
	} else {
		if(parseInt(aaa[1]) > parseInt(bbb[1])) {
			return 1;
		} else if (parseInt(aaa[1]) < parseInt(bbb[1])) {
			return -1;
		} else {
			return 0;
		}
	}
}
/***
* sort streaks (W-5 || L - 2 || L3)
***/
ibsys.TableSort.prototype.ts_sort_streak = function(a,b) {
  aa = ibsys.extract(a.cells[ibsys.SORT_COLUMN_INDEX]);
	bb = ibsys.extract(b.cells[ibsys.SORT_COLUMN_INDEX]);
	
	modA = 1;
	if (aa.substring(0,1)=="L") modA = -1;
	modB = 1;
	if (bb.substring(0,1)=="L") modB = -1;
	
	re = /\d+/;
	aaVal = parseInt(re.exec(aa),10) * modA;
	bbVal = parseInt(re.exec(bb),10) * modB;
	
	if (aaVal==bbVal) return 0;
	if (aaVal<bbVal) return -1;
	return 1;
}
////////////////
/***
* make all tables with class "sortable" sortable on this page
***/
ibsys.DomLoadedEvent.add(function() {
	var tables = document.getElementsByTagName("table");
	
	var sortableTables = new Array();
	$A(tables).each(function(value) {
		if(Element.hasClassName(value,"sortable")) {
			sortableTables.push(value);
		}
	});
	var ignoreClassName = "nosort";
	$A(sortableTables).each(function(value,index) {
		var headerRow = document.getElementsByClassName("headerRow",value);
		if(headerRow.length > 0) {
			value.tablesort = new ibsys.TableSort(value,headerRow[0],ignoreClassName);
		}
	},false);
});

