/**
 *  Licensed under GPL. For more information, see
 *    http://jaxodraw.sourceforge.net/license.html
 *  or the LICENSE file in the jaxodraw distribution.
 */
package net.sf.jaxodraw.gui.panel;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;

import java.io.File;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import javax.swing.AbstractAction;
import javax.swing.BorderFactory;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JSeparator;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.border.CompoundBorder;
import javax.swing.event.ListDataEvent;
import javax.swing.event.ListDataListener;
import javax.swing.plaf.basic.BasicComboBoxRenderer;

import net.sf.jaxodraw.graph.JaxoGraph;
import net.sf.jaxodraw.gui.JaxoDialogs;
import net.sf.jaxodraw.gui.swing.JaxoTitledBorder;
import net.sf.jaxodraw.io.JaxoIO;
import net.sf.jaxodraw.io.JaxoPreview;
import net.sf.jaxodraw.plugin.JaxoExportPlugin;
import net.sf.jaxodraw.util.JaxoDictionary;
import net.sf.jaxodraw.util.JaxoLocalized;
import net.sf.jaxodraw.util.JaxoLooknFeel;


/** The parent class for exporting: brings up a dialog to choose
 * the export format and executes the corresponding actions.
 * @since 2.0
 */
public class JaxoExportPanel implements JaxoLocalized {
    private static final JaxoDictionary LANGUAGE = new JaxoDictionary(JaxoExportPanel.class);

    private JaxoGraph graph;
    private String currentTab = "";
    private final JDialog dialog;
    private final Component parent;
    private final JaxoPreview preview;
    private List<?> exports;
    private JComboBox chooseFileFormat;
    private JTextField chooseFileName;
    private JLabel warningLabel;
    private JaxoExportPlugin oldSelectedExport;
    private JComponent configurationPanelParent;
    private JButton previewButton;
    private JButton exportButton;
    private JButton cancelButton;
    private JLabel fileFormatLabel;
    private JLabel fileNameLabel;

    private boolean newPreviewFrame = true; // for watchFile mode

    /** Constructor.
     * @param parentc Component whose Window to use a parent for dialogs.
     * @param exportl List of exports to show, in the format of
     * {@link net.sf.jaxodraw.io.exports.JaxoExport#getBuiltInFormats()}.
     * @param p Preview to use for showing previews.
     */
    public JaxoExportPanel(final Component parentc, final List<?> exportl, final JaxoPreview p) {
        this.parent = parentc;
        this.exports = exportl;
        this.dialog = JaxoDialogs.newDialog(parent, "", false);

        preview = p.copy();
        preview.setParentComponent(dialog);
    }

    /**
     * Set the watchFile mode. Defaults to true.
     *
     * @param newFrame true if a new Frame should be opened for every preview.
     */
    public void setNewPreviewFrame(final boolean newFrame) {
        this.newPreviewFrame = newFrame;
    }

    /**
     * Updates the list of export formats.
     *
     * @param expts the list of export formats.
     */
    public void setExports(final List<?> expts) {
        this.exports = new ArrayList<Object>(expts);
    }

    /** {@inheritDoc} */
    public void updateLanguage() {
        if (previewButton == null) {
            // not initialized yet: ignore
            return;
        }

        previewButton.setText(LANGUAGE.value("/Preview"));
        previewButton.setToolTipText(LANGUAGE.value(
                "/Open_a_preview_window"));
        exportButton.setText(LANGUAGE.value("/Export"));
        cancelButton.setText(LANGUAGE.value("/Cancel"));
        fileFormatLabel.setText(LANGUAGE.label("/File_format"));
        fileNameLabel.setText(LANGUAGE.label("/File_name"));
        configurationPanelParent.setBorder(getExportPanelBorder());

        final int nofExports = chooseFileFormat.getItemCount();
        for (int i = 0; i < nofExports; i++) {
            final Object o = chooseFileFormat.getItemAt(i);
            if (o instanceof JaxoExportPlugin) {
                ((JaxoExportPlugin) o).updateLanguage();
            }
        }

        dialog.setTitle(JaxoDialogs.windowTitle(LANGUAGE, "/Export"));
    }

