/*
 * 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.Container;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.image.BufferStrategy;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.WindowConstants;
import org.opensourcephysics.tools.FontSizer;
import org.opensourcephysics.tools.ToolsRes;

/**
 * OSPFrame is the base class for Open Source Physics JFrames such as DrawingFrame and DataTableFrame.
 *
 * Copyright:    Copyright (c) 2002
 * @author       Wolfgang Christian
 * @version 1.1
 */
public class OSPFrame extends JFrame implements Hidable, AppFrame {
  /** Location of OSP icon. */
  static final String OSP_ICON_FILE = "/org/opensourcephysics/resources/controls/images/osp_icon.gif"; //$NON-NLS-1$
  // value is set in static block
  protected ArrayList<JButton> customButtons = new ArrayList<JButton>();                               // list of custom buttons, custom buttons are enabled when the animation is stopped
  static int topx = 10;
  static int topy = 100;

  /** Set to <I>true</I> if a simulation should automatically render this frame after every animation step. */
  protected boolean animated = false;

  /** Set to <I>true</I> if a simulation should automatically clear the data when it is initialized. */
  protected boolean autoclear = false;

  /** Set <I>true</I> if the Frame's defaultCloseOperation has been changed by Launcher. */
  private volatile boolean wishesToExit = false;

  /** The thread group that created this object. */
  public ThreadGroup constructorThreadGroup = Thread.currentThread().getThreadGroup();
  protected boolean keepHidden = false;
  protected BufferStrategy strategy;
  protected JPanel buttonPanel = new JPanel();
  protected Collection<JFrame> childFrames = new ArrayList<JFrame>();

  /**
   * Gets a file chooser that is the same for all OSPFrames.
   *
   * @deprecated  use <code>OSPRuntime.getChooser()<\code>.
   * @return the chooser
   */
  public static JFileChooser getChooser() {
    return OSPRuntime.getChooser();
  }

  /**
   * OSPFrame constructor with a title.
   * @param title
   */
  public OSPFrame(String title) {
    super(TeXParser.parseTeX(title));
    if(OSPRuntime.appletMode) {
      keepHidden = true;
    }
    buttonPanel.setVisible(false);
    setLocation(topx, topy);
    Dimension d = java.awt.Toolkit.getDefaultToolkit().getScreenSize();
    topx = Math.min(topx+20, (int) d.getWidth()-100);
    topy = Math.min(topy+20, (int) d.getHeight()-100);
    setDefaultCloseOperation(WindowConstants.HIDE_ON_CLOSE);
    // Changes font size to current level
    setFontLevel(FontSizer.getLevel());
    FontSizer.addPropertyChangeListener("level", new PropertyChangeListener() { //$NON-NLS-1$
      public void propertyChange(PropertyChangeEvent e) {
        int level = ((Integer) e.getNewValue()).intValue();
        setFontLevel(level);
      }

    });
    ToolsRes.addPropertyChangeListener("locale", new PropertyChangeListener() { //$NON-NLS-1$
      public void propertyChange(PropertyChangeEvent e) {
        refreshGUI();
      }

    });
    try {
      URL url = OSPFrame.class.getResource(OSP_ICON_FILE);
      ImageIcon icon = new ImageIcon(url);
      setIconImage(icon.getImage());
      //setIconImage(ResourceLoader.getImage(OSPRuntime.OSP_ICON_FILE));
    } catch(Exception ex) {
      // image not found  
    }
    addWindowListener(new WindowAdapter() {
      /**
       * Closes and disposes child windows when this window is about to be closed.
       */
      public void windowClosed(WindowEvent e) {
        disposeChildWindows();
      }

    });
  }

  void disposeChildWindows() {
    //if(OSPRuntime.applet!=null) return;  // applets do not have a main window so return
    Iterator<JFrame> it = childFrames.iterator();
    while(it.hasNext()) {
      JFrame f = it.next();
      if(!f.isDisplayable()) {
        continue; // frame has already been disposed
      }
      if(f instanceof OSPFrame) {
        ((OSPFrame) f).setKeepHidden(true);
      } else {
        f.setVisible(false);
      }
      f.dispose();
    }
    childFrames.clear();
  }

  /**
   * OSPFrame constructor.
   */
  public OSPFrame() {
    this("Open Source Physics"); //$NON-NLS-1$
  }

