/*
 * 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.Component;
import java.awt.Container;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.JComponent;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JMenuBar;
import javax.swing.JOptionPane;
import javax.swing.WindowConstants;
import org.jibble.epsgraphics.EpsGraphics2D;
import org.opensourcephysics.controls.XML;
import org.opensourcephysics.media.gif.GIFEncoder;

public class GUIUtils {
  private GUIUtils() {} // prohibits instantiation

  /**
   * Converts TeX-like notation for Greek symbols to unicode characters.
   * @param input
   * @return
   * @deprecated  use TeXParser class.
   */
  public static String parseTeX(String input) {
    return TeXParser.parseTeX(input);
  }

  /**
   * Removes TeX subscripting from the input.
   * @param input
   * @return
   * @deprecated  use TeXParser class.
   */
  public static String removeSubscripting(String input) {
    return TeXParser.removeSubscripting(input);
  }

  /**
   * Finds an instance of a class in the given container.
   *
   * @param container Container
   * @param c Class
   * @return Component
   */
  public static Component findInstance(Container container, Class<?> c) {
    if((container==null)||c.isInstance(container)) {
      return container;
    }
    Component[] components = container.getComponents();
    for(int i = 0, n = components.length; i<n; i++) {
      if(c.isInstance(components[i])) {
        return components[i];
      }
      if(components[i] instanceof Container) {
        Component comp = findInstance((Container) components[i], c);
        if(c.isInstance(comp)) {
          return comp;
        }
      }
    }
    return null;
  }

  /**
   * Shows all drawing and table frames.
   *
   * Usually invoked when a model is initialized but may be invoked at other times
   * to show frames that have been closed.
   */
  public static void showDrawingAndTableFrames() {
    Frame[] frames = Frame.getFrames();
    for(int i = 0; i<frames.length; i++) {
      if(!frames[i].isDisplayable()) {
        continue;
      }
      if((frames[i].getName()!=null)&&(frames[i].getName().indexOf("Tool")>-1)) { //$NON-NLS-1$
        continue;
      }
      if(OSPFrame.class.isInstance(frames[i])) {
        if(DataTableFrame.class.isInstance(frames[i])) {
          ((DataTableFrame) frames[i]).refreshTable();
        }
        frames[i].setVisible(true);
        ((OSPFrame) frames[i]).invalidateImage();                                 // make sure buffers are up to date
        frames[i].repaint();                                                      // repaint if frame is already showing
        frames[i].toFront();
      }
    }
    if((OSPRuntime.applet!=null)) {
      OSPRuntime.applet.getRootPane().repaint();
    }
  }

  /**
   * Renders all OSPFrames whose animated property is true.
   *
   * Usually invoked by an animation thread after every animation step.
   */
  public static void renderAnimatedFrames() {
    Frame[] frames = Frame.getFrames();
    for(int i = 0; i<frames.length; i++) {
      if(!frames[i].isDisplayable()||!OSPFrame.class.isInstance(frames[i])) {
        continue;
      }
      if(((OSPFrame) frames[i]).isAnimated()) {
        ((OSPFrame) frames[i]).render();
      }
    }
    if((OSPRuntime.applet!=null)&&(OSPRuntime.applet instanceof Renderable)) {
      ((Renderable) OSPRuntime.applet).render();
    }
  }

  /**
   * Repaints all OSPFrames whose animated property is true.
   *
   * Usually invoked by a control's single-step button.
   */
  public static void repaintAnimatedFrames() {
    Frame[] frames = Frame.getFrames();
    for(int i = 0; i<frames.length; i++) {
      if(!frames[i].isDisplayable()||!OSPFrame.class.isInstance(frames[i])) {
        continue;
      }
      if(((OSPFrame) frames[i]).isAnimated()) {
        ((OSPFrame) frames[i]).invalidateImage(); // make sure buffers are up to date
        ((OSPFrame) frames[i]).repaint();
      }
    }
  }

  /**
   * Repaints all OSPFrames.
   */
  public static void repaintOSPFrames() {
    Frame[] frames = Frame.getFrames();
    for(int i = 0; i<frames.length; i++) {
      if(!frames[i].isVisible()||!frames[i].isDisplayable()||!OSPFrame.class.isInstance(frames[i])) {
        continue;
      }
      ((OSPFrame) frames[i]).repaint();
    }
  }

  /**
   * Clears the data in animated DrawingFrames and repaints the frame's content.
   *
   * All frames are cleared if <code> clearAll<\code> is true; otherwise only frames whose <code>autoClear<\code> flag is
   * true will be cleared.
   *
   * @param clearAll  clears all frames if true
   */
  public static void clearDrawingFrameData(boolean clearAll) {
    Frame[] frames = Frame.getFrames();
    for(int i = 0; i<frames.length; i++) {
      if(!frames[i].isDisplayable()) {
        continue;
      }
      if(OSPFrame.class.isInstance(frames[i])) {
        OSPFrame frame = ((OSPFrame) frames[i]);
        if(clearAll||frame.isAutoclear()) {
          frame.clearDataAndRepaint();
        }
      }
    }
  }

  /**
   * Sets the IgnorRepaint for all animated frames to the given value.
   * @param ignoreRepaint boolean
   */
  public static void setAnimatedFrameIgnoreRepaint(boolean ignoreRepaint) {
    Frame[] frames = Frame.getFrames();
    for(int i = 0; i<frames.length; i++) {
      if(!frames[i].isDisplayable()||!DrawingFrame.class.isInstance(frames[i])) {
        continue;
      }
      if(((DrawingFrame) frames[i]).isAnimated()) {
        DrawingPanel dp = ((DrawingFrame) frames[i]).getDrawingPanel();
        if(dp!=null) {
          dp.setIgnoreRepaint(ignoreRepaint);
        }
      }
    }
  }

  /**
   * Enables and disables the menu bars in DrawingFrames and DrawingFrame3D.
   *
   * Usually invoked when a model is initialized but may be invoked at other times.
   */
  public static void enableMenubars(boolean enable) {
    Frame[] frames = Frame.getFrames();
    for(int i = 0; i<frames.length; i++) {
      if(!frames[i].isDisplayable()) {
        continue;
      }
      if((frames[i].getName()!=null)&&(frames[i].getName().indexOf("Tool")>-1)) {       //$NON-NLS-1$
        continue;
      }
      Class<?> frame3d = null;
      try {
        frame3d = Class.forName("org.opensourcephysics.display3d.core.DrawingFrame3D"); //$NON-NLS-1$
      } catch(ClassNotFoundException ex) {}
      if(DrawingFrame.class.isInstance(frames[i])||((frame3d!=null)&&frame3d.isInstance(frames[i]))) {
        JMenuBar bar = ((JFrame) frames[i]).getJMenuBar();
        if(bar!=null) {
          for(int j = 0, n = bar.getMenuCount(); j<n; j++) {
            bar.getMenu(j).setEnabled(enable);
          }
        }
      }
    }
  }

  /**
   * Disposes all OSP frames except the given frame.
   *
   * Usually invoked when the control window is being closed.
   *
   * @param frame will not be disposed
   */
  public static void closeAndDisposeOSPFrames(Frame frame) {
    Frame[] frames = Frame.getFrames();
    for(int i = 0; i<frames.length; i++) {
      if(frames[i]==frame) {
        continue;
      }
      // if (frames[i] instanceof org.opensourcephysics.controls.Launcher.LauncherFrame)continue;
      if(OSPFrame.class.isInstance(frames[i])) {
        ((OSPFrame) frames[i]).setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
        ((OSPFrame) frames[i]).setVisible(false);
        ((OSPFrame) frames[i]).dispose();
      }
    }
  }

  /**
   *   Pops up a "Save File" file chooser dialog and takes user through process of saving a file.
   *
   *   @param    parent  the parent component of the dialog,
   *                     can be <code>null</code>;
   *                    see <code>showDialog</code> in class JFileChooser for details
   *   @return   the file or null if an error occurred:
   */
  public static File showSaveDialog(Component parent) {
    return showSaveDialog(parent, DisplayRes.getString("GUIUtils.Title.Save")); //$NON-NLS-1$
  }

  /**
   *   Pops up a "Save File" file chooser dialog and takes user through process of saving a file.
   *
   *   @param    parent  the parent component of the dialog,
   *                     can be <code>null</code>;
   *                    see <code>showDialog</code> in class JFileChooser for details
   *   @param    title
   *   @return   the file or null if an error occurred:
   */
  public static File showSaveDialog(Component parent, String title) {
    // JFileChooser fileChooser =  new JFileChooser();
    JFileChooser fileChooser = OSPRuntime.getChooser();
    // fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
    if(fileChooser==null) {
      return null;
    }
    String oldTitle = fileChooser.getDialogTitle();
    fileChooser.setDialogTitle(title);
    int result = fileChooser.showSaveDialog(parent);
    fileChooser.setDialogTitle(oldTitle);
    if(result!=JFileChooser.APPROVE_OPTION) {
      return null;
    }
    OSPRuntime.chooserDir = fileChooser.getCurrentDirectory().toString();
    File file = fileChooser.getSelectedFile();
    if(file.exists()) {
      int selected = JOptionPane.showConfirmDialog(parent, DisplayRes.getString("DrawingFrame.ReplaceExisting_message")+" "+file.getName()+DisplayRes.getString("DrawingFrame.QuestionMark"), //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
        DisplayRes.getString("DrawingFrame.ReplaceFile_option_title"), //$NON-NLS-1$
          JOptionPane.YES_NO_CANCEL_OPTION);
      if(selected!=JOptionPane.YES_OPTION) {
        return null;
      }
    }
    return file;
  }

  public static File showOpenDialog(Component parent) {
    JFileChooser fileChooser = OSPRuntime.getChooser(); // new JFileChooser();
    int result = fileChooser.showOpenDialog(parent);
    if(result!=JFileChooser.APPROVE_OPTION) {
      return null;
    }
    OSPRuntime.chooserDir = fileChooser.getCurrentDirectory().toString();
    File file = fileChooser.getSelectedFile();
    return file;
  }

  /**
   * Test the time to render a drawable component.
   * @param drawable
   */
  public static void timingTest(Drawable drawable) {
    DrawingPanel dp = new DrawingPanel();
    DrawingFrame df = new DrawingFrame(dp);
    df.setVisible(true);
    dp.addDrawable(drawable);
    dp.scale();
    dp.setPixelScale();
    Graphics g2 = dp.getGraphics();
    if(g2==null) {
      return;
    }
    long startTime = System.currentTimeMillis();
    drawable.draw(dp, g2);                                                     // first drawing often takes longer because of initialization
    System.out.print("first drawing="+(System.currentTimeMillis()-startTime)); //$NON-NLS-1$
    startTime = System.currentTimeMillis(); // reset the time
    for(int i = 0; i<5; i++) {
      drawable.draw(dp, g2);
    }
    System.out.println("  avg time/drawing="+((System.currentTimeMillis()-startTime)/5)); //$NON-NLS-1$
    g2.dispose();
  }

  /**
   *  Saves the contents of the specified component in the given file format. Note method requires Java 1.4
   *
   * @param  comp
   * @param  outputFile  the output file
   * @param  outputFileFormat output file format. One of eps, gif, jpeg, or png
   */
  public static void saveImage(JComponent comp, File outputFile, String outputFileFormat) throws IOException {
    FileOutputStream fos = null;
    try {
      fos = new FileOutputStream(outputFile);
      if(outputFileFormat.equals("eps")) {                                                     //$NON-NLS-1$
        EpsGraphics2D g = new EpsGraphics2D("", fos, 0, 0, comp.getWidth(), comp.getHeight()); //$NON-NLS-1$
        comp.paint(g);
        g.scale(0.24, 0.24);                                                                   // Set resolution to 300 dpi (0.24 = 72/300)
        // g.setColorDepth(EpsGraphics2D.BLACK_AND_WHITE); // Black & white
        g.close();
      } 
      else {
        BufferedImage bi = new BufferedImage(comp.getWidth(), comp.getHeight(), BufferedImage.TYPE_3BYTE_BGR);
        Graphics g = bi.getGraphics();
        comp.paint(g);
        g.dispose();
        if (outputFileFormat.equals("gif")) { //$NON-NLS-1$
          GIFEncoder encoder = new GIFEncoder(bi);
          encoder.Write(fos);
        }
        else {
        	ImageIO.write(bi, outputFileFormat, fos);
        }
        fos.close();
      }
    } catch (Exception e) {
			e.printStackTrace();
		} finally {
      if(fos!=null) {
        fos.close();
      }
    }
  }

  /**
   *  Saves the contents of the specified component in the given file format.  Pops
   *  open a save file dialog to allow the user to select the output file. Note method requires Java 1.4
   *
   * @param  component   comp the component
   * @param  ouputFileFormat output file format. One of eps, jpeg, or png
   * @param  parent  dialog parent
   */
  public static void saveImage(JComponent component, String ouputFileFormat, Component parent) {
    File outputFile = GUIUtils.showSaveDialog(component, DisplayRes.getString("GUIUtils.Title.SaveImage")); //$NON-NLS-1$
    if(outputFile==null) {
      return;
    }
    try {
      GUIUtils.saveImage(component, outputFile, ouputFileFormat);
    } catch(IOException ioe) {
      JOptionPane.showMessageDialog(parent, "An error occurred while saving the file "+outputFile.getName()+".'"); //$NON-NLS-1$ //$NON-NLS-2$
    }
  }
  
  public static void saveImageAs(JComponent component,String type, String title, String description, String[] extensions) {
	    JFileChooser chooser = OSPRuntime.createChooser(title, description, extensions);
	    String fileName = OSPRuntime.chooseFilename(chooser);
	    if(fileName==null) {
	      return;
	    }
	    File file = new File(fileName);
	    String extension = XML.getExtension(fileName);
	    if((extension==null)||".".equals(extension)) {                                                                      //$NON-NLS-1$
	      fileName = XML.stripExtension(fileName)+"."+extensions[0];                                                        //$NON-NLS-1$
	      file = new File(fileName);
	      if(file.exists()) {
	        int selected = JOptionPane.showConfirmDialog(null, DisplayRes.getString("DrawingFrame.ReplaceExisting_message") //$NON-NLS-1$
	          +" "+file.getName()+DisplayRes.getString("DrawingFrame.QuestionMark"), //$NON-NLS-1$ //$NON-NLS-2$
	            DisplayRes.getString("DrawingFrame.ReplaceFile_option_title"),       //$NON-NLS-1$
	              JOptionPane.YES_NO_CANCEL_OPTION);
	        if(selected!=JOptionPane.YES_OPTION) {
	          return;
	        }
	      }
	    }
	    try {
	      GUIUtils.saveImage(component, file, type);
	    } catch(IOException ex) {
	      ex.printStackTrace();
	    }
	  }

}

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