
var Table = Class.create( 
{
	/**
	 * new Table ( table, input, contentProvider, columnProperties )
	 * @param table: The table element we control.
	 * @param input: Something we can use Enumerable on.
	 * @param contentProvider: A function taking an item from 
	 * 		  input and returning an array of column contents 
	 * 		  for this row.
	 * @param columnProperties: columnProperties are objects
	 * 		  containing attributes for the actual <td> 
	 * 		  elements.
	 */
	initialize : function ( table, input, contentProvider, columnProperties ){
		this.table = $(table);
		if ( table.childNodes.length == 0 ){
			table.appendChild (new Element("tbody"));
		}else{
			if ($A(table.childNodes).any(
				function (ob){
					return
					ob.name != 'thead' &&
					ob.name != 'tbody' &&
					ob.name != 'tfooter';
				})
									){
				throw new Error ("Bad table!");
			}
		}
		this.contentProvider = contentProvider;
		this.columnProperties = columnProperties;
		this.input = input;
		this.rowToData = new Hash ();
		if (input){
			this.inputChanged ();
		}
	},
	
	/**
	 * The input changed (or maybe our content provider or something),
	 * so rebuild the table.
	 */
	inputChanged : function (){
		
		this.rowToData = new Hash ();
		var tb;
		for ( var i=0;i<this.table.childNodes.length;i++){
			var kid = this.table.childNodes[i];
			if ( kid.nodeName == 'TBODY' ){
				tb=kid;
				break;
			}
		}
		
		/*Hack: this doesn't work with text nodes.  A better thing 
		 * would be to make it so it could, but for now we just "normalize"
		 * the html to be safe.
		 */
		 var bad = []
		 for (var i = 0;i<tb.childNodes.length;i++) {
		 	if(tb.childNodes[i].nodeType==document.TEXT_NODE){
		 		bad.push(tb.childNodes[i]);
		 	}
		 }
		 bad.each (
		 	function ( b ) {
		 		tb.removeChild(b);
		 	} 
		 )
		
		var input = this.input;
		for (var i=0;i<input.length;i++){
			
			var row = new Element("tr");
			var cel = this.contentProvider ( input[i] );
			for (var j=0;j<cel.length;j++){
				var props = this.columnProperties[j]?this.columnProperties[j]:{};
				row.appendChild (new Element('td',props).update(cel[j]));
			}
			row.__tableRowKey = '__tr__' + this.id + '__' + i;
			this.rowToData [row.__tableRowKey] = input[i]; 
			if(i<tb.childNodes.length){
				$(tb.childNodes[i]).replace (row);
			}else{
				tb.appendChild(row);
				
				/*For some reason we need to do this...*/
				$(tb.childNodes[i]).replace(row);
			}
		}
		
		while ( tb.childNodes.length > input.length ){
			tb.removeChild ( tb.lastChild );
		}
	},
	
	/**
	 * Return the data corresponding to the row that contains an element.
	 * @param elem: An element contained by one of the table rows.
	 */
	 getData : function ( elt ){
	 	while (elt.parent && elt.parent != this.table){
	 		if (elt.__tableRowKey in this.rowToData){
	 			return this.rowToData[elt.__tableRowKey];
	 		}
	 		elt=elt.parent;
	 	}
	 },
	 
	 /**
	  * Get the element corresponding to this data.  Not too fast...
	  * @param data: The data corresponding to the element.
	  */
	  getElement : function (data){
	  	var tb ;
	  	for  (var i=0;i<this.table.childNodes.length;i++){
	  		var e=this.table.childNodes[i];
	  		if(e.nodeName == 'TBODY') {
	  			tb=e;
	  			break;
	  		}
	  	}
	  	for (var i=0;i<tb.childNodes.length;i++){
	  		var e = tb.childNodes[i];
	  		if(e.__table_key&&this.rowToData[e.__table_key]==data){
	  			return e;
	  		}
	  	}
	  }
} );

/**
 * Utility to remove an item from an array.
 * @param array: The array in question.
 * @param item:  The item to remove.
 * @param cmp :  The (optional) compairison function to use.
 * @return: If the array is empty after removing item, null.
 * 			If item is the last element, the previous one.
 * 			Otherwise, the subsequent element. 
 */
function remove ( array, item, cmp ){
	if(!cmp) cmp = function (a,b){return a==b;};
	for(var i=0;i<array.length;i++){
		if (cmp(item ,array[i])){ 
			if(i+1 == array.length){
				array.pop();
				if(array.length==0){
					return null;
				}
				return array[i-1];
			}  
			array.splice(i,1);
			return array[i];
		}
	}
	throw new Error("Item not found");
}

function format ( fmt, props ){
	return new Template(fmt).evaluate( props );
}

function compare( a, b ){
	return a > b ? -1 : b > a ? -1 : 0;
	
}

if( window.navigator.appName == "Netscape" ){
	window.fromJSON = function (text){
		return eval( "(" + text + ")");
	}
}else{
	window.fromJSON = function (text){
		eval ("var obj = " + text);
		return obj;
	}
}