/** datatable : a datatable represents raw data.*/
var JSDC_DATATABLE_EXISTS = true;

// A datatable insert action

function datatable_update_action(target, row_idx, col_idx, old_data, new_data)
{
	this.target = target;
	this.is_update = true;
	this.row_idx 	= row_idx;
	this.col_idx 	= col_idx;
	this.old_data 	= old_data;
	this.new_data 	= new_data;

}

datatable_update_action.prototype = new action;

function datatable_insert_action(target, row_idx)
{
	this.is_remove = false;
	this.target 	= target;
	this.row_idx 	= row_idx;
}

datatable_insert_action.prototype = new action;

function datatable_remove_action(target, row_idx)
{
	this.is_remove  = true;
	this.target 	= target;
	this.row_idx 	= row_idx;
}

datatable_remove_action.prototype = new action;


// A column metadata object.
function datatablecolumn(datatable, column_name)
{
  
	this.column_name = column_name;
	this.data 		 = new Array();

	if (datatable) 
	{
		this.datatable = datatable;
		datatable.columns.push(this);
	}
	
}

function datatable_propagate(datatable, action, row_idx, col_idx)
{

	var obj_idx;
	for (obj_idx in datatable.dataviews)
	{
		dataview_data_event(datatable.dataviews[obj_idx], action, row_idx, col_idx);
	}
}

function datatable_populate_from_csv(datatable,srcdata, rowsep, colsep, noheader)
{
	
	var rowseplength = rowsep.length;
	// Split rows using separator in rowsep.
	var rows = srcdata.split(rowsep);
	var curr_rowdata = rows[rows.length -1];
	// If the last row doesn't end with a row separator, add it here, for tolerance and speed.

	if (!noheader)
	{
		var headeroffset = 1;
	}
	else
	{
		var headeroffset = 0;
	}	
	var data_length = rows.length;
	
	if (colsep) // If the column separator is set, it means that we should assume the data has multiple columns. 
	{
		// Base on first column
		var cols = rows[0].split(colsep);
		var col_idx, curr_column;
		if (!noheader)
		{
			for (col_idx in cols) // If noheader = NULL, the datatable expects the first row to have headings and reads them here.
			{
				curr_column = new datatablecolumn(datatable, cols[col_idx], 80); 
				curr_column.data.length = data_length;
			}
		}
		else
		{
			for (col_idx in cols) // Noheader is set. Set 
			{
				curr_column = new datatablecolumn(datatable, "column_" + col_idx, 80); 
				curr_column.data.length = data_length;
			}
		}
		var dt_colslength = datatable.columns.length;
	
		// Iterate through rows
		if (data_length - headeroffset > 0)
		{
			var row_idx;
			for (row_idx=headeroffset;row_idx<data_length;row_idx++)
			{   
				var curr_rowdata = rows[row_idx];
				// Remove row separator from row
				cols = curr_rowdata.substring(0,curr_rowdata.length-rowseplength).split(colsep);
				if (cols.length > dt_colslength) // Check data width
				{
					alert("Data has more columns than heading("+datatable.columns.length +") on row "+ row_idx + ". \nCheck format and column separator settings.\n Column index : " + col_idx + ". Data \"" + cols[col_idx] +". \n"+ rows[row_idx]);
				}
				// Iterate through columns.
				for (col_idx in cols)
				{
					{
						datatable.columns[col_idx].data[row_idx - 1] = cols[col_idx];
					}
				}
			}
		}
	}
	else
	{ 	// if colsep is NULL, it means that only a single column is used for the data.
		var col_idx, curr_column;
		var data_length = rows.length;
		curr_column = new datatablecolumn(datatable, 'main', 80); 
		curr_column.data.length = rows.length -1;

		// Iterate through rows
		if (data_length > 1)
		{
			var row_idx;
			for (row_idx=headeroffset;row_idx<data_length;row_idx++)
			{   
				curr_column.data[row_idx - 1] = rows[row_idx];
			}
		}	
	}
	datatable.count = data_length - headeroffset;
	datatable.refresh;
}

