/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files
 * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify,
 * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
 * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

package edu.mit.csail.sdg.alloy4;

import java.io.File;
import java.io.FilenameFilter;
import java.util.Locale;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.FileDialog;
import java.awt.Frame;
import java.awt.GraphicsEnvironment;
import java.awt.HeadlessException;
import java.awt.event.KeyListener;
import java.awt.event.KeyEvent;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.filechooser.FileFilter;
import static javax.swing.JOptionPane.YES_NO_OPTION;
import static javax.swing.JOptionPane.QUESTION_MESSAGE;
import static javax.swing.JOptionPane.WARNING_MESSAGE;
import static javax.swing.JOptionPane.ERROR_MESSAGE;

/** Graphical dialog methods for asking the user some questions.
 *
 * <p><b>Thread Safety:</b> Can be called only by the AWT event thread.
 */

public final class OurDialog {

   /** The constructor is private, since this utility class never needs to be instantiated. */
   private OurDialog() { }

   /** Helper method for constructing an always-on-top modal dialog. */
   private static Object show(String title, int type, Object message, Object[] options, Object initialOption) {
      if (options == null) { options = new Object[]{"Ok"};  initialOption = "Ok"; }
      JOptionPane p = new JOptionPane(message, type, JOptionPane.DEFAULT_OPTION, null, options, initialOption);
      p.setInitialValue(initialOption);
      JDialog d = p.createDialog(null, title);
      p.selectInitialValue();
      d.setAlwaysOnTop(true);
      d.setVisible(true);
      d.dispose();
      return p.getValue();
   }

   /** Popup the given informative message, then ask the user to click Close to close it. */
   public static void showmsg(String title, Object... msg) {
      JButton dismiss = new JButton(Util.onMac() ? "Dismiss" : "Close");
      Object[] objs = new Object[msg.length + 1];
      System.arraycopy(msg, 0, objs, 0, msg.length);
      objs[objs.length - 1] = OurUtil.makeH(null, dismiss, null);
      JOptionPane about = new JOptionPane(objs, JOptionPane.PLAIN_MESSAGE, JOptionPane.DEFAULT_OPTION, null, new Object[]{});
      JDialog dialog = about.createDialog(null, title);
      dismiss.addActionListener(Runner.createDispose(dialog));
      dialog.setAlwaysOnTop(true);
      dialog.setVisible(true);
      dialog.dispose();
   }

   /** Popup the given error message. */
   public static void alert(Object message) {
      show("Error", ERROR_MESSAGE, message, null, null);
   }

   /** Popup the given error message, then terminate the program. */
   public static void fatal(Object message) {
      try { show("Fatal Error", ERROR_MESSAGE, message, null, null); } finally { System.exit(1); }
   }

   /** Ask if the user wishes to save the file, discard the file, or cancel the entire operation (default is cancel).
    * @return 'c' if cancel, 's' if save, 'd' if discard
    */
   public static char askSaveDiscardCancel(String description) {
      description = description + " has not been saved. Do you want to";
      Object ans = show(
            "Warning", WARNING_MESSAGE,
            new String[] {description, "cancel the operation, close the file without saving, or save it and close?"},
            new Object[] {"Save", "Don't Save", "Cancel"},
            "Cancel"
      );
      return (ans == "Save") ? 's' : (ans == "Don't Save" ? 'd' : 'c');
   }

   /** Ask if the user really wishes to overwrite the file (default is no).
    * @return true if the user wishes to overwrite the file, false if the user does not wish to overwrite the file.
    */
   public static boolean askOverwrite(String filename) {
      return "Overwrite" == show("Warning: file already exists", WARNING_MESSAGE,
            new String[] {"The file \"" + filename + "\"", "already exists. Do you wish to overwrite it?"},
            new String[] {"Overwrite", "Cancel"},
            "Cancel"
      );
   }

   /** This caches the result of the call to get all fonts. */
   private static String[] allFonts = null;

   /** Returns true if a font with that name exists on the system (comparison is case-insensitive). */
   public synchronized static boolean hasFont(String fontname) {
      if (allFonts == null) allFonts = GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames();
      for(int i = 0; i < allFonts.length; i++) if (fontname.compareToIgnoreCase(allFonts[i]) == 0) return true;
      return false;
   }

   /** Asks the user to choose a font; returns "" if the user cancels the request. */
   public synchronized static String askFont() {
      if (allFonts == null) allFonts = GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames();
      JComboBox jcombo = new OurCombobox(allFonts);
      Object ans = show("Font", JOptionPane.INFORMATION_MESSAGE,
            new Object[] {"Please choose the new font:", jcombo}, new Object[] {"Ok", "Cancel"}, "Cancel"
      );
      Object value = jcombo.getSelectedItem();
      if (ans=="Ok" && (value instanceof String)) return (String)value; else return "";
   }

   /** True if we should use AWT (instead of Swing) to display the OPEN and SAVE dialog. */
   private static boolean useAWT = Util.onMac();

