// just ideas for now - not implemented
const SELECTIONMODE_1CELL		= 1; // allows for the selection of one cell
const SELECTIONMODE_XCELL		= 2; // allows for the selection of multiple cells
const SELECTIONMODE_1ROW		= 4; // allow for the selection of a row
const SELECTIONMODE_XROW		= 8; // allow for the selection of many rows.




/* Responsible for rendering cell contents. */ 
var PTableCellRenderer = new Class({

	Implements: [Events, Options],

	options: {
	},

	initialize: function(lists, options) {
		this.setOptions(options);

	},

	/* used to create the contents of a table cell to render content.
		*** subject to change ***
	value: (mixed) the content to render visible to the user.
	column: object. the column from the schema.
	header: boolean. are we writing a heading or a body cell?
	table: object. the entire table.
	*/
	writeCell: function(value, column) {
		return new Element('text', {
			'html': value
		});
	}
	
});

var DefaultTableCellRenderer = new PTableCellRenderer(); // this is here as a singleton to draw cells in all tables



var PTable = new Class({

	Implements: [Events, Options],

	options: {
		skin: 'def',

		// schema / table header
		schema: null,
		// some schema options
		reorderingAllowed: true,
		resizingAllowed: true,
		// some more peripheral schema options
		draggedColumn: null,
		resizingColumn: null,
		/*editingRow: null,
		editingColumn: null,
		editorComp: null,*/
		
		// the table data
		data: [],
		
		
		
		
		// table options
		selectionMode: null,
		defaultCellRenderer: DefaultTableCellRenderer,

		delim: false // this is here becuase i dont like making sure there shouldnt be a comma after my options.
	},
	
	table: null, 				// the main table element.
	tableHead: null,			// the table head
	tableBody: null,			// the table body
	tableFooter: null,			// the table footer
	

	initialize: function(options){
		this.setOptions(options);
		this.table = new Element('table', {
		    'class': 'list ' + this.options.skin,
		    'events': {
		        'mouseover': function(){
		            // for future use
		        }
		    }
		}).adopt([
			new Element('thead').grab(
				new Element('tr').grab(
					this.tableHead = new Element('td')
				)
			),
			new Element('tbody').grab(
				new Element('tr').grab(
					this.tableBody = new Element('td')
				)
			),
			new Element('tfoot').grab(
				new Element('tr').grab(
					this.tableFooter = new Element('td')
				)
			)
		]);
		
		
		if (this.options.schema)
			this.setSchema(this.options.schema);
		
		
		
	},

	/* Inject the table into a physical element on the page.
	el: The element that will parent the table.
	*/
	setSchema: function(s) {
		this.options.schema = new PTableSchema($merge(s, {table:this}));
//		alert(JSON.encode(this.schema));
		this.tableHead.grab(this.options.schema.options.root.table);
		//alert(this.schema.options.root);
	},

	/* Inject the table into a physical element on the page.
	el: The element that will parent the table.
	returns: this table.
	*/
	injectInto: function(el) {
		$(el).adopt(this.table);
		return this;
	},

	/* add a single object to the table. object will be mapped into the table using the table column schema.
	data: (mixed). an object or an array of objects.
	*/
	setData: function(data) {
		this.removeAll();
		this.addRow(data);
	},
	
	/* add a/some object/s to the table. object will be mapped into the table using the table column schema.
	data: (mixed). an object or an array of objects.
	*** The following two parameters are not yet implemented. ***
	dimension: (mixed). optional. a dimension object or index of one. used to add objects to sub-root dimensions.
	location: (array). optional. an array row/object ids that represent the specific node path to follow to the 
		correct dimension to add the rows.
	*/
	addRow: function(data, dimension) {
		// for now only whole objects are to be passed in to the table. ie: they will be put into the root.
		$splat(data).each(function(d, i) {
			trow = new PTableRow(this.options.schema.options.root, d);
			this.options.data.extend($splat(trow));
			this.tableBody.grab(trow.table);
		}, this);
	},
	
	/* remove all data from table. 
	*/
	removeAll: function() {
		this.options.data.each(function(d, i) {
			d.destroy();
		});
	},
	
	/* remove a row. 
	i: (mixed) the index of that row, or an array of indexes. 
	*/
	removeRow: function(i) {
	},
	
	/* move some rows around.
	from: (mixed) either an index of a row, or an array of indexes of rows to be moved.
	offset: int the number of rows to move these rows around.
	*/
	moveRow: function(from, offset) {
	},
	
	/* returns the number of rows in the table.
	d: (mixed). optional. can be a dimension object or an index of one. can also be an array of either to 
		count in multiple dimensions. if left blank then count all rows from all dimensions.
	*/
	getRowCount: function(d) {
	},
	
	/*
	return the class/type of data stored in a particular column
	i: (mixed). either the column object, the name of the column, or the index of the column.
	d: (mixed). either a dimension object, or the index of one. if dimension is specified then function will
		return the i'th column in that dimension, otherwise the i'th column from the start of the dimensions.
	*/
	getColumnClass: function(i, d) {
	},
	
	/*
	return the number of columns.
	d: int. optional. the dimension of columns to count. if left blank then return total for all dimensions.
	*/
	getColumnCount: function() {
	
	},
	
	/*
	return the name of a column. if i is an array, then return an array of column names.
	i: (mixed). a column object, the index of a column, or an array of columns.
	*/
	getColumnName: function(i) {
	},
	
	/*
	return the indexs of columns whose options match the options .
	options: (object). an object of options. { type: 'string' }. 
		*** Future intentions to support structures like: { type: ['string', 'int'], visible: true }. thus allowing more complex filters.
	*/
	getColumns: function(i) {
	},
	
	/*
	return a multidimensional array row ids of selected rows/items.
	d: (mixed) a dimension, index or array of either specifying dimension to use. if null/blank use all dimensions.
	*** the structure of the output of this function will most likely change ***
	returns something like:
		{
			invoice: [1, 3, 5],
			items: [12, 13, 17, 23, 24, 34,39]
	*/
	getSelected: function(d) {
	
	},
	
	/* clear the selection of the specified dimensions. if d is null hten clear all dimensions.
	d: (mixed) a dimension, the index of one, or an array of them.
	*/
	clearSelection: function() {
	
	},
	
	setHeaderVisible: function(v) {
		this.tableHead.setStyle('display', v ? 'block' : 'none');
	},
	
	setBodyVisible: function(v) {
		this.tableBody.setStyle('display', v ? 'block' : 'none');
	},
	
	/* *** to be implemented ***
	getValueAt(row, column) {
	},
	
	setValueAt(row, column, value) {
	},
	
	isCellEditable(row, column) {
	},*/
	
	/*addColumn: function(schema, data) {
	},*/
	
	


	serialize: function(){
		return "";
	}

});



