function gz_Table(opts) {
	
	//required options
	//r : return value of ajax
	//json_var : the name of the array to get data from in the json string
	//tbody : the obj of the tbody of the table to update with rows
	
	//default options
	this.o = {
		catOrder 	: 	new Array( 1,2,3,4,5,6  ,-1 ), 	//category ids
		noFilterId 	: 	6,							//filter id that specifies that there is no filter
		sort		:	'cat',						
		
		//layout - display
		show_filter	:	true,
		show_cats	: 	true,
		show_all_factors :	false,
		arrow_mode	:	'fade', //options: 'fade' - same size arrows, opacity indicates strength, 'normal' - arrows grow in size
		
		//Cookie/session saving features
		cookie		:	false, 						//give a unique cookie name to have it store/reuse split settings,
		cookie_expire:	14, 						//in days
		
		//Custom Filters that allow for changing elements
		rowFilter	:	function(f){return f;},		
		
		//Custom events that are triggered at various times
		events		:	{
							onFinish:	function(){},
							onFilterChanged: function(f, noRefresh){},
							rowAddedChanged: function(f,a){} //If a row that is being added was overriden, event fires
						}
	}
	
	//apply custom options
	for(var i in opts) {
		this.o[i] = opts[i];
	}
	
	//define arrays/objects	
	this.filterClassesAdded = [];
	this.usedCats = {};
	this.catTDs = {};
	this.sort_funcs = { adv : this.sort_adv, cat: this.sort_cat }
	
	
}
gz_Table.prototype = {
	
	_init : function() {
	
	},
	
	_load : function() {

		//try { 
		
		var t = this.o.tbody;
		var self = this;
			
		//Clear Loaded Values
		this.factors = {};
		this.catTotals = []; 
		removeChildrenFromNode( t );
	
		//Load JSON data
		if (!this.o.data) {
			//this.o.data = JSONparse(this.o.r)[this.o.json_var];
			this.rParsed = JSON.parse(this.o.r);
			this.o.data = this.rParsed[this.o.json_var];
		}
		
		//Append Row
		if (this.o.append_row) {
			this.o.data[-1] = this.o.append_row;	
		}
		
		var data = new clone(this.o.data);

		//Sort data into default sort
		//var factorsByCat = this.IntoCats( data );
	
	///*	
		// -- Override Factors with changed cookie -- //
		this.changed = false;
		if (this.o.cookie) {
			var changedJSON = readCookie( this.o.cookie );
			if ( changedJSON && changedJSON.length > 2 ) {
					//alert( changedJSON.length + "\n" + changedJSON);
				this.changed = JSONparse( changedJSON );
			}
		}
		
		//Set tBody styles
		if (this.o.show_all_factors) {
			Addclass( t , 'gz_show_all_factors' );
		}
		if (!this.o.show_cats) {
			Addclass( t , 'gz_show_no_cats' );
		}
		
		

		// -- Load Factors -- //
		var factor_n=1;				
		
		for( var i in data ) { 
			
			if (data[i].factor_id) {
				var f = data[i];
				this.factors[ f.factor_id ] = f;
				f._default = new clone(f);
				f._sort = factor_n;
				if (!f.changed) { f.changed = {}; }
				var e = f.elements = {};
			
				 
				//Override from cookie
				if (this.changed[ f.factor_id ] && this.changed[ f.factor_id ].changed) {
					if (f.values && f.values[f.selected_filter]) {
						f.selected_filter = this.changed[ f.factor_id ].selected_filter;					
					}
					f.importance = this.changed[ f.factor_id ].importance;			
					f.changed = this.changed[ f.factor_id ].changed;
				}
				
				
	
				//Create Table Elements			
				var filter_select = false;
				
				e.tr 			= createNodeN('TR', {'class':this.GetFilterClass(f.selected_filter)} );
				e.td_label		= createNodeN('TD', {innerHTML:f.factor_name,'class':'gz_factor_label'} );
				
				if (this.o.show_filter) {
					e.td_filter	= createNodeN('TD', {} );
					if ( f.selected_filter != this.noFilterId ) {
						
						if (!this.objItemsMoreThanOne( f.values ) == 1) {
							
							for( var first in f.values ) {
								e.td_filter.innerHTML = f.values[first]['filter_label'];
								break; //stop after the first item - which should actually only be one anyway
							}
							
						} else {
							//Add Filter
							filter_select = CreateSelect('filter_' + f.factor_id, f.values, f.selected_filter, 'filter_label', 'filter_id');
							e.td_filter.appendChild( filter_select );				
						
							//Add Filter Event
							f.filter = filter_select;
							f.filter.setAttribute('factor_id', f.factor_id);
							f.filter.onchange = function(e) { self.FilterChanged(e, this) }
						}
					}
				}
				
				e.td_values = {};
				for( var filter_id in f.values) {
					var val = f.values[filter_id]['value'];
					e.td_values[ filter_id ] = {};
					
					if (typeof val == 'string' || typeof val == 'number') { 
						e.td_values[ filter_id ].span = createNodeN('TD', {'class':this.GetFilterItemClass(filter_id)+'  gz_filtered_item gz_a_values',innerHTML:val} );
						this.setColSpan( e.td_values[ filter_id ].span , 3 );
					} else {
						
						if (val.visitor_rank) {
							e.td_values[ filter_id ].visitor_rank	= createNodeN('TD', {'class':this.GetFilterItemClass(filter_id)+' gz_filtered_item gz_a_values ',innerHTML:val['visitor_rank']} );
							this.setColSpan( e.td_values[ filter_id ].visitor_rank , !val.visitor?2:1 );
						}						
						
						if (val.visitor) {
							e.td_values[ filter_id ].visitor	= createNodeN('TD', {'class':this.GetFilterItemClass(filter_id)+' gz_filtered_item gz_a_values gz_visitor '+((val['adv']<=-3)?'gz_bold':''),innerHTML:val['visitor']} );
						}
						e.td_values[ filter_id ].adv	= createNodeN('TD', {
																'class':this.GetFilterItemClass(filter_id)+' gz_filtered_item gz_a_adv gz_a_values gz_a_adv_' +  this.o.arrow_mode,
																innerHTML:	val.adv ? GetArrow(val['adv']) : ''
															} );	
						
						if (val.home) {
							e.td_values[ filter_id ].home	= createNodeN('TD', {'class':this.GetFilterItemClass(filter_id)+' gz_filtered_item gz_a_values '+((val['adv']>=3)?'gz_bold':''),innerHTML:val['home']} );				
						}
						if (val.home_rank) {
							e.td_values[ filter_id ].home_rank	= createNodeN('TD', {'class':this.GetFilterItemClass(filter_id)+' gz_filtered_item gz_a_values ',innerHTML:val['home_rank']} );
							this.setColSpan( e.td_values[ filter_id ].home_rank , !val.home?2:1 );
						}	
					}
				}
	
				
				//Add Elements to Row				
				e.tr			.appendChild(	e.td_label	);
				if ( e.td_filter ) { e.tr			.appendChild(	e.td_filter	); }
				
				for(filter_id in e.td_values) {
					for(r in e.td_values[filter_id]) {
						e.tr			.appendChild(	e.td_values[filter_id][r] 	);	
					}
				}
	
				
				//Custom row filter
				f = this.o.rowFilter(f);
				
				
				//Update Changed Filter Display
				if (f.changed) {
					a = {factor_id:f.factor_id};					
					if (filter_select && f.changed['filter']) {
						this.FilterChanged(null, filter_select, true);
					}
					this.o.events.rowAddedChanged(a);
				}	
				
				if ( !this.catTotals[ f.factor_cat ] ) { this.catTotals[ f.factor_cat ] = 0; }
				this.catTotals[ f.factor_cat ]++;
				factor_n++;
			}
		}
		
		
		
		// Create Category Tabs
		for( var cat in this.catTotals) { 
			if (this.catTotals[cat] > 0) { 
				this.catTDs[ cat ] = createNodeN('TD', {'class':'gz_a_cat',style: (this.catTotals[cat]*1 > 1) ? {backgroundImage:'url(/images/gz/tab_'+cat+'.gif)'} : '' });
				this.setRowSpan( this.catTDs[ cat ], this.catTotals[cat]);
			}
		}
		
		// Default Sort
		this.sort( this.o.sort );
		
		this.o.events.onFinish();
		
		//} catch(e) { alerterror(e); }

	},	
		
	IntoCats : function( d ) {
		var factors = this.InitCategoryFactors();
		for( var i in d ) {
			if ( !factors[ d[i].factor_cat ] ) { //category not in sort list, just append to bottom
				factors = this.InitCategory( factors, d[i].factor_cat );
			}
			
			factors[ d[i].factor_cat ][i] = d[i];
			this.catTotals[ d[i].factor_cat ]++;
		}
		return factors;
	},
	
	InitCategoryFactors : function() {
		var factors = [];
		this.catTotals = [];
		for( var i in this.o.catOrder ) {
			factors = this.InitCategory( factors, this.o.catOrder[i] );
		}
		return factors;
	},
	
	InitCategory : function(factors, i) {
		factors[ i ] = {};
		this.catTotals[ i ] = 0;
		return factors;
	},
	
	GetFilterClass : function(f) {
		var cName = 'gz_show_filter_' + f;
		var itemsName = this.GetFilterItemClass(f);
		
		if (!this.filterClassesAdded[ cName ]) {
			AddStyleRule("."+cName+' .'+itemsName, "display:" + ( navigator.appName == 'Microsoft Internet Explorer' ? "block" : "table-cell") );
			this.filterClassesAdded[ cName ] = true;
		}
		return cName;
	},
	
	GetFilterItemClass : function(f) {
		return 'gz_filtered_item_' + f;			
	},	
		
	FilterChanged : function(e, sel, noRefresh) { 
		var f = this.factors[ sel.getAttribute('factor_id') ];
		
		Removeclass( f.elements.tr , this.GetFilterClass( f.selected_filter ) );		
		Addclass( f.elements.tr , this.GetFilterClass( sel.value ) );
		
		f.selected_filter = sel.value;
		
		this.factorList = false; //resets cache

		//custom event
		this.o.events.onFilterChanged( f, noRefresh );
		
		this.reDraw();
	},
	
	SetAllFilters : function(val, selectObj, noRefresh) {
		for(var i in this.factors) {
			if (this.factors[i].filter) {
				this.factors[i].filter.value = val;
				this.FilterChanged( null, this.factors[i].filter, noRefresh);
			}
		}
		if (selectObj) {
			selectObj.selectedIndex = 0;
		}
	},
	
	// -- //
	
	shade : function() {			
		// do alt shading
		trs = this.o.tbody.getElementsByTagName('TR');
		var n = 0;
		for(var i in trs) {
			if (trs[i].tagName == 'TR') {
				if ( n%2 != 0) {
					Addclass( trs[i] , 'tr_alt' );
				} else {
					Removeclass( trs[i] , 'tr_alt' );
				}
				n++;
			}
		}
	},
	
	// -- //
	
	sort : function(t) {

		// get array of values
		if (!this.factorList) {
			this.factorList = [];
			this.factorListN = 0;
			for(var i in this.factors) {			
				if ( this.factors[i].values && this.factors[i].values[ this.factors[i].selected_filter ] ) {
					this.factorList[i] = ObjToArr( this.factors[i].values[ this.factors[i].selected_filter ]['value'] );
					this.factorList[i]['factor_id'] = i;
					this.factorList[i]['factor_cat'] = this.factors[i].factor_cat;
					this.factorList[i]['factor_name'] = this.factors[i].factor_name;
					this.factorList[i]['_sort'] = this.factors[i]._sort;
					this.factorList[i]['self'] = this; 
					this.factorListN++;
				}
			}
		}
		
		// sort
		this.sort_funcs[t](this);
		
		// update table class
		if (this.o.tbody != null) {
			this.o.tbody.className = this.o.tbody.className.replace(/gz_sort_([^ ]+)/, '');
			this.o.tbody.className += ' gz_sort_'+t;
		
		
			// rearrange the table
			var e, f;
			var catsAdded = {};
			for(var i=0; i<this.factorListN; i++) {
				if (typeof this.factorList[i] != 'function') { 
					f = this.factors[ this.factorList[i].factor_id ];
					e = f.elements;
					
					this.o.tbody.appendChild( e.tr );
					switch(t) {
						case('adv'):
							this.setColSpan( e.td_label, 2 );
							break;
						case('cat'):
							this.setColSpan( e.td_label, this.o.show_cats ? 1 : 2 );
							if (!catsAdded[f.factor_cat]) {
								catsAdded[f.factor_cat]  = e.tr.insertBefore( this.catTDs[ f.factor_cat ] , e.tr.firstChild );
							}
							break;	
					}
				}
			}
			
			// re-adjust alt shading
			this.shade();
		}

	},
	
	sort_cat : function(self) {
		if (!self.catSortIndex) {
			self.catSortIndex = {};
			for(var i in self.o.catOrder) {
				self.catSortIndex[ self.o.catOrder[i] ] = i;
			}
		}
		self.factorList.sort( self.sort_cat_callback );		
	},
	
	sort_cat_callback : function(a,b) {
		x = a['self'].catSortIndex[ a['factor_cat'] ];
		y = b['self'].catSortIndex[ b['factor_cat'] ];
		if (x == y) {
			xS = a['_sort'];
			yS = b['_sort'];
			d = ((xS < yS) ? -1 : ((xS > yS) ? 1 : 0));
		} else {
			d = ((x < y) ? -1 : 1);
		}
		return d;
	},
	
	sort_adv : function(self) {	
		self.factorList.sort( self.sort_adv_callback );
	},
	
	sort_adv_callback : function(a,b) {
		x = a['adv'];
		y = b['adv'];
		return ((x < y) ? -1 : ((x > y) ? 1 : 0));
	},
	
	setColSpan : function(o, c) {
		if (o.colSpan) {
			o.colSpan = c;	
		} else {
			o.setAttribute('colspan', c);	
		}
	},
	
	setRowSpan : function(o, c) {
		if (o.rowSpan) {
			o.rowSpan = c;	
		} else {
			o.setAttribute('rowspan', c);	
		}
	},
	
	objItemsMoreThanOne : function(obj) {
		var c=0, i;
		for(i in obj) {
			c++;
			if (c == 2) { return true; }
		}
		return false;
	},
	
	reDraw : function() { 
		//This forces IE to redraw the table, hiding the newly disapeared cells
		//Can this be improved performance wise?  Any other ways to make IE rerender the table?

	if ( navigator.appName == 'Microsoft Internet Explorer' ) {
			redrawKey = createNodeN('tfoot');
			$i('gz_algos').appendChild( redrawKey );
			$i('gz_algos').removeChild( redrawKey );	
		}

	}
	
};