/*
 * Open Source Physics software is free software as described near the bottom of this code file.
 *
 * For additional information and documentation on Open Source Physics please see:
 * <http://www.opensourcephysics.org/>
 */

package org.opensourcephysics.display;
import java.awt.Color;
import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.text.DecimalFormat;
import javax.swing.JLabel;
import javax.swing.JTable;
import javax.swing.ListSelectionModel;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.DefaultTableColumnModel;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;

/**
 * A JTable to display rows of integers, doubles and Strings.
 *
 * @author Wolfgang Christian
 * @version 1.0
 */
public class DataRowTable extends JTable implements ActionListener {
  static final Color PANEL_BACKGROUND = javax.swing.UIManager.getColor("Panel.background"); //$NON-NLS-1$
  static final Color LIGHT_BLUE = new Color(204, 204, 255);
  protected int labelColumnWidth = 40;
  DataRowModel rowModel = new DataRowModel();
  RowNumberRenderer indexRenderer = new RowNumberRenderer();
  CellRenderer cellRenderer = new CellRenderer();
  String formatPattern = "0.000";                                                           //$NON-NLS-1$
  DecimalFormat defaultFormat = new DecimalFormat(formatPattern);
  int refreshDelay = 0;                                                                     // time in ms to delay refresh events
  javax.swing.Timer refreshTimer = new javax.swing.Timer(refreshDelay, this);               // delay for refreshTable
  java.util.Dictionary<Integer, DecimalFormat> formats = new java.util.Hashtable<Integer, DecimalFormat>();

  /**
   * Constructor DataRowTable
   */
  public DataRowTable() {
    init();
  }

