/*
 *  Draco Econometrics
 *  Copyright (C) 2006-2007 Approximatrix, LLC
 *  http://www.approximatrix.com
 *  
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.approximatrix.draco.datamodel;

import javax.swing.table.AbstractTableModel;
import javax.swing.event.TableModelListener;

import javax.swing.event.TableModelEvent;

import javax.swing.JTable;

import java.util.ArrayList;


import com.approximatrix.draco.general.ui.StatTableModelUpdatable;
import com.approximatrix.draco.math.EquationEvaluation;

/*
 * Created on Jan 24, 2005
 *
 * TODO To change the template for this generated file go to
 * Window - Preferences - Java - Code Style - Code Templates
 */

/**
 * @author Jeffrey Armstrong
 *
 * TODO To change the template for this generated type comment go to
 * Window - Preferences - Java - Code Style - Code Templates
 */
public class StatTableModel extends AbstractTableModel implements 
	TableModelListener  {
	
	// Some defaults
	static private final int DEFAULT_ROWS = 1;
	static private final int DEFAULT_COLUMNS = 2;
	static private final String DEFAULT_COL_PREFIX = "ser";
	static public final double DEFAULT_VALUE = 0.0;
	static private final boolean EDITABLE_CELLS = true;
	
	private ArrayList<VariableModel> data = null;
	
	private ArrayList<Constant> constants = null;
	
	private ArrayList updaters = null;
	
	/** Default constructor - creates a table filled with DEFAULT_VALUE that is 
	 * DEFAULT_ROWS x DEFAULT_COLUMNS.
	 *
	 */
	public StatTableModel() {
		data = generate_default();
		
		// Intialize constants
		constants = new ArrayList();
		updaters = new ArrayList();
		
		this.addTableModelListener(this);
	}
	
	/** Constructor that offers the ability to create a table that is completely empty.
	 * This is achieved by calling the default constructor then removing values.
	 * 
	 * @param empty true to create an empty model
	 */
	public StatTableModel(boolean empty) {
		this();
		if(empty) {
			while(this.getColumnCount() > 0)
				this.removeColumn(0);
		}
	}
	
	/** Constructor that creates a new copy of a StatTableModel from data present in
	 * an old data model.
	 * @param old_model Original model to extract data from
	 */
	public StatTableModel(StatTableModel old_model) {
		data = new ArrayList();
		for(int i=0;i<old_model.getColumnCount();i++) {
			data.add(((VariableModel)old_model.getVariable(i)).clone());
		}
		
		//constants = new ArrayList();
		constants = (ArrayList)old_model.getConstants().clone();
		updaters = new ArrayList();
		
		this.addTableModelListener(this);
	}
	
	/** Constructor that builds a new StatTableModel based on passed
	 * numeric double data
	 * 
	 * @param incoming_data array of doubles to build the model from
	 * @param columns column names
	 */
	public StatTableModel(double[][] incoming_data, 
			String[] columns) {
		build_with_double_data(incoming_data,columns);
		
		constants = new ArrayList();
		updaters = new ArrayList();
		
		this.addTableModelListener(this);
		
	}
	
	/** Constructor that builds a new StatTableModel based on passed
	 * generic Object data for a single column
	 * 
	 * @param incoming_data array of Objects to build the model from
	 * @param column_name column name
	 */
	public StatTableModel(Object[] incoming_data, 
			String column_name) {
		
		int number_rows = incoming_data.length;
		int number_columns = 1;	
		
		data = new ArrayList();
		data.add(new VariableModel(column_name,incoming_data));
		
		constants = new ArrayList();
		updaters = new ArrayList();
		
		this.addTableModelListener(this);
		
	}
	
	/** Constructor that builds a new StatTableModel based on passed
	 * generic Object data
	 * 
	 * @param incoming_data array of Objects to build the model from
	 * @param columns column names
	 */
	public StatTableModel(Object[][] incoming_data, 
			String[] columns) {
		build_with_data(incoming_data,columns);
		
		constants = new ArrayList();
		updaters = new ArrayList();
		
		this.addTableModelListener(this);
		
	}
	
	/** Returns a specified variable in its full glory
	 * 
	 * @param i the column to access
	 * @return the VariableModel object or null if NA
	 */
	public VariableModel getVariable(int i) {
		if(!isValidColumn(i)) return null;
		return (VariableModel)data.get(i);
	}
	
	/** Constructs this StatTableModel using a 2D array of doubles
	 * 
	 * @param incoming_data 2D array of double numeric data
	 * @param columns the names of each column
	 */
	private void build_with_double_data(double[][] incoming_data, 
			String[] columns) {
		int number_rows = incoming_data.length;
		int number_columns = columns.length;	
		
		if(incoming_data == null) {
			data = generate_default(); 
		} else {					
			data = new ArrayList(); 
			for(int i=0;i<number_columns;i++) {
				double[] one_var = new double[number_rows];
				for(int j=0;j<number_rows;j++) {
					one_var[j] = incoming_data[j][i];
				}	
				data.add(new VariableModel(columns[i],one_var));
			}
		}
		
	}
	
	/** Determines if this model is simply an initial, unmodified model
	 * 
	 * @return true if jut an initial model, false otherwise
	 */
	public boolean isInitialModel() {
		if(this.getColumnCount() != DEFAULT_COLUMNS) return false;
		
		for(int i=0;i<this.getColumnCount();i++) {
			VariableModel var = this.getVariable(i);
			if(!var.getName().equals(DEFAULT_COL_PREFIX+Integer.toString(i))) return false;
			if(var.size() != DEFAULT_ROWS) return false;
			for(int j=0;j<var.size();j++) {
				if(var.getValueAt(j) == null) return false;
				if(!(var.getValueAt(j) instanceof Number)) return false;
				if(((Number)var.getValueAt(j)).doubleValue() != DEFAULT_VALUE) return false;
			}
		}
		
		return true;
	}
	
	/** Constructs this StatTableModel using a 2d array of Objects
	 * 
	 * @param incoming_data 2D array of Objects
	 * @param columns the names of each column
	 */
	private void build_with_data(Object[][] incoming_data, 
			String[] columns) {

		int number_rows = incoming_data.length;
		int number_columns = columns.length;	
		
		if(incoming_data == null) {
			data = generate_default(); 
		} else {					
			data = new ArrayList(); 
			for(int i=0;i<number_columns;i++) {
				Object[] one_var = new Object[number_rows];
				for(int j=0;j<number_rows;j++) {
					one_var[j] = incoming_data[j][i];
				}
				data.add(new VariableModel(columns[i],one_var));
			}
		}
		
	}
	
	/** Returns the name of the requested variable
	 * 
	 * @param col the index of the column
	 * @return a String containing the column's name
	 */
	public String getColumnName(int col) {
		if(isValidColumn(col)) {
			return getVariable(col).getName();
		} else {
			return null;
		}
	}
	
	/** Returns an array containing all column names
	 * 
	 * @return an array of Strings containing column names
	 */
	public String[] getColumnNames() {
		String[] return_val = new String[data.size()];
		for(int i=0;i<data.size();i++) {
			return_val[i] = getVariable(i).getName();
		}
		return return_val;
	}
	
	/** Returns an array containing the name sof the columns specified by the input
	 * integer array
	 * 
	 * @param columns the columns of interest
	 * @return a String array of names
	 */
	public String[] getColumnNames(int[] columns) {
		if(columns == null) return null;
		String[] return_val = new String[columns.length];
		for(int i=0;i<columns.length;i++)
			return_val[i] = getColumnName(columns[i]);
		return return_val;
	}
	
	/** Returns the column index based on the specified name
	 * 
	 * @param col_name a String containing the name of the column
	 * @return the column index as an integer or -1 if not found
	 */
	public int getColumnNumber(String col_name) {
		int holding = -1;
		for(int i=0;i<data.size();i++) {
			if(getVariable(i).getName().compareTo(col_name) == 0) {
				holding = i;
				break;
			}
		}
		return holding;
	}
	
	/** Returns the column indices based on the specified names
	 * 
	 * @param col_name a vector Strings containing the names of the columns
	 * @return an integer vector containing the column indices or -1 if not found
	 */
	public int[] getColumnNumber(String[] col_names) {
		if(col_names == null) return null;
		
		int[] return_val = new int[col_names.length];
		for(int i=0;i<col_names.length;i++) {
			return_val[i] = getColumnNumber(col_names[i]);
		}
		return return_val;
	}
	
	/** Returns the number of columns in the StatTableModel
	 * 
	 * @return number of columns as an int
	 * @see javax.swing.table.TableModel#getColumnCount()
	 */
	public int getColumnCount() {
		return data.size();
	}
	
	/** Returns the number of rows in the model
	 * 
	 * @return the number of rows as an int
	 * @see javax.swing.table.TableModel#getRowCount()
	 */
	public int getRowCount() {
		if(data.size() > 0) {
			return getVariable(0).size();
		} else {
			return 0;
		}
	}
	
	/** Returns the Objects that comprise a single column
	 * 
	 * @param col the column of interest
	 * @return an array of Objects
	 */
	public Object[] getColumn(int col) {
		if(!isValidColumn(col)) return null;
		return getVariable(col).getValues(); 
	}
	
	/** Returns an entire column's data as an array of numeric doubles
	 * 
	 * @param col the column index
	 * @return an array of double containing the values
	 */
	public double[] getColumnDouble(int col) {
		if(!isValidColumn(col)) return null;
		return getVariable(col).getAlldouble();
	}
	
	/** Returns a specified row as double values
	 * 
	 * @param row the row of interest
	 * @return a vector of row values
	 */
	public double[] getRowDouble(int row) {
		if(row >= getRowCount() || row < 0) return null;
		double[] data = new double[getColumnCount()];
		for(int i=0;i<data.length;i++) 
			data[i] = getVariable(i).getNumberAt(row).doubleValue();
		
		return data;
	}
	
	/** Returns values on a specified row for only the columns of interest
	 * 
	 * @param row the row to extract
	 * @param cols an integer vector of column indices
	 * @return a vector of double values, or NaN if a column exceeds bounds
	 */
	public double[] getRowDouble(int row, int[] cols) {
		if(row >= getRowCount() || row < 0) return null;
		double[] all_data = getRowDouble(row);
		double[] data = new double[cols.length];
		for(int i=0;i<data.length;i++) {
			if(cols[i] >= 0 && cols[i] < getColumnCount())
				data[i] = all_data[cols[i]];
			else
				data[i] = Double.NaN;
		}
		
		return data;
	}
	
	/** Returns multiple columns as specified by the integers in the col array
	 * as an array of Objects
	 * 
	 * @param col an integer array containing column indices
	 * @return an array of Objects from each column
	 */
	public Object[][] getMultipleColumns(int[] col) {
		for(int i=0;i<col.length;i++) {
			if(col[i] >= data.size() || col[i] < 0) return null;
		}
		
		Object[][] holding = new Object[getRowCount()][col.length];
		
		for(int i=0;i<col.length;i++) {
			Object[] temp = getVariable(col[i]).getValues();
			for(int j=0;j<temp.length;j++) {
				holding[j][i] = temp[j];
			}
			
		}
		return holding;		
	}
	
	/** Returns the double values of multiple columns specified by the integer
	 * array
	 * @param col an integer vector representing column indices
	 * @return a double array of values
	 */
	public double[][] getMultipleColumnsDouble(int[] col) {
		for(int i=0;i<col.length;i++) {
			if(col[i] >= data.size() || col[i] < 0) return null;
		}
		
		double[][] holding = new double[getRowCount()][col.length];
		
		for(int i=0;i<col.length;i++) {
			double[] passed = getColumnDouble(col[i]);
			
			for(int j=0;j<passed.length;j++) {
				holding[j][i] = passed[j];
			}
			
		}
		
		return holding;		
	}
	
	/** Returns selected rows of a specified column as raw Objects
	 * 
	 * @param rows array of integers specifying the rows of interest
	 * @param col the column of interest
	 * @return an array of Objects cast as they were originally in their respective VariableModel object
	 */
	public Object[] getData(int[] rows, int col) {
		if(rows == null || col >= this.getColumnCount() || col < 0) return null;
		
		Object[] return_val = new Object[rows.length];
		VariableModel var = this.getVariable(col);
		
		Object[] all = var.getAll();
		for(int i=0;i<rows.length;i++) {
			if(rows[i] >= 0 && rows[i] < all.length)
				return_val[i] = all[rows[i]];
			else
				return_val[i] = null;
		}
		return return_val;
	}
	
	/** Returns the selected rows and columns as raw Objects
	 * 
	 * @param rows array of integers specifying the rows of interest
	 * @param cols array of integers specifying the columns of interest
	 * @return 2D array of Objects cast as they were originally in their respective VariableModel objects
	 */
	public Object[][] getData(int[] rows, int[] cols) {
		if(rows == null || cols == null) return null;
		
		Object[][] return_val = new Object[rows.length][cols.length];
		for(int j=0;j<cols.length;j++) {
			Object[] applicable = getData(rows,cols[j]);
			for(int i=0;i<rows.length;i++) {
				return_val[i][j] = applicable[i];
			}
		}
		return return_val;
	}
	
	/** Returns the double values on the rows and columns specified by
	 * the rows and cols index vectors
	 * 
	 * @param rows indices of rows to return
	 * @param cols indices of columns to return
	 * @return a double array of values
	 */
	public double[][] getDataDouble(int[] rows, int[] cols) {
		if(rows == null || cols == null) return null;
		
		double[][] return_val = new double[rows.length][cols.length];
		for(int j=0;j<cols.length;j++) {
			double[] applicable = getDataDouble(rows,cols[j]);
			for(int i=0;i<rows.length;i++) {
				return_val[i][j] = applicable[i];
			}
		}
		return return_val;
	}
	
	/** Returns the double values on the rows and columns specified by
	 * the rows index vectors and the column index
	 * 
	 * @param rows indices of rows to return
	 * @param cols indices of columns to return
	 * @return a double array of values
	 */
	public double[] getDataDouble(int[] rows, int col) {
		if(rows == null || col >= this.getColumnCount() || col < 0) return null;
		
		double[] return_val = new double[rows.length];
		VariableModel var = this.getVariable(col);
		double[] all = var.getAlldouble();
		for(int i=0;i<rows.length;i++) {
			return_val[i] = all[rows[i]];
		}
		return return_val;
	}
	
	/** Returns the Object located at the specified location
	 * 
	 * @param arg0 row of interest
	 * @param arg1 column of interest
	 * @return Object containing the value or null if NA
	 * @see javax.swing.table.TableModel#getValueAt(int, int)
	 */
	public Object getValueAt(int arg0, int arg1) {
		if(arg0 >= getRowCount() || arg0 < 0) return null;
		if(arg1 >= data.size() || arg1 < 0) return null;
		
		return getVariable(arg1).getValueAt(arg0);
	}
	
	/** Returns the Number located at the specified location
	 * 
	 * @param arg0 row of interest
	 * @param arg1 column of interest
	 * @return Number containing the value or null if NA
	 */
	public Number getNumberAt(int arg0, int arg1) {
		if(arg0 >= getRowCount() || arg0 < 0) return null;
		if(arg1 >= data.size() || arg1 < 0) return null;
		
		return getVariable(arg1).getNumberAt(arg0);
	}
	
	/** Returns whether cells are editable, which is controlled
	 * by the constant EDITABLE_CELLS and the type of the variable.
	 * DateObjects are not editable because they do not handle
	 * String parsing at all.
	 * 
	 * @return true if editable, false otherwise
	 */
	public boolean isCellEditable(int row, int col) {
		if(!EDITABLE_CELLS) return false;
		
		if(row >= getRowCount() || row < 0) return false;
		if(col >= data.size() || col < 0) return false;
		
		return getVariable(col).isEntryEditable(row);
	}
	
	/** Sets a value at a specified location
	 * 
	 * @param value Object containing the value
	 * @param row the row at which to set the value
	 * @param col the column at which to set the value
	 */
	public void setValueAt(Object value, int row, int col) {
		if(row >= getRowCount() || row < 0) return;
		if(col >= data.size() || col < 0) return;
		
		// Do not allow setting a value if the Class is different
		//if(value != null) {
		//	if(getVariable(col).getContentsClass() != value.getClass()) return;
		//}
		getVariable(col).setValueAt(row,value);
	}
	
	/** Wrapper for setting column values based on name rather than index
	 * 
	 * @param column_name name of the column to set
	 * @param values the values to set
	 */
	public void setColumnValuesDouble(String column_name, double[] values) {
		setColumnValuesDouble(getColumnNumber(column_name),values);
	}
	
	/** Sets all variables in a specifed column to the numeric double values
	 * 
	 * @param column the column to set
	 * @param values an array of doubles containing the values
	 */
	public void setColumnValuesDouble(int column, double[] values) {
		if(column >= data.size() || column < 0) return;
		Double[] value_objects = new Double[values.length];
		for(int i=0;i<values.length;i++) {
			value_objects[i] = new Double(values[i]);
		}
		getVariable(column).setAllValues(value_objects);
	}
	
	/** Sets all variables in a specifed column to the Object values
	 * 
	 * @param column the column to set
	 * @param values an array of Objects containing values
	 */
	public void setColumnValues(int column, Object[] values) {
		if(column >= data.size() || column < 0) return;
		getVariable(column).setAllValues(values);
		getVariable(column).resetContentsClass();
		fireTableDataChanged(); 
	}
	
	/** Sets all variables in a specifed column based on the passed equation
	 * 
	 * @param column the column to set
	 * @param equation a valid equation to use for evaluating values
	 */
	public void setColumnValues(int column, String equation) {
		if(column >= data.size() || column < 0) return;
		
		EquationEvaluation new_column_data = new EquationEvaluation(this,equation);
		if(!new_column_data.processValues()) {
			return;
		}
		Double[] new_values = new_column_data.getResults();
		setColumnValues(column,new_values);
		getVariable(column).setEquation(equation);
	}
	
	/** Returns the mean of the column as a numeric double
	 * 
	 * @param column_name the name column of interest
	 * @return the mean or zero if not possible to compute
	 * @see #getColumnMean(int)
	 */
	public double getColumnMean(String column_name) {
		return getColumnMean(getColumnNumber(column_name));
	}
	
	/** Returns the mean of the column as a numeric double
	 * 
	 * @param col the column index of interes
	 * @return the mean or zero if not possible to compute
	 */
	public double getColumnMean(int col) {
		if(col < 0 || col >= data.size()) return 0;
		Double returned = getVariable(col).getMean();
		if(returned == null)
			return 0;
		else
			return returned.doubleValue();
	}
	
	/** Returns the maximum column value as a double, if possible
	 * 
	 * @param column_name the name of the column of interest
	 * @return the maximum value
	 * @see #getColumnMaximumValue(int)
	 */
	public double getColumnMaximumValue(String column_name) {
		return getColumnMaximumValue(getColumnNumber(column_name));
	}
	
	/** Returns the maximum value of a column as a double or NaN 
	 * if not possible.
	 * 
	 * @param column_number the column of interest
	 * @return the maximum value as a double or NaN if NA
	 */
	public double getColumnMaximumValue(int column_number) {
		if(column_number < 0 || column_number >= data.size()) return Double.NaN;

		Number value = (Number)getVariable(column_number).getMaximumValue();
		if(value == null) return Double.NaN;
		return value.doubleValue();
	}
	
	/** Returns the minimum column value as a double, if possible
	 * 
	 * @param column_name the name of the column of interest
	 * @return the maximum value
	 * @see #getColumnMinimumValue(int)
	 */
	public double getColumnMinimumValue(String column_name) {
		return getColumnMinimumValue(getColumnNumber(column_name));
	}
	
	/** Returns the minimum value of a column as a double or NaN 
	 * if not possible.
	 * 
	 * @param column_number the column of interest
	 * @return the minimum value as a double or NaN if NA
	 */
	public double getColumnMinimumValue(int column_number) {
		if(column_number < 0 || column_number >= data.size()) return Double.NaN;

		Number value = (Number)getVariable(column_number).getMinimumValue();
		if(value == null) return Double.NaN;
		return value.doubleValue();
	}
	
	/** Returns the standard deviation of a column specified by its name
	 * 
	 * @param column_name the name of the column of interest
	 * @return the column's standard deviation as a double
	 * @see #getColumnStandardDeviation(int)
	 */
	public double getColumnStandardDeviation(String column_name) {
		return getColumnStandardDeviation(getColumnNumber(column_name));
	}
	
	/** Returns the standard deviation of a column or NaN if not possible
	 * 
	 * @param column_number the index of the column
	 * @return the column's standard deviation as a double or NaN if NA
	 */
	public double getColumnStandardDeviation(int column_number) {
		if(column_number < 0 || column_number >= data.size()) return Double.NaN;

		Double value = (Double)getVariable(column_number).getColumnStandardDeviation();
		if(value == null) return Double.NaN;
		return value.doubleValue();
	}
	
	/** Adds a new column filled with the default double value with
	 * a name based on the number of existing columns
	 *
	 */
	public void addColumn() {
		String name = DEFAULT_COL_PREFIX+data.size();
		data.add(new VariableModel(name,getRowCount(),DEFAULT_VALUE));
		
		fireTableStructureChanged();
		fireTableDataChanged(); 
	}
	
	/** Adds a new column based on an array of doubles that has no name
	 * 
	 * @param values double values to fill the column with
	 */
	public void addColumn(double[] values) {		
		String name = DEFAULT_COL_PREFIX+data.size();
		addColumn(values,name);
	}
	
	/** Adds a new column of doubles with the specified name
	 * 
	 * @param values an array of double values
	 * @param title the name of the new variable
	 */
	public void addColumn(double[] values, String title) {
		if(values == null) return;
		
		Object[] passed_values = new Object[getRowCount()];
		for(int i=0;i<passed_values.length; i++) {
			if(i<values.length) {
				passed_values[i] = new Double(values[i]);
			} else {
				passed_values[i] = null;
			}
		}
		
		// Actually add the column
		addColumn(passed_values,title);
		
	}
	
	/** Adds a new column of a given title filled with the contents
	 * of the Object array
	 * 
	 * @param values array of Objects to construct the new variable with
	 * @param title the name of the new variable 
	 */
	public void addColumn(Object[] values, String title) {
		data.add(new VariableModel(title,values));
		fireTableStructureChanged();
		fireTableDataChanged(); 
	}
	
	/** Adds a new column of a given title and sets all values of the
	 * column according to the provided equation
	 *
	 * @param equation a String specifying an equation 
	 * @param title the name of the new variable
	 */
	public void addColumn(String equation, String title) {
		
		EquationEvaluation new_column_data = new EquationEvaluation(this,equation);
		if(!new_column_data.processValues()) {
			return;
		}
		Double[] new_values = new_column_data.getResults();
		
		VariableModel to_add = new VariableModel(title,new_values);
		to_add.setEquation(equation);
		
		data.add(to_add);

		fireTableStructureChanged();
		fireTableDataChanged(); 	
	}
	
	/** Deletes the specified column from the data model
	 * 
	 * @param column the column number to remove
	 */
	public void removeColumn(int column) {
		if(column >= 0 && column < data.size()) {
			data.remove(column);
		}
		fireTableStructureChanged();
		fireTableDataChanged(); 
	}
	
	/** Adds a row and fills the row with null values
	 *
	 */
	public void addRow() {
		for(int i=0;i<data.size();i++) {
			getVariable(i).add(null);
		}
		fireTableStructureChanged();
		fireTableDataChanged(); 
	}
	
	/** Adds a row and fills the row with null values
	 *
	 * @param row position of the row to insert at
	 */
	public void addRow(int row) {
		for(int i=0;i<data.size();i++) {
			getVariable(i).add(row,null);
		}
		fireTableStructureChanged();
		fireTableDataChanged(); 
	}
	
	/** Inserts a row prior to the specified row
	 * 
	 * @param where the index of the row before which to insert a new row
	 */
	public void insertRow(int where) {
		if(where < 0 || where >= getRowCount()) return;
		for(int i=0;i<data.size();i++) {
			getVariable(i).add(where,null);
		}
		fireTableStructureChanged();
		fireTableDataChanged(); 
	}
	
	/** Removes a row from the table
	 * 
	 * @param where the row index to remove
	 */
	public void removeRow(int where) {
		if(where < 0 || where >= getRowCount()) return;
		
		for(int i=0;i<data.size();i++) {
			getVariable(i).remove(where);
		}
		//data.remove(where);
		fireTableStructureChanged();
		fireTableDataChanged(); 
	}
	
	/** Renames a selected column
	 * 
	 * @param old_name the original name of the column
	 * @param new_name the new name of the column
	 */
	public void renameColumn(String old_name, String new_name) {
		renameColumn(this.getColumnNumber(old_name),new_name);
	}
	
	/** Renames a selected column
	 * 
	 * @param old_name the original name of the column
	 * @param new_name the new name of the column
	 */
	public void renameColumn(int col, String new_name) {
		if(col < 0 || col >= data.size()) return;
		
		if(new_name != null) {
			String clean = new_name.replaceAll("[\\s-\\+/*]+","_");
			getVariable(col).setName(clean);
		}
		
		fireTableStructureChanged();
		fireTableDataChanged(); 
	}
	
	/** Generates a default StatTableModel based on the size constants
	 * DEFAULT_COLUMNS and DEFAULT_ROWS
	 * 
	 * @return an ArrayList containing all the variables
	 */
	private ArrayList generate_default() {
		ArrayList holding = new ArrayList(); 	
		for(int i=0;i<DEFAULT_COLUMNS;i++) {
			holding.add(new VariableModel(DEFAULT_COL_PREFIX+new Integer(i).toString(),DEFAULT_ROWS,DEFAULT_VALUE));
		}	
		return holding;
	}
	
	/** Returns the first column index that contains date data
	 *
	 * @return the date column index or -1 if no dates are present
	 */
	public int getDateColumn() {
		for(int i=0;i<getColumnCount();i++) {
			if(getVariable(i).getContentsClass() == DateObject.class) return i;
		}
		return -1;
	}
	
	/** Returns the date associated with a particular row, or null if not applicable
	 *
	 * @return DateObject associated with the requested row or null if no DateObjects are present
	 */
	public DateObject getDateAt(int row) {
		int i = getDateColumn();
		if(i == -1) return null;
		
		return (DateObject)getValueAt(row,i);
	}
	
	/** Gets the type of DateObjects defining a time series, or -1 if no time
	 * series data is present.
	 * 
	 * @return the type of date, as enumerated in DateObject
	 */
	public int getDateType() {
		int i = getDateColumn();
		if(i == -1) return -1;
	
		DateObject temp = null;
		for(int j=0;j<getRowCount();j++) {
			temp = (DateObject)getVariable(i).getNumberAt(j);
			if(temp != null) return temp.getType();
		}
		return -1;
	}
	
	/** Returns the base date for all date-conversion-to-double calculations.
	 * 
	 * @return the base DateObject for the date column, or null if none exists.
	 */
	public DateObject getDateBase() {
		int i = getDateColumn();
		if(i == -1) return null;
		for(int j=0;j<getRowCount();j++) {
			DateObject temp = (DateObject)getVariable(i).getNumberAt(j);
			if(temp == null) continue;
			
			if(temp.getBase() == null) continue;
			
			return temp.getBase();
		}
		return null;
	}
	
	/** Forces an update of a column that is defined by an equation
	 * 
	 * @param column the index of the column to update
	 */
	public void updateColumn(int column) {
		if(column >= data.size() || column < 0) return;
		String column_equation = getVariable(column).getEquation();
		if(column_equation != null) {
			setColumnValues(column,column_equation);
		}
	}
	
	/** Returns the complete ArrayList of Constants
	 * 
	 * @return an ArrayList of Constants
	 */
	public ArrayList<Constant> getConstants() {
		return constants;
	}
	
	/** Returns the value of a Constant as a Number or null if the name is not valid
	 * 
	 * @param name the name of the Constant of interest
	 * @return the value or null if no Constant is found
	 */ 
	public Number getConstantValue(String name) {
		Number return_val = null;
		for(int i=0;i<constants.size();i++) {
			if(((Constant)constants.get(i)).getName().compareTo(name) == 0) {
				return_val = ((Constant)constants.get(i)).getValue();
				break;
			}
		}
		return return_val;
	}

	/** Returns the indices of rows that do not contain null data.  The method will check
	 * every variable in the model for nulls.
	 * 
	 * @return a vector of integers specifying the row indices without null entries
	 */
	public int[] getNonNullRows() {
		return getNonNullRows((String[])null);
	}
	
	/** Returns the indices of rows that do not contain null data for a
	 * specified variable.
	 * 
	 * @param variable The name of the variable of interest
	 * @return a vector of integers specifying the row indices without null entries
	 */
	public int[] getNonNullRows(String variable) {
		String[] variables = new String[1];
		variables[0] = variable;
		return getNonNullRows(variables);
	}
	
	/** Returns the indices of rows that do not contain null data in the specified
	 * columns.  If the array of columns specified is null, it will check all columns
	 * in the model
	 * 
	 * @param variables the variables to check for nulls
	 * @return a vector of integers specifying the row indices without null entries
	 */
	public int[] getNonNullRows(String[] variables) {
		String[] int_variables = variables;
		if(int_variables == null) int_variables = getColumnNames();
		
		ArrayList rows = new ArrayList();
		int[] column_indices = new int[int_variables.length];
		for(int i=0;i<int_variables.length;i++) {
			column_indices[i] = this.getColumnNumber(int_variables[i]);
		}
		
		for(int i=0;i<getRowCount();i++) {
			boolean acceptable = true;
			for(int j=0;j<column_indices.length;j++) {
				if(getValueAt(i,column_indices[j])==null) {
					acceptable = false;
					break;
				}
			}
			if(acceptable)
				rows.add(new Integer(i));
		}
		
		int[] return_val = new int[rows.size()];
		for(int i=0;i<rows.size();i++) {
			return_val[i] = ((Integer)rows.get(i)).intValue();
		}
		
		return return_val;
	}

	/** Returns the description of a specified variable
	 * 
	 * @param variable the column number of the variable
	 * @return a String describing the variable or null if none
	 */
	public String getDescription(int variable) {
		if(!isValidColumn(variable))
			return null;
		return getVariable(variable).getDescription();
	}
	
	/** Returns the description of a specified variable
	 * 
	 * @param variable the name of the variable of interest
	 * @return a String describing the variable or null if none
	 */
	public String getDescription(String variable) {
		return getDescription(getColumnNumber(variable));
	}
	
	/** Sets the description for a specified variable
	 * 
	 * @param variable the column number of the variable
	 * @param describe the String describing the variable or null to erase
	 */
	public void setDescription(int variable, String describe) {
		VariableModel var = getVariable(variable);
		if(var != null) 
			var.setDescription(describe);
	}
	
	/** Sets the description for a specified variable
	 * 
	 * @param variable the name of the variable of interest
	 * @param describe the String describing the variable or null to erase
	 */
	public void setDescription(String variable, String describe) {
		setDescription(getColumnNumber(variable),describe);
	}
	
	/** Returns the number of non-null observations of a variable
	 * 
	 * @param variable the column number of interest
	 * @return the number of non-null rows stored in the variable
	 */
	public int getObeservationCount(int variable) {
		VariableModel var = getVariable(variable);
		if(var != null) 
			return var.getObservationCount();
			
		return 0;
	}
	
	public int getObeservationCount(String variable) {
		return getObeservationCount(getColumnNumber(variable));
	}
	
	public void sortAscending(int index_column) {
		if(!isValidColumn(index_column)) return;
		int[] placement = getVariable(index_column).sortAscending();
		for(int i=0; i < getColumnCount(); i++) {
			if(i!=index_column) getVariable(i).setNewOrder(placement);
		}
		fireTableDataChanged(); 
	}
	
	public void sortDescending(int index_column) {
		if(!isValidColumn(index_column)) return;
		int[] placement = getVariable(index_column).sortDescending();
		for(int i=0; i < getColumnCount(); i++) {
			if(i!=index_column) getVariable(i).setNewOrder(placement);
		}
		fireTableDataChanged(); 
	}
	
	// Table changes
	public void tableChanged(TableModelEvent e) {
		for(int i=0;i<updaters.size();i++) {
			((StatTableModelUpdatable)updaters.get(i)).update(this);
		}
	}
	
	public void addUpdater(StatTableModelUpdatable just_one) {
		updaters.add(just_one);
	}
	
	public void removeUpdater(StatTableModelUpdatable just_one) {
		updaters.remove(just_one);
	}
	
	public Class<?> getColumnClass(int columnIndex) {
		VariableModel variable = getVariable(columnIndex);
		if(variable == null) return null;		
		return variable.getContentsClass();
	}

	public void moveColumn(int source, int destination) {
		if(isValidColumn(source) && isValidColumn(destination)) {
			VariableModel one = (VariableModel)data.get(source);

			if(destination < source){
				data.add(destination, one);
				data.remove(source+1);
			} else {
				data.add(destination+1, one);
				data.remove(source);
			}
			
			fireTableStructureChanged();
			fireTableDataChanged(); 
		}
	}
	
	/** Returns true if a column index passed in is valid (between 0 and the last column index)
	 * 
	 * @param value the column index
	 * @return true if the index is valid, false otherwise
	 */
	private boolean isValidColumn(int value) {
		return (value >=0 && value < getColumnCount());
	}
}
