/*
 * 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:
 * <https://www.compadre.org/osp/>
 */

package org.opensourcephysics.frames;
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.lang.reflect.Method;
import java.util.ArrayList;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.KeyStroke;
import javax.swing.WindowConstants;
import org.opensourcephysics.controls.OSPLog;
import org.opensourcephysics.controls.XML;
import org.opensourcephysics.controls.XMLControl;
import org.opensourcephysics.display.DataTable;
import org.opensourcephysics.display.DataTableFrame;
import org.opensourcephysics.display.DisplayRes;
import org.opensourcephysics.display.Drawable;
import org.opensourcephysics.display.DrawingFrame;
import org.opensourcephysics.display.Histogram;
import org.opensourcephysics.display.HistogramDataset;
import org.opensourcephysics.display.OSPFrame;
import org.opensourcephysics.display.OSPRuntime;
import org.opensourcephysics.display.PlottingPanel;
import org.opensourcephysics.tools.DataTool;
import org.opensourcephysics.tools.LocalJob;
import org.opensourcephysics.tools.Tool;

/**
 * HistogramFrame displays a histogram using a dedicated Histogram object.
 *
 * @author W. Christian
 * @version 1.0
 */
public class HistogramFrame extends DrawingFrame {
  protected Histogram histogram = new Histogram();
  protected DataTable dataTable = new DataTable();
  protected DataTableFrame tableFrame;
  DataTool tool;
  HistogramDataset dataset;
  JCheckBoxMenuItem logItem;

  /**
   * A DrawingFrame with a Histogram as its drawable.
   *
   * @param xlabel String
   * @param ylabel String
   * @param title String
   */
  public HistogramFrame(String xlabel, String ylabel, String title) {
    super(new PlottingPanel(xlabel, ylabel, null));
    // histogram.setDiscrete(false) ;
    drawingPanel.addDrawable(histogram);
    setTitle(title);
    dataTable.add(histogram);
    setAnimated(true);
    setAutoclear(true);
    addMenuItems();
  }

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