function datatable_populate_from_csv_URL(datatable,srcURL, rowsep, colsep, noheader)
{
	try
	{
		var data = MakeRequest(srcURL);
	}
	catch(err)
	{
		alert('Failed to fetch data from "'+ srcURL +'" \n' + err)
	}
	datatable_populate_from_csv(datatable,data, rowsep, colsep, noheader);
}

function datatable_insertat(datatable, row_idx)
{
	var col_idx;
	for (col_idx in datatable.columns)
	{
		datatable.columns[col_idx].data.splice(row_idx,0, null); // ?
	}
	datatable.count++;
	
	if (!datatable.application.changing_history)
	{
		datatable.application.register_action(new datatable_insert_action(datatable, row_idx));
	}
	
}

function datatable_removeat(datatable, row_idx)
{
	var col_idx;
	
	if (!datatable.application.changing_history)
	{
		datatable.application.begin_history(); // calling removeat should be possible to undo at one time.
		for (col_idx in datatable.columns)
		{
				datatable.updateat(row_idx, col_idx, null); // For undo/redo history.
		}
	}
	for (col_idx in datatable.columns)
	{
		datatable.columns[col_idx].data.splice(row_idx,1); // ?
	}
	datatable.count--;
	
	if (!datatable.application.changing_history)
	{
		datatable.application.register_action(new datatable_remove_action(datatable, row_idx));
		datatable.application.end_history();
	}		
}

function datatable_updateat(datatable, row_idx, col_idx, value)
{

	var oldvalue = datatable.columns[col_idx].data[row_idx];
	datatable.columns[col_idx].data[row_idx] = value;

	if (!datatable.application.changing_history)
	{
		datatable.application.register_action(new datatable_update_action(datatable, row_idx, col_idx, oldvalue, value));
	}
}
function datatable_undo(datatable, action)
{
	if (action.is_update)
	{
		// Update
		datatable.updateat(action.row_idx, action.col_idx, action.old_data);
	}
	else
	{
		if (action.is_remove)
		{
			datatable.insertat(action.row_idx);		
		}
		else
		{
			datatable.removeat(action.row_idx);		
		}	
	}
}

function datatable_redo(datatable, action)
{
	if (action.is_update)
	{
		// Update
		datatable.updateat(action.row_idx, action.col_idx, action.new_data);
	}
	else
	{
		if (action.is_remove)
		{
			datatable.removeat(action.row_idx);		
		}
		else
		{
			datatable.insertat(action.row_idx);		
		}	
	}
}

function datatable(datatable_name, application)
{
	// Fields
	this.count = 0;
	this.name = name;
	this.columns = new Array();
	this.dataviews = new Array();
	this.application = application;
	
	// Functions
	this.populate_from_csv_string = function populate_from_csv_string(srcdata, rowsep, colsep, noheader) {datatable_populate_from_csv(this, srcdata, rowsep, colsep, noheader);} 
	this.populate_from_csv_URL = function populate_from_csv_URL(srcURL, rowsep, colsep, noheader) {datatable_populate_from_csv_URL(this, srcURL, rowsep, colsep, noheader);} 
	this.refresh = function refresh() {datatable_propagate(this, "refresh"); } 

	this.removeat = function removeat(row_idx) {datatable_removeat(this, row_idx);datatable_propagate(this, "remove", row_idx);}
	this.insertat = function insertat(row_idx) {datatable_insertat(this, row_idx);datatable_propagate(this, "insert", row_idx);}
  
	this.updateat = function updateat(row_idx, col_idx, value) {datatable_updateat(this, row_idx, col_idx, value);datatable_propagate(this, "update", row_idx, col_idx);}
  
  	this.undo = function undo(action) {datatable_undo(this, action);} 
  	this.redo = function redo(action) {datatable_redo(this, action);} 
  
//	this.next = function next() { curr_row++; propagate(this, "next");}
//	this.previous = function previous() { curr_row--; propagate(this, "previous");}
//	this.first = function next() { curr_row = 0; propagate(this, "first");}
//	this.previous = function previous() { curr_row = count; propagate(this, "previous");}

}