/**
 *
 *  EDatasetManager maintains a list of datasets. Datasets are added
 *  automatically to this DatasetCollection by calling a method in this
 *  EDatasetManager with a dataset index greater than the maximum value for
 *  the dataset index that has been used previously. For example the statements:
 *  <code> EDatasetManager datasetManager = new EDatasetManager();
 *  datasetManager.append(0,3,4);
 *  datasetManager.append(1,5,6);</code> appends the point (3,4) to the 0th
 *  dataset (and creates this dataset automatically) and appends the point (5,6)
 *  to the 1-st dataset (and also creates this dataset automatically).
 *
 *  Modified June 27, 2002 by W. Christian.
 *  
 * @author     jgould
 * @created    February 17, 2002
 *
 */
 
 /* This class was modified by Max Perkins and Dr. J. Hasbun in February 2004.
 * It was modified to allow for data editing and data table initialization.
 * Two functions were added. initDataTable() was added to initialize the data
 * table with "blank" characters. setValueAt() was added to allow for dataset
 * editing
 * 
 * Max Perkins was sponsored by the NASA Space Grant Consortium through Dr. Ben de Mayo 
 * of the Physics Department at the State University of West Georgia.
 */

package org.opensourcephysics.jahasbun.QE.display;

import java.awt.*;
import java.util.*;
import javax.swing.table.AbstractTableModel;
import org.opensourcephysics.display.*;

public class EDatasetManager extends AbstractTableModel implements Measurable {

  ArrayList      datasets = new ArrayList();
  boolean        connected;                              // default values for new datasets
  boolean        sorted;
  int            markerShape;  int            stride       = 1;
  boolean        linked       = false;                   // whether x data in datasets is linked. If set to true, then x data for datasets > 0 will not be shown in a table view.
  static Color[] lineColors   = {Color.red, Color.green, Color.blue, Color.yellow};
  static Color[] markerColors = {Color.black, Color.blue, Color.red, Color.green};
  String         xColumnName  = "x", yColumnName = "y";  // default names for new datasets

  /**
   *
   *  EDatasetManager constructor.
   *
   */
  public EDatasetManager() {
    this(false, false, false, EDataset.SQUARE);
  }

  /**
   *
   *  EDatasetManager constructor.
   *
   * @param linked
   *
   */
  public EDatasetManager(boolean linked) {
    this(false, false, linked, EDataset.SQUARE);
  }

  /**
   *
   *  EDatasetManager constructor specifying whether points are connected and
   *  sorted.
   * @param  _connected  Description of Parameter
   *
   * @param  _sorted     Description of Parameter
   *
   */
  public EDatasetManager(boolean _connected, boolean _sorted) {
    this(_connected, _sorted, false, EDataset.SQUARE);
  }

  /**
   *
   *  EDatasetManager constructor specifying whether points are connected,
   *
   *  sorted, and the marker shape.
   *
   *
   *
   * @param  _connected    Description of Parameter
   *
   * @param  _sorted       Description of Parameter
   *
   * @param _linked
   *
   * @param  _markerShape  Description of Parameter
   *
   */
  public EDatasetManager(boolean _connected, boolean _sorted, boolean _linked, int _markerShape) {
    connected   = _connected;
    sorted      = _sorted;
    markerShape = _markerShape;
    linked      = _linked;
  }

  /**
   *
   *  Sets the linked flag. X data for datasets > 0 will not be shown in a table
   *
   *  view.
   *
   *
   *
   * @param  _linked  The new value
   *
   */
  public void setXPointsLinked(boolean _linked) {
    linked = _linked;
    for(int i = 1; i < datasets.size(); i++) {
      EDataset dataset = (EDataset) datasets.get(i);
      dataset.setXColumnVisible(!linked);
    }
  }

