/*
 * 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.frames;
import java.awt.Color;
import java.awt.Shape;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.KeyStroke;
import javax.swing.WindowConstants;
import org.opensourcephysics.controls.XML;
import org.opensourcephysics.controls.XMLControl;
import org.opensourcephysics.display.DataTable;
import org.opensourcephysics.display.DataTableFrame;
import org.opensourcephysics.display.Dataset;
import org.opensourcephysics.display.DatasetManager;
import org.opensourcephysics.display.DisplayRes;
import org.opensourcephysics.display.Drawable;
import org.opensourcephysics.display.DrawingFrame;
import org.opensourcephysics.display.PlottingPanel;
import org.opensourcephysics.display.TeXParser;
import org.opensourcephysics.display.dialogs.LogAxesInspector;
import org.opensourcephysics.display.dialogs.ScaleInspector;

/**
 * PlotFrame displays a plot using a dedicated DatasetManager.
 *
 * PlotFrame is a composite object that forwards methods to other objects such as a DatasetManager
 * or a DataTable.
 *
 * @author W. Christian
 * @version 1.0
 */
public class PlotFrame extends DrawingFrame {
  protected DatasetManager datasetManager = new DatasetManager();
  protected DataTable dataTable = new DataTable();
  protected DataTableFrame tableFrame;

  /**
   * Constructs the PlottingFrame with the given frame title and axes labels.
   *
   * @param xlabel String
   * @param ylabel String
   * @param frameTitle String
   */
  public PlotFrame(String xlabel, String ylabel, String frameTitle) {
    super(new PlottingPanel(xlabel, ylabel, null));
    setTitle(frameTitle);
    drawingPanel.addDrawable(datasetManager);
    datasetManager.setXPointsLinked(true);
    dataTable.add(datasetManager);
    setAnimated(true);
    setAutoclear(true);
    addMenuItems();
  }

  /**
   *  Sets the name of this component and the Dataset Manager.
   */
  public void setName(String name) {
    name = TeXParser.parseTeX(name);
    super.setName(name);
    datasetManager.setName(name);
  }

