/*
 * 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.tools;
import java.awt.Color;
import java.awt.Component;
import java.awt.Font;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.ArrayList;
import javax.swing.JLabel;
import javax.swing.JTable;
import javax.swing.ListSelectionModel;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.event.TableColumnModelEvent;
import javax.swing.event.TableColumnModelListener;
import javax.swing.event.TableModelEvent;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.table.TableModel;
import org.opensourcephysics.display.CellBorder;

/**
 * This displays statistics of data columns in a DataToolTable.
 *
 * @author Douglas Brown
 * @version 1.0
 */
public class DataToolStatsTable extends JTable {
  // instance fields
  DataToolTable dataTable;
  StatsTableModel statsModel;
  DataToolTable.LabelRenderer labelRenderer;
  NumberRenderer numberRenderer = new NumberRenderer(3);
  Object[][] statsData; // statsData[col] contains stats for a table column

  /**
   * Constructor.
   *
   * @param table the datatable
   */
  public DataToolStatsTable(DataToolTable table) {
    dataTable = table;
    statsModel = new StatsTableModel();
    init();
  }

  /**
   * Initializes the table.
   */
  protected void init() {
    dataTable.getColumnModel().addColumnModelListener(new TableColumnModelListener() {
      public void columnAdded(TableColumnModelEvent e) {
        /** empty block */
      }
      public void columnRemoved(TableColumnModelEvent e) {
        /** empty block */
      }
      public void columnSelectionChanged(ListSelectionEvent e) {
        /** empty block */
      }
      public void columnMarginChanged(ChangeEvent e) {
        refreshTable();
      }
      public void columnMoved(TableColumnModelEvent e) {
        refreshTable();
      }

    });
    // create statistics data array
    refreshStatistics();
    // set and configure table model and header
    setModel(statsModel);
    setGridColor(Color.blue);
    setTableHeader(null); // no table header
    labelRenderer = dataTable.labelRenderer;
    setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
    ListSelectionModel selectionModel = dataTable.getSelectionModel();
    selectionModel.addListSelectionListener(new ListSelectionListener() {
      public void valueChanged(ListSelectionEvent e) {
        // workaround to prevent exceptions
        if(e.getFirstIndex()>-1) {
          refreshStatistics();
        }
      }

    });
    refreshCellWidths();
  }

  /**
   * Returns labels that describe the statistics.
   *
   * @return labels for max, min, mean, SD, SE and data count
   */
  private String[] getStatLabels() {
    return new String[] {ToolsRes.getString("Table.Entry.Max"),           //$NON-NLS-1$
                         ToolsRes.getString("Table.Entry.Min"),           //$NON-NLS-1$
                         ToolsRes.getString("Table.Entry.Mean"),          //$NON-NLS-1$
                         ToolsRes.getString("Table.Entry.StandardDev"),   //$NON-NLS-1$
                         ToolsRes.getString("Table.Entry.StandardError"), //$NON-NLS-1$
                         ToolsRes.getString("Table.Entry.Count")};        //$NON-NLS-1$
  }

  /**
   * Calculates statistical values for a data array.
   *
   * @param data the data array
   * @return the max, min, mean, SD, SE and non-NaN data count
   */
  private Object[] getStatistics(double[] data) {
    double max = -Double.MAX_VALUE;
    double min = Double.MAX_VALUE;
    double sum = 0.0;
    double squareSum = 0.0;
    int count = 0;
    for(int i = 0; i<data.length; i++) {
      if(Double.isNaN(data[i])) {
        continue;
      }
      count++;
      max = Math.max(max, data[i]);
      min = Math.min(min, data[i]);
      sum += data[i];
      squareSum += data[i]*data[i];
    }
    double mean = sum/count;
    double sd = (count<2) ? Double.NaN : Math.sqrt((squareSum-count*mean*mean)/(count-1));
    if(max==-Double.MAX_VALUE) {
      max = Double.NaN;
    }
    if(min==Double.MAX_VALUE) {
      min = Double.NaN;
    }
    return new Object[] {new Double(max), new Double(min), new Double(mean), new Double(sd), new Double(sd/Math.sqrt(count)), new Integer(count)};
  }