  /**
   * OSPFrame constructor with a new content pane.
   * @param contentPane
   */
  public OSPFrame(Container contentPane) {
    this();
    setContentPane(contentPane);
  }

  /**
   * Sets the title for this frame to the specified string after converting TeX math symbols to characters.
   * @param title the title to be displayed in the frame's border.
   *              A <code>null</code> value
   *              is treated as an empty string, "".
   * @see      #getTitle
   */
  public void setTitle(String title) {
    super.setTitle(TeXParser.parseTeX(title));
  }

  /**
   * Adds a child frame that depends on this frame.
   * Child frames are closed when this frame is closed.
   *
   * @param frame JFrame
   */
  public void addChildFrame(JFrame frame) {
    if((frame==null)||!frame.isDisplayable()) {
      return;
    }
    childFrames.add(frame);
  }

  /**
   * Clears all frames from the child frame list.
   */
  public void clearChildFrames() {
    childFrames.clear();
  }

  /**
   * Gets a copy of the ChildFrames collection.
   * @return Collection
   */
  public Collection<JFrame> getChildFrames() {
    return new ArrayList<JFrame>(childFrames);
  }

  /**
   * Gets the ICONIFIED flag for this frame.
   *
   * @return boolean true if frame is iconified; false otherwise
   */
  public boolean isIconified() {
    return(getExtendedState()&ICONIFIED)==1;
  }

  /**
   * Invalidates image buffers if a drawing panel is buffered.
   */
  public void invalidateImage() {
    // default does nothing
  }

  /**
   * Sets the font level.
   *
   * @param level the level
   */
  protected void setFontLevel(int level) {
    FontSizer.setFonts(getJMenuBar(), level);
    FontSizer.setFonts(getContentPane(), level);
  }

  /**
   * Reads the animated property.
   *
   * @return boolean
   */
  public boolean isAnimated() {
    return animated;
  }

  /**
   * Sets the animated property.
   *
   * @param animated
   */
  public void setAnimated(boolean animated) {
    this.animated = animated;
  }

  /**
   * Reads the animated property.
   *
   * @return boolean
   */
  public boolean isAutoclear() {
    return autoclear;
  }

  /**
   * Sets the autoclear property.
   *
   * @param autoclear
   */
  public void setAutoclear(boolean autoclear) {
    this.autoclear = autoclear;
  }

  /**
   * Adds a Display menu to the menu bar.
   *
   * The default method does nothing.
   * Override this method to create a menu item that is appropriate for the frame.
   */
  protected JMenu loadDisplayMenu() {
    return null;
  }

  /**
   * Adds a Tools menu to the menu bar.
   *
   * The default method does nothing.
   * Override this method to create a menu item that is appropriate for the frame.
   */
  protected JMenu loadToolsMenu() {
    return null;
  }

  /**
   * Clears data from drawing objects within this frame.
   *
   * The default method does nothing.
   * Override this method to select the object(s) and the data to be cleared.
   */
  public void clearData() {}

  /**
   * Clears data and repaints the drawing panel within this frame.
   *
   * The default method does nothing.
   * Override this method to clear and repaint objects that have data.
   */
  public void clearDataAndRepaint() {}

  public void setSize(int width, int height) {
    super.setSize(width, height);
    validate();
  }

  /**
   * Shows the frame on the screen if the keep hidden flag is false.
   *
   * @deprecated
   */
  public void show() {
    if(!keepHidden) {
      super.show();
    }
  }

  /**
   * Disposes all resources.
   */
  public void dispose() {
    keepHidden = true;
    this.clearData();
    disposeChildWindows();
    super.dispose();
  }

  /**
   * Shows or hides this component depending on the value of parameter
   * <code>b</code> and the <code>keepHidden</code> flag.
   *
   * OSP Applets often keep windows hidden.
   *
   * @param b
   */
  public void setVisible(boolean b) {
    if(!keepHidden) {
      boolean shouldRender = (!isVisible())&&animated; // render animated frames when made visible
      super.setVisible(b);
      if(shouldRender) {
        render();
      }
    }
  }

  /**
   * Sets the keepHidden flag.
   *
   * @param _keepHidden
   */
  public void setKeepHidden(boolean _keepHidden) {
    keepHidden = _keepHidden;
    if(keepHidden) {
      super.setVisible(false);
    }
  }