    /** Brings a dialog to choose the export format.
     * @param newGraph The graph to be exported.
     * @param tab The current tab.
     * @since 2.0.2
     */
    public final void export(final JaxoGraph newGraph, final String tab) {
        this.graph = newGraph;
        this.currentTab = tab;

        setupFileFormatComboBox();

        final JPanel optionsPanel = getOptionsPanel();
        updateSelectedExport();

        dialog.getContentPane().removeAll();

        dialog.getContentPane().add(optionsPanel);
        dialog.getContentPane().add(getButtonPanel(), BorderLayout.PAGE_END);

        updateLanguage();

        dialog.pack();
        dialog.setLocationRelativeTo(parent);
        dialog.setVisible(true);
    }

    /** Brings a dialog to choose the export format.
     * @param newGraph The graph to be exported.
     * @param d The current dimension of the canvas. This is not used!
     * @param tab The current tab.
     * @deprecated use {@link #export(JaxoGraph,String)} instead.
     */
    @Deprecated
    public final void export(final JaxoGraph newGraph, final Dimension d, final String tab) {
        export(newGraph, tab);
    }

    private JPanel getOptionsPanel() {
        chooseFileName = new JTextField(24);

        final JPanel optionsPanel = new JPanel(new GridBagLayout(), false);
        final GridBagConstraints labels = new GridBagConstraints();
        final GridBagConstraints components = new GridBagConstraints();
        labels.anchor = GridBagConstraints.LINE_END;
        components.fill = GridBagConstraints.BOTH;
        labels.gridx = 0;
        labels.gridy = 0;
        components.gridx = 1;
        components.gridy = 0;

        fileFormatLabel = new JLabel("", JLabel.TRAILING);
        optionsPanel.add(fileFormatLabel, labels);
        optionsPanel.add(chooseFileFormat, components);
        labels.gridy++;
        components.gridy++;

        final JPanel chooseFileNamePanel = JaxoDialogs.newLineBoxLayoutPanel();
        final JButton openFileChooser = new JButton("...");
        chooseFileNamePanel.add(chooseFileName);
        chooseFileNamePanel.add(openFileChooser);
        openFileChooser.addActionListener(new ActionListener() {
                public void actionPerformed(final ActionEvent e) {
                    chooseFileName();
                }
            });

        fileNameLabel = new JLabel("", JLabel.TRAILING);
        optionsPanel.add(fileNameLabel, labels);
        optionsPanel.add(chooseFileNamePanel, components);
        labels.gridy++;
        components.gridy++;

        configurationPanelParent =
            new JPanel(new BorderLayout(), false) {
                    private static final long serialVersionUID = 7526471155622L;
                    @Override
                    public Dimension getMinimumSize() {
                        return getPreferredSize();
                    }
                };

        components.gridx = 0;
        components.gridwidth = 2;
        components.weightx = 1;
        components.weighty = 1;
        optionsPanel.add(configurationPanelParent, components);
        components.gridx = 1;
        components.gridwidth = 1;
        components.weighty = 0;
        labels.gridy++;
        components.gridy++;

        warningLabel = new JLabel();
        components.gridx = 0;
        components.gridwidth = 2;
        optionsPanel.add(warningLabel, components);
        components.gridx = 1;
        components.gridwidth = 1;
        components.weighty = 0;
        labels.gridy++;
        components.gridy++;

        optionsPanel.setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8));

        optionsPanel.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke
            .getKeyStroke(KeyEvent.VK_ESCAPE, 0), "pressed");
        optionsPanel.getActionMap().put("pressed",
            new AbstractAction() {
                private static final long serialVersionUID = 75264711556227767L;
                public final void actionPerformed(final ActionEvent e) {
                    cancelButtonClicked();
                }
            });

        final String f = graph.getSaveFileName();

        if ("".equals(f.trim())) {
            chooseFileName.setText(JaxoIO.currentDirectoryString() + currentTab);
        } else {
            chooseFileName.setText(JaxoIO.baseName(f));
        }

        return optionsPanel;
    }

    private JPanel getButtonPanel() {
        previewButton = new JButton();
        previewButton.addActionListener(new ActionListener() {
                public final void actionPerformed(final ActionEvent e) {
                    preview();
                }
            });

        exportButton = new JButton();
        exportButton.addActionListener(new ActionListener() {
                public final void actionPerformed(final ActionEvent e) {
                    exportButtonClicked();
                }
            });

        cancelButton = new JButton();
        cancelButton.addActionListener(new ActionListener() {
                public final void actionPerformed(final ActionEvent e) {
                    cancelButtonClicked();
                }
            });

        final JPanel buttonPanel = new JPanel(false);
        buttonPanel.add(previewButton);
        buttonPanel.add(exportButton);
        buttonPanel.add(cancelButton);

        return buttonPanel;
    }

    private void setupFileFormatComboBox() {
        final DefaultComboBoxModel fileFormats =
            new DefaultComboBoxModel() {
                private static final long serialVersionUID = 7526471155622777L;
                // Do not allow separators to become selected, but
                // allow some keyboard navigation.
                // Separators are any non-JaxoExportPlugin objects, which must
                // be unique for this to work
                @Override
                public void setSelectedItem(final Object value) {
                    if (value instanceof JaxoExportPlugin) {
                        super.setSelectedItem(value);
                    } else {
                        int index = getIndexOf(value);
                        int selectedIndex = getIndexOf(getSelectedItem());

                        // These are recursive calls - could be flattened
                        // to a loop, but it is unlikely that there are multiple
                        // separators in a row
                        if ((index < selectedIndex) && (index > 0)) {
                            setSelectedItem(getElementAt(index - 1));
                        } else if ((index > selectedIndex)
                                && (index < (getSize() - 1))) {
                            setSelectedItem(getElementAt(index + 1));
                        }
                    }
                }
            };

        boolean addSeparator = false;

        for (final Iterator<?> i = exports.iterator(); i.hasNext();) {
            final Object o = i.next();

            if (o instanceof List<?>) {
                if (addSeparator) {
                    fileFormats.addElement(new Object());
                }

                for (final Iterator<?> j = ((List<?>) o).iterator(); j.hasNext();) {
                    fileFormats.addElement(j.next());
                }

                addSeparator = true;
            } else {
                if (addSeparator) {
                    fileFormats.addElement(new Object());
                }
                fileFormats.addElement(o);
                addSeparator = false;
            }
        }

        chooseFileFormat = new JComboBox(fileFormats);
        chooseFileFormat.setMaximumRowCount(20);
        chooseFileFormat.setSelectedIndex(0);
        chooseFileFormat.getModel().addListDataListener(new ListDataListener() {
                public void intervalAdded(final ListDataEvent e) {
                    // do nothing
                }

                public void intervalRemoved(final ListDataEvent e) {
                    // do nothing
                }

                public void contentsChanged(final ListDataEvent e) {
                    if (oldSelectedExport != chooseFileFormat.getSelectedItem()) {
                        updateSelectedExport();
                        oldSelectedExport =
                            (JaxoExportPlugin) chooseFileFormat.getSelectedItem();
                    }
                }
            });

        oldSelectedExport = (JaxoExportPlugin) chooseFileFormat.getSelectedItem();

        chooseFileFormat.setRenderer(new BasicComboBoxRenderer() {
                private static final long serialVersionUID = 7526471155622777L;
                private JSeparator separator = new JSeparator();
                {
                    JaxoLooknFeel.registerComponent(this);
                    JaxoLooknFeel.registerComponent(separator);
                }

                @Override
                public Component getListCellRendererComponent(final JList list,
                    final Object value, final int index, final boolean selected, final boolean focused) {
                    if (!(value instanceof JaxoExportPlugin)) {
                        return separator;
                    }

                    final JaxoExportPlugin e = (JaxoExportPlugin) value;
                    return super.getListCellRendererComponent(list,
                        e.getFormatName(), index, selected, focused);
                }
            });
    }

    private CompoundBorder getExportPanelBorder() {
        final String title = LANGUAGE.value("/Options");
        return BorderFactory.createCompoundBorder(BorderFactory
            .createCompoundBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3),
                new JaxoTitledBorder(title)),
            BorderFactory.createEmptyBorder(3, 3, 3, 3));
    }

    private void chooseFileName() {
        final String extension = getSelectedExport().getFileExtension();

        final String describe = getSelectedExport().getFileExtensionDescription();

        final JaxoChooseFile c = new JaxoChooseFile(parent);
        c.setApproveText(LANGUAGE.value(
                "fileChooser.approveText"));

        if (currentTab.length() > 0) {
            c.setDialogTitle(LANGUAGE.message(
                    "fileChooser.named%0DialogTitle", currentTab));
        } else {
            c.setDialogTitle(LANGUAGE.value("fileChooser.dialogTitle"));
        }

        final String fileName =
            c.chooseFile(new String[]{extension}, describe,
                chooseFileName.getText()).trim();

        if (fileName.length() > 0) {
            chooseFileName.setText(fileName);
            addExtension();
        }
    }

    private void updateSelectedExport() {
        cutExtension();
        addExtension();

        final JaxoExportPlugin e = getSelectedExport();

        configurationPanelParent.removeAll();
        final JComponent c = e.getConfigurationPanel();
        if (c == null) {
            configurationPanelParent.add(new JLabel(" "));
        } else {
            configurationPanelParent.add(c);
        }

        configurationPanelParent.revalidate();
        configurationPanelParent.repaint();

        if (dialog.isVisible()) {
            final Dimension d1 = dialog.getPreferredSize();
            final Dimension d2 = dialog.getSize();

            d2.width = Math.max(d2.width, d1.width);
            d2.height = Math.max(d2.height, d1.height);

            dialog.setSize(d2);
        }

        final String warning = e.getWarningForGraph();

        warningLabel.setText((warning == null) ? "<html><p>&nbsp;<p>&nbsp;"
                                               : warning);
    }

    private JaxoExportPlugin getSelectedExport() {
        final JaxoExportPlugin e = (JaxoExportPlugin) chooseFileFormat.getSelectedItem();

        if (e != null) {
            e.setParentComponent(dialog);
            e.setShortGraphName(currentTab);
            e.setGraph(graph);
        }

        return e;
    }

    /** Remove extension of old export from file name. */
    private void cutExtension() {
        final String extension = oldSelectedExport.getFileExtension();
        String fileName = chooseFileName.getText().trim();

        final int index = fileName.lastIndexOf('.');

        if ((index != -1) && fileName.substring(index + 1).equals(extension)) {
            fileName = fileName.substring(0, index);
            chooseFileName.setText(fileName);
        }
    }

    /** Add extension of current export to file name, unless already there. */
    private void addExtension() {
        final String extension = getSelectedExport().getFileExtension();
        final String fileName = chooseFileName.getText();

        final String newFileName = JaxoIO.withExtension(fileName.trim(), extension);

        if (!newFileName.equals(fileName)) {
            chooseFileName.setText(newFileName);
        }
    }

    private void exportButtonClicked() {
        final JaxoExportPlugin e = getSelectedExport();

        if (e == null) {
            dialog.dispose();
            return;
        }

        cutExtension();
        addExtension();

        String fileName = chooseFileName.getText().trim();

        if (chooseFileName.getText().length() == 0) {
            chooseFileName();

            fileName = chooseFileName.getText().trim();

            if (fileName.length() == 0) {
                return;
            }
        }

        final File f = new File(fileName);

        if (!JaxoIO.shouldOverwrite(dialog, f)) {
            return;
        }

        graph.setSaveFileName(JaxoIO.baseName(f.getAbsolutePath()));
        e.commitConfiguration();
        e.export(chooseFileName.getText());

        dialog.dispose();
    }

    private void cancelButtonClicked() {
        dialog.dispose();
    }

    /** Called for previewing the export result. */
    private void preview() {
        final JaxoExportPlugin e = getSelectedExport();

        if (e != null) {
            e.commitConfiguration();
            e.preview(preview.copy(), !newPreviewFrame);
        }
    }
}