   /** Use the platform's preferred file chooser to ask the user to select a file.
    * <br> Note: if it is a save operation, and the user didn't include an extension, then we'll add the extension.
    * @param isOpen - true means this is an Open operation; false means this is a Save operation
    * @param dir - the initial directory (or null if we want to use the default)
    * @param ext - the file extension (including "."; using lowercase letters; for example, ".als") or ""
    * @param description - the description for the given extension
    * @return null if the user didn't choose anything, otherwise it returns the selected file
    */
   public static File askFile (boolean isOpen, String dir, final String ext, final String description) {
      if (dir == null) dir = Util.getCurrentDirectory();
      if (!(new File(dir).isDirectory())) dir = System.getProperty("user.home");
      dir = Util.canon(dir);
      String ans;
      if (useAWT) {
         Frame parent = new Frame("Alloy File Dialog"); // this window is unused and not shown; needed by FileDialog and nothing more
         FileDialog open = new FileDialog(parent, isOpen ? "Open..." : "Save...");
         open.setAlwaysOnTop(true);
         open.setMode(isOpen ? FileDialog.LOAD : FileDialog.SAVE);
         open.setDirectory(dir);
         if (ext.length()>0) open.setFilenameFilter(new FilenameFilter() {
            public boolean accept(File dir, String name) { return name.toLowerCase(Locale.US).endsWith(ext); }
         });
         open.setVisible(true); // This method blocks until the user either chooses something or cancels the dialog.
         parent.dispose();
         if (open.getFile() == null) return null; else ans = open.getDirectory() + File.separatorChar + open.getFile();
      } else {
         try {
            JFileChooser open = new JFileChooser(dir) {
               private static final long serialVersionUID = 0;
               public JDialog createDialog(Component parent) throws HeadlessException {
                  JDialog dialog = super.createDialog(null);
                  dialog.setAlwaysOnTop(true);
                  return dialog;
               }
            };
            open.setDialogTitle(isOpen ? "Open..." : "Save...");
            open.setApproveButtonText(isOpen ? "Open" : "Save");
            open.setDialogType(isOpen ? JFileChooser.OPEN_DIALOG : JFileChooser.SAVE_DIALOG);
            if (ext.length()>0) open.setFileFilter(new FileFilter() {
               public boolean accept(File file) { return !file.isFile() || file.getPath().toLowerCase(Locale.US).endsWith(ext); }
               public String getDescription() { return description; }
            });
            if (open.showDialog(null, null) != JFileChooser.APPROVE_OPTION || open.getSelectedFile() == null) return null;
            ans = open.getSelectedFile().getPath();
         } catch(Exception ex) {
            // Some combination of Windows version and JDK version will trigger this failure.
            // In such a case, we'll fall back to using the "AWT" file open dialog
            useAWT = true;
            return askFile(isOpen, dir, ext, description);
         }
      }
      if (!isOpen) {
         int lastSlash = ans.lastIndexOf(File.separatorChar);
         int lastDot = (lastSlash>=0) ? ans.indexOf('.', lastSlash) : ans.indexOf('.');
         if (lastDot < 0) ans = ans + ext;
      }
      return new File(Util.canon(ans));
   }

   /** Display "msg" in a modal dialog window, and ask the user to choose "yes" versus "no" (default is "no"). */
   public static boolean yesno(Object msg, String yes, String no) {
      return show("Question", WARNING_MESSAGE, msg, new Object[]{yes, no}, no) == yes;
   }

   /** Display "msg" in a modal dialog window, and ask the user to choose "Yes" versus "No" (default is "no"). */
   public static boolean yesno(Object msg) { return yesno(msg, "Yes", "No"); }

   /** Display a modal dialog window containing the "objects"; returns true iff the user clicks Ok. */
   public static boolean getInput(String title, Object... objects) {
      // If there is a JTextField or a JTextArea here, then let the first JTextField or JTextArea be the initially focused widget
      Object main = "Ok";
      for(Object obj: objects) if (obj instanceof JTextField || obj instanceof JTextArea) { main = obj; break; }
      // Construct the dialog panel
      final JOptionPane pane = new JOptionPane(objects, QUESTION_MESSAGE, YES_NO_OPTION, null, new Object[]{"Ok", "Cancel"}, main);
      final JDialog dialog = pane.createDialog(null, title);
      // For each JTextField and JCheckBox, add a KeyListener that detects VK_ENTER and treat it as if the user clicked OK
      for(Object obj: objects) if (obj instanceof JTextField || obj instanceof JCheckBox) {
         ((JComponent)obj).addKeyListener(new KeyListener() {
            public void keyPressed(KeyEvent e)  { if (e.getKeyCode()==KeyEvent.VK_ENTER) { pane.setValue("Ok"); dialog.dispose(); } }
            public void keyReleased(KeyEvent e) { }
            public void keyTyped(KeyEvent e)    { }
         });
      }
      dialog.setAlwaysOnTop(true);
      dialog.setVisible(true); // This method blocks until the user either chooses something or cancels the dialog.
      dialog.dispose();
      return pane.getValue() == "Ok";
   }

   /** Display a simple non-modal window showing some text. */
   public static JFrame showtext(String title, String text) {
      JFrame window = new JFrame(title);
      JButton done = new JButton("Close");
      done.addActionListener(Runner.createDispose(window));
      JScrollPane scrollPane = OurUtil.scrollpane(OurUtil.textarea(text, 20, 60, false, false));
      window.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
      window.getContentPane().setLayout(new BorderLayout());
      window.getContentPane().add(scrollPane, BorderLayout.CENTER);
      window.getContentPane().add(done, BorderLayout.SOUTH);
      window.pack();
      window.setSize(500, 500);
      window.setLocationRelativeTo(null);
      window.setVisible(true);
      return window;
   }
}