  /**
   * Reads the keepHidden flag.
   *
   */
  public boolean isKeepHidden() {
    return keepHidden;
  }

  /**
   * Gets the ThreadGroup that constructed this frame.
   *
   * @return the ThreadGroup
   */
  public ThreadGroup getConstructorThreadGroup() {
    return constructorThreadGroup;
  }

  /**
   * Creates a BufferStrategy based on the capabilites of the hardware.
   */
  public void createBufferStrategy() {
    createBufferStrategy(2);
    strategy = this.getBufferStrategy();
  }

  /**
   * Shows (repaints) the frame useing the current BufferStrategy.
   */
  public void bufferStrategyShow() {
    if((strategy)==null) {
      createBufferStrategy();
    }
    if(isIconified()||!isShowing()) {
      return;
    }
    Graphics g = strategy.getDrawGraphics();
    paintComponents(g);
    g.dispose();
    strategy.show();
  }

  /**
   * Renders the frame.  Subclass this method to render the contents of this frame in the calling thread.
   */
  public void render() {}

  /**
   * Gets a menu with the given name from the menu bar.  Returns null if menu item does not exist.
   *
   * @param menuName String
   * @return JMenu
   */
  public JMenu getMenu(String menuName) {
    JMenuBar menuBar = getJMenuBar();
    if(menuBar==null) {
      return null;
    }
    menuName = menuName.trim();
    JMenu menu = null;
    for(int i = 0; i<menuBar.getMenuCount(); i++) {
      JMenu next = menuBar.getMenu(i);
      if(next.getText().trim().equals(menuName)) {
        menu = next;
        break;
      }
    }
    return menu;
  }

  /**
   * Removes a menu with the given name from the menu bar and returns the removed item.
   * Returns null if menu item does not exist.
   *
   * @param menuName String
   * @return JMenu
   */
  public JMenu removeMenu(String menuName) {
    JMenuBar menuBar = getJMenuBar();
    if(menuBar==null) {
      return null;
    }
    menuName = menuName.trim();
    JMenu menu = null;
    for(int i = 0; i<menuBar.getMenuCount(); i++) {
      JMenu next = menuBar.getMenu(i);
      if(next.getText().trim().equals(menuName)) {
        menu = next;
        menuBar.remove(i);
        break;
      }
    }
    return menu;
  }

  /**
   * Removes a menu item with the given name from the menu bar and returns the removed item.
   * Returns null if menu item does not exist.
   *
   * @param menuName String
   * @return JMenu
   */
  public JMenuItem removeMenuItem(String menuName, String itemName) {
    JMenu menu = getMenu(menuName);
    if(menu==null) {
      return null;
    }
    itemName = itemName.trim();
    JMenuItem item = null;
    for(int i = 0; i<menu.getItemCount(); i++) {
      JMenuItem next = menu.getItem(i);
      if(next.getText().trim().equals(itemName)) {
        item = next;
        menu.remove(i);
        break;
      }
    }
    return item;
  }

  /*
   * Creates a menu in the menu bar from the given XML document.
   * @param xmlMenu name of the xml file with menu data
   */
  public void parseXMLMenu(String xmlMenu) {
    parseXMLMenu(xmlMenu, null);
  }

  public void parseXMLMenu(String xmlMenu, Class<?> type) {
    System.out.println("The parseXMLMenu method has been disabled to reduce the size OSP jar files."); //$NON-NLS-1$
  }

  /*
   * Creates a menu in the menu bar from the given XML document.
   * @param xmlMenu name of the xml file with menu data
   * @param type the class to load the menu, may be null
   *
   * public void parseXMLMenu(String xmlMenu, Class type) {
   *  XMLControl xml = null;
   *  if(type!=null) {
   *     org.opensourcephysics.tools.Resource res = org.opensourcephysics.tools.ResourceLoader.getResource(xmlMenu,
   *        type);
   *     if(res!=null) {
   *        xml = new XMLControlElement(res.getString());
   *     }
   *  }
   *  if(xml==null) {
   *     xml = new XMLControlElement(xmlMenu);
   *  }
   *  if(xml.failedToRead()) {
   *     OSPLog.info("Menu not found: "+xmlMenu);
   *  } else {
   *     type = xml.getObjectClass();
   *     if((type!=null)&&org.opensourcephysics.tools.LaunchNode.class.isAssignableFrom(type)) {
   *        // load the xml data into a launch node and add the menu item
   *        org.opensourcephysics.tools.LaunchNode node = (org.opensourcephysics.tools.LaunchNode) xml.loadObject(null);
   *        JMenuBar menuBar = getJMenuBar();
   *        if(menuBar==null) {
   *           return;
   *        }
   *        // get the menu name and create the menu if null
   *        String menuName = node.toString();
   *        JMenu menu = getMenu(menuName);
   *        if(menu==null) {
   *           menu = new JMenu(menuName);
   *           menuBar.add(menu);
   *           menuBar.validate();
   *        }
   *        // add the node item to the menu
   *        node.addMenuItemsTo(menu);
   *        OSPLog.finest("Menu loaded: "+xmlMenu);
   *     }
   *  }
   * }
   */