var PTableSchema = new Class({

	Implements: [Events, Options],

	options: {
		root: null,
		table: null
	},

	initialize: function(options){
		this.setOptions(options);
		if (this.options.root) {
			this.options.root = new PTableDimension(
				$merge(this.options.root, {table:this.options.table})
			);
		}
	}

});




var PTableDimension = new Class({

	Implements: [Events, Options],

	options: {
		name: null,
		displayName: null,
		columns: [],
		child: null,
		childProperty: null,
		selectable: true, // is this dimension of row selectable..
		depth: 0, // the level of this dimension... root = 0
		table: null,
		// events fired by rows (not header rows)...
		rowClick: $empty,		// event args: (row data, row object, event)
		rowMouseOut: $empty,
		rowMouseDown: $empty,
		rowMouseUp: $empty,
		rowMouseOver: $empty,
		columnDrag: $empty,		// event args: (column object, sortable, moved cell)
		columnDrop: $empty,
		columnMove: $empty
	},

	table: null, // this dimensions table container. first row = this dimensions columns,.. second row = any child dimensions
	tableColumnRow: null, // this dimensions table columns container
	tableChildCell: null, // this dimensions table child container.
	columnTerminator: null,
	sortable: null, // the object that manages column sorting
	
	initialize: function(options){
		this.setOptions(options);
		this.table = new Element('table', {
		    'events': {
		        'mouseover': function() {
		            // for future use
		        }
		    }
		}).adopt([
			this.tableColumnRow = new Element('tr', {
					'class': 'head d' + (this.options.depth + 1)// adds a class like 'd1', 'd2' etc so that style theme can address each dimension
				}).grab(
					this.columnTerminator = new Element('td', { 'class': 't3rm1n4t0r' }) // terminator cell fills gap at end of row
				).store('obj', this), // store a reference to this object,
			this.tableChildRow = new Element('tr').grab(
				this.tableChildCell = new Element('td')
			)
		]); 
		if (this.options.columns) {
			this.options.columns.each(function(c, i) {
				c = new PTableColumn($merge(c, { parent: this, position: i }));
				this.tableColumnRow.grab(c.element);
				this.tableColumnRow.grab(this.columnTerminator);
				this.tableChildCell.setProperty('colspan', this.tableChildCell.getProperty('colspan') + 1);
				this.options.columns[i] = c;
			}, this);
			this.sortable = new Sortables(this.tableColumnRow, {
				clone: true, constrain: true, revert: true,
				onStart: function(a) {
					if (this.options.columnDrag != $empty)
						this.options.columnDrag(this.sortable, a);
				}.bind(this),
				onComplete: function(a) {
					if (this.options.columnDrop != $empty)
						this.options.columnDrop(this.sortable, a);
				}.bind(this),
				onSort: function(a) {
					before = [];
					after = [];
					this.options.table.tableHead.getElements('.d' + (this.options.depth + 1)).each(function(e, i) {
						co = 0;
						e.getChildren().each(function(ec, j) {
						column = ec.retrieve('obj');
							if (column) {
								before.extend([column.options.position]);
								column.options.position = co;
								after.extend([co++]);
							}
						});
					});
					cx = 0;
					for (; (cx<before.length) && (before[cx]==after[cx]); cx++);
					this.options.table.tableBody.getElements('.d' + (this.options.depth + 1)).each(function(e, i) {
						kids = e.getChildren();
						kids[cx].inject(kids[cx+1], 'after');
					});
					if (this.options.columnMove != $empty)
						this.options.columnMove(this.sortable, a);
				}.bind(this)
			});
		}
		if (this.options.child) {
			this.options.child = new PTableDimension($merge(this.options.child, { 
				depth: this.options.depth + 1, 
				table: this.options.table
			}));
			this.tableChildCell.adopt(this.options.child.table);
		}
	},
	
	hasChild: function() {
		return (this.options.child != null);
	},

	/* returns the number of rows in the table.
	d: (mixed). optional. can be a dimension object or an index of one. can also be an array of either to 
		count in multiple dimensions. if left blank then count all rows from all dimensions.
	*/
	getRowCount: function(d) {
		
	}

});




