/*
 * COPYRIGHT (c) 1998-1999 Jan-Henrik Haukeland, http://www.tildeslash.com/
 */

package ts.ui;

import java.util.*;
import javax.swing.table.*;


/**
 * A Table model with a simple set of methods for handling the model.
 * The internal data structure for the dataset (cells) is a vector 
 * of vectors.
 *
 * @author  Jan-Henrik Haukeland
 * @version $Id: TsTableModel.java,v 1.4 1999/02/21 08:47:23 hauk Exp $
 *
 * @see TsTable
 */
public class TsTableModel extends AbstractTableModel 
{
  protected Vector        myColumnTitles= null;  // Column titles
  protected Vector        myDataSet=      null;  // The dataset 
  protected int           hiddenColumns=  0;     // Number of hidden columns
  protected boolean       isEditable=     false; // Can edit cells in table
  protected volatile int  numColumns=     0,    
                          numRows=        0;


  
  //---------------------- Constructors --------------------------//

  
  /**
   * Create a new TsTableModel with column titles and a dataset
   *
   * @param titles Column titles
   * @param data   A dataset (vector of vector)
   */
  public TsTableModel(Vector titles, Vector data)
  {
    super();

    if ( titles == null )
	myColumnTitles= new Vector();
    else
	myColumnTitles= titles;
	
    if ( data == null )
	myDataSet= new Vector();
    else
	myDataSet= data;
	
    // Save size for performance reasons
    numColumns= myColumnTitles.size();
    numRows=    myDataSet.size();
  }


  /**
   * Create a new TsTableModel with column titles and a dataset
   *
   * @param titles Column titles
   * @param data   A dataset 
   */
  public TsTableModel(Object[] titles, Object[][] data)
  {
    this(convertToVector(titles), convertToVector(data));
  }


  /**
   * Create a new TsTableModel with titles and a empty dataset
   *
   * @param titles Column titles
   */
  public TsTableModel(Object[] titles)
  {
    this(convertToVector(titles), (Vector)null);
  }


  
  /**
   * Create a new empty TsTableModel
   */
  public TsTableModel()
  {
    this((Vector)null, (Vector)null);
  }


  //------------------------ public methods -------------------------//
  
  
  /**
   * TsTableModel factory method
   */
  public static TableModel createTsTableDataModel()
  {
    return new TsTableModel();
  }


  /**
   * Set if cells in the table could be edited. 
   * @param true if the table will allow edit in cells else false
   */
  public void setEditable(boolean b)
  {
    isEditable= b;
  }


  
  /**
   * Set new column titles in the model
   *
   * @param titles Column titles
   */
  public void setTitles(Vector titles)
  {
    if ( titles == null )
	myColumnTitles= new Vector();
    else 
	myColumnTitles= titles;
	
    numColumns= myColumnTitles.size();
    fireTableStructureChanged();
  }


  /**
   * Set new column titles in the model
   *
   * @param titles Column titles
   */
  public void setTitles(Object[] titles)
  {
    setTitles(convertToVector(titles));
  }


  /**
   * Set a new dataset for the model
   *
   * @param data The new dataset for this table model
   */
  public void setData(Vector data)
  {
    if ( data == null )
	myDataSet= new Vector();
    else 
	myDataSet= data;
    
    numRows= myDataSet.size();
    fireTableDataChanged();
  }


  /**
   * Set a new dataset for the model
   *
   * @param data The new dataset for this table model
   */
  public void setData(Object[][] data)
  {
    setData(convertToVector(data));
  }


  /**
   * Returns the number of hidden columns
   *
   * @returns The number of hidden columns
   */
  public int getHiddenColumnsCount()
  {
    return hiddenColumns;
  }


  /**
   * Returns the number of columns managed by the data source object.
   * The TsTable uses this method to determine how many columns it
   * should create and display on initialization. 
   *
   * @return the number of columns in the dataset
   */
  public int getColumnCount()
  {
    return numColumns-hiddenColumns;
  }
  
  /**
   * Returns the number of records in the dataset. The TsTable uses 
   * this method to determine how many rows it should create and display.
   * This method should be  quick, as it is called by TsTable quite 
   * frequently. 
   *
   * @return number of rows in the dataset
   */
  public int getRowCount()
  {
    return numRows;
  }
  
  /**
   * Get an attribute value for the cell at columnIndex and rowIndex. 
   *
   * @return The object at the given row and column
   */
  public Object getValueAt(int row, int col)
  {
    return (Object)((Vector)myDataSet.elementAt(row)).
	elementAt(col+hiddenColumns);
  }
  
  /**
   * Get the name of the column at columnIndex. 
   *
   * @return the name of the specified column index
   */
  public String getColumnName(int col)
  {
    return (String)myColumnTitles.elementAt(col+hiddenColumns);
  }
  
  /**
   * Get the lowest common denominator Class in the column. 
   *
   * @return the common ancestor class of the object values in the model. 
   */
  public Class getColumnClass(int c)
  {
    return getValueAt(0, c).getClass();
  }
  

  /**
   * Returns the row at the given index as a Vector
   *
   * @return the row at row index
   */
  public Vector getRowVector(int rowIndex)
  {
    return numRows == 0 ? null : (Vector)myDataSet.elementAt(rowIndex);
  }

  
  /**
   * Returns the row at the given index as an array of objects
   *
   * @return the row at row index
   */
  public Object[] getRow(int rowIndex)
  {
    return convertToArray(getRowVector(rowIndex));
  }