  /**
   *  Refresh the data display in this table.
   */
  public void refreshTable() {
    Runnable refresh = new Runnable() {
      public synchronized void run() {
        tableChanged(new TableModelEvent(statsModel, TableModelEvent.HEADER_ROW));
        refreshCellWidths();
      }

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

  /**
   *  Refresh the statistics data.
   */
  public void refreshStatistics() {
    // assemble statistics data array
    TableModel model = dataTable.getModel();
    int[] rows = dataTable.getSelectedRows();
    int[] cols = dataTable.getSelectedColumns();
    statsData = new Object[model.getColumnCount()][0];
    ArrayList<Double> datalist = new ArrayList<Double>();
    // for each column, assemble valid selected data and get stats
    statsData[0] = getStatLabels();
    for(int j = 1; j<model.getColumnCount(); j++) {
      datalist.clear();
      for(int i = 0; i<model.getRowCount(); i++) {
        Double val = (Double) model.getValueAt(i, j);
        if(val==null) {
          val = new Double(Double.NaN);
        }
        datalist.add(val);
      }
      double[] x = new double[datalist.size()];
      for(int i = 0; i<x.length; i++) {
        x[i] = datalist.get(i).doubleValue();
      }
      double[] selected = x;
      if(rows.length>0) {
        // is column selected?
        boolean colSelected = false;
        int col = dataTable.convertColumnIndexToView(j);
        for(int k = 0; k<cols.length; k++) {
          colSelected = colSelected||(col==cols[k]);
        }
        if(colSelected) {
          selected = new double[rows.length];
          for(int i = 0; i<rows.length; i++) {
            selected[i] = x[rows[i]];
          }
        }
      }
      statsData[j] = getStatistics(selected);
    }
    refreshTable();
  }

  /**
   *  Refresh the cell widths in the table.
   */
  public void refreshCellWidths() {
    // set width of columns
    if(getColumnCount()!=dataTable.getColumnCount()) {
      return;
    }
    for(int i = 0; i<getColumnCount(); i++) {
      TableColumn propColumn = getColumnModel().getColumn(i);
      TableColumn dataColumn = dataTable.getColumnModel().getColumn(i);
      propColumn.setMaxWidth(dataColumn.getWidth());
      propColumn.setMinWidth(dataColumn.getWidth());
      propColumn.setWidth(dataColumn.getWidth());
    }
  }

  /**
   *  Refresh the GUI.
   */
  public void refreshGUI() {
    refreshStatistics();
  }

  /**
   * 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 = dataTable.convertColumnIndexToModel(column);
    if(i==0) {
      return labelRenderer;
    }
    return numberRenderer;
  }

  public void setFont(Font font) {
    super.setFont(font);
    if(numberRenderer!=null) {
      numberRenderer.font = font;
    }
    setRowHeight(font.getSize()+4);
  }

  /**
   * A class to provide model data for this table.
   */
  class StatsTableModel extends AbstractTableModel {
    public String getColumnName(int col) {
      return dataTable.getColumnName(col);
    }

    public int getRowCount() {
      return statsData[0].length;
    }

    public int getColumnCount() {
      return dataTable.getModel().getColumnCount();
    }

    public Object getValueAt(int row, int col) {
      int i = dataTable.convertColumnIndexToModel(col);
      return statsData[i][row];
    }

    public boolean isCellEditable(int row, int col) {
      return false;
    }

    public Class<?> getColumnClass(int c) {
      return getValueAt(0, c).getClass();
    }

  }

  /**
   * A class to render numbers and strings.
   */
  class NumberRenderer extends JLabel implements TableCellRenderer {
    NumberFormat format = NumberFormat.getInstance();
    Font font;

    /**
     * Constructor NumberRenderer
     * @param sigfigs
     */
    public NumberRenderer(int sigfigs) {
      sigfigs = Math.min(sigfigs, 6);
      if(format instanceof DecimalFormat) {
        String pattern = "0.0"; //$NON-NLS-1$
        for(int i = 0; i<sigfigs-1; i++) {
          pattern += "0";       //$NON-NLS-1$
        }
        pattern += "E0";        //$NON-NLS-1$
        ((DecimalFormat) format).applyPattern(pattern);
      }
    }

    public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
      if(font==null) {
        font = getDefaultRenderer(String.class).getTableCellRendererComponent(DataToolStatsTable.this, "", false, false, 0, 0).getFont(); //$NON-NLS-1$
      }
      setFont(font);
      setHorizontalAlignment(SwingConstants.TRAILING);
      setBorder(new CellBorder(new Color(240, 240, 240)));
      if(value instanceof Integer) {
        setText(String.valueOf(value));
      } else {
        setText(format.format(value));
      }
      return this;
    }

  }

}

/*
 * 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
 */