  /**
   *
   *  Sets the sorted flag. Data is sorted by increasing x.
   *
   *
   *
   * @param  _sorted       <code>true<\code> to sort
   *
   *
   *
   *
   *
   *
   *
   * @param  datasetIndex  The new sorted value
   *
   */
  public void setSorted(int datasetIndex, boolean _sorted) {
    checkDatasetIndex(datasetIndex);
    EDataset dataset = (EDataset) datasets.get(datasetIndex);
    dataset.setSorted(_sorted);
  }

  /**
   *
   *  Sets the sorted flag for all datasets.
   *
   * @param _sorted
   *
   */
  public void setSorted(boolean _sorted) {
    sorted = _sorted;  // sorted for future datasets
    for(int i = 0; i < datasets.size(); i++) {
      ((EDataset) (datasets.get(i))).setSorted(_sorted);
    }
  }

  /**
   *
   *  Sets the data connected flag. Points are connected by straight lines.
   *
   *
   *
   * @param  _connected    <code>true<\code> if points are connected
   *
   *
   *
   *
   *
   *
   *
   * @param  datasetIndex  The new connected value
   *
   */
  public void setConnected(int datasetIndex, boolean _connected) {
    checkDatasetIndex(datasetIndex);
    EDataset dataset = (EDataset) datasets.get(datasetIndex);
    dataset.setConnected(_connected);
  }

  /**
   *
   *  Sets the connected flag for all datasets.
   *
   *
   *
   * @param _connected true if connected; false otherwise
   *
   */
  public void setConnected(boolean _connected) {
    connected = _connected;  // sorted for future datasets
    for(int i = 0; i < datasets.size(); i++) {
      ((EDataset) (datasets.get(i))).setConnected(_connected);
    }
  }

  /**
   *
   *  Sets the stride for the given dataset.
   *
   *
   *
   * @param  stride
   *
   * @param  datasetIndex  The new markerColor value
   *
   */
  public void setStride(int datasetIndex, int stride) {
    checkDatasetIndex(datasetIndex);
    EDataset dataset = (EDataset) datasets.get(datasetIndex);
    dataset.setStride(stride);
  }

  /**
   *
   *  Sets the stride for all datasets.
   *
   *
   *
   * @param _stride
   *
   */
  public void setStride(int _stride) {
    stride = _stride;  // default stride for future datasets
    // set the stride for current datasets
    for(int i = 0; i < datasets.size(); i++) {
      ((EDataset) (datasets.get(i))).setStride(stride);
    }
  }

  /**
   *
   *  Sets the data point marker color.
   *
   *
   *
   * @param  _markerColor
   *
   * @param  datasetIndex
   *
   */
  public void setMarkerColor(int datasetIndex, Color _markerColor) {
    checkDatasetIndex(datasetIndex);
    EDataset dataset = (EDataset) datasets.get(datasetIndex);
    dataset.setMarkerColor(_markerColor);
  }

  /**
   *
   *  Sets the data point marker shape. Shapes are: NO_MARKER CIRCLE SQUARE
   *
   *  FILLED_CIRCLE FILLED_SQUARE
   *
   *
   *
   * @param  _markerShape
   *
   * @param  datasetIndex  The new markerShape value
   *
   */
  public void setMarkerShape(int datasetIndex, int _markerShape) {
    checkDatasetIndex(datasetIndex);
    EDataset dataset = (EDataset) datasets.get(datasetIndex);
    dataset.setMarkerShape(_markerShape);
  }

  /**
   *
   *  Sets the visibility of the x column in a table view.
   *
   *
   *
   * @param  visible       new visibility
   *
   * @param  datasetIndex  The new xColumnVisible value
   *
   */
  public void setXColumnVisible(int datasetIndex, boolean visible) {
    checkDatasetIndex(datasetIndex);
    EDataset dataset = (EDataset) datasets.get(datasetIndex);
    dataset.setXColumnVisible(visible);
  }

  /**
   *
   *  Sets the visibility of the y column in a table view.
   *
   *
   *
   * @param  visible       new visibility
   *
   * @param  datasetIndex  The new yColumnVisible value
   *
   */
  public void setYColumnVisible(int datasetIndex, boolean visible) {
    checkDatasetIndex(datasetIndex);
    EDataset dataset = (EDataset) datasets.get(datasetIndex);
    dataset.setYColumnVisible(visible);
  }

