/**
 * 
 */
package icy.gui.dialog;

import icy.file.FileUtil;
import icy.file.ImageFileFormat;
import icy.file.Saver;
import icy.file.SequenceFileExporter;
import icy.gui.component.RangeComponent;
import icy.gui.util.GuiUtil;
import icy.main.Icy;
import icy.preferences.ApplicationPreferences;
import icy.preferences.XMLPreferences;
import icy.sequence.Sequence;
import icy.system.thread.ThreadUtil;
import icy.util.StringUtil;

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;

import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.JCheckBox;
import javax.swing.JFileChooser;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSpinner;
import javax.swing.SpinnerNumberModel;
import javax.swing.border.Border;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.filechooser.FileFilter;

import loci.formats.IFormatWriter;
import loci.formats.gui.ExtensionFileFilter;
import loci.plugins.out.Exporter;

/**
 * Saver dialog used to save resource or image from the {@link Exporter} or
 * {@link SequenceFileExporter}.
 * 
 * @author Stephane
 * @see Saver
 */
public class SaverDialog extends JFileChooser
{

    private static final String PREF_ID = "frame/imageSaver";

    private static final String ID_WIDTH = "width";
    private static final String ID_HEIGHT = "height";
    private static final String ID_PATH = "path";
    private static final String ID_MULTIPLEFILE = "multipleFile";
    private static final String ID_OVERWRITENAME = "overwriteName";
    private static final String ID_EXTENSION = "extension";

    // GUI
    final JCheckBox multipleFileCheck;
    final JPanel multiplesFilePanel;

    final JSpinner fpsSpinner;
    final JPanel fpsPanel;

    final JSpinner zSpinner;
    final JPanel zPanel;
    final JSpinner tSpinner;
    final JPanel tPanel;

    final RangeComponent zRange;
    final JPanel zRangePanel;
    final RangeComponent tRange;
    final JPanel tRangePanel;

    final JCheckBox overwriteNameCheck;
    final JPanel overwriteNamePanel;

    // internal
    final boolean singleZ;
    final boolean singleT;
    final boolean singleImage;

