/*global document, $, $$, window, makeSortable, initTables, determineColumnType, sortColumn, sortDate, sortCurrency, sortNumeric, sortText, parseDate, parseTime, attachSortLink, extractText, parseDateNumeric, parseDateShort, parseDateLong */
/***************************************************************************************************
****************************************************************************************************

	Automatic sorting of table rows.
	Any table with a class of 'sortable' will become a sortable table.
	Rows with class 'sortbottom' will be left at the bottom of the table.
	Assumptions:
		First row is headers for the columns
		webdev.css is also loaded
		
	Options: (all options are global for all sortable tables on a page)
		START_ASC	- If true, the first click on a column will sort ascending, else descending
		ASC_CLASS	- CSS class to apply to column header for ascending sort
		DESC_CLASS	- CSS class to apply to column header for descending sort
		SORT_CLASS	- CSS class to apply to column header when not being sorted
		ROW_CLASS	- CSS class to apply to rows in the table
		ALT_CLASS	- CSS class to apply to alternating rows in the table
		ALT_ROWS	- If true, use ROW_CLASS and ALT_CLASS alternatingly on rows, else just ROW_CLASS
		NOSORT_CLASS	- CSS class to specify a column is not sortable
		BOTTOM_CLASS	- CSS class to specify a row stays at the bottom of a table
		TOP_CLASS		- CSS class to specify a row stays at the top of a table
		START_INDEX	- If not -1, the column index to do an initial sort on page-load
		
****************************************************************************************************
***************************************************************************************************/
function initTables() {
	// Setup tables for sorting
	var tables = document.getElementsByClassName("sortable", "table");
	for (var i=0, j=0; i<tables.length; ++i ) {
		makeSortable(tables[i], j);
		++j;
	}
}
window.addLoad(initTables);

// Some code inspired by (or taken from) http://www.kryogenix.org/code/browser/sorttable/sorttable.js

var START_ASC = true,
	ASC_CLASS = 'WDSortASC',
	DESC_CLASS = 'WDSortDESC',
	SORT_CLASS = 'WDSort',
	ROW_CLASS = 'WDRow',
	ALT_CLASS = 'WDAltRow',
	ALT_ROWS = true,
	NOSORT_CLASS = 'nosort',
	BOTTOM_CLASS = 'sortbottom',
	TOP_CLASS = 'sorttop',
	START_INDEX = 0,

	SORT_INDEX = [],	// Array of currently sorted by column indices for all tables
	COL_TYPES = [],	// 2D Array of column data types for all tables
	SORT_TABLE_INDEX = -1;	// Index of the table currently being acted upon

function hoverCol() {
	this.addClassName('WDSortHover');
}
function unhoverCol() {
	this.removeClassName('WDSortHover');
}

// Make a table sortable
function makeSortable(table, tableIndex) {
	if (!isset(typeof(tableIndex))) { tableIndex = SORT_INDEX.length; }
	if (table.rows.length > 0) {
		COL_TYPES[tableIndex] = [];
		SORT_INDEX[tableIndex] = START_INDEX;

		var headers = table.rows[0].cells, i;
		
		for( i=0; i<headers.length; i++ ) {
			headers[i].tableIndex = tableIndex;
			if (attachSortLink(table, $$(headers[i]), i)) {
				headers[i].addEvent('mouseover', hoverCol.bind(headers[i]));
				headers[i].addEvent('mouseout', unhoverCol.bind(headers[i]));
				headers[i].isSortable = true;
			} else {
				headers[i].isSortable = false;
			}
		}
		for( i=0; i<headers.length; i++ ) {
			if (headers[i].isSortable) {
				determineColumnType(table, i);
			}
		}
		
		// Extend the row's for later
		for( i=0; i<(table.rows.length-1); i++ ) {
			$$(table.rows[i+1]);
		}
	
		// Do an initial sort of the table if specified
		if ( START_INDEX != -1 ) {
			if (!headers[START_INDEX].isSortable) {
				START_INDEX++;
				SORT_INDEX[tableIndex] = START_INDEX;
			}
			sortColumn(table, START_INDEX);
		}
	}
}