  /**
   *
   *  Sets the half-width of the data point marker.
   *
   *
   *
   * @param  _markerSize   in pixels
   *
   * @param  datasetIndex  The new markerSize value
   *
   */
  public void setMarkerSize(int datasetIndex, int _markerSize) {
    checkDatasetIndex(datasetIndex);
    EDataset dataset = (EDataset) datasets.get(datasetIndex);
    dataset.setMarkerSize(_markerSize);
  }

  /**
   *
   *  Sets the color of the lines connecting data points.
   *
   *
   *
   * @param  _lineColor
   *
   * @param  datasetIndex  The new lineColor value
   *
   */
  public void setLineColor(int datasetIndex, Color _lineColor) {
    checkDatasetIndex(datasetIndex);
    EDataset dataset = (EDataset) datasets.get(datasetIndex);
    dataset.setLineColor(_lineColor);
  }

  /**
   *
   *  Sets the column names when rendering this dataset in a JTable.
   *
   *
   *
   * @param  _xColumnName
   *
   * @param  _yColumnName
   *
   * @param  datasetIndex  The new xYColumnNames value
   *
   */
  public void setXYColumnNames(int datasetIndex, String _xColumnName, String _yColumnName) {
    checkDatasetIndex(datasetIndex);
    EDataset dataset = (EDataset) datasets.get(datasetIndex);
    dataset.setXYColumnNames(_xColumnName, _yColumnName);
  }

  /**
   *
   *  Gets the valid measure flag. The measure is valid if the min and max values
   *
   *  have been set for at least one dataset.
   *
   *
   *
   * @return    <code>true<\code> if measure is valid
   *
   *
   *
   */
  public boolean isMeasured_jg() {
    for(int i = 0; i < datasets.size(); i++) {
      EDataset d = (EDataset) datasets.get(i);
      if(!d.isMeasured()) {
        return false;
      }
    }
    return true;
  }

  /**
   *
   *  Gets the valid measure flag. The measure is valid if the min and max values
   *
   *  have been set for at least one dataset.
   *
   *
   *
   * @return    <code>true<\code> if measure is valid
   *
   *
   *
   *
   *
   *
   *
   */
  public boolean isMeasured() {
    for(int i = 0; i < datasets.size(); i++) {
      EDataset d = (EDataset) datasets.get(i);
      if(d.isMeasured()) {
        return true;
      }
    }
    return false;
  }

  /**
   *
   *  Gets the x world coordinate for the left hand side of the panel.
   *
   *
   *
   * @return    xmin
   *
   */
  public double getXMin() {
    double xmin = Double.MAX_VALUE;
    for(int i = 0; i < datasets.size(); i++) {
      EDataset d = (EDataset) datasets.get(i);
      if(d.isMeasured()) {
        xmin = Math.min(xmin, d.getXMin());
      }
    }
    return xmin;
  }

  /**
   *
   *  Gets the x world coordinate for the right hand side of the panel.
   *
   *
   *
   * @return    xmax
   *
   */
  public double getXMax() {
    double xmax = -Double.MAX_VALUE;
    for(int i = 0; i < datasets.size(); i++) {
      EDataset d = (EDataset) datasets.get(i);
      if(d.isMeasured()) {
        xmax = Math.max(xmax, d.getXMax());
      }
    }
    return xmax;
  }

  /**
   *
   *  Gets y world coordinate for the bottom of the panel.
   *
   *
   *
   * @return    ymin
   *
   */
  public double getYMin() {
    double ymin = Double.MAX_VALUE;
    for(int i = 0; i < datasets.size(); i++) {
      EDataset d = (EDataset) datasets.get(i);
      if(d.isMeasured()) {
        ymin = Math.min(ymin, d.getYMin());
      }
    }
    return ymin;
  }