  /**
   * 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 views exists
      menu.addSeparator();
    }
    if(helpMenu!=null) {
      menuBar.add(helpMenu);
    }
    // add a menu 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 tableListener = new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        showDataTable(true);
      }

    };
    tableItem.addActionListener(tableListener);
    menu.add(tableItem);
    // log scale
    menu.addSeparator();
    logItem = new JCheckBoxMenuItem(DisplayRes.getString("HistogramFrame.MenuItem.LogScale"), false); //$NON-NLS-1$
    logItem.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        histogram.logScale = logItem.isSelected();
        drawingPanel.repaint();
      }

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

  /**
   * Adds launchable tools to the specified menu.
   *
   */
  protected JMenu loadToolsMenu() {
    JMenuBar menuBar = getJMenuBar();
    if(menuBar==null) {
      return null;
    }
    // add menu item
    JMenu toolsMenu = new JMenu(DisplayRes.getString("DrawingFrame.Tools_menu_title")); //$NON-NLS-1$
    menuBar.add(toolsMenu);
    // test dataset tool
    JMenuItem datasetItem = new JMenuItem(DisplayRes.getString("DrawingFrame.DatasetTool_menu_item")); //$NON-NLS-1$
    toolsMenu.add(datasetItem);
    /*
     * datasetItem.addActionListener(new ActionListener() {
     *  public void actionPerformed(ActionEvent e) {
     *      tool=DataTool.getTool();
     *      if(tool==null || !tool.isDisplayable()){
     *        tool = new DataTool(histogram, histogram.getName());
     *      }else{
     *        tool.addTab(histogram, histogram.getName());
     *      }
     *      tool.setVisible(true);
     *    }
     *    });
     */
    Class<?> datasetToolClass = null;
    if(OSPRuntime.loadDataTool) {
      try {
        datasetToolClass = Class.forName("org.opensourcephysics.tools.DataTool");      //$NON-NLS-1$
      } catch(Exception ex) {
        OSPRuntime.loadDataTool = false;
        datasetItem.setEnabled(false);
        OSPLog.finest("Cannot instantiate data analysis tool class:\n"+ex.toString()); //$NON-NLS-1$
      }
    }
    final Class<?> finalDatasetToolClass = datasetToolClass; // class must be final for action listener
    datasetItem.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        try {
          Method m = finalDatasetToolClass.getMethod("getTool", (Class[]) null); //$NON-NLS-1$
          Tool tool = (Tool) m.invoke(null, (Object[]) null);
          tool.send(new LocalJob(drawingPanel), reply);
          if(tool instanceof OSPFrame) {
            ((OSPFrame) tool).setKeepHidden(false);
          }
          ((JFrame) tool).setVisible(true);
        } catch(Exception ex) {}
      }

    });
    return toolsMenu;
  }

  /**
   *  Gets an array containing the bin centers.
   *
   * @return   the bins
   */
  public double[] getXPoints() {
    return histogram.getXPoints();
  }

  /**
   * Gets an array containing the values stored in the bins.
   *
   * @return    the values of the bins
   */
  public double[] getYPoints() {
    return histogram.getYPoints();
  }

  /**
   * Gets an array containing the log values of the values stored in the bins.
   *
   * @return    the values of the bins
   */
  public double[][] getLogPoints() {
    return histogram.getLogPoints();
  }

  /**
   * Gets a data array containing both the bin centers and the values within the bins.
   *
   * @return a double[index][2] array of data
   */
  public double[][] getPoints() {
    return histogram.getPoints();
  }

  /**
   * Removes drawable objects added by the user from this frame.
   */
  public void clearDrawables() {
    drawingPanel.clear();                // removes all drawables
    drawingPanel.addDrawable(histogram); // puts complex dataset back into panel
    showDataTable(false);
  }

  /**
   * 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(histogram);
    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(histogram);
    return list;
  }

  /**
   * Clears all the data stored.
   */
  public void clearData() {
    histogram.clear();
    dataTable.refreshTable();
    if(drawingPanel!=null) {
      drawingPanel.invalidateImage();
    }
  }

  /**
   * Appends a data point to the histogram.
   * @param v  data point
   */
  public void append(double v) {
    histogram.append(v);
    // this may be slow if the table is large
    if((tableFrame!=null)&&tableFrame.isShowing()) {
      dataTable.refreshTable();
    }
  }

  /**
   *  Append a value with number of occurences to the Histogram.
   *
   * @param  value
   * @param  numberOfOccurences
   */
  public void append(double value, double numberOfOccurences) {
    histogram.append(value, numberOfOccurences);
    if((tableFrame!=null)&&tableFrame.isShowing()) {
      dataTable.refreshTable();
    }
  }

  /**
   *  Appends an array of values with 1 occurence.
   *
   * @param  values
   */
  public void append(double[] values) {
    histogram.append(values);
    // this may be slow if the table is large
    if((tableFrame!=null)&&tableFrame.isShowing()) {
      dataTable.refreshTable();
    }
  }

  /**
   *  Sets the discrete flag in the histogram.
   *
   * @param  b  <code>true<\code> if bins are discrete, <code>false<\code> if bins are continuous.
   */
  public void setDiscrete(boolean b) {
    histogram.setDiscrete(b);
  }

  /**
   * Histogram uses logarithmic scale (true/false)
   */
  public void setLogScale(boolean b) {
    histogram.logScale = b;
    logItem.setSelected(b);
  }

  /**
   * Gets the histogram's log scale value.
   * @return boolean
   */
  public boolean isLogScale() {
    return histogram.logScale;
  }

  /**
   *  Sets the width of the bins.
   *
   * @param  binWidth
   */
  public void setBinWidth(double binWidth) {
    histogram.setBinWidth(binWidth);
  }

  /**
   * Gets the width of the bins.
   *
   * @param  binWidth
   */
  public double getBinWidth() {
    return histogram.getBinWidth();
  }

  /**
   * Sets the bin's fill and edge colors.  If the fill color is null the bin is not filled.
   *
   * @param fillColor
   * @param edgeColor
   */
  public void setBinColor(Color fillColor, Color edgeColor) {
    histogram.setBinColor(fillColor, edgeColor);
  }

  /**
   *  Sets the style for drawing this histogram. Options are DRAW_POINT, which
   *  draws a point at the top of the bin, and DRAW_BIN which draws the entire
   *  bin down to the x axis. Default is DRAW_BIN.
   *
   * @param  style
   */
  public void setBinStyle(short style) {
    histogram.setBinStyle(style);
  }

  /**
   * Sets the offset of the bins. Default is 0.
   * A value will be appended to bin n if
   * n*binWidth +binOffset <= value < (n+1)*binWidth +binOffset
   *
   * @param  binOffset
   */
  public void setBinOffset(double binOffset) {
    histogram.setBinOffset(binOffset);
  }

  /**
   * Normalizes the occurrences in this histogram to one (true/false).
   */
  public void setNormalizedToOne(boolean b) {
    histogram.setNormalizedToOne(b);
    histogram.adjustForWidth = b;
  }

  /**
   * Makes the x axis positive by default.
   */
  public void positiveX() {
    boolean b = drawingPanel.isAutoscaleX();
    drawingPanel.setPreferredMinMaxX(0, drawingPanel.getPreferredXMax());
    drawingPanel.setAutoscaleX(b);
  }

  /**
   * 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();
      dataTable.sort(0);
      tableFrame.setVisible(true);
    } else {
      tableFrame.setVisible(false);
      tableFrame.dispose();
      tableFrame = null;
    }
  }

  public static XML.ObjectLoader getLoader() {
    return new HistogramFrameLoader();
  }

  static protected class HistogramFrameLoader extends DrawingFrame.DrawingFrameLoader {
    /**
     * Creates a PlotFame.
     *
     * @param control XMLControl
     * @return Object
     */
    public Object createObject(XMLControl control) {
      HistogramFrame frame = new HistogramFrame("x", "y", //$NON-NLS-1$ //$NON-NLS-2$
        DisplayRes.getString("HistogramFrame.Title"));    //$NON-NLS-1$
      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);
      HistogramFrame frame = ((HistogramFrame) obj);
      ArrayList<?> list = frame.getObjectOfClass(Histogram.class);
      if(list.size()>0) { // assume the first Histogram belongs to this frame
        frame.histogram = (Histogram) list.get(0);
        frame.histogram.clear();
        frame.dataTable.add(frame.histogram);
      }
      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) 2019  The Open Source Physics project
 *                     https://www.compadre.org/osp
 */
