// $Id: ListLib.js,v 1.20.2.1 2011-11-28 13:33:30 jrixon Exp $

function ListLib(name, props)
{
	// check all the mandatory properties were given
	var i,
	mandProps = [
		'renderTR',
		'itemCols',
		'numVisCols',
		'stripyRows',
		'els',
		'respOrderIsDflt',
		'clientOrdering',
		'dfltOrder',
		'orderWeight'
	];
	for(i = 0; i < mandProps.length; i++) {
		if(typeof props[mandProps[i]] === 'undefined') {
			alert([name, " is broken for ListLib: ", mandProps[i], " is not defined"].join(''));
			non_existant_function();
		}
	}

	// public vars in a container object
	var __pub = {
		items : [],
		els   : props.els
	},

	// public vars that have defaults but can be overidden
	overPubProps = [];
	for(i = 0; i < overPubProps.length; i++) {
		if(typeof props[overPubProps[i]] !== 'undefined') {
			__pub[overPubProps[i]] = props[overPubProps[i]];
		}
	}

	// private vars
	var _par = {},
	_orders = {},
	_hash = [],
	_orderAttrs = {},
	_orderCol = {
		col  : null,
		dir  : null,
		key  : null
	},
	_headTR = null,
	_matchTR = null,
	_matchTDs = [],
	_orderTHs = {},
	_orderTH = null,
	_numWeights = 0,
	_filterIdx = -1,
	_selected = {
		id : null,
		tr : null
	},
	_centreHeight = 0,
	_colWidths = {
		done : false,
		allButLastWidth : 0,
		prevTRWidth : 0,
		colVis : []
	},

	// server abbreviations
	_abbrs = {
		'game_code'    : {'dflt': 'HOLDEM', 'O': 'OMAHA', 'P': 'OMAHA_HILO'},
		'fund_type'    : {'dflt': 'R'},
		'ccy_code'     : {'dflt': '&pound;'},
		'limit_type'   : {'dflt': 'NL', 'P': 'PL', 'F': 'FL'},
		'limit_grp'    : {'dflt': 'micro', 'l': 'low', 'm': 'medium', 'h':'high'},
		'num_seats'    : {'dflt': '6'},
		'high'         : {'dflt': 'N'},
		'televised'    : {'dflt': 'N'},
		'private'      : {'dflt': 'N'},
		'speed_grp'    : {'dflt': 'normal', 's': 'slow', 'q': 'quick', 'f': 'fast'},
		'filter_status': {'R': 'REG', 'N': 'RUNNING', 'C': 'CLOSED'},
		'status'       : {
			'F': 'Finished',
			'C': 'Cancelled',
			'W': 'Waiting',
			'A': 'Active'
		},
		'type'         : {'C': 'Cash'}
	},

	// private vars from the supplied props
	_renderTR             = props.renderTR,
	_itemCols             = props.itemCols,
	_numVisCols           = props.numVisCols,
	_stripyRows           = props.stripyRows,
	_respOrderIsDflt      = props.respOrderIsDflt,
	_clientOrdering       = props.clientOrdering,
	_dfltOrder            = props.dfltOrder,
	_orderWeight          = props.orderWeight,
	_preListData          = props.preListData,
	_postListData         = props.postListData,
	_clickTR              = props.clickTR,
	_unclickTR            = props.unclickTR,
	_dblClickTR           = props.dblClickTR,
	_indexBy              = typeof props.indexBy === 'string' && props.indexBy.length
							? props.indexBy : null;

	if(!_clientOrdering && !_respOrderIsDflt) {
		alert([name, ' is broken for ListLib: if !clientOrdering, must respOrderIsDflt'].join(''));
		non_existant_function();
	}

	// private vars that have defaults but can be overidden

	if(props.paging) {
		var Paging = PagingLib(name, {
			divId   : __pub.els.divId,
			pageLen : props.paging.pageLen
		});
	}

	var Filters = [];
	if(props.filters) {
		for(i = 0; i < props.filters.length; i++) {
			Filters.push(FilterLib(name, props.filters[i]));
		}
	}
	if(Filters.length > 1) {
		if(typeof props.whichFilter === 'function') {
			var _whichFilter = props.whichFilter;
		}
		else {
			alert([name,
				   " is broken for ListLib: >1 filter but whichFilter is not defined"].join(''));
			non_existant_function();
		}
	}
	else {
		var _whichFilter = function() {
			return (Filters.length - 1);
		};
	}



	function regPar(o)
	{
		_par = o;

		if(Paging) {
			__pub.par = _par;
		}

		_setupTable();
	}



	function dispCentre(show)
	{
		var i, filterIdx;

		if(show) {
			document.getElementById(__pub.els.divId).style.display = '';
			if(!_centreHeight) {
				// tell the filters, once, about the newly-revealed scroll div
				var scrollDiv = document.getElementById(__pub.els.wrapTabId).getElementsByTagName('tbody')[0].getElementsByTagName('td')[0].getElementsByTagName('div')[0];
				_centreHeight = scrollDiv.clientHeight;
				for(i = 0; i < Filters.length; i++) {
					Filters[i].setOverlap(scrollDiv, _centreHeight);
				}
			}
		}
		else {
			document.getElementById(__pub.els.divId).style.display = 'none';
		}

		filterIdx = show ? _whichFilter() : -1;

		for(i = 0; i < Filters.length; i++) {
			if(i == filterIdx) {
				Filters[i].dispFilter(true);
			}
			else {
				Filters[i].dispFilter(false);
			}
		}

	}



	function render(partial)
	{
		printfire('List.render:', _par.pub.name, partial);

		_filterIdx = _whichFilter();

		_par.hint('done');

		if(__pub.items.length === 0) {
			dispBody('no_tables');

			// hide paginate details
			if(Paging) {
				Paging.paginateAdjust();
			}

			return;
		}

		var body = document.getElementById(__pub.els.itemsTabId).getElementsByTagName('tbody')[2],
		trs  = body.childNodes,
		num_trs = trs.length;

		if(!_orderAttrs[_orderCol.col]) {
			// order col looks dodgy so use default
			_orderCol.col = _dfltOrder.col;
			_orderCol.dir = _dfltOrder.dir;
		}
		else if(_orderCol.dir != 'f' && _orderCol.dir != 'r') {
			// order dir looks dodgy so use default
			_orderCol.dir = _orderAttrs[_orderCol.col].dfltOrder;
		}

		var orderKey = [_orderCol.col, ',', _orderCol.dir].join(''),
		orderKeyChanged = false;

		if(!_orderCol.key || orderKey != _orderCol.key) {
			_orderCol.key = orderKey;
			orderKeyChanged = true;
		}

		printfire('List.render:', _par.pub.name, 'with order', orderKey, _orderCol.col);

		// sort out order arrow
		if(_orderTH != _orderTHs[_orderCol.col]) {
			if(_orderTH) {
				sky.removeClass(_orderTH, 'asc');
				sky.removeClass(_orderTH, 'desc');
			}
			_orderTH = _orderTHs[_orderCol.col];
		}
		if(orderKeyChanged && _orderTH) {
			if(_orderCol.dir == 'r') {
				sky.addClass(_orderTH, 'desc');
				sky.removeClass(_orderTH, 'asc');
			}
			else {
				sky.addClass(_orderTH, 'asc');
				sky.removeClass(_orderTH, 'desc');
			}
		}

		if(_clientOrdering) {
			_makeOrder();
		}
		else {
			orderKey = [_dfltOrder.col, ',', _dfltOrder.dir].join('');
		}

		var i, t, c, tr, td, classNames, className;
		var selectedOK = false;

		var shownCount = 0;

		// paginate the data; determine where to start and how many items to display
		var t_idx, total_items, prevWeightedTR, hide;

		if(Paging) {
			Paging.paginateAdjust();
			t_idx = Paging.pub.pageStart;
  			total_items = Paging.pub.pageEnd - Paging.pub.pageStart;
		}
		else {
			t_idx = 0;
			total_items = __pub.items.length;
		}

		for(i = 1; i <= total_items; i++) {

			t = _orders[orderKey][t_idx++].i;

			if(i >= num_trs) {
				// create new tr
				tr = document.createElement('tr');

				for(c = 0; c < _numVisCols; c++) {
					td = document.createElement('td');
					tr.appendChild(td);
				}

				body.appendChild(tr);

			}
			else {
				// re-use tr
				tr = trs[i];

				if(tr.childNodes.length < _numVisCols) {
					// add missing tds
					var num_missing_tds = _numVisCols - tr.childNodes.length;
					for(var j = 0; j < num_missing_tds; j++) {
						td = document.createElement('td');
						tr.appendChild(td);
					}
				}
				else if(tr.childNodes.length > _numVisCols) {
					// remove extra tds
					var num_extra_tds = tr.childNodes.length - _numVisCols;
					for(var j = 0; j < num_extra_tds; j++) {
						tr.removeChild(tr.childNodes[0]);
					}
				}
			}

			if(typeof _clickTR === 'function') {
				tr.clickable = true;
			}
			else {
				tr.clickable = false;
			}

			// render the TR (unless partial render)
			if(!partial) {
				_renderTR(tr, t, __pub.items[t]);
			}

			if(_filterIdx >= 0) {
				hide = Filters[_filterIdx].applyFilter(__pub.items[t]);
			}
			else {
				hide = false;
			}

			// work out the classes and set className for the row
			classNames = [];

			if(hide) {
				classNames.push('hide');
			}
			else {
				// sort out stripe, promo and selected classes
				if(_stripyRows && shownCount % 2 == 0) {
					classNames.push('poker_');
				}
				if(i <= _numWeights) {
					classNames.push('tb_promo');
					prevWeightedTR = tr;
				}
				else if(prevWeightedTR) {
					sky.addClass(prevWeightedTR, 'promo_last');
					prevWeightedTR = null;
				}
				if(_selected.id != null && tr.itemId == _selected.id && tr.clickable) {
					classNames.push('selected');
					_selected.tr = tr;
					selectedOK = true;
				}
				if(tr.trClass) {
					classNames.push(tr.trClass);
				}
				if(tr.clickable) {
					classNames.push('clickable');
				}
				shownCount++;
			}

			className = classNames.join(' ');
			if(tr.className != className) {
				tr.className = classNames.join(' ');
			}
		}

		if(_selected.id != null && !selectedOK) {
			unselectItem();
			if(typeof _unclickTR === 'function') {
				_unclickTR();
			}
		}

		if(i < num_trs) {
			var spare_trs = [];
			for( ; i < num_trs; i++) {
				spare_trs.push(trs[i]);
			}

			for(i = 0; i < spare_trs.length; i++) {
				body.removeChild(spare_trs[i]);
			}
		}

		if(shownCount > 0) {
			dispBody('main');
			matchCols();
		}
		else {
			dispBody('no_tables');
		}

		if(_filterIdx >= 0) {

			Filters[_filterIdx].dispXofY(shownCount, total_items);

			// Deep linking
			if(State.applyFilters) {

				// Only do on first time
				State.applyFilters = false;

				var args        = window.location.search.substring(1).split('&'),
			    filterTypes = ['Game', 'Type', 'Stake'],
			    filterId = ['filter', _par.pub.name],
				filterVals, j, as;

				if(_par.pub.name === 'TnmtList') {
					filterId[filterId.length] = TnmtList.pub.tnmtType.toUpperCase();
				}
				filterId = filterId.join('');

				for(i = 0; i < args.length; i++) {

					// Find 'filter' arg
					as = args[i].split('=');
					if(as[0] != 'filter') {
						continue;
					}

					// Filter arg should be of the form 'Variant,Type,Stake', eg 'Holdem,All,High'.
					// See lobby.html for ids.
					filterVals = as[1].split(',');
					for(j = 0; j < filterVals.length && j < filterTypes.length; j++) {
						try {
							Filters[_filterIdx].itemClkA(
								document.getElementById(
									[filterId, filterTypes[j], filterVals[j]].join('')));
						} catch(err) {}
					}

					break;
				}
			}
		}
	}



	function dispBody(which)
	{
		var tbodies = document.getElementById(__pub.els.itemsTabId).getElementsByTagName('tbody');

		switch (which) {
		case 'loading':
			tbodies[0].style.display = 'none';
			tbodies[1].style.display = '';
			tbodies[2].style.display = 'none';
			break;

		case 'no_tables':
			tbodies[0].style.display = '';
			tbodies[1].style.display = 'none';
			tbodies[2].style.display = 'none';
			break;

		case 'main':
			tbodies[0].style.display = 'none';
			tbodies[1].style.display = 'none';
			tbodies[2].style.display = '';
			break;
		}
	}



	function hint(which)
	{
		switch (which) {
		case 'update':
			break;

		case 'new':
			dispBody('loading');
			if(Paging) {
				Paging.paginateHide();
			}
			break;

		case 'clear':
			if(Paging) Paging.paginateHide();

			var tbody = document.getElementById(__pub.els.itemsTabId).getElementsByTagName('tbody'),
			i = 0,
			len = tbody !== null ? tbody.length : 0;

			for(; i < len; i++) tbody[i].style.display = 'none';
			break;

		case 'done':
			break;
		}
	}



	/* Add an item to the item list
	 * NB: Push Message + _indexBy must be specified!
	 *
	 *   item        - item to add
 	 *   preListItem - pre-listed item ; default none
 	 *   returns     - true if added, false if a duplicate
	 */
	function addItem(item, preListItem)
	{
		// do not add a duplicate
		if(_indexBy === null || typeof item[_indexBy] === 'undefined') return false;

		var k = ['K', item[_indexBy]].join('');
		if(typeof _hash[k] !== 'undefined') return false;


		var d = [],
		i = 0,
		len = _itemCols.length,
		item_obj = {},
		items =	__pub.items,
		col, index;

		if(typeof preListItem !== 'undefined'&& preListItem !== null) d[0] = preListItem;
		d[d.length] = 0;


		// add to items
		for(; i < len; i++) {
			if(typeof item[(col = _itemCols[i])] === 'undefined') continue;
			item_obj[col] = item[col];
		}

		// -hash
		if(typeof item_obj[_indexBy] === 'undefined') {
			errorfire('ListLib.addItem: cannot find ', _indexBy, 'column within', item);
			return false;
		}
		index = ['K', item_obj[_indexBy]].join('');
		_hash[index] = items.length;

		items[items.length] = item_obj;


		// re-build order details
		suckListData(d, 0, false);

		return true;
	}



	/* Remove an item from item list
	 * NB: Push Message + _indexBy must be specified!
	 *
	 *   item        - column item to find
 	 *   preListItem - pre-listed item (default none)
	 *   returns     - true if removed the item, false if not found
	 */
	function removeItem(item, preListItem)
	{
		if(_indexBy === null || typeof _hash[(item = ['K', item].join(''))] === 'undefined') {
			return false;
		}

		var items = __pub.items,
		d = [];

		items.splice(_hash[item], 1);
		delete _hash[item];
		_rebuildHash(items);

		if(typeof preListItem !== 'undefined' && preListItem !== null) d[0] = preListItem;
		d[d.length] = 0;

		// need a better way?
		suckListData(d, 0, false);

		return true;
	}



	/* Update an item
	 * NB: Push Message + _indexBy must be specified!
	 *
 	 *   item        - column item to find
 	 *   data        - data changes
 	 *   preListItem - pre-listed item (default none)
	 *   returns     - status
	 */
	function updateItem(item, data, preListItem)
	{
		if(_indexBy === null || typeof _hash[(item = ['K', item].join(''))] === 'undefined') {
			return {found: false};
		}

		printfire('ListLib.updateItem', item, data, preListItem, _hash[item], __pub.items.length);

		item = __pub.items[_hash[item]];

		var d = [],
		i = 0,
		len = _itemCols.length,
		status = {found: true, render: false, item: item},
		order = _orderCol.col,
		f = null,
		toggle_full = false,
		c;

		for(; i < len; i++) {
			if(typeof data[(c = _itemCols[i])] === 'undefined') continue;

			// check if table is changing its 'full' status
			if (c === "num_seated"
			 && (data.num_seated === item.num_seats && item.num_seated !== item.num_seats
			  || data.num_seated !== item.num_seats && item.num_seated === item.num_seats)
			) {
				toggle_full = true;
			}

			item[c] = data[c];

			if(f === null) f = (item.force_upd = []);
			f[c] = 1;

			// changing data which is ordered or highlight flag; or we are sorting
			// by primary sort and secondary sort column value has changed; or
			// 'full' status has changed
			if(!status.render &&
			       (c === order
			     || c === "high"
			     || order === _dfltOrder.col && c === _dfltOrder.col2
			     || toggle_full)
			) {
				status.render = true;
			}
		}

		if(typeof preListItem !== 'undefined' && preListItem !== null) d[0] = preListItem;
		d[d.length] = 0;

		// need a better way?
		suckListData(d, 0, false);

		return status;
	}



	function suckListData(data, startIdx, clear, insert)
	{
		var i, t, r, c, num_items, item_obj, order_obj, items, index,
		colsLen = _itemCols.length,
		normalOrder = [],
		highOrder   = [];

		i = startIdx;

		if(typeof _preListData === 'function') {
			i += _preListData(data, startIdx);
		}

		clear = typeof clear !== 'boolean' ? true : clear;
		if(clear) {
			_hash = [];
			items = (__pub.items = []);
			num_items = data[i];
		}
		else {
			items = __pub.items;
			num_items = _respOrderIsDflt ? items.length : data[i];
		}

		_orders = {};
		_numWeights = 0;

		i++;
		t = 0;
		for(r = 0; r < num_items; r++) {

			if(clear) {
				item_obj = {};
				for(c = 0; c < colsLen; c++) {
					item_obj[_itemCols[c]] = _decompress_value(_itemCols[c], data[i + c]);
				}

				i += _itemCols.length;

				// quick lookup hash
				if(_indexBy !== null) {
					index = ['K', item_obj[_indexBy]].join('');
					_hash[index] = items.length;
				}

				items[items.length] = item_obj;
			}
			else {
				item_obj = items[r];
			}

			if(_respOrderIsDflt) {
				order_obj = {i : t};
				if(_orderWeight.col && item_obj[_orderWeight.col] === _orderWeight.val) {
					_numWeights++;
					highOrder[highOrder.length] = order_obj;
				}
				else {
					normalOrder[normalOrder.length] =  order_obj;
				}
				t++;
			}
		}

		if(typeof _postListData === 'function') {
			_postListData(data, i, clear);
		}

		if(_respOrderIsDflt) {
			// form default order list, of highlighted items, then normal items
			var orderKey = [_dfltOrder.col, ',', _dfltOrder.dir].join(''),
			len = highOrder.length,
			o = (_orders[orderKey] = []);

			for(i = 0; i < len; i++) o[o.length] = highOrder[i];
			for(i = 0, len =  normalOrder.length; i < len; i++) o[o.length] = normalOrder[i];
		}
	}



	function takeListData(items)
	{
		_rebuildHash((__pub.items = items));

		_orders = {};
		_numWeights = 0;
	}



	function shareListDataTake(listObj)
	{
		var other = listObj.shareListDataGive();

		_orders = other.orders;
		_numWeights = other.numWeights;

		_rebuildHash((__pub.items = other.items));
	}



	function shareListDataGive()
	{
		return {
			items: __pub.items,
			orders : _orders,
			numWeights : _numWeights
		};
	}



	/* Rebuild the hash mappings
	 *
	 *   items - item list
	 */
	function _rebuildHash(items)
	{
		if(_indexBy !== null) {
			_hash = [];
			for(var index, i = 0, len = items.length; i < len; i++) {
				index = ['K', items[i][_indexBy]].join('');
				_hash[index] = i;
			}
		}
	}


	function _sortCol(o)
	{
		if(__pub.items.length == 0) {
			return;
		}

		var col = o.getAttribute('sortcol');

		// re-enable paginate
		if(Paging) {
			Paging.pub.pageEnd = 0;
		}

		printfire('List.sortCol:', _par.pub.name, col);

		if(col == _orderCol.col) {
			if(_orderCol.dir == 'f') {
				_orderCol.dir = 'r';
			}
			else {
				_orderCol.dir = 'f';
			}
		}
		else {
			_orderCol.dir = _orderAttrs[col].dfltOrder;
		}

		_orderCol.col = col;

		if(_clientOrdering) {
			render();
		}
		else {
			_par.sortCol();
		}

		if(document.isLobby) {
			pageMemStore();
		}
	}



	function _makeOrder()
	{
		var col = _orderCol.col,
		f_key = [col, ',f'].join(''),
		r_key = [col, ',r'].join(''),
		orig_key, new_key;

		if(_orders[f_key] && _orders[r_key]) {
			return;
		}

		var i, obj;
		if(!_orders[f_key] && !_orders[r_key]) {
			// got neither dir so produce forwards
			printfire('List._makeOrder:', _par.pub.name, f_key);
			_orders[f_key] = [];
			_numWeights = 0;

			for(i = 0; i < __pub.items.length; i++) {
				obj = {
					i   : i,
					val : __pub.items[i][col]
				};

				switch (_orderAttrs[col].valType) {
				case 'n': // number
					obj.val = padNum(obj.val, 8);
					break;
				case 'l': // limit: use limit_1, ie remove anything after '/'
					obj.val = obj.val.replace(/\/.*/, '');
					// fall-through
				case 'm': // money : remove ccy symbol
					obj.val = parseFloat(obj.val.replace(/[^0-9-.]/g, ''));
					break;
				case 'lt' : // limit type
					if(obj.val == 'NL') {
						obj.val = '1';
					}
					else if(obj.val == 'PL') {
						obj.val = '2';
					}
					else if(obj.val == 'FL') {
						obj.val = '3';
					}
					break;
				default:
					obj.val = obj.val.toUpperCase();
				}

				if(_orderWeight.col) {
					// add weighting
					if(__pub.items[i][_orderWeight.col] == _orderWeight.val) {
						_numWeights++;
						obj.val = ['0', obj.val].join('');
					}
					else {
						obj.val = ['1', obj.val].join('');
					}
				}

				switch(typeof _dfltOrder.col2) {
				case 'string':
					obj.val += __pub.items[i][_dfltOrder.col2];
				break;
				case 'function':
					obj.val += _dfltOrder.col2(col, __pub.items[i]);
				break;
				}

				_orders[f_key].push(obj);
			}
			_orders[f_key].sort(_f_sort);
		}

		// now produce the opposite ordering

		if(_orders[f_key]) {
			// produce reverse order by going backwards over the forwards order
			orig_key = f_key;
			new_key  = r_key;
		}
		else {
			// produce forwards order by going backwards over the reverse order
			// (the reverse order can pre-exists the forwards for data whose
			// default ordering is a reverse column)
			orig_key = r_key;
			new_key  = f_key;
		}

		printfire('List._makeOrder:', _par.pub.name, new_key, 'reverse of', orig_key);
		var tmp = [];

		// go backwards from end to just before the first weighted, storing in
		// temporary array
		for(i = _orders[orig_key].length - 1; i >= _numWeights; i--) {
			tmp.push({i : _orders[orig_key][i].i});
		}

		// create final array
		_orders[new_key] = [];

		// go backwards from first weighted to beginning, storing in final array
		for(i = _numWeights - 1; i >= 0; i--) {
			_orders[new_key].push({i : _orders[orig_key][i].i});
		}

		// add temporary array onto final array
		for(i = 0; i < tmp.length; i++) {
			_orders[new_key].push(tmp[i]);
		}
	}



	function getOrderedItems()
	{
		return _orders[_orderCol.key];
	}



	function getOrderCol()
	{
		if(!_orderAttrs[_orderCol.col]) {
			// order col looks dodgy so use default
			_orderCol.col = _dfltOrder.col;
			_orderCol.dir = _dfltOrder.dir;
		}
		else if(_orderCol.dir != 'f' && _orderCol.dir != 'r') {
			// order dir looks dodgy so use default
			_orderCol.dir = _orderAttrs[_orderCol.col].dfltOrder;
		}

		return _orderCol;
	}



	function getSelected()
	{
		return _selected;
	}



	function setOrderCol(col, dir)
	{
		if(!_orderAttrs[col]) {
			// order col looks dodgy so use default
			_orderCol.col = _dfltOrder.col;
		}
		else {
			_orderCol.col = col;
		}

		if(dir != 'f' && dir != 'r') {
			// order dir looks dodgy so use default
			_orderCol.dir = _orderAttrs[_orderCol.col].dfltOrder;
		}
		else {
			_orderCol.dir = dir;
		}
	}



	function setVisCols(arr)
	{
		if(arr.length != _colWidths.colVis.length) {
			alert(['setVisCols called badly from ', _par.pub.name].join(''));
			return;
		}

		var ths = _headTR.getElementsByTagName('th');
		var i, numVis = 0, changed = false;

		for(i = 0; i < _colWidths.colVis.length; i++) {
			if(arr[i]) {
				if(!_colWidths.colVis[i]) {
					ths[i].style.display       = '';
					_matchTDs[i].style.display = '';
					_colWidths.colVis[i] = true;
					changed = true;
				}
				numVis++;
			}
			else {
				if(_colWidths.colVis[i]) {
					ths[i].style.display       = 'none';
					_matchTDs[i].style.display = 'none';
					_colWidths.colVis[i] = false;
					changed = true;
				}
			}
		}

		if(changed) {
			_colWidths.done = false;
		}

		_numVisCols = numVis;
	}



	function _setupTable()
	{

		var table = document.getElementById(__pub.els.itemsTabId);

		cleanWhitespace(table);

		var thead = document.getElementById(__pub.els.wrapTabId).getElementsByTagName('thead')[0];

		table.getElementsByTagName('tbody')[2].clickTR    = _clickTR;
		table.getElementsByTagName('tbody')[2].dblClickTR = _dblClickTR;

		_doMatch(thead, table.getElementsByTagName('tbody')[2]);

		_doSort(thead);
	}



	function _doMatch(thead, tbody)
	{
		_matchTR = document.createElement('tr');
		sky.addClass(_matchTR, 'match');

		var i, td;
		var ths = thead.getElementsByTagName('th');
		for(i = 0; i < ths.length; i++) {
			if(!_headTR) {
				_headTR = ths[i].parentNode;
			}
			td = document.createElement('td');
			td.innerHTML = '&nbsp;';
			_matchTR.appendChild(td);
			_matchTDs.push(td);
			_colWidths.colVis[i] = true;
		}
		tbody.appendChild(_matchTR);
	}



	function _doSort(thead)
	{
		var ths = thead.getElementsByTagName('th'),
		i = 0,
		len = ths.length,
		sortCol, sortType, txt, sortOrder, tt_title;

		for(; i < len; i++) {
			sortCol   = ths[i].getAttribute('sortcol');
			sortType  = ths[i].getAttribute('sorttype');
			sortOrder = ths[i].getAttribute('sortorder');
			tt_title  = ths[i].getAttribute('title');
			if(!tt_title) {
				tt_title = '';
			}
			if(sortCol && sortCol != '') {
				txt = getTextValue(ths[i]);
				ths[i].innerHTML = ['<span nothelp="1" title="',
									tt_title,
									'" class="sortheader" sortcol="',
									sortCol,
									'">',
									txt,
									'</span>'].join('');
				ths[i].getElementsByTagName('span')[0].onclick = function() {_sortCol(this); };
				ths[i].setAttribute('title','');
				_orderTHs[sortCol] = ths[i];
				if(sortType != 'n' && sortType != 'm' && sortType != 'l' && sortType != 'lt') {
					sortType = 's';
				}
				if(sortOrder != 'r') {
					sortOrder = 'f';
				}
				_orderAttrs[sortCol] = {
					valType   : sortType,
					dfltOrder : sortOrder
				};
			}
		}
	}
	
	
	
	function setOrderAttrs(sortCol, valType, dfltOrder) {
		if(typeof valType   !== 'undefined' && valType   != null) _orderAttrs[sortCol].valType   = valType;
		if(typeof dfltOrder !== 'undefined' && dfltOrder != null) _orderAttrs[sortCol].dfltOrder = dfltOrder;
	}



	function matchCols()
	{
		// nothing to do unless the match tr's width has changed
		var TRWidth = _matchTR.clientWidth;
		if(TRWidth == 0) {
			// some browsers (Opera) don't have clientWidth for tr
			TRWidth = _matchTR.offsetWidth;
		}
		if(TRWidth == 0 || (_colWidths.done && TRWidth == _colWidths.prevTRWidth)) {
			return;
		}
		_colWidths.prevTRWidth = TRWidth;

		var i, width, totalWidth = 0;

		// we'll only pay attention to visible columns
		var visIdxs = [];
		for(i = 0; i < _colWidths.colVis.length; i++) {
			if(_colWidths.colVis[i]) {
				visIdxs.push(i);
			}
		}

		if(!_colWidths.done) {
			// set all but last column to the same width as its head td
			var headTDs = _headTR.getElementsByTagName('th');
			_colWidths.allButLastWidth = 0;
			for(i = 0; i < visIdxs.length - 1; i++) {
				width = headTDs[visIdxs[i]].clientWidth;
				_matchTDs[visIdxs[i]].style.width = width + 'px';
				_colWidths.allButLastWidth += width;
			}
			_colWidths.done = true;

			// set the last column to the remaining width
			for(i = 0; i < visIdxs.length; i++) {
				totalWidth += headTDs[visIdxs[i]].clientWidth;
			}
		}

		width = totalWidth - _colWidths.allButLastWidth;
		_matchTDs[visIdxs[visIdxs.length - 1]].style.width = [width, 'px'].join('');
	}



	function scrollToTop()
	{
		try {
			document.getElementById(__pub.els.itemsTabId).parentNode.scrollTop = 0;
		} catch(e) {}
	}



	function scrollToTR(tr)
	{
		try {
			var i, trs, tr_pos, scroll_div, scroll_height, scroll_pos;
			scroll_div = document.getElementById(__pub.els.itemsTabId).parentNode;
			scroll_height = scroll_div.clientHeight;
			scroll_pos = scroll_div.scrollTop;

			trs = tr.parentNode.getElementsByTagName('tr');
			for(i = 0; i < trs.length; i++) {
				if(trs[i] == tr) {
					tr_pos = (i - 1) * tr.clientHeight;
					break;
				}
			}
			if(tr_pos < scroll_pos) {
				scroll_div.scrollTop = tr_pos - tr.clientHeight;
			}
			else if(tr_pos > scroll_pos + scroll_height - tr.clientHeight) {
				scroll_div.scrollTop = tr_pos - scroll_height + (2 * tr.clientHeight);
			}
		} catch(e) {}
	}



	function selectItem(tr, id)
	{
		if(tr != null) {
			if(tr != _selected.tr) {
				sky.addClass(tr, 'selected');
				if(_selected.tr != null) {
					sky.removeClass(_selected.tr, 'selected');
				}
			}
			_selected.tr = tr;
		}
		_selected.id = id;

		// get the selected item
		if(_indexBy !== null && typeof _hash[(id = ['K', id].join(''))] !== 'undefined') {
			return __pub.items[_hash[id]];
		}

		return null;
	}



	function unselectItem()
	{
		if(_selected.tr != null) {
			sky.removeClass(_selected.tr, 'selected');
		}
		_selected.id = null;
		_selected.tr = null;
	}



	function memGetString()
	{
		var params = [], i;

		params.push(_par.Req.getId());

		params.push(_orderCol.col);
		params.push(_orderCol.dir);

		for(i = 0; i < Filters.length; i++) {
			params.push(Filters[i].memGetString());
		}

		return params.join('|');
	}



	function memRecover(cookieStr, urlStr, reqFn)
	{
		var cookieParams = cookieStr.split('|'), i;

		if(cookieStr == '') {
			setOrderCol('', '');
			for(i = 0; i < Filters.length; i++) {
				Filters[i].memRecover('');
			}
		}
		else {

			setOrderCol(cookieParams[1], cookieParams[2]);

			for(i = 0; i < Filters.length; i++) {
				if(typeof cookieParams[i + 3] === 'undefined') {
					Filters[i].memRecover('');
				}
				else {
					Filters[i].memRecover(cookieParams[i + 3]);
				}
			}
		}

		if(urlStr != '') {
			reqFn(urlStr);
		}
		else if(cookieStr != '') {
			reqFn(cookieParams[0]);
		}
		else {
			reqFn('');
		}
	}


	/* Prviate function to decompress a value
	 * -expands abbreviated value
	 * -adds leading zeros to decimals
	 * -decompress base64 encoded numbers
	 *
	 *   item     - name of item to decompress
	 *   val      - value to decompress
	 *   returns  - decompressed value
	 */
	function _decompress_value(item, val) {

		// Status: expand a number n to "n players needed"; expand "Ln" to "Level n"
		if(item === 'status') {
			if (/^\d+$/.test(val)) return [val, ' players needed'].join('');
			else if (/^L\d+$/.test(val)) return val.replace('L', 'Level ');
		}

		if(item === 'status_disporder') {
			var m = val.match(/^([1-5]:)(![A-Za-z0-9\+/]+)$/);
			if(m !== null && m.length === 3) {
				return [m[1], padNum(decompress_num(m[2]), 10)].join('');
			}

			return val;
		}

		// expand any server abbreviations
		var idx = val.length === 0 ? 'dflt' : val;
		if(typeof _abbrs[item] === 'object' && typeof _abbrs[item][idx] === 'string') {
			return _abbrs[item][idx];
		}

		val = decompress_num(val);

		// add leading "0"
		if(/^\.\d+$/.test(val)) return ['0', val].join('');

		return val;
	}


	var _this = {
		pub : __pub,
		Paging : Paging,
		Filters : Filters,
		regPar : regPar,
		dispCentre : dispCentre,
		hint : hint,
		addItem : addItem,
		removeItem : removeItem,
		updateItem : updateItem,
		suckListData : suckListData,
		takeListData : takeListData,
		shareListDataTake : shareListDataTake,
		shareListDataGive : shareListDataGive,
		render : render,
		dispBody : dispBody,
		matchCols : matchCols,
		getOrderedItems : getOrderedItems,
		getOrderCol : getOrderCol,
		getSelected : getSelected,
		setOrderCol : setOrderCol,
		scrollToTop : scrollToTop,
		scrollToTR : scrollToTR,
		setVisCols : setVisCols,
		selectItem : selectItem,
		unselectItem : unselectItem,
		memGetString : memGetString,
		memRecover : memRecover,
		setOrderAttrs : setOrderAttrs
	};

	if(Paging) {
		Paging.regPar(_this);
	}
	if(Filters.length) {
		for(i = 0; i < Filters.length; i++) {
			Filters[i].regPar(_this);
		}
	}

	return _this;
}