  /**
   *
   *  Gets y world coordinate for the top of the panel.
   *
   *
   *
   * @return    ymax
   *
   */
  public double getYMax() {
    double ymax = -Double.MAX_VALUE;
    for(int i = 0; i < datasets.size(); i++) {
      EDataset d = (EDataset) datasets.get(i);
      if(d.isMeasured()) {
        ymax = Math.max(ymax, d.getYMax());
      }
    }
    return ymax;
  }

  /**
   *
   *  Gets a copy of the xpoints array.
   *
   *
   *
   * @param  datasetIndex  Description of Parameter
   *
   * @return               xpoints[]
   *
   */
  public double[] getXPoints(int datasetIndex) {
    checkDatasetIndex(datasetIndex);
    EDataset dataset = (EDataset) datasets.get(datasetIndex);
    return dataset.getXPoints();
  }

  /**
   *
   *  Gets a copy of the ypoints array.
   *
   *
   *
   * @param  datasetIndex  Description of Parameter
   *
   * @return               ypoints[]
   *
   */
  public double[] getYPoints(int datasetIndex) {
    checkDatasetIndex(datasetIndex);
    EDataset dataset = (EDataset) datasets.get(datasetIndex);
    return dataset.getYPoints();
  }

  /**
   *
   *  Gets the sorted flag.
   *
   *
   *
   * @param  datasetIndex  Description of Parameter
   *
   * @return               <code>true<\code> if the data is sorted
   *
   *
   *
   *
   *
   *
   *
   */
  public boolean isSorted(int datasetIndex) {
    checkDatasetIndex(datasetIndex);
    EDataset dataset = (EDataset) datasets.get(datasetIndex);
    return dataset.isSorted();
  }

  /**
   *
   *  Gets the data connected flag.
   *
   *
   *
   * @param  datasetIndex  Description of Parameter
   *
   * @return               <code>true<\code> if points are connected
   *
   *
   *
   *
   *
   *
   *
   */
  public boolean isConnected(int datasetIndex) {
    checkDatasetIndex(datasetIndex);
    EDataset dataset = (EDataset) datasets.get(datasetIndex);
    return dataset.isConnected();
  }

  /**
   *
   *  Gets the number of columns for rendering in a JTable.
   *
   *
   *
   * @return    the count
   *
   */
  public int getColumnCount() {
    int columnCount = 0;
    for(int i = 0; i < datasets.size(); i++) {
      EDataset d = (EDataset) datasets.get(i);
      columnCount += d.getColumnCount();
    }
    return columnCount;
  }

  /**
   *
   *  Gets the number of rows for rendering in a JTable.
   *
   *
   *
   * @return    the count
   *
   */
  public int getRowCount() {
    int rowCount = 0;
    for(int i = 0; i < datasets.size(); i++) {
      EDataset d = (EDataset) datasets.get(i);
      rowCount = Math.max(rowCount, d.getRowCount());
    }
    return rowCount;
  }

  /**
   *
   *  Gets the name of the colummn for rendering in a JTable
   *
   *
   *
   * @param tableColumnIndex
   *
   * @return              the name
   *
   */
  public String getColumnName(int tableColumnIndex) {
    if(datasets.size() == 0) {
      return null;
    }
    int totalColumns = 0;
    for(int i = 0; i < datasets.size(); i++) {
      EDataset tableModel  = (EDataset) datasets.get(i);
      int     columnCount = tableModel.getColumnCount();
      totalColumns += columnCount;
      if(totalColumns > tableColumnIndex) {
        int columnIndex = Math.abs(totalColumns - columnCount - tableColumnIndex);
        return tableModel.getColumnName(columnIndex);
      }
    }
    return null;
  }

  /**
   *
   *  Gets an x or y value for rendering in a JTable.
   *
   *
   *
   * @param  rowIndex
   *
   * @param tableColumnIndex
   *
   * @return              the datum
   *
   */
  public Object getValueAt(int rowIndex, int tableColumnIndex) {
    if(datasets.size() == 0) {
      return null;
    }
    int totalColumns = 0;
    for(int i = 0; i < datasets.size(); i++) {
      EDataset tableModel  = (EDataset) datasets.get(i);
      int     columnCount = tableModel.getColumnCount();
      totalColumns += columnCount;
      if(totalColumns > tableColumnIndex) {
        if(rowIndex >= tableModel.getRowCount()) {
          return null;
        }
        int columnIndex = Math.abs(totalColumns - columnCount - tableColumnIndex);
        return tableModel.getValueAt(rowIndex, columnIndex);
      }
    }
    return null;
  }