var PTableColumn = new Class({

	Implements: [Events, Options],

	options: {
		parent: null, // this must be set - its not really an option.
		name: null,
		displayName: null,
		type: 'string',
		sortable: true,
		visible: true,
		width: 100,
		filterable: true,
		editable: false,
		position: 0, // horizontal position in the row/dimension
		compare: null, // null uses the default mechanism for comparing items. function should be of the form compare(a, b) and return a <= b.
		renderer: null // null: use tables default renderer.
	},
	element: null, // the physical element that represents this column table cell.
	container: null, // the element that will hold the contents of the cell.
	
	resizeElement: null, // the element used to resize this column (this.element)
	resizer: null, // the object that handles resizing

	initialize: function(options){
		this.setOptions(options);
		this.element = new Element('td', {
			'width': '1%',
		    'events': {
		        'mouseover': function(){
		            // for future use
		        }
		    }
		}).grab(
			this.container = new Element('div', {
				'class': '_' + this.options.name + ' d' + (this.options.parent.options.depth + 1) + (this.options.position + 1),
				'html': ''
			}).adopt([
				this.resizeElement = new Element('span', {
					'class': 'rzr',
					'html': '',
					events: {
						'mousedown': function(e) {
							e.stop(); // dont propogate this event - or the sortable will kick into action
						}.bind(this),
						'mousemove': function(e) {
							e.stop();
						}.bind(this),
						'mouseup': function(e) {
						}.bind(this)
					}
				}),
				new Element('span', {
					'class': 'text',
					'html': this.options.displayName
				})
			])
		).store('obj', this);
		this.resizer = new Drag(this.container, {
			handle: this.resizeElement,
			modifiers: {
				'x': 'width', 
				'y': null
			},
			snap: 2,
			onStart: function(e) {
				$$('body')[0].setStyle('cursor', 'col-resize');
				this.element.setStyle('cursor', 'col-resize'); // cell on the right still causes flicker :/
			}.bind(this),
			onComplete: function(e) {
				$$('body')[0].setStyle('cursor', 'default');
				this.element.setStyle('cursor', 'default');
				this.options.parent.options.table.tableBody.getElements('._' + this.options.name).each(function(f) {
					f.setStyle('width', e.getStyle('width'));
				});
			}.bind(this)
		});
	},
	
	/* if has specific renderer then return that, else the tables default renderer. */
	getRenderer: function() {
		return this.options.renderer ? this.options.renderer : this.getTable().options.defaultCellRenderer;
	},
	
	getTable: function() {
		return this.options.parent.options.table;
	}

});