// Wrap a clickable div around the header contents.
// When clicked, performs a sort operation on the table
function attachSortLink(table, cell, colIndex) {
	if (cell.childNodes.length > 0) {
		// Create the DIV
		var cellDiv = document.create("div");
		cellDiv.addClassName(SORT_CLASS);
		if (!cell.hasClassName(NOSORT_CLASS)) {
			cellDiv.addClick(function () {sortColumn(table, colIndex);});
		}
		
		// Remove the contents from the cell, add them to the DIV, and add the DIV to the cell
		// Treats the first child of the cell as the cell's content
		var cellContent = cell.firstChild;
		cell.removeChild(cellContent);
		cellDiv.appendChild(cellContent);
		cell.appendChild(cellDiv);
		cell.sortDiv = cellDiv;
		return true;
	}
	return false;
}

function resortTable(table) {
	var first = table.rows[0].cells[0],
		index = first.tableIndex,
		colindex = SORT_INDEX[index];
	sortColumn(table, colindex);
	sortColumn(table, colindex);
}

// Sort a table by a particular column
function sortColumn(table, colIndex) {
	var clicked = table.rows[0].cells[colIndex],
		index = clicked.tableIndex,
		sameColumn = (SORT_INDEX[index] == colIndex);
	SORT_TABLE_INDEX = index;
	
	// Clean up the CSS for the last column
	if ((SORT_INDEX[index] != -1) && !sameColumn) {
		var oldColumn = table.rows[0].cells[SORT_INDEX[index]].sortDiv;
		oldColumn.removeClassName(ASC_CLASS);
		oldColumn.removeClassName(DESC_CLASS);
		oldColumn.addClassName(SORT_CLASS);
	}
	
	SORT_INDEX[index] = colIndex;
	
	// Get the rows to be sorted
	var rows = [], i, j;
	for( i=0; i<(table.rows.length-1); i++ ) {
		rows[i] = table.rows[i+1];
	}
	
	rows.sort(COL_TYPES[index][colIndex]);
	var sortDiv = clicked.sortDiv;
	
	var asc = true;
	if (sortDiv.hasClassName(ASC_CLASS) ||
		(!sortDiv.hasClassName(DESC_CLASS) && sortDiv.hasClassName(SORT_CLASS) && !START_ASC)
	) {
		// Descending sort
		asc = false;
	} else if (sortDiv.hasClassName(DESC_CLASS) ||
			   (!sortDiv.hasClassName(ASC_CLASS) && sortDiv.hasClassName(SORT_CLASS) && START_ASC)
	) {
		// Ascending sort, flag already set, do nothing
	} else if (sameColumn) {
		// must switch
		asc = !sortDiv.hasClassName(ASC_CLASS);
	}
	
	sortDiv.removeClassName(SORT_CLASS);
	sortDiv.toggleClassName(ASC_CLASS, !!asc);
	sortDiv.toggleClassName(DESC_CLASS, !asc);
	if (!asc) { rows.reverse(); }

	var alt;
	// Add the TOP_CLASS rows
	for( i=0; i<rows.length; i++) {
		if ( rows[i].hasClassName(TOP_CLASS) ) {
			if (!rows[i].isHidden()) {
				alt = ALT_ROWS && (j%2 == 1);
				rows[i].toggleClassName(ALT_CLASS, !!alt);
				rows[i].toggleClassName(ROW_CLASS, !alt);
				++j;
			}
			table.tBodies[0].appendChild(rows[i]);
		}
	}
	// Add all the rows to the table in order except rows with a class of BOTTOM_CLASS or TOP_CLASS
	for( i=0,j=0; i<rows.length; i++ ) {
		if ( !rows[i].hasClassName(BOTTOM_CLASS) && !rows[i].hasClassName(TOP_CLASS) ) {
			if (!rows[i].isHidden()) {
				alt = ALT_ROWS && (j%2 == 1);
				rows[i].toggleClassName(ALT_CLASS, !!alt);
				rows[i].toggleClassName(ROW_CLASS, !alt);
				++j;
			}
			table.tBodies[0].appendChild(rows[i]);
		}
	}
	// Add the BOTTOM_CLASS rows
	for( i=0; i<rows.length; i++) {
		if ( rows[i].hasClassName(BOTTOM_CLASS) ) {
			if (!rows[i].isHidden()) {
				alt = ALT_ROWS && (j%2 == 1);
				rows[i].toggleClassName(ALT_CLASS, !!alt);
				rows[i].toggleClassName(ROW_CLASS, !alt);
				++j;
			}
			table.tBodies[0].appendChild(rows[i]);
		}
	}
}