 /**
   *
   *  Sets cell at (rowIndex, columnIndex) equal to 'value'
   *
   *  added by Max Perkins and Dr. J. Hasbun
   *
   * @param  rowIndex, columnIndex
   *
   * @return              
   *
   */
  public void setValueAt(Object value, int rowIndex, int columnIndex) {
      for(int i = 0; i < datasets.size(); i++) {
          EDataset tableModel = (EDataset) datasets.get(i);
          tableModel.setValueAt(value, rowIndex, columnIndex);
      }
  }
 
  /**
   *
   *  Initializes the data table with n rows
   *
   *  added by Max Perkins and Dr. J. Hasbun
   *
   * @param  rows
   *
   * @return              
   *
   */
  public void initDataTable(int n) {      
    EDataset tableModel = null;
    
    // if n is greater than the row count (example is when you have
    // no rows -- initializing), add the rows
    if(n > getRowCount()) {        
        for(int i = 0; i < datasets.size(); i++) {
            tableModel = (EDataset) datasets.get(i);
            
            int rowsToAdd = n - getRowCount();       
            for(int j = 0; j < rowsToAdd; j++) {            
                tableModel.append(Double.NaN, Double.NaN);                            
            }
        }
    } 
    // else, save the current X and Y points, clear the
    // table, and put them back in. data is truncated    
    else if(n < getRowCount()) {
        double [] xpts;
        double [] ypts;
        
        for(int i = 0; i < datasets.size(); i++) {            
            tableModel = (EDataset) datasets.get(i);            
            xpts = tableModel.getXPoints();
            ypts = tableModel.getYPoints();            
        
            tableModel.clear();                       
            for(int j = 0; j < n; j++)
                tableModel.append(xpts[j], ypts[j]);
            xpts = null;
            ypts = null;
        }        
    }    
  }

  /**
   *
   *  Gets the type of object for JTable entry.
   *
   *
   *
   * @param  columnIndex
   *
   * @return              the class
   *
   */
  public Class getColumnClass(int columnIndex) {
    return Double.class;
  }

  /**
   *
   *  Append an (x,y) datum to the EDataset.
   *
   *
   *
   * @param  x
   *
   * @param  y
   *
   * @param  datasetIndex  Description of Parameter
   *
   */
  public void append(int datasetIndex, double x, double y) {
    checkDatasetIndex(datasetIndex);
    EDataset dataset = (EDataset) datasets.get(datasetIndex);
    dataset.append(x, y);
  }

  /**
   *
   *  Appends a data point and its uncertainty to the EDataset.
   *
   *
   *
   * @param datasetIndex
   * @param  x
   *
   * @param  y
   *
   * @param  delx
   *
   * @param  dely
   *
   */
  public void append(int datasetIndex, double x, double y, double delx, double dely) {
    checkDatasetIndex(datasetIndex);
    EDataset dataset = (EDataset) datasets.get(datasetIndex);
    dataset.append(x, y, delx, dely);
  }

  /**
   *
   *  Appends (x,y) arrays to the EDataset.
   *
   *
   *
   * @param  xpoints
   *
   * @param  ypoints
   *
   * @param  datasetIndex  Description of Parameter
   *
   */
  public void append(int datasetIndex, double[] xpoints, double[] ypoints) {
    checkDatasetIndex(datasetIndex);
    EDataset dataset = (EDataset) datasets.get(datasetIndex);
    dataset.append(xpoints, ypoints);
  }