  /**
   * Initializes the table.
   */
  protected void init() {
    refreshTimer.setRepeats(false);
    refreshTimer.setCoalesce(true);
    setModel(rowModel);
    setColumnSelectionAllowed(true);
    setGridColor(Color.blue);
    setSelectionBackground(LIGHT_BLUE);
    setSelectionForeground(Color.red);                         // foreground color for selected cells
    setColumnModel(new DataTableColumnModel());
    setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION);
    setColumnSelectionAllowed(true);
    rowModel.addTableModelListener(new TableModelListener() {
      public void tableChanged(TableModelEvent e) {
        // forward the table model event to property change listeners
        DataRowTable.this.firePropertyChange("cell", null, e); //$NON-NLS-1$
      }

    });
    setDefaultRenderer(Object.class, cellRenderer);
    getTableHeader().setForeground(Color.blue); // set text color
    getTableHeader().setReorderingAllowed(true);
    getTableHeader().setDefaultRenderer(new HeaderRenderer());
    setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
    int width = 24;
    String name;
    TableColumn column;
    if(getColumnCount()>0) {
      // set width of column 0 (row index)
      name = getColumnName(0);
      column = getColumn(name);
      column.setMinWidth(width);
      column.setMaxWidth(2*width);
      column.setWidth(width);
    }
    // set width of other columns
    width = 60;
    for(int i = 1, n = getColumnCount(); i<n; i++) {
      name = getColumnName(i);
      column = getColumn(name);
      column.setMinWidth(width);
      column.setMaxWidth(3*width);
      column.setWidth(width);
    }
  }

  /**
   * Sets the <code>Timer</code>'s initial time delay (in milliseconds)
   * to wait after the timer is started
   * before firing the first event.
   * @param delay
   */
  public void setRefreshDelay(int delay) {
    if(delay>0) {
      refreshTimer.setDelay(delay);
      refreshTimer.setInitialDelay(delay);
    } else if(delay<=0) {
      refreshTimer.stop();
    }
    refreshDelay = delay;
  }

  /**
   * Sets the default numeric display format pattern.
   * @param defaultFormat
   */
  public void setNumericFormat(String str) {
    if((str!=null)&&!str.equals(formatPattern)) { // format has changed
      formatPattern = str;
      defaultFormat = new DecimalFormat(str);
      refreshTable();
    }
  }

  /**
   *  Sets the column decimal format.
   *
   * @param  column  the column index
   * @param  format  the format
   */
  public void setColumnFormat(int column, String format) {
    DecimalFormat f = new DecimalFormat(format);
    DecimalFormat val = formats.get(column);
    if((val!=null)&&val.equals(f)) {
      return; // nothing changed
    }
    formats.put(column, f);
    refreshTable();
  }

  /**
   * Clears data from this table.  Column names and format patterns are not affected.
   */
  public synchronized void clearData() {
    rowModel.rowList.clear();
    rowModel.colCount = 0;
    refreshTable();
  }

  /**
   * Clears data, column names, and format patterns.
   */
  public synchronized void clear() {
    rowModel.rowList.clear();
    rowModel.colNames.clear();
    rowModel.colCount = 0;
    formats = new java.util.Hashtable<Integer, DecimalFormat>();
    refreshTable();
  }

  /**
   *  Sets the stride between rows.
   *
   * @param  tableModel
   * @param  stride
   */
  public void setStride(int stride) {
    stride = Math.max(1, stride);
    if(rowModel.stride==stride) {
      return; // nothing changed
    }
    rowModel.setStride(stride);
    refreshTable();
  }

  /**
   *  Refresh the data in the DataTable, as well as other changes to the table,
   *  such as row number visibility. Changes to the TableModels displayed in the
   *  table will not be visible until this method is called.
   */
  public void refreshTable() {
    if(refreshDelay>0) {
      refreshTimer.start();
    } else {
      Runnable doRefreshTable = new Runnable() {
        public synchronized void run() {
          tableChanged(new TableModelEvent(getModel(), TableModelEvent.HEADER_ROW));
        }

      };
      if(SwingUtilities.isEventDispatchThread()) {
        doRefreshTable.run();
      } else {
        SwingUtilities.invokeLater(doRefreshTable);
      }
    }
  }

  /**
   * Returns the renderer for a cell specified by row and column.
   *
   * @param row the row number
   * @param column the column number
   * @return the cell renderer
   */
  public TableCellRenderer getCellRenderer(int row, int column) {
    int i = convertColumnIndexToModel(column);
    if((i==0)&&rowModel.rowNumberVisible) {
      return indexRenderer;
    }
    return cellRenderer;
    //return getDefaultRenderer(getColumnClass(column));
  }

  /**
   *  Performs the action for the refresh timer by refreshing the data in the DataTable.
   *
   * @param  evt
   */
  public void actionPerformed(ActionEvent evt) {
    tableChanged(new TableModelEvent(getModel(), TableModelEvent.HEADER_ROW));
  }

  private class CellRenderer extends DefaultTableCellRenderer {
    /**
     * PrecisionRenderer constructor
     *
     */
    public CellRenderer() {
      super();
      setHorizontalAlignment(SwingConstants.RIGHT);
      setBorder(new CellBorder(new Color(224, 224, 224)));
      setBackground(Color.WHITE);
    }

    /**
     *  returns a JLabel that is highlighted if the row is selected.
     *
     * @param  table
     * @param  value
     * @param  isSelected
     * @param  hasFocus
     * @param  row
     * @param  column
     * @return
     */
    public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
      super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
      if(!rowModel.rowNumberVisible) {
        column++;
      }
      DecimalFormat f = formats.get(column);
      if(f==null) {
        f = defaultFormat;
      }
      if(value==null) {
        setText(""); //$NON-NLS-1$
      } else if(value instanceof String) {
        setText((String) value);
      } else if(f==null) {
        setText(value.toString());
      } else {
        try {
          setText(f.format(value));
        } catch(IllegalArgumentException ex) {
          setText(value.toString());
        }
      }
      return this;
    }

  }

  private class HeaderRenderer extends RowNumberRenderer {
    /**
     * Constructor HeaderRenderer
     */
    public HeaderRenderer() {
      super();
      setHorizontalAlignment(SwingConstants.CENTER);
      setForeground(Color.BLUE);
      setBorder(javax.swing.BorderFactory.createLineBorder(new Color(224, 224, 224)));
    }

  }

  private static class RowNumberRenderer extends JLabel implements TableCellRenderer {
    /**
     *  RowNumberRenderer constructor
     *
     */
    public RowNumberRenderer() {
      super();
      setHorizontalAlignment(SwingConstants.RIGHT);
      setOpaque(true); // make background visible.
      setForeground(Color.BLACK);
      setBackground(PANEL_BACKGROUND);
      setBorder(new CellBorder(new Color(224, 224, 224)));
    }

    /**
     *  returns a JLabel that is highlighted if the row is selected.
     *
     * @param  table
     * @param  value
     * @param  isSelected
     * @param  hasFocus
     * @param  row
     * @param  column
     * @return
     */
    public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
      if(table.isRowSelected(row)) {
        int[] i = table.getSelectedColumns();
        if((i.length==1)&&(table.convertColumnIndexToModel(i[0])==0)) {
          setBackground(PANEL_BACKGROUND);
        } else {
          setBackground(Color.gray);
        }
      } else {
        setBackground(PANEL_BACKGROUND);
      }
      setText(value.toString());
      return this;
    }

  }

  private class DataTableColumnModel extends DefaultTableColumnModel {
    /**
     *  Method getColumn
     *
     * @param  columnIndex
     * @return
     */
    public TableColumn getColumn(int columnIndex) {
      TableColumn tableColumn;
      try {
        tableColumn = super.getColumn(columnIndex);
      } catch(Exception ex) { // return an empty column if the columnIndex is not valid.
        return new TableColumn();
      }
      String headerValue = (String) tableColumn.getHeaderValue();
      if(headerValue==null) {
        return tableColumn;
      } else if(headerValue.equals("row")) { //$NON-NLS-1$
        tableColumn.setMaxWidth(labelColumnWidth);
        tableColumn.setMinWidth(labelColumnWidth);
        tableColumn.setResizable(true);
      }
      return tableColumn;
    }

  }

}

/*
 * Open Source Physics software is free software; you can redistribute
 * it and/or modify it under the terms of the GNU General Public License (GPL) as
 * published by the Free Software Foundation; either version 2 of the License,
 * or(at your option) any later version.

 * Code that uses any portion of the code in the org.opensourcephysics package
 * or any subpackage (subdirectory) of this package must must also be be released
 * under the GNU GPL license.
 *
 * This software 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; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston MA 02111-1307 USA
 * or view the license online at http://www.gnu.org/copyleft/gpl.html
 *
 * Copyright (c) 2007  The Open Source Physics project
 *                     http://www.opensourcephysics.org
 */