    /**
     * <b>Saver Dialog</b><br>
     * <br>
     * Display a dialog to select the destination file then save the specified sequence.<br>
     * <br>
     * To only get selected file from the dialog you must do:<br>
     * <code> ImageSaverDialog dialog = new ImageSaverDialog(sequence, 0, 0, false);</code><br>
     * <code> File selectedFile = dialog.getSelectedFile()</code><br>
     * <br>
     * To directly save specified sequence to the selected file just use:<br>
     * <code>new ImageSaverDialog(sequence, 0, 0, true);</code><br>
     * or<br>
     * <code>new ImageSaverDialog(sequence, 0, 0);</code>
     * 
     * @param sequence
     *        The {@link Sequence} we want to save.
     * @param defZ
     *        default Z slice to save if output format support only single slice image.
     * @param defT
     *        default T frame to save if output format support only single frame image.
     * @param autoSave
     *        If true the sequence is automatically saved to selected file.
     */
    public SaverDialog(Sequence sequence, int defZ, int defT, boolean autoSave)
    {
        super();

        final XMLPreferences preferences = ApplicationPreferences.getPreferences().node(PREF_ID);

        singleZ = (sequence.getSizeZ() == 1);
        singleT = (sequence.getSizeT() == 1);
        singleImage = singleZ && singleT;

        // can't use WindowsPositionSaver as JFileChooser is a fake JComponent
        // only dimension is stored
        setCurrentDirectory(new File(preferences.get(ID_PATH, "")));
        setPreferredSize(new Dimension(preferences.getInt(ID_WIDTH, 600), preferences.getInt(ID_HEIGHT, 400)));

        setDialogTitle("Save image file");

        // remove default filter
        removeChoosableFileFilter(getAcceptAllFileFilter());
        // then add our supported save format
        addChoosableFileFilter(ImageFileFormat.TIFF.getExtensionFileFilter());
        addChoosableFileFilter(ImageFileFormat.PNG.getExtensionFileFilter());
        addChoosableFileFilter(ImageFileFormat.JPG.getExtensionFileFilter());
        addChoosableFileFilter(ImageFileFormat.AVI.getExtensionFileFilter());

        // set last used file filter
        setFileFilter(getFileFilter(preferences.get(ID_EXTENSION, ImageFileFormat.TIFF.getDescription())));

        setMultiSelectionEnabled(false);
        // setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
        // so the filename information is not lost when changing directory
        setFileSelectionMode(JFileChooser.FILES_ONLY);

        String filename = FileUtil.getFileName(sequence.getFilename(), true);
        // empty filename --> use sequence name as default filename
        if (StringUtil.isEmpty(filename))
            filename = sequence.getName();
        if (!StringUtil.isEmpty(filename))
        {
            // test if filename has already a valid extension
            final String ext = getDialogExtension(filename);
            // remove file extension
            if (ext != null)
                FileUtil.setExtension(filename, "");
            // set dialog filename
            setSelectedFile(new File(filename));
        }

        multipleFileCheck = new JCheckBox();
        multipleFileCheck.setToolTipText("Save each sequence image in a separate file");
        multipleFileCheck.setSelected(preferences.getBoolean(ID_MULTIPLEFILE, false));
        multipleFileCheck.addChangeListener(new ChangeListener()
        {
            @Override
            public void stateChanged(ChangeEvent arg0)
            {
                updateSettingPanel();
            }
        });
        multiplesFilePanel = GuiUtil.createLineBoxPanel(new JLabel("Save as multiple files "),
                Box.createHorizontalGlue(), multipleFileCheck);

        fpsSpinner = new JSpinner(new SpinnerNumberModel(15, 1, 99, 1));
        fpsPanel = GuiUtil.createLineBoxPanel(GuiUtil.createFixedWidthLabel("Frame per second ", 100),
                Box.createHorizontalGlue(), fpsSpinner);

        zSpinner = new JSpinner(new SpinnerNumberModel((defZ == -1) ? 0 : defZ, 0, sequence.getSizeZ() - 1, 1));
        zPanel = GuiUtil.createLineBoxPanel(GuiUtil.createFixedWidthLabel("Z slice ", 100), Box.createHorizontalGlue(),
                zSpinner);
        tSpinner = new JSpinner(new SpinnerNumberModel((defT == -1) ? 0 : defT, 0, sequence.getSizeT() - 1, 1));
        tPanel = GuiUtil.createLineBoxPanel(GuiUtil.createFixedWidthLabel("T slice ", 100), Box.createHorizontalGlue(),
                tSpinner);

        zRange = new RangeComponent(0, sequence.getSizeZ() - 1, 1);
        zRange.setSliderVisible(false);
        zRangePanel = GuiUtil.createLineBoxPanel(new JLabel("Z range "), Box.createHorizontalGlue(), zRange);
        tRange = new RangeComponent(0, sequence.getSizeT() - 1, 1);
        tRange.setSliderVisible(false);
        tRangePanel = GuiUtil.createLineBoxPanel(new JLabel("T range "), Box.createHorizontalGlue(), tRange);

        overwriteNameCheck = new JCheckBox();
        overwriteNameCheck.setToolTipText("Overwrite metadata name with filename");
        overwriteNameCheck.setSelected(preferences.getBoolean(ID_OVERWRITENAME, true));
        overwriteNamePanel = GuiUtil.createLineBoxPanel(new JLabel("Overwrite metadata name "),
                Box.createHorizontalGlue(), overwriteNameCheck);

        final JPanel settingPanel = new JPanel();
        settingPanel.setBorder(BorderFactory.createTitledBorder((Border) null));
        settingPanel.setLayout(new BorderLayout());

        settingPanel.add(
                GuiUtil.createPageBoxPanel(multiplesFilePanel, fpsPanel, zPanel, tPanel, zRangePanel, tRangePanel),
                BorderLayout.NORTH);
        settingPanel.add(Box.createVerticalGlue(), BorderLayout.CENTER);
        settingPanel.add(overwriteNamePanel, BorderLayout.SOUTH);

        setAccessory(settingPanel);
        updateSettingPanel();

        // listen file filter change
        addPropertyChangeListener(JFileChooser.FILE_FILTER_CHANGED_PROPERTY, new PropertyChangeListener()
        {
            @Override
            public void propertyChange(PropertyChangeEvent evt)
            {
                updateSettingPanel();
            }
        });

        // display loader
        final int value = showSaveDialog(Icy.getMainInterface().getMainFrame());

        // action confirmed ?
        if (value == JFileChooser.APPROVE_OPTION)
        {
            final IFormatWriter writer = Saver.getWriter(getSelectedFileFormat());

            // Choose writer should be compatible
            if (Saver.isCompatible(writer, sequence.getColorModel()))
            {
                File file = getSelectedFile();
                final String outFilename = file.getAbsolutePath();

                // destination is a folder ?
                if (isFolderRequired())
                {
                    // test if filename has a image file extension
                    final String ext = getDialogExtension(outFilename);
                    // remove extension
                    if (ext != null)
                    {
                        file = new File(FileUtil.setExtension(outFilename, ""));
                        // set it so we can get it from getSelectedFile()
                        setSelectedFile(file);
                    }
                }
                else
                {
                    // test and add extension if needed
                    final ExtensionFileFilter extensionFilter = (ExtensionFileFilter) getFileFilter();

                    // add file filter extension to filename if not already present
                    if (!hasExtension(outFilename.toLowerCase(), extensionFilter))
                    {
                        file = new File(outFilename + "." + extensionFilter.getExtension());
                        // set it so we can get it from getSelectedFile()
                        setSelectedFile(file);
                    }
                }

                // save requested ?
                if (autoSave)
                {
                    // ask for confirmation as file already exists
                    if (!file.exists() || ConfirmDialog.confirm("Overwrite existing file(s) ?"))
                    {
                        if (file.exists())
                            FileUtil.delete(file, true);

                        // store current path
                        preferences.put(ID_PATH, getCurrentDirectory().getAbsolutePath());

                        // overwrite sequence name with filename
                        if (isOverwriteNameEnabled())
                            sequence.setName(FileUtil.getFileName(file.getAbsolutePath(), false));

                        final Sequence s = sequence;
                        final File f = file;

                        // do save in background process
                        ThreadUtil.bgRun(new Runnable()
                        {
                            @Override
                            public void run()
                            {
                                Saver.save(writer, s, f, getZMin(), getZMax(), getTMin(), getTMax(), getFps(),
                                        isSaveAsMultipleFilesEnabled(), true, true);
                            }
                        });
                    }
                }
            }
            else
            {
                // incompatible saver for this sequence
                new IncompatibleImageFormatDialog();
            }

            // store interface option
            preferences.putInt(ID_WIDTH, getWidth());
            preferences.putInt(ID_HEIGHT, getHeight());
            preferences.putBoolean(ID_MULTIPLEFILE, multipleFileCheck.isSelected());
            preferences.putBoolean(ID_OVERWRITENAME, overwriteNameCheck.isSelected());
            preferences.put(ID_EXTENSION, getFileFilter().getDescription());
        }
    }

