
package uk.co.wingpath.gui;

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.io.*;

/**
* <p>This class provides a file-chooser dialog that contains a standard
* {@link JFileChooser} and a {@link StatusBar}.
* <p>The dialog requires the user to confirm over-writing of an existing file.
* The user is not required to confirm overwriting if the file is the same
* as the file selected when the dialog was last used.
* <p>The dialog also checks whether a file to be loaded actually exists.
* If it does not exist, an error message is displayed and the dialog is not
* closed.
* <p>Permissions are also checked, and the user is not permitted to select
* a file for which s/he does not have the appropriate access permission.
* <p>A JComponent may be provided to be included in the dialog.
*/
public class WFileChooser
{
    private final JFileChooser fileChooser;
    private final WDialog dialog;
    private final WWindow parent;
    private final StatusBar statusBar;
    private File selectedFile;
    private File lastSelectedFile;
    private boolean saving;
    private boolean appending;
    private final Action overwriteAction;

    /**
    * Constructs a {@code WFileChooser} with the specified parent.
    * @param parent parent frame or dialog.
    */
    public WFileChooser (WWindow parent)
    {
        this (parent, null);
    }

    /**
    * Constructs a {@code WFileChooser} with the specified parent and
    * component to be included in the dialog.
    * The component is included using the {@link JFileChooser#setAccessory}
    * method.
    * @param parent parent frame or dialog.
    * @param accessory component to be included in the dialog.
    */
    public WFileChooser (WWindow parent, JComponent accessory)
    {
        this.parent = parent;
        selectedFile = null;
        lastSelectedFile = null;
        saving = false;
        appending = false;
        statusBar = new StatusBar ("fc");
        dialog = WDialog.createModal (parent);
        Container contentPane = dialog.getContentPane ();

        fileChooser = new JFileChooser ()
            {
                // Override a couple of methods so we can clear the status
                // bar if the user moves to a different file/directory.

                @Override
                public void setCurrentDirectory (File dir)
                {
                    statusBar.clear ();
                    super.setCurrentDirectory (dir);
                }

                @Override
                public void setSelectedFile (File file)
                {
                    statusBar.clear ();
                    super.setSelectedFile (file);
                }
            };
        fileChooser.setFileSelectionMode (JFileChooser.FILES_ONLY);
        fileChooser.setCurrentDirectory (
            new File (System.getProperty ("user.dir")));
        if (accessory != null)
            fileChooser.setAccessory (accessory);

        contentPane.add (fileChooser, BorderLayout.CENTER);
        contentPane.add (statusBar, BorderLayout.SOUTH);

        dialog.pack ();

        overwriteAction =
            new AbstractAction ("Overwrite")
            {
                public void actionPerformed (ActionEvent e)
                {
                    selectedFile = fileChooser.getSelectedFile ();
                    dialog.setVisible (false);
                }
            };
        overwriteAction.putValue (AbstractAction.SHORT_DESCRIPTION,
            "Overwrite the file");
        overwriteAction.putValue (AbstractAction.MNEMONIC_KEY,
            KeyEvent.VK_O);

        fileChooser.addActionListener (
            new ActionListener ()
            {
                public void actionPerformed (ActionEvent e)
                {
                    String command = e.getActionCommand ();
                    if (command.equals (JFileChooser.APPROVE_SELECTION))
                    {
                        File file = fileChooser.getSelectedFile ();
                        if (file == null)
                        {
                            // Defensive - don't know if it can happen.
                            return;
                        }
                        if (!checkPermission (file))
                            return;
                        selectedFile = file;
                        dialog.setVisible (false);
                    }
                    else if (command.equals (JFileChooser.CANCEL_SELECTION))
                    {
                        assert selectedFile == null;
                        dialog.setVisible (false);
                    }
                }
            });

        Gui.addShortCut (dialog.getRootPane (), "ESCAPE_KEY",
            KeyEvent.VK_ESCAPE, 0,
            new AbstractAction ()
            {
                public void actionPerformed (ActionEvent e)
                {
                    assert selectedFile == null;
                    dialog.setVisible (false);
                }
            });
    }

    private boolean checkPermission (File file)
    {
        statusBar.clear ();
        if (saving || appending)
        {
            if (file.exists ())
            {
                if (!file.canWrite ())
                {
                    statusBar.showError (
                        "Can't write to file: " + "Permission denied");
                    return false;
                }
            }
            else
            {
                File parent =
                    file.getAbsoluteFile ().getParentFile ();
                if (parent != null && !parent.canWrite ())
                {
                    statusBar.showError (
                        "Can't create file: " + "Permission denied");
                    return false;
                }
            }
        }
        if (saving)
        {
            if (file.exists () &&
                !file.equals (lastSelectedFile))
            {
                statusBar.showWarning ("File exists", overwriteAction);
                return false;
            }
        }
        if (!saving && !appending)
        {
            if (!file.exists ())
            {
                statusBar.showError ("File does not exist");
                return false;
            }
            if (!file.canRead ())
            {
                statusBar.showError ("Can't read file: " + "Permission denied");
                return false;
            }
        }

        return true;
    }

    /**
    * Sets the selected file.
    * If the file's parent directory is not the current directory, changes
    * the current directory to be the file's parent directory.
    * @param file the file to be selected.
    */
    public void setSelectedFile (File file)
    {
        fileChooser.setSelectedFile (file);
    }

    /**
    * Gets the selected file.
    * @return the selected file.
    */
    public File getSelectedFile ()
    {
        return fileChooser.getSelectedFile ();
    }

    /**
    * Sets the current directory.
    * @param dir the current directory to point to.
    */
    public void setCurrentDirectory (File dir)
    {
        fileChooser.setCurrentDirectory (dir);
    }

    private File chooseFile (String title, String label, String tooltip)
    {
        dialog.setTitle (title);
        fileChooser.setApproveButtonText (label);
        fileChooser.setApproveButtonToolTipText (tooltip);
        statusBar.clear ();
        lastSelectedFile = fileChooser.getSelectedFile ();
        selectedFile = null;
        dialog.setVisible (true);
        return selectedFile;
    }

    /**
    * Displays the dialog so that the user can select a file to load from.
    * @param title text to be used as the dialog title.
    * @param label text to be used as the label of the approve button.
    * @param tooltip text to be used as the tooltip of the approve button.
    * @return the file selected by the user, or {@code null} if the user
    * cancels the selection.
    */
    public File chooseLoadFile (String title, String label, String tooltip)
    {
        saving = false;
        appending = false;
        return chooseFile (title, label, tooltip);
    }

    /**
    * Displays the dialog so that the user can select a file to save to.
    * @param title text to be used as the dialog title.
    * @param label text to be used as the label of the approve button.
    * @param tooltip text to be used as the tooltip of the approve button.
    * @return the file selected by the user, or {@code null} if the user
    * cancels the selection.
    */
    public File chooseSaveFile (String title, String label, String tooltip)
    {
        saving = true;
        appending = false;
        return chooseFile (title, label, tooltip);
    }

    /**
    * Displays the dialog so that the user can select a file to append to.
    * @param title text to be used as the dialog title.
    * @param label text to be used as the label of the approve button.
    * @param tooltip text to be used as the tooltip of the approve button.
    * @return the file selected by the user, or {@code null} if the user
    * cancels the selection.
    */
    public File chooseAppendFile (String title, String label, String tooltip)
    {
        saving = false;
        appending = true;
        return chooseFile (title, label, tooltip);
    }
}

