function StatsPage() {
	var self = this;
	var objCanvas = document.getElementById('tsnStatsHolder');
	var objPlayerSearchBox;
	var _pageload = true;
	var _defaultsortcolumn;
	var _defaultsortdirection;
	var _querystring = document.location.search;
	var _headings = {};
	var _data = [];
	var _sourceData = [];
	var _sortCol = 2;
	var _hilightCol = _sortCol;
	var _sortColDirection = 1;
	var _page = 0;
	var _maxRowsPerPage = 30;
	var _pages = 0;
	var _leaf = 0;
	var _leafs = 0;
	var _headColumnDepth = 0;
	var _cells = [];
	var intMaxDepth = 0;
	var _sourceColumnDefinitions = [];
	var _filters = [];
	var _globalFilters = [];
	var _globalFilterSelectors = [];
	var _activeSelectedFilters = [];
	var _dropdowns = [];
	var _dropdownsDefaultState = [];
	var _showPlayerSearch = false;
	var _playerSearchDatasource;
	var _cookieIdentifier = 'StatPlayers';
	var _cookie = ['','',''];
	var _searchLabel = ' ';
	var _searchInvitation = 'Search by name';
	var _errors = [];
	var _advisories = [];

	// Error and advisory feedback
	this.showStatus = function() {
		alert('Errors:\n\n' + _errors.join('\n'));
		alert('Advisories:\n\n' + _advisories.join('\n'));
	};
	
	// Setter methods
	this.setHeadings = function(o) { 
		_headings = o; 
		_leafs = o.length; 
		if (_pageload && (_defaultsortcolumn || _defaultsortcolumn === 0)) {
			_sortCol = _hilightCol = _defaultsortcolumn;
			_sortColDirection = _defaultsortdirection ? -1 : 1;
		}
		else {
			_sortCol = _hilightCol = _headings[_leaf].defaultsortcolumn;
			_sortColDirection = _headings[_leaf].defaultsortascending ? -1 : 1;
		}
		_pageload = false;
	};
	this.setDropdowns = function(a) {
		_dropdowns = a;
	}
	this.setData = function(o) { _data = o; };
	this.setSortColumnAscending = function(b) { _sortColDirection = b == false ? -1 : 1; };
	this.setSortColumn = function(i) { _sortCol = i; };
	this.showPlayerSearch = function(i) {
		if (i || i === 0) {
			_showPlayerSearch = true;
			_playerSearchDatasource = i;
		}
	}
	this.setCanvas = function(s) {
		objCanvas = document.getElementById(s);
	};
	
	// Initializers
	this.initialize = function() {
		var rxSortCol = /sortcol=(\d+)/i;
		var rxSortDirection = /sortdir=asc/i;
		var rxFilter = /prefilter=([0-9-]+)/;
		
		var arrSortCol = _querystring.match(rxSortCol);
		var arrFilterPairs = []; 
		var a,b;
		
		_defaultsortcolumn = arrSortCol && arrSortCol.length ? arrSortCol[1] : null;
		_defaultsortdirection = _querystring.match(rxSortDirection);
		self.defaultsortcolumn = _defaultsortcolumn;
		self.defaultsortdirection = _defaultsortdirection;
				
		var arrFilterMatches = _querystring.match(rxFilter);
		if (arrFilterMatches) {
			arrFilterPairs = arrFilterMatches[1].split('-');
			while(arrFilterPairs.length > 1) {
				a = arrFilterPairs.shift();
				b = arrFilterPairs.shift();
				_dropdownsDefaultState[a] = b;
			}
		}
		
		if (window['intSearchPlayerNameColumn'] != 'undefined' && (intSearchPlayerNameColumn || intSearchPlayerNameColumn === 0)) { self.showPlayerSearch(intSearchPlayerNameColumn);} else { _advisories.push('No player name search column specified. Player search will not be displayed.')}
		if (window['arrSelect']) { self.setDropdowns(arrSelect) } else { _advisories.push('No dropdowns specified, so none will be displayed.'); }
		if (window['arrCols']) { self.setHeadings(arrCols); } else { _errors.push("No columns specified"); }
		if (arrPlayerStats) self.setData(arrPlayerStats);
		objCanvas.parentNode.insertBefore(self.renderDropdowns(),objCanvas);
		self.render(true);
	}
	this.resetPage = function() {
		_page=0;
		self.render(true);
	}

	// Data processing
	this.findMaxDepth = function(a) { // THIS IS A RECURSIVE FUNCTION
		var objSource;
		var intDepth = 1;
		var intSubDepth;
		var intMaxDepth=0;
		for(var i=0;i < a.length;i++) {
			objSource = a[i];
			if (objSource['cols'] != undefined) {
				intSubDepth = self.findMaxDepth(objSource['cols'])
				if (intSubDepth > intMaxDepth) { intMaxDepth = intSubDepth; }
			}
		}
		intDepth += intMaxDepth;
		return intDepth;
	}
	this.rebuildGlobalFilter = function() {
		_activeSelectedFilters = [];
		for(var i = 0; i<_globalFilterSelectors.length; i++) {
			if (_globalFilters[_globalFilterSelectors[i].value] && _globalFilters[_globalFilterSelectors[i].value].filter) {
				_activeSelectedFilters.push(_globalFilters[_globalFilterSelectors[i].value].filter);
			}
			if (_globalFilters[_globalFilterSelectors[i].value] && _globalFilters[_globalFilterSelectors[i].value].redirect) {
				window.location.href=_globalFilters[_globalFilterSelectors[i].value].redirect;
				return false;
			}
		}
	}
	this.processCols = function(a,i) { // THIS IS A RECURSIVE FUNCTION
		var objPageColumn;
		var arrPageColumns = a;
		var intDepth = i;
		
		if (_cells[intDepth]==undefined) {
			_cells[intDepth] = [];
		}
		for(var i = 0; i < arrPageColumns.length; i++) {
			objPageColumn = arrPageColumns[i];
			self.findColSpan(objPageColumn);
			_cells[intDepth].push(objPageColumn);
			if (objPageColumn.cols != undefined) {
				self.processCols(objPageColumn.cols,intDepth + 1);
			}
			else {
				_sourceColumnDefinitions.push(objPageColumn);
			} 
		}
	}
	this.filter = function(i) {
		var intSortCol = i;
		var boolFilterPlayersByName = false;
		if (objPlayerSearchBox && objPlayerSearchBox.value && objPlayerSearchBox.value != _searchInvitation && objPlayerSearchBox.value.length > 1) {
			boolFilterPlayersByName = true;
		}
		
		_sourceData = [];

		if (boolFilterPlayersByName) {
			var rxPlayerSearch;
			var rxValidCharacters = /\s+/gi;
			var strPlayerSearch = objPlayerSearchBox.value;
			strPlayerSearch = self.trim(strPlayerSearch);
			try { // Expect RegEx errors to occur with user input
				strPlayerSearch = strPlayerSearch.replace(rxValidCharacters,'|');
				rxPlayerSearch = new RegExp('(' + strPlayerSearch + ')','i');
				for (var i = 0; i < _data.length; i++) {
					if ((_data[i] || _data[i] === 0) && _data[i][intSortCol] !== null && _data[i][intSortCol] !== '' && self.applyFilters(_data[i]) && rxPlayerSearch.test(_data[i][_playerSearchDatasource])) {
						_sourceData.push(_data[i])
					}
				}
			}
			catch (e) {
				for (var i = 0; i < _data.length; i++) {
					if ((_data[i] || _data[i] === 0) && _data[i][intSortCol] !== null && _data[i][intSortCol] !== '' && self.applyFilters(_data[i])) {
						_sourceData.push(_data[i])
					}
				}
			}
		}
		else {
			for (var i = 0; i < _data.length; i++) {
				if ((_data[i] || _data[i]===0) && _data[i][intSortCol] !== null && _data[i][intSortCol] !== '' && self.applyFilters(_data[i])) {
					_sourceData.push(_data[i])
				}
			}
		}
	}
	
	
	this.trim = function(s) {
		var rxTrimLeft = /^\s+/;
		var rxTrimRight = /\s+$/;
		return s.replace(rxTrimLeft,'').replace(rxTrimRight,'');
	}
	this.applyFilters = function(o) {
		var objCell = o;
		var bReturn = true;
		for (var i = 0; i <_activeSelectedFilters.length; i++) {
			try {
				bReturn &= _activeSelectedFilters[i](o);
//				_page = 0;
			}
			catch (e) {
				_errors.push(e.message);
			}
		}
		return bReturn;
	}
	this.sortColumn = function(o) {
		var objCell = o;
		self.filter(objCell);
		var bSortAscending;
		var intDataColumn;
		_sourceData.sort(self.sort);
		_pages = Math.ceil(_sourceData.length / _maxRowsPerPage);
	}
	this.sort = function(a,b) {
		if (a[_sortCol] === b[_sortCol]) { return 0;}
		if (a[_sortCol] > b[_sortCol]) return -_sortColDirection;
		return _sortColDirection;
	}

	// Rendering methods
	this.render = function(bRescroll) {
		var objLeafNavigationButtons,objPageNavigationButtons;
		if (bRescroll) {
			objCanvas.scrollTop=0;
		}
		var objTable = self.buildTable();
		objTable.appendChild(self.renderHead());
		self.rebuildGlobalFilter();
		self.sortColumn(_sortCol);
		objTable.appendChild(self.renderBody());

		objPageNavigationButtons = self.renderPageNavigationButtons();
		
		if (_headings.length > 1 && _headings[_leaf] && (_headings[_leaf].nextleaf || _headings[_leaf].nextleaf === 0)) {
			objLeafNavigationButtons = self.renderLeafNavigationButtons();
		}

		while(objCanvas.firstChild) objCanvas.removeChild(objCanvas.firstChild);

		if (objLeafNavigationButtons) {
			objCanvas.appendChild(objLeafNavigationButtons);
		}
		objCanvas.appendChild(objTable);
		if (objPageNavigationButtons) {
			objCanvas.appendChild(objPageNavigationButtons);
		}
	}
	this.renderPageNavigationButtons = function() {
		var objDiv = document.createElement('div');
		objDiv.className = 'statsPageNavigation';
		
		if (_pages < 2) {
			return objDiv;
		}
		var objPreviousButton = document.createElement('a');
		objPreviousButton.onclick = function() {_page--; self.render(true)}
		var objPreviousButtonText = document.createTextNode('Previous Page');
		objPreviousButton.appendChild(objPreviousButtonText);

		var objBar = document.createTextNode(" | ");
		
		var objNextButton = document.createElement('a');
		objNextButton.onclick = function() {_page++; self.render(true)}
		var objNextButtonText = document.createTextNode('Next Page');
		objNextButton.appendChild(objNextButtonText);

		if (_page == 0) {
			objDiv.appendChild(objNextButton);
		}
		else if(_page == _pages -1) {
			objDiv.appendChild(objPreviousButton);
		}
		else {
			objDiv.appendChild(objPreviousButton);
			objDiv.appendChild(objBar);
			objDiv.appendChild(objNextButton);
		}
		
		return objDiv;
	}
	this.renderLeafNavigationButtons = function() {
		var intNextLeaf = _headings[_leaf].nextleaf;
		var intPreviousLeaf = _headings[_leaf].previousleaf;
		var objDiv = document.createElement('div');
		objDiv.className = 'statsLeafNavigation';

		var objNextButton = document.createElement('a');
		objNextButton.onclick = function() {_leaf = intNextLeaf; self.render()}
		var objNextButtonText = document.createTextNode('More stats');
		objNextButton.appendChild(objNextButtonText);

		objDiv.appendChild(objNextButton);
		return objDiv;
	}
	this.renderDropdowns = function() {
		var arrSelectDetails;
		var objSelect;
		var objOption;
		var objOptionText;
		var bRegisterListener;
		var bRegisterLeafReader;
		var objDiv = document.createElement('div');
		_globalFilterSelectors = [];
		_globalFilters = [];
		objDiv.className = 'statSelectors';
		
		for (var i = 0; i < _dropdowns.length; i++) {
			bRegisterListener = false;
			bRegisterLeafReader = false;
			arrSelectDetails = _dropdowns[i];
			objSelect = document.createElement('select');
			//console.log(_dropdowns);
			if(_dropdowns[i].length < 2)
			{
				objSelect.style.display='none';
			}
			for (var j=0; j < arrSelectDetails.length; j++) {
				if (arrSelectDetails[j] !== undefined) {
					objOption = document.createElement('option');
					if (_dropdownsDefaultState[i] !== undefined && _dropdownsDefaultState[i]==j) {
						objOption.selected = true;
					}
					objOptionText = document.createTextNode(arrSelectDetails[j].label);
					objOption.appendChild(objOptionText);
					objSelect.appendChild(objOption);
					if (arrSelectDetails[j].filter || arrSelectDetails[j].redirect) { objOption.value = _globalFilters.length; _globalFilters.push(arrSelectDetails[j]); bRegisterListener = true; }
					if (typeof(arrSelectDetails[j].leaf)=='number') { objOption.value = arrSelectDetails[j].leaf; bRegisterLeafReader = true; }
				}
			}
			if (bRegisterListener) {
				_globalFilterSelectors.push(objSelect);
				objSelect.onchange = function() { self.render(); };
			}
			else if (bRegisterLeafReader) {
				objSelect.onchange=function() {_leaf= this.value; _page=0;self.render(); };
			}
			objDiv.appendChild(objSelect);
		}
		if (_showPlayerSearch) {
			objDiv.appendChild(objCanvas.parentNode.insertBefore(self.renderPlayerSearch(),objCanvas));
		}
		return objDiv;
	}
	this.renderPlayerSearch = function() {
		var objSpan = document.createElement('span');
		objSpan.className = 'search';
		
		objPlayerSearchBox = document.createElement('input');
		objPlayerSearchBox.className='playerSearch';
		objPlayerSearchBox.value=_searchInvitation;
		objPlayerSearchBox.onfocus = self.clearSearch;
		objPlayerSearchBox.onkeyup = self.render;
		
		var objLabel = document.createTextNode(_searchLabel);

		objSpan.appendChild(objLabel);
		objSpan.appendChild(objPlayerSearchBox);

		return objSpan;
	}
	this.clearSearch = function() {
		if (objPlayerSearchBox.value == _searchInvitation) {
			objPlayerSearchBox.value=''; 
		}
	}
	
	// Table
	this.buildTable = function() {
		var objTable = document.createElement('table');
		var objTableId = document.createAttribute('id');
		objTableId.nodeValue = 'extendedstats1';
		objTable.setAttributeNode(objTableId);
		return objTable;
	}

	// Table Head
	this.renderHead = function() { 
		var objThead = document.createElement('thead');
		var arrPageColumns = _headings[_leaf].columns;
		intMaxDepth = self.findMaxDepth(arrPageColumns);
		_sourceColumnDefinitions = [];
		_cells = [];
		self.processCols(arrPageColumns,0);
		for (var i = 0; i < _cells.length; i++) {
			objThead.appendChild(self.buildHeaderRow(i));
		}
		return objThead;
	}
	this.findColSpan = function(o) {
		var objCell = o;
		var intWidth = 0;
		if (objCell['cols'] == undefined) {
			intWidth++;
		}
		else {
			for (var i = 0; i < objCell.cols.length; i++) {
				intWidth += self.findColSpan(objCell.cols[i]);
			}
		}
		objCell.colSpan = intWidth;
		return intWidth;
	}
	this.buildHeaderRow = function(i) {
		var intRow = i;
		var objTr = document.createElement('tr');
		for (var i = 0; i < _cells[intRow].length;i++) {
			objTr.appendChild(self.buildHeaderCell(intRow,i));
		}
		return objTr;
	}
	this.buildHeaderCell = function(i,j) {
		var intRow = i;
		var intCol = j;
		var objCell = _cells[intRow][intCol];
		var objTd = document.createElement('th');

		if (objCell.sortable) {
			var objA = document.createElement('a');
			var objAHref = document.createAttribute('href');
			objAHref.nodeValue = '#';
			objA.setAttributeNode(objAHref);
			objTd.appendChild(objA);
			var objTdText = document.createTextNode(_cells[intRow][intCol].heading);
			objA.appendChild(objTdText);
		}
		else {
			var objTdText = document.createTextNode(_cells[intRow][intCol].heading);
			objTd.appendChild(objTdText);
		}

		if (_cells[intRow][intCol].colSpan > 1) {
			var objColSpan = document.createAttribute('colspan');
			objColSpan.nodeValue = objCell.colSpan;
			objTd.setAttributeNode(objColSpan);
		}
		else {
			var objRowSpan = document.createAttribute('rowspan');
			objRowSpan.nodeValue = intMaxDepth - intRow + 1;
			objTd.setAttributeNode(objRowSpan);
		}
		if (objCell.classname != undefined) {
			objTd.className = objCell.classname;
		}
		if (objCell.label != undefined) {
			objTd.title = objCell.label;
		}
		if (objCell.sortable) {
			objTd.onclick=function() {
				_page=0;
				if (_sortCol == objCell.sortcolumn || _sortCol == objCell.datasource) {
					_sortColDirection = -_sortColDirection;
				}
				else {
					_sortColDirection = objCell['sortascending'] && objCell['sortascending'] == true ? -1 : 1;
				}
				_hilightCol = objCell.sortcolumn;
				_sortCol = objCell.sortcolumn ? objCell.sortcolumn : objCell.datasource;
				_filters = objCell.filter ? objCell.filter : [];
				self.render();
				return false;
			}
		}

		return objTd;
	},

	// Table Body
	this.renderBody = function() {
		var objTbody = document.createElement('tbody');
		var objTr;
		for(var i = _page * _maxRowsPerPage; i < (_page + 1) * _maxRowsPerPage && i<_sourceData.length; i++) {
			objTr = self.buildBodyRow(_sourceData[i],(i % 2) + 1);
			if (objTr != null) {
				objTbody.appendChild(objTr);
			}
		}
		return objTbody;
	}
	this.buildBodyRow = function(arrDataRow,intRowClass) {
		if (arrDataRow[_sortCol] == null) {
			return null;
		}
		var objTr = document.createElement('tr');
		var objTCell;
		objTr.className = "bg" + intRowClass;
		for (var i = 0; i < _sourceColumnDefinitions.length; i++) {
			objTCell = self.buildBodyCell(arrDataRow,i);
			objTr.appendChild(objTCell);
		}
		return objTr;
	}
	this.buildBodyCell = function(a,i) {
		var arrDataRow = a;
		var bUseTd = i % 2; 
		var objTCell = document.createElement(bUseTd ? 'td' : 'th');
		if (_hilightCol != undefined) {
			if (_hilightCol == _sourceColumnDefinitions[i].sortcolumn) {
				objTCell.className = 'hiLite';
			}
		}
		else if (_sortCol == _sourceColumnDefinitions[i].datasource) {
			objTCell.className = 'hiLite';
		}
		var strDisplay = self.buildBodyCellText(arrDataRow,i);
		var objTCellValue = document.createTextNode(strDisplay);
		if (_sourceColumnDefinitions[i].link) {
			var objTCellValue = self.buildBodyCellLink(objTCellValue,arrDataRow,i);
		}
		objTCell.appendChild(objTCellValue);
		return objTCell;
	}
	this.buildBodyCellText = function(a,i) {
		var arrDataRow = a;
		var arrDisplayFormat = _sourceColumnDefinitions[i].display;
		
		var strCellText = arrDataRow[_sourceColumnDefinitions[i].datasource];
		strCellText = _sourceColumnDefinitions[i].mapping && _sourceColumnDefinitions[i].mapping[strCellText] ? _sourceColumnDefinitions[i].mapping[strCellText] : (strCellText == null ? '' : strCellText);
		if (arrDisplayFormat) {
			strCellText = arrDisplayFormat[0];
			arrReplacements = arrDisplayFormat[1];
			for (var i = 0; i < arrReplacements.length; i++) {
				strCellText = strCellText.replace('{' + i.toString() + '}',arrDataRow[arrReplacements[i]]);
			}
		}
		return strCellText;
	}
	this.buildBodyCellLink = function(o,a,i) {
		var objTCellValue = o;
		var arrDataRow = a;
		var objA = document.createElement('a');
		var objAHref = document.createAttribute('href');
		
		var strCellLink = _sourceColumnDefinitions[i].link[0];
		var arrReplacements = _sourceColumnDefinitions[i].link[1];
		for (var i = 0; i < arrReplacements.length; i++) {
			strCellLink = strCellLink.replace('{' + i.toString() + '}',arrDataRow[arrReplacements[i]]);
		}
		
		objAHref.nodeValue = strCellLink;
		objA.setAttributeNode(objAHref);
		objA.appendChild(objTCellValue);
		return objA
	}

	// Cookies
	this.readCookies = function() {
		var rxCookie = new RegExp('^(.*)' + _cookieIdentifier + '=([^;]*)(.*)*$');
		_cookie = [document.cookie + '; ','',''];
		if (rxCookie.test(document.cookie)) {
			var arrCookie = rxCookie.exec(document.cookie);
			arrCookie.shift();
			_cookie = [arrCookie[0],arrCookie[1],arrCookie[2] ? arrCookie[2] : ''];
		}
	}
	this.setCookie=function(s) { _cookie[1] = s; }
	this.writeCookies = function() {
		var strCookie;
		strCookie = _cookie[0] + _cookieIdentifier + '=' + _cookie[1] + _cookie[2];
		document.cookie=strCookie;
	}
}