    /**
     * <b>Saver Dialog</b><br>
     * <br>
     * Display a dialog to select the destination file then save the specified sequence.<br>
     * 
     * @param sequence
     *        The {@link Sequence} we want to save.
     * @param defZ
     *        default Z slice to save if output format support only single slice image.
     * @param defT
     *        default T frame to save if output format support only single frame image.
     */
    public SaverDialog(Sequence sequence, int defZ, int defT)
    {
        this(sequence, defZ, defT, true);
    }

    /**
     * <b>Saver Dialog</b><br>
     * <br>
     * Display a dialog to select the destination file then save the specified sequence.<br>
     * <br>
     * To only get selected file from the dialog you must do:<br>
     * <code> ImageSaverDialog dialog = new ImageSaverDialog(sequence, false);</code><br>
     * <code> File selectedFile = dialog.getSelectedFile()</code><br>
     * <br>
     * To directly save specified sequence to the selected file just use:<br>
     * <code>new ImageSaverDialog(sequence, true);</code><br>
     * or<br>
     * <code>new ImageSaverDialog(sequence);</code>
     * 
     * @param sequence
     *        The {@link Sequence} we want to save.
     * @param autoSave
     *        If true the sequence is automatically saved to selected file.
     */
    public SaverDialog(Sequence sequence, boolean autoSave)
    {
        this(sequence, 0, 0, autoSave);
    }

    /**
     * <b>Saver Dialog</b><br>
     * <br>
     * Display a dialog to select the destination file then save the specified sequence.
     * 
     * @param sequence
     *        The {@link Sequence} we want to save.
     */
    public SaverDialog(Sequence sequence)
    {
        this(sequence, 0, 0, true);
    }

    protected FileFilter getFileFilter(String description)
    {
        final FileFilter[] filters = getChoosableFileFilters();

        for (FileFilter filter : filters)
            if (StringUtil.equals(filter.getDescription(), description))
                return filter;

        // default one
        return ImageFileFormat.TIFF.getExtensionFileFilter();
    }

    private boolean hasExtension(String name, ExtensionFileFilter extensionFilter)
    {
        return getExtension(name, extensionFilter) != null;
    }