function reapplyRowClasses(table) {
	var rows = table.rows;
	for( var i=1, j=1; i<rows.length; i++ ) {
		if (!rows[i].isHidden()) {
			var alt = ALT_ROWS && (j%2 == 0);
			rows[i].toggleClassName(ALT_CLASS, !!alt);
			rows[i].toggleClassName(ROW_CLASS, !alt);
			++j;
		}
	}
}

// Looks at all the elements in a column and determine the majority type of the column
function determineColumnType(table, colIndex) {
	var types = [];
	types.date = 0;
	types.currency = 0;
	types.numeric = 0;
	types.text = 0;
	var maxCount = 0,
		maxType = 'date';
	
	// Get the table index
	var tableIndex = table.rows[0].cells[colIndex].tableIndex;
	
	for( var i=1; i<table.rows.length; i++ ) {
		var text = extractText(table.rows[i].cells[colIndex]);
		var thistype = '';
		if (text !== "" && typeof(text)==='string') {
			text = text.trim();
			// Check for type using a series of regular expressions
			if ( text.match(/^\d{1,2}[\.\/-]\d{1,2}[\.\/-](\d\d){1,2}([ -]+\d{1,2}(:\d{2}){1,2}( [apAP][mM])?)?$/) ) {
				++types.date;
				thistype = 'date';
			} else if ( text.match(/^(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)\. \d{1,2}, (\d\d){1,2}([ -]+\d{1,2}(:\d{2}){1,2}( [ap]m)?)?$/i) ) {
				++types.date;
				thistype = 'date';
			} else if ( text.match(/^((jan|febr)uary|march|april|may|ju(ne|ly)|august|(sept|nov|dec)ember|october) \d{1,2}, (\d\d){1,2}([ -]+\d{1,2}(:\d{2}){1,2}( [ap]m)?)?$/i) ) {
				++types.date;
				thistype = 'date';
			} else if ( text.match(/^[$£][\d\.\,]+$/) ) {
				++types.currency;
				thistype = 'currency';
			} else if ( text.match(/^[\d\.]+$/) ) {
				++types.numeric;
				thistype = 'numeric';
			} else {
				++types.text;
				thistype = 'text';
			}
		}
		if ( (thistype !== '') && (types[thistype] > maxCount) ) {
			maxCount = types[thistype];
			maxType = thistype;
		}
	}
	switch( maxType ) {
		case 'date':		COL_TYPES[tableIndex][colIndex] = sortDate;		break;
		case 'currency':	COL_TYPES[tableIndex][colIndex] = sortCurrency;	break;
		case 'numeric':		COL_TYPES[tableIndex][colIndex] = sortNumeric;	break;
		case 'text':		COL_TYPES[tableIndex][colIndex] = sortText;		break;
	}
}

// Get the raw text in a cell (assuming the first child holds the textual element to sort by)
function extractText(element) {
	var retval = "";
	if (element) {
		if (!element.childNodes || element.childNodes.length === 0 )
			{ retval = element.nodeValue===null ? '' : element.nodeValue; }
		else { retval = extractText(element.childNodes[0]); }
	}
	return retval;
}

//////////////////////////////////////////// Sorting Functions ////////////////////////////////////////////

function sortDate(row1, row2) {
	var item1 = extractText(row1.cells[SORT_INDEX[SORT_TABLE_INDEX]]),
		item2 = extractText(row2.cells[SORT_INDEX[SORT_TABLE_INDEX]]),
		retval;
	
	if ( item1 === '' ) { retval = (item2 === '') ? 0 : 1; }
	else if ( item2 === '' ) { retval = -1; }
	else {
		// Put into a sortable date format
		item1 = parseDate(item1);
		item2 = parseDate(item2);
		retval = (item1 == item2) ? 0 : (item1 > item2) ? 1 : -1;
	}
	return retval;
}