  /**
   * Refreshes the user interface in response to display changes such as Language.
   */
  protected void refreshGUI() {
    Iterator<JButton> it = customButtons.iterator();
    while(it.hasNext()) {
      TranslatableButton b = (TranslatableButton) it.next();
      b.refreshGUI();
    }
    buttonPanel.validate();
  }

  /**
   *  Adds a custom button to the control's frame.
   *
   * @param  methodName   the name of the method; the method has no parameters
   * @param  text         the button's text label
   * @param  toolTipText  the button's tool tip text
   * @param  target       the target for the method
   * @return              the custom button
   */
  public JButton addButton(String methodName, String text, String toolTipText, final Object target) {
    TranslatableButton b = new TranslatableButton(text, toolTipText, target);
    // changed by D Brown 2007-10-17
    if(OSPRuntime.getTranslator()!=null) {
      text = OSPRuntime.getTranslator().getProperty(target.getClass(), "custom_button."+text, text);                      //$NON-NLS-1$
      toolTipText = OSPRuntime.getTranslator().getProperty(target.getClass(), "custom_button."+toolTipText, toolTipText); //$NON-NLS-1$
    }
    b.setText(text);
    b.setToolTipText(toolTipText);
    Class<?>[] parameters = {};
    try {
      final java.lang.reflect.Method m = target.getClass().getMethod(methodName, parameters);
      b.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          Object[] args = {};
          try {
            m.invoke(target, args);
          } catch(IllegalAccessException iae) {
            System.err.println(iae);
          } catch(java.lang.reflect.InvocationTargetException ite) {
            System.err.println(ite);
          }
        }

      });
      buttonPanel.setVisible(true);
      buttonPanel.add(b);
      validate();
      pack();
    } catch(NoSuchMethodException nsme) {
      System.err.println("Error adding custom button "+text+". The method "+methodName+"() does not exist."); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
    }
    customButtons.add(b);
    return b;
  }

  class TranslatableButton extends JButton {
    String text, tip;
    Object target;

    /**
     * Constructor TranslatableButton
     * @param text
     * @param tip
     * @param target
     */
    public TranslatableButton(String text, String tip, Object target) {
      this.text = text;
      this.tip = tip;
      this.target = target;
    }

    void refreshGUI() {
      if(OSPRuntime.getTranslator()!=null) {
        setText(OSPRuntime.getTranslator().getProperty(target.getClass(), "custom_button."+text, text));      //$NON-NLS-1$
        setToolTipText(OSPRuntime.getTranslator().getProperty(target.getClass(), "custom_button."+tip, tip)); //$NON-NLS-1$
      }
    }

  }

  /**
   * Overrides JFrame method. This converts EXIT_ON_CLOSE to DISPOSE_ON_CLOSE
   * and sets the wishesToExit flag.
   *
   * @param  operation the operation
   */
  public void setDefaultCloseOperation(int operation) {
    if((operation==JFrame.EXIT_ON_CLOSE)&&OSPRuntime.launchingInSingleVM) {
      operation = WindowConstants.DISPOSE_ON_CLOSE;
      wishesToExit = true;
    }
    try {
      super.setDefaultCloseOperation(operation);
    } catch(Exception ex) {
      // cannot set the default close operation for java applets frames in Java 1.6
    }
  }

  /**
   * Returns true if this frame wishes to exit.
   * Launcher uses this to identify control frames.
   *
   * @return true if this frame wishes to exit
   */
  public boolean wishesToExit() {
    return wishesToExit;
  }

}

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