    private String getExtension(String name, ExtensionFileFilter extensionFilter)
    {
        for (String ext : extensionFilter.getExtensions())
            if (name.endsWith(ext.toLowerCase()))
                return ext;

        return null;
    }

    private String getDialogExtension(String name)
    {
        for (FileFilter filter : getChoosableFileFilters())
        {
            final String ext = getExtension(name, (ExtensionFileFilter) filter);

            if (ext != null)
                return ext;
        }

        return null;
    }

    public ImageFileFormat getSelectedFileFormat()
    {
        final FileFilter ff = getFileFilter();

        // default
        if ((ff == null) || !(ff instanceof ExtensionFileFilter))
            return ImageFileFormat.TIFF;

        return ImageFileFormat.getWriteFormat(((ExtensionFileFilter) ff).getExtension(), ImageFileFormat.TIFF);
    }

    /**
     * Returns <code>true</code> if we require a folder to save the sequence with selected options.
     */
    public boolean isFolderRequired()
    {
        final int sizeT = (getTMax() - getTMin()) + 1;
        final int sizeZ = (getZMax() - getZMin()) + 1;
        final int numImages = sizeT * sizeZ;

        return (numImages > 1) && isSaveAsMultipleFilesEnabled();
    }

    /**
     * Returns <code>true</code> if user chosen to save the sequence as multiple image files.
     */
    public boolean isSaveAsMultipleFilesEnabled()
    {
        return multiplesFilePanel.isVisible() && multipleFileCheck.isSelected();
    }

    /**
     * Returns <code>true</code> if user chosen to overwrite the sequence internal name by filename.
     */
    public boolean isOverwriteNameEnabled()
    {
        return overwriteNameCheck.isSelected();
    }

    /**
     * Returns the minimum Z of the selected Z range to save.
     */
    public int getZMin()
    {
        if (zPanel.isVisible())
            return ((Integer) zSpinner.getValue()).intValue();

        if (zRangePanel.isVisible())
            return (int) zRange.getLow();

        return 0;
    }

    /**
     * Returns the maximum Z of the selected Z range to save.
     */
    public int getZMax()
    {
        if (zPanel.isVisible())
            return ((Integer) zSpinner.getValue()).intValue();

        if (zRangePanel.isVisible())
            return (int) zRange.getHigh();

        return 0;
    }

    /**
     * Returns the minimum T of the selected T range to save.
     */
    public int getTMin()
    {
        if (tPanel.isVisible())
            return ((Integer) tSpinner.getValue()).intValue();

        if (tRangePanel.isVisible())
            return (int) tRange.getLow();

        return 0;
    }

    /**
     * Returns the maximum T of the selected T range to save.
     */
    public int getTMax()
    {
        if (tPanel.isVisible())
            return ((Integer) tSpinner.getValue()).intValue();

        if (tRangePanel.isVisible())
            return (int) tRange.getHigh();

        return 0;
    }

    /**
     * Returns the desired FPS (Frame Per Second, only for AVI file).
     */
    public int getFps()
    {
        if (fpsPanel.isVisible())
            return ((Integer) fpsSpinner.getValue()).intValue();

        return 1;
    }

    void updateSettingPanel()
    {
        final ImageFileFormat fileFormat = getSelectedFileFormat();

        final boolean tif = (fileFormat == ImageFileFormat.TIFF);
        final boolean jpg = (fileFormat == ImageFileFormat.JPG);
        final boolean avi = (fileFormat == ImageFileFormat.AVI);

        if (singleImage)
        {
            // single image, no need to display selection option
            multiplesFilePanel.setVisible(false);
            zPanel.setVisible(false);
            zRangePanel.setVisible(false);
            tPanel.setVisible(false);
            tRangePanel.setVisible(false);
        }
        else
        {
            multiplesFilePanel.setVisible(!avi);

            if (multiplesFilePanel.isVisible() && multipleFileCheck.isSelected())
            {
                // save as multiple file so display range option
                zPanel.setVisible(false);
                zRangePanel.setVisible(true && !singleZ);
                tPanel.setVisible(false);
                tRangePanel.setVisible(true && !singleT);
            }
            else
            {
                // save as single file so type give restriction here
                zPanel.setVisible(!tif && !singleZ);
                zRangePanel.setVisible(tif && !singleZ);
                tPanel.setVisible(jpg && !singleT);
                tRangePanel.setVisible(!jpg && !singleT);
            }
        }

        fpsPanel.setVisible(avi);
    }
}