function sortCurrency(row1, row2) {
	var item1 = extractText(row1.cells[SORT_INDEX[SORT_TABLE_INDEX]]),
		item2 = extractText(row2.cells[SORT_INDEX[SORT_TABLE_INDEX]]),
		retval;
	
	if ( item1 === '' ) { retval = (item2 === '') ? 0 : 1; }
	else if ( item2 === '' ) { return -1; }
	else {
		// Remove dollar signs, commas, etc.
		item1 = parseFloat(item1.replace(/[^0-9.]/g,''));
		item2 = parseFloat(item2.replace(/[^0-9.]/g,''));
		if ( isNaN(item1) ) { item1 = 0; }
		if ( isNaN(item2) ) { item2 = 0; }
		retval = item1 - item2;
	}
	return retval;
}

function sortNumeric(row1, row2) {
	var item1 = extractText(row1.cells[SORT_INDEX[SORT_TABLE_INDEX]]),
		item2 = extractText(row2.cells[SORT_INDEX[SORT_TABLE_INDEX]]),
		retval;
	
	if ( item1 === '' ) { retval = (item2 === '') ? 0 : 1; }
	else if ( item2 === '' ) { return -1; }
	else {
		item1 = parseFloat(item1);
		item2 = parseFloat(item2);
		if ( isNaN(item1) ) { item1 = 0; }
		if ( isNaN(item2) ) { item2 = 0; }
		retval = item1 - item2;
	}
	return retval;
}

function sortText(row1, row2) {
	var item1 = extractText(row1.cells[SORT_INDEX[SORT_TABLE_INDEX]]).toLowerCase(),
		item2 = extractText(row2.cells[SORT_INDEX[SORT_TABLE_INDEX]]).toLowerCase(),
		retval;
	
	if ( item1 === '' ) { retval = (item2 === '') ? 0 : 1; }
	else if ( item2 === '' ) { retval = -1; }
	else { retval = (item1 == item2) ? 0 : (item1 > item2) ? 1 : -1; }
	return retval;
}

////////////////////////////////////////////// Date Parsing ///////////////////////////////////////////////

// Put a date string into a constant format good for sorting: yyyy-mm-dd hh24:mm:ss
function parseDate(date) {
	if (date) {
		if ( date.match(/^\d{1,2}[\.\/-]\d{1,2}[\.\/-](\d\d){1,2}([ -]+\d{1,2}(:\d{2}){1,2}( [apAP][mM])?)?$/) )
			{ return parseDateNumeric(date); }
		if ( date.match(/^(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)\. \d{1,2}, (\d\d){1,2}([ -]+\d{1,2}(:\d{2}){1,2}( [ap]m)?)?$/i) )
			{ return parseDateShort(date); }
		if ( date.match(/^((jan|febr)uary|march|april|may|ju(ne|ly)|august|(sept|nov|dec)ember|october) \d{1,2}, (\d\d){1,2}([ -]+\d{1,2}(:\d{2}){1,2}( [ap]m)?)?$/i) )
			{ return parseDateLong(date); }
	}
	return '9999-99-99 99:99:99';
}