  /**
   * Adds Views menu items on the menu bar.
   */
  protected void addMenuItems() {
    JMenuBar menuBar = getJMenuBar();
    if(menuBar==null) {
      return;
    }
    JMenu helpMenu = this.removeMenu(DisplayRes.getString("DrawingFrame.Help_menu_item")); //$NON-NLS-1$
    JMenu menu = getMenu(DisplayRes.getString("DrawingFrame.Views_menu"));                 //$NON-NLS-1$
    if(menu==null) {
      menu = new JMenu(DisplayRes.getString("DrawingFrame.Views_menu")); //$NON-NLS-1$
      menuBar.add(menu);
      menuBar.validate();
    } else {                                                             // add a separator if tools already exists
      menu.addSeparator();
    }
    if(helpMenu!=null) {
      menuBar.add(helpMenu);
    }
    // add a scale option
    JMenuItem scaleItem = new JMenuItem(DisplayRes.getString("DrawingFrame.Scale_menu_item")); //$NON-NLS-1$
    ActionListener actionListener = new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        scale();
      }

    };
    scaleItem.addActionListener(actionListener);
    menu.add(scaleItem);
    // add a log scale option
    JMenuItem logItem = new JMenuItem(DisplayRes.getString("DrawingFrame.LogAxes_menu_item")); //$NON-NLS-1$
    actionListener = new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        logAxes();
      }

    };
    logItem.addActionListener(actionListener);
    menu.add(logItem);
    menu.addSeparator();
    // add a data table item to show the data table
    JMenuItem tableItem = new JMenuItem(DisplayRes.getString("DrawingFrame.DataTable_menu_item")); //$NON-NLS-1$
    tableItem.setAccelerator(KeyStroke.getKeyStroke('T', MENU_SHORTCUT_KEY_MASK));
    actionListener = new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        showDataTable(true);
      }

    };
    tableItem.addActionListener(actionListener);
    menu.add(tableItem);
    // add data table to the popup menu
    if((drawingPanel!=null)&&(drawingPanel.getPopupMenu()!=null)) {
      JMenuItem item = new JMenuItem(DisplayRes.getString("DrawingFrame.DataTable_menu_item")); //$NON-NLS-1$
      item.addActionListener(actionListener);
      drawingPanel.getPopupMenu().addSeparator();
      drawingPanel.getPopupMenu().add(item);
    }
  }

  /**
   * Sets the log scale property for the x axis.
   *
   * @param log boolean
   */
  public void setLogScaleX(boolean log) {
    if(drawingPanel instanceof PlottingPanel) {
      ((PlottingPanel) drawingPanel).setLogScaleX(log);
    }
  }

  /**
   * Sets the log scale property for the y axis.
   *
   * @param log boolean
   */
  public void setLogScaleY(boolean log) {
    if(drawingPanel instanceof PlottingPanel) {
      ((PlottingPanel) drawingPanel).setLogScaleY(log);
    }
  }

  protected void scale() {
    ScaleInspector plotInspector = new ScaleInspector(drawingPanel);
    plotInspector.setLocationRelativeTo(drawingPanel);
    plotInspector.updateDisplay();
    plotInspector.setVisible(true);
  }

  protected void logAxes() {
    if(!(drawingPanel instanceof PlottingPanel)) {
      return;
    }
    LogAxesInspector logAxesInspector = new LogAxesInspector((PlottingPanel) drawingPanel);
    logAxesInspector.setLocationRelativeTo(drawingPanel);
    logAxesInspector.updateDisplay();
    logAxesInspector.setVisible(true);
  }

  /**
   *  Appends an (x,y) datum to the Dataset with the given index.
   *
   * @param  datasetIndex  Description of Parameter
   * @param  x
   * @param  y
   */
  public void append(int datasetIndex, double x, double y) {
    datasetManager.append(datasetIndex, x, y);
    // may be too slow if lots of data is being added to the table
    if((tableFrame!=null)&&tableFrame.isShowing()) {
      dataTable.refreshTable();
    }
  }

  /**
   *  Appends a data point and its uncertainty to the Dataset.
   *
   * @param datasetIndex
   * @param  x
   * @param  y
   * @param  delx
   * @param  dely
   *
   */
  public void append(int datasetIndex, double x, double y, double delx, double dely) {
    datasetManager.append(datasetIndex, x, y, delx, dely);
    if((tableFrame!=null)&&tableFrame.isShowing()) {
      dataTable.refreshTable();
    }
  }

  /**
   * Appends (x,y) arrays to the Dataset.
   *
   * @param  datasetIndex  Description of Parameter
   * @param  xpoints
   * @param  ypoints
   */
  public void append(int datasetIndex, double[] xpoints, double[] ypoints) {
    datasetManager.append(datasetIndex, xpoints, ypoints);
    if((tableFrame!=null)&&tableFrame.isShowing()) {
      dataTable.refreshTable();
    }
  }

  /**
   *  Appends arrays of data points and uncertainties to the Dataset.
   *
   * @param datasetIndex
   * @param  xpoints
   * @param  ypoints
   * @param  delx
   * @param  dely
   */
  public void append(int datasetIndex, double[] xpoints, double[] ypoints, double[] delx, double[] dely) {
    datasetManager.append(datasetIndex, xpoints, ypoints, delx, dely);
    if((tableFrame!=null)&&tableFrame.isShowing()) {
      dataTable.refreshTable();
    }
  }

  /**
   *  Sets the connected flag for all datasets.
   *
   * @param connected true if connected; false otherwise
   */
  public void setConnected(boolean connected) {
    datasetManager.setConnected(connected);
  }

  /**
   * Sets the maximum number of allowed datapoints.
   *
   * Points will be dropped from the beginning of the dataset after the maximum number has been reached.
   *
   * @param maxPoints int
   */
  public void setMaximumPoints(int datasetIndex, int maxPoints) {
    datasetManager.getDataset(datasetIndex).setMaximumPoints(maxPoints);
  }

  /**
   * Sets a custom marker shape.
   *
   * @param datasetIndex int
   * @param marker Shape
   */
  public void setCustomMarker(int datasetIndex, Shape marker) {
    datasetManager.setCustomMarker(datasetIndex, marker);
  }

  /**
   *  Sets the data point marker shape. Shapes are: NO_MARKER, CIRCLE, SQUARE,
   *  AREA, PIXEL, BAR, POST
   *
   * @param  datasetIndex  The new markerShape value
   * @param  markerShape
   */
  public void setMarkerShape(int datasetIndex, int markerShape) {
    datasetManager.setMarkerShape(datasetIndex, markerShape);
  }

  /**
   *  Sets the half-width of the data point marker.
   *
   * @param  datasetIndex
   * @param  markerSize   in pixels
   */
  public void setMarkerSize(int datasetIndex, int markerSize) {
    datasetManager.setMarkerSize(datasetIndex, markerSize);
  }

  /**
   * Sets the data marker color for the given index.
   *
   * @param datasetIndex int
   * @param color Color
   */
  public void setMarkerColor(int datasetIndex, Color color) {
    datasetManager.setMarkerColor(datasetIndex, color);
  }

  /**
   * Sets the data line color for the given index.
   *
   * @param datasetIndex int
   * @param color Color
   */
  public void setLineColor(int datasetIndex, Color color) {
    datasetManager.setLineColor(datasetIndex, color);
  }

  /**
   * Sets the background color of this component.
   */
  public void setBackground(Color color) {
    super.setBackground(color);
    if(drawingPanel!=null) {
      drawingPanel.setBackground(color);
    }
  }

  /**
   * Sets the marker's fill and edge colors.
   *
   * The error bar color is set equal to the edge color.
   *
   * @param datasetIndex
   * @param fillColor
   * @param edgeColor
   */
  public void setMarkerColor(int datasetIndex, Color fillColor, Color edgeColor) {
    datasetManager.setMarkerColor(datasetIndex, fillColor, edgeColor);
  }

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

  /**
   * 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) {
    datasetManager.setXPointsLinked(linked);
  }

  /**
   *  Sets the column names and the dataset name.
   *
   * @param  datasetIndex  The new xYColumnNames value
   * @param  xColumnName
   * @param  yColumnName
   * @param datasetName
   */
  public void setXYColumnNames(int datasetIndex, String xColumnName, String yColumnName, String datasetName) {
    datasetManager.setXYColumnNames(datasetIndex, xColumnName, yColumnName, datasetName);
  }

  /**
   *  Sets the column names when rendering this dataset in a JTable.
   *
   * @param  datasetIndex  The new xYColumnNames value
   * @param  xColumnName
   * @param  yColumnName
   */
  public void setXYColumnNames(int datasetIndex, String xColumnName, String yColumnName) {
    datasetManager.setXYColumnNames(datasetIndex, xColumnName, yColumnName);
  }

  /**
   *  Sets the maximum number of fraction digits to display for cells that have
   *  type Double
   *
   * @param  maximumFractionDigits  - maximum number of fraction digits to display
   */
  public void setMaximumFractionDigits(int maximumFractionDigits) {
    dataTable.setMaximumFractionDigits(maximumFractionDigits);
  }

  /**
   *  Sets the maximum number of fraction digits to display in all data table columns with
   *  cthe given columnName.
   *
   * @param  maximumFractionDigits  - maximum number of fraction digits to display
   * @param  columnName             The new maximumFractionDigits value
   */
  public void setMaximumFractionDigits(String columnName, int maximumFractionDigits) {
    dataTable.setMaximumFractionDigits(columnName, maximumFractionDigits);
  }

  /**
   *  Sets the display row number flag. Table displays row number.
   *
   * @param  vis  <code>true<\code> if table display row number
   */
  public void setRowNumberVisible(boolean vis) {
    dataTable.setRowNumberVisible(vis);
  }

  /**
   * Clears drawable objects added by the user to this frame.
   */
  public void clearDrawables() {
    drawingPanel.clear(); // removes all drawables
    drawingPanel.addDrawable(datasetManager);
  }

  /**
   *  Gets a dataset with the given index.
   *
   * @param index
   * @return    Dataset
   *
   */
  public Dataset getDataset(int index) {
    return datasetManager.getDataset(index);
  }

  /**
   *  Gets the dataset manager.
   *
   * @return    DatasetManager
   *
   */
  public DatasetManager getDatasetManager() {
    return datasetManager;
  }

  /**
   * Gets Drawable objects added by the user to this frame.
   *
   * @return the list
   */
  public synchronized ArrayList<Drawable> getDrawables() {
    ArrayList<Drawable> list = super.getDrawables();
    list.remove(datasetManager);
    return list;
  }

  /**
   * Gets Drawable objects added by the user of an assignable type. The list contains
   * objects that are assignable from the class or interface.
   *
   * @param c the type of Drawable object
   *
   * @return the cloned list
   *
   * @see #getObjectOfClass(Class c)
   */
  public synchronized <T extends Drawable> ArrayList<T> getDrawables(Class<T> c) {
    ArrayList<T> list = super.getDrawables(c);
    list.remove(datasetManager);
    return list;
  }

  /**
   * Clears the data from all datasets.
   * Dataset properties are preserved because only the data is cleared.
   */
  public void clearData() {
    datasetManager.clear();
    dataTable.refreshTable();
    if(drawingPanel!=null) {
      drawingPanel.invalidateImage();
    }
  }

  /**
   * Removes all Datasets and removes all objects from the drawing panel except the dataset manager.
   * Datasets are removed from the manager and dataset properties are not preserved.
   */

  /**
   * Removes datasets from the manager. New datasets will be created with default properties as needed.
   */
  public void removeDatasets() {
    datasetManager.removeDatasets();
    dataTable.refreshTable();
    if(drawingPanel!=null) {
      drawingPanel.invalidateImage();
    }
  }

  /**
   * Shows or hides the data table.
   *
   * @param show boolean
   */
  public synchronized void showDataTable(boolean show) {
    if(show) {
      if((tableFrame==null)||!tableFrame.isDisplayable()) {
        tableFrame = new DataTableFrame(getTitle()+" "+DisplayRes.getString("TableFrame.TitleAddOn.Data"), dataTable); //$NON-NLS-1$ //$NON-NLS-2$
        tableFrame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
      }
      dataTable.refreshTable();
      tableFrame.setVisible(true);
    } else {
      tableFrame.setVisible(false);
      tableFrame.dispose();
      tableFrame = null;
    }
  }

  /**
   * Returns an XML.ObjectLoader to save and load data.
   *
   * @return the object loader
   */
  public static XML.ObjectLoader getLoader() {
    return new PlotFrameLoader();
  }

  static protected class PlotFrameLoader extends DrawingFrame.DrawingFrameLoader {
    /**
    * Creates a PlotFame.
    *
    * @param control XMLControl
    * @return Object
    */
    public Object createObject(XMLControl control) {
      PlotFrame frame = new PlotFrame("x", "y", DisplayRes.getString("PlotFrame.Title")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
      return frame;
    }

    /**
     * Loads the object with data from the control.
     *
     * @param control XMLControl
     * @param obj Object
     * @return Object
     */
    public Object loadObject(XMLControl control, Object obj) {
      super.loadObject(control, obj);
      PlotFrame frame = ((PlotFrame) obj);
      ArrayList<?> list = frame.getObjectOfClass(DatasetManager.class);
      if(list.size()>0) { // assume the first DatasetManager is the manager for this frame
        frame.datasetManager = (DatasetManager) list.get(0);
        frame.dataTable.clear();
        frame.dataTable.add(frame.datasetManager);
      }
      return obj;
    }

  }

}

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