var PTableRow = new Class({

	Implements: [Events, Options],

	options: {
	},
	
	//cells: [],
	dimension: null, // the dimension that defines this row
	children: [], // the child rows if this row has any
	table: null, 
	tableColumnRow: null,
	tableChildCell: null,
	columnTerminator: null,
	
	initialize: function(dimension, options){ // dimension the schema dimension that contains meta data about formatting this row
		this.dimension = dimension;
		this.setOptions(options);
		this.table = new Element('table', {
		    'events': {
		        'mouseover': function(){
		            // for future use
		        }
		    }
		}).adopt([
			this.tableColumnRow = new Element('tr', {
				'class': 'd' + (this.dimension.options.depth + 1),
				'events': {
						'click': function(e){
							if (this.dimension.options.rowClick != $empty)
								this.dimension.options.rowClick(this.options, this, e);
						}.bind(this),
						'mousedown': function(e){
							if (this.dimension.options.rowMouseDown != $empty)
								this.dimension.options.rowMouseDown(this.options, this, e);
						}.bind(this),
						'mouseup': function(e){
							if (this.dimension.options.rowMouseUp != $empty)
								this.dimension.options.rowMouseUp(this.options, this, e);
						}.bind(this),
						'mouseover': function(e){
							if (this.dimension.options.rowMouseOver != $empty)
								this.dimension.options.rowMouseOver(this.options, this, e);
						}.bind(this),
						'mouseout': function(e){
							if (this.dimension.options.rowMouseOut != $empty)
								this.dimension.options.rowMouseOut(this.options, this, e);
						}.bind(this),
					}
				}).grab(
					this.columnTerminator = new Element('td', { 'class': 't3rm1n4t0r' })
				).store('obj', this),
			this.tableChildRow = new Element('tr').grab(
				this.tableChildCell = new Element('td')
			)
		]);
		this.dimension.options.columns.each(function(c, i) {
			c = new PTableCell(this.dimension.options.columns[i], { 
						parent: this, 
						data: this.options[this.dimension.options.columns[i].options.name]
			});
			this.tableColumnRow.grab(c.element);
			this.tableColumnRow.grab(this.columnTerminator);
			this.tableChildCell.setProperty('colspan', this.tableChildCell.getProperty('colspan') + 1);
		}, this);
		if (this.dimension.hasChild() && this.options[this.dimension.options.childProperty]) {
			this.options[this.dimension.options.childProperty].each(function(c, i) { // adopt any child elemetns this row might have
				child = new PTableRow(dimension.options.child, this.options[dimension.options.childProperty][i]);
				$extend(this.children, [child]);
				this.tableChildCell.adopt(child.table);
			}, this);
		}
	},
	
	hasChild: function() {
		return (this.options.child != null);
	},
	
	/* remove this row from screen and from the table object.
	*/
	destroy: function() {
		
	}

});




var PTableCell = new Class({

	Implements: [Events, Options],

	options: {
		parent: null, // this must be set - its not really an option.
		data: null
	},
	
	column: null,
	element: null, // the physical element that represents this column table cell.
	container: null, // the element that will hold the contents of the cell.
	
	initialize: function(column, options){ // the thead column.
		this.column = column;
		this.options = options; // the recursive example breaks here..... mmm... moo-nc line 146
		this.element = new Element('td', {
			'width': '1%',
		    'events': {
		        'mouseover': function(){
		            // for future use
		        }
		    }
		}).grab(
			this.container = new Element('div', {
				'class': '_' + this.column.options.name + ' d' + (this.column.options.parent.options.depth + 1) + (this.column.options.position + 1)
//				,'html': this.options.data + '&nbsp;'
			}).grab(
				this.column.getRenderer().writeCell(options.data, this.column)
			)
		).store('obj', this);;
	}

});