  /**
   *
   *  Appends arrays of data points and uncertainties to the EDataset.
   *
   *
   *
   * @param datasetIndex
   * @param  xpoints
   *
   * @param  ypoints
   *
   * @param  delx
   *
   * @param  dely
   *
   */  
  
  public void append(int datasetIndex, double[] xpoints, double[] ypoints, double[] delx, double[] dely) {
    checkDatasetIndex(datasetIndex);
    EDataset dataset = (EDataset) datasets.get(datasetIndex);
    dataset.append(xpoints, ypoints, delx, dely);
  }

  /**
   *
   *  Draw this EDataset in the drawing panel.
   *
   *
   *
   * @param  drawingPanel
   *
   * @param  g
   *
   */
  public void draw(DrawingPanel drawingPanel, Graphics g) {
    for(int i = 0; i < datasets.size(); i++) {
      ((EDataset) (datasets.get(i))).draw(drawingPanel, g);
    }
  }

  /**
   *
   *  Clear all data from EDataset with the given datasetIndex.
   *
   *
   *
   * @param  datasetIndex  Description of Parameter
   *
   */
  public void clear(int datasetIndex) {
    checkDatasetIndex(datasetIndex);
    EDataset dataset = (EDataset) datasets.get(datasetIndex);
    dataset.clear();
  }

  /**
   * Clears all data from all Datasets.
   *
   * EDataset properties are preserved because only the data is cleared.
   */
  public void clear() {
    for(int i = 0; i < datasets.size(); i++) {
      ((EDataset) (datasets.get(i))).clear();
    }
  }

  /**
   * Removes all Datasets from the manager.
   *
   * New datasets will be created with default properties as needed.
   */
  public void removeDatasets() {
    clear();
    datasets.clear();
  }

  /**
   *  Gets a dataset with the given index.
   *
   * @param datasetIndex
   * @return    the index
   *
   */
  public EDataset getDataset(int datasetIndex) {
    checkDatasetIndex(datasetIndex);
    return (EDataset) datasets.get(datasetIndex);
  }

  /**
   * Gets a shallow clone of the dataset list.
   * @return cloned list
   */
  public ArrayList getDatasets() {
    return (ArrayList) datasets.clone();
  }

  /**
   *  Create a string representation of the data.
   * @return    the data
   */
  public String toString() {
    if(datasets.size() == 0) {
      return "No data in datasets.";
    }
    StringBuffer b = new StringBuffer();
    for(int i = 0; i < datasets.size(); i++) {
      b.append("EDataset ");
      b.append(i);
      b.append('\n');
      b.append(datasets.get(i).toString());
    }
    return b.toString();
  }

  /**
   *
   *  Sets the column names for all datasets when rendering this dataset in a JTable.
   *
   *
   *
   * @param  _xColumnName
   *
   * @param  _yColumnName
   *
   */
  public void setXYColumnNames(String _xColumnName, String _yColumnName) {
    xColumnName = _xColumnName;  // default names for future datasets
    yColumnName = _yColumnName;  // default names for future datasets
    // set the column names for current datasets
    for(int i = 0, size = datasets.size(); i < size; i++) {
      ((EDataset) (datasets.get(i))).setXYColumnNames(_xColumnName, _yColumnName);
    }
  }

  /**
   *
   *  Ensures capacity
   *
   *
   *
   * @param  datasetIndex
   *
   */
  protected void checkDatasetIndex(int datasetIndex) {
    while(datasetIndex >= datasets.size()) {
      EDataset d = null;
      if(datasetIndex < lineColors.length - 1) {
        d = new EDataset(markerColors[datasetIndex], lineColors[datasetIndex], connected);  // use specified colors
      } else {
        d = new EDataset(GUIUtils.randomColor(), GUIUtils.randomColor(), connected);
      }
      if(linked && (datasets.size() > 0)) {
        d.setXColumnVisible(false);                                                        // hide all x points in new datasets (except the 0th dataset)
      }
      d.setSorted(sorted);
      d.setXYColumnNames(xColumnName, yColumnName);
      d.setMarkerShape(markerShape);
      datasets.add(d);
    }
  }
}