  /**
   * Remove the row with row index from the dataset and notify
   * any TableModel listeners of changes in the model.
   *
   * @param rowIndex The index of the row to remove
   */
  public void deleteRow(int rowIndex)
  {
    myDataSet.removeElementAt(rowIndex);
    fireTableRowsDeleted(rowIndex,rowIndex);
    updateRows();
  }

  
  /**
   * Insert a row as the first row in the dataset. Notfies
   * any TableModel listeners of changes in the model.
   *
   * @param row The row to insert at the start of the list
   */
  public void insertRow(Vector row)
  {
    this.insertRow(row,0);
  }


  /**
   * Insert a row as the first row in the dataset. Notfies
   * any TableModel listeners of changes in the model.
   *
   * @param row The row to insert at the start of the list
   */
  public void insertRow(Object[] row)
  {
    this.insertRow(convertToVector(row));
  }
  

  /**
   * Insert a row in the dataset at the given row index. Notify
   * any listeners of changes in the model.
   *
   * @param row   The row to insert
   * @param index Where to insert the row in the dataset
   */
  public void insertRow(Vector row, int rowIndex)
  {
    if ( row == null ) return;

    if ( numRows == 0 )
	myDataSet.addElement((Object)row);
    else
	myDataSet.insertElementAt((Object)row,rowIndex);
    updateRows();
    fireTableRowsInserted(rowIndex,rowIndex);
  }


  /**
   * Insert a row in the dataset at the given row index. Notify
   * any listeners of changes in the model.
   *
   * @param row   The row to insert
   * @param index Where to insert the row in the dataset
   */
  public void insertRow(Object[] row, int rowIndex)
  {
    this.insertRow(convertToVector(row), rowIndex);
  }

  
  /**
   * Insert several rows at the begining of the dataset. Notify
   * any listeners of changes in the model.
   *
   * @param row  A dataset (vector of vector)
   */
  public void insertRows(Vector rows)
  {
    this.insertRows(rows, 0);
  }


  /**
   * Insert several rows at the begining of the dataset. Notify
   * any listeners of changes in the model.
   *
   * @param row  A dataset (vector of vector)
   */
  public void insertRows(Object[][] rows)
  {
    this.insertRows(convertToVector(rows));
  }


  /**
   * Insert several rows into the dataset at the given row index.
   * Listeners on this model is notified of the changes.
   *
   * @param rows  A dataset (vector of vector)
   * @param index The row index to start adding the new rows from
   */
  public void insertRows(Vector rows, int rowIndex)
  {
    if ( rows == null ) return;
    
    int i= rowIndex;
    for (Enumeration e= rows.elements(); e.hasMoreElements();) {
      if ( numRows == 0 )
	  myDataSet.addElement((Object)e.nextElement());
      else
	  myDataSet.insertElementAt((Object)e.nextElement(), i++);
    }
    
    int size= rows.size();
    updateRows();
    fireTableRowsInserted(rowIndex,size);
  }

  
  /**
   * Insert several rows into the dataset at the given row index.
   * Listeners on this model is notified of the changes.
   *
   * @param rows  A dataset (vector of vector)
   * @param index The row index to start adding the new rows from
   */
  public void insertRows(Object[][] rows, int rowIndex)
  {
    this.insertRows(convertToVector(rows), rowIndex);
  }


  /**
   * Clear the dataset and notify any listeners of changes in
   * the model.
   */
  public void clear()
  {
    myDataSet.removeAllElements();
    numRows=    0;
    fireTableDataChanged();
  }


  /**
   * Set number of hidden columns in the model
   *
   * @param hidden Number of columns that should be hidden
   */
  public void setHiddenColumns(int hidden)
  {
    hiddenColumns= hidden;
    fireTableStructureChanged();
  }



  /**
   * Allow edit in cell if edit is turned on
   */
  public boolean isCellEditable(int row, int col) 
  {
    return isEditable;
  }


  /**
   * Set a new value in a cell
   *
   * @param aValue - the new value 
   * @param row - the row whose value is to be changed 
   * @param column - the column whose value is to be changed    
   */
  public void setValueAt(Object value, int row, int col) 
  {
    ((Vector)myDataSet.elementAt(row)).
        setElementAt(value,col+hiddenColumns);
  }


  //----------------------- protected methods -------------------------//

  
  /** 
   * @return an Object array that contains the same objects as the vector 
   */
  protected static Object[] convertToArray(Vector aVector) 
  {
    if (aVector == null)
	return null;

    Object[] o= new Object[aVector.size()];

    aVector.copyInto(o);

    return o;
  }


  /** 
   * @return a Vector that contains the same objects as the array 
   */
  protected static Vector convertToVector(Object[] anArray) 
  {
    if (anArray == null)
	return null;
    
    Vector v= new Vector(anArray.length);
    for (int i=0; i < anArray.length; i++) {
      v.addElement(anArray[i]);
    }
    return v;
  }
  
  /** 
   * @return a Vector of Vectors that contains the same objects as the array 
   */
  protected static Vector convertToVector(Object[][] anArray) 
  {
    if (anArray == null)
	return null;
    
    Vector v= new Vector(anArray.length);
    for (int i=0; i < anArray.length; i++) {
      v.addElement(convertToVector(anArray[i]));
    }
    return v;
  }


  //----------------------- private methods -------------------------//

  
  /**
   * Refresh the rowcount variable
   */
  private void updateRows()
  {
    numRows=    myDataSet.size();
  }


}
 