function parseDateLong(date) {
	var dateString = "",
		months = ["january", "february", "march", "april", "may", "june", "july", "august", "september", "october", "november", "december"],
		firstSpace = date.indexOf(" "),
		month = date.substring(0, firstSpace).toLowerCase();
	date = date.substring(firstSpace+1);
	var monthIndex, i;
	for( i=0; i < months.length; i++ )				// get month index
		{ if ( month == months[i] ) { break; } }
	monthIndex = i+1;
	if ( monthIndex < 10 ) { dateString = "0"; }
	dateString += monthIndex;
	
	if ( date.match(/^\d,/) ) {						// matches 1 digit day
		dateString += "-0" + date.substring(0,1);
		date = date.substring(3);
	} else {										// matches 2 digit day
		dateString += "-" + date.substring(0,2);
		date = date.substring(4);
	}
	if ( date.match(/^\d{4}/) ) {					// matches 4 digit year
		dateString = date.substring(0,4) + "-" + dateString;
		date = date.substring(4);
	} else {										// matches 2 digit year (>50 => 19XX, <50 => 20XX)
		var yr = date.substring(0,2);
		if ( parseInt(yr,10) > 50 )
			{ yr = "19" + yr; }
		else
			{ yr = "20" + yr; }
		dateString = yr + "-" + dateString;
		date = date.substring(2);
	}
	if ( date !== "" ) { dateString += parseTime(date); }
	return dateString;
}
function parseDateShort(date) {
	var dateString = "",
		months = "janfebmaraprmayjunjulaugsepoctnovdec",
		month = date.substring(0,3).toLowerCase();
	date = date.substring(5);
	var monthIndex = (months.indexOf(month)/3)+1;	// get month index
	if ( monthIndex < 10 ) { dateString = "0"; }
	dateString += monthIndex;
	
	if ( date.match(/^\d,/) ) {						// matches 1 digit day
		dateString += "-0" + date.substring(0,1);
		date = date.substring(3);
	} else {										// matches 2 digit day
		dateString += "-" + date.substring(0,2);
		date = date.substring(4);
	}
	if ( date.match(/^\d{4}/) ) {					// matches 4 digit year
		dateString = date.substring(0,4) + "-" + dateString;
		date = date.substring(4);
	} else {										// matches 2 digit year (>50 => 19XX, <50 => 20XX)
		var yr = date.substring(0,2);
		if ( parseInt(yr,10) > 50 )
			{ yr = "19" + yr; }
		else
			{ yr = "20" + yr; }
		dateString = yr + "-" + dateString;
		date = date.substring(2);
	}
	if ( date !== "" ) { dateString += parseTime(date); }
	return dateString;
}
function parseDateNumeric(date) {
	var dateString = "";
	if ( date.match(/^\d[\.\/-]/) ) {				// matches one digit month
		dateString = "0" + date.substring(0,1);
		date = date.substring(2);
	} else {										// matches two digit month
		dateString = date.substring(0,2);
		date = date.substring(3);
	}
	if ( date.match(/^\d[\.\/-]/) ) {				// matches one digit day
		dateString += "-0" + date.substring(0,1);
		date = date.substring(2);
	} else {										// matches two digit day
		dateString += "-" + date.substring(0,2);
		date = date.substring(3);
	}
	if ( date.match(/^\d\d\d\d/) ) {					// matches 4 digit year
		dateString = date.substring(0,4) + "-" + dateString;
		date = date.substring(4);
	} else {										// matches 2 digit year (>50 => 19XX, <50 => 20XX)
		var yr = date.substring(0,2);
		if ( parseInt(yr,10) > 50 )
			{ yr = "19" + yr; }
		else
			{ yr = "20" + yr; }
		dateString = yr + "-" + dateString;
		date = date.substring(2);
	}
	if ( date !== "" ) { dateString += parseTime(date); }
	return dateString;
}
function parseTime(time) {
	var timeString = "",
		first = time.substring(0,1);
	while ( !first.match(/\d/) ) {
		time = time.substring(1);
		first = time.substring(0,1);
	}

	var marker = time.substring(time.length-3),
		offset = 0;
	
	if ( marker.match(/[pP][mM]/) )				// add 12 for military time
		{ offset = 12; }
	if ( time.match(/^\d:/) ) {					// matches 1 digit hour
		if ( offset === 0 )
			{ timeString += " 0"; }
		else
			{ timeString += " "; }
		timeString += parseInt(time.substring(0,1) + offset,10) + ":";
		time = time.substring(2);
	} else {									// matches 2 digit hour
		var hr = parseInt(time.substring(0,2),10);
		if ( hr == 12 )							// 12 is a special case in military time
			{ hr = 0; }
		hr += offset;
		if ( hr < 10 )
			{ timeString += " 0" + hr + ":"; }
		else
			{ timeString += " " + hr + ":"; }
		time = time.substring(3);
	}
	timeString += time.substring(0,2);			// minutes
	time = time.substring(2);
	if ( time.length != 3 ) {					// seconds
		timeString += ":" + time.substring(1,3);
	}
	return timeString;
}

