/**
 *  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.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.plugin.JaxoImportPlugin;
import net.sf.jaxodraw.util.JaxoDictionary;
import net.sf.jaxodraw.util.JaxoLocalized;
import net.sf.jaxodraw.util.JaxoLooknFeel;


/**
 * Brings up a dialog to choose an import format and a file to import.
 *
 * @since 2.0
 */
public class JaxoImportPanel implements JaxoLocalized {
    private static final JaxoDictionary LANGUAGE = new JaxoDictionary(JaxoImportPanel.class);

    private final JDialog dialog;
    private final Component parent;
    private List<JaxoImportPlugin> imports;
    private JComboBox chooseFileFormat;
    private JTextField chooseFileName;
    private JaxoImportPlugin oldSelectedImport;
    private JComponent configurationPanelParent;
    private JButton importButton;
    private JButton cancelButton;
    private JLabel fileFormatLabel;
    private JLabel fileNameLabel;

    private JaxoGraph importedGraph;

    /**
     * Constructor.
     *
     * @param parentc Component whose Window to use a parent for dialogs.
     * @param imp List of imports to show, in the format of
     * {@link net.sf.jaxodraw.io.imports.JaxoImport#getBuiltInFormats()}.
     */
    public JaxoImportPanel(final Component parentc, final List<JaxoImportPlugin> imp) {
        this.parent = parentc;
        this.imports = new ArrayList<JaxoImportPlugin>(imp);
        this.dialog = JaxoDialogs.newDialog(parent, "", /*needs to be modal!*/ true);
    }

    /**
     * Updates the list of import formats.
     *
     * @param impts the list of import formats.
     */
    public void setImports(final List<JaxoImportPlugin> impts) {
        this.imports = new ArrayList<JaxoImportPlugin>(impts);
    }

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

        importButton.setText(LANGUAGE.value("/Import"));
        cancelButton.setText(LANGUAGE.value("/Cancel"));
        fileFormatLabel.setText(LANGUAGE.label("/File_format"));
        fileNameLabel.setText(LANGUAGE.label("/File_name"));
        configurationPanelParent.setBorder(getImportPanelBorder());

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

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

    /**
     * Shows the import panel.
     */
    public void show() {
        this.importedGraph = null;

        setupFileFormatComboBox();

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

        dialog.getContentPane().removeAll();

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

        updateLanguage();

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

    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++;

        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();
                }
            });

        return optionsPanel;
    }

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

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

        final JPanel buttonPanel = new JPanel(false);
        buttonPanel.add(importButton);
        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-JaxoImportPlugin objects, which must
                // be unique for this to work
                @Override
                public void setSelectedItem(final Object value) {
                    if (value instanceof JaxoImportPlugin) {
                        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 = imports.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 (oldSelectedImport != chooseFileFormat.getSelectedItem()) {
                        updateSelectedImport();
                        oldSelectedImport =
                            (JaxoImportPlugin) chooseFileFormat.getSelectedItem();
                    }
                }
            });

        oldSelectedImport = (JaxoImportPlugin) 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 JaxoImportPlugin)) {
                        return separator;
                    }

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

    private CompoundBorder getImportPanelBorder() {
        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 = getSelectedImport().getFileExtension();

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

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

        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 updateSelectedImport() {
        cutExtension();
        addExtension();

        configurationPanelParent.add(getSelectedImport().getConfigurationPanel());

        configurationPanelParent.removeAll();
        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);
        }
    }

    private JaxoImportPlugin getSelectedImport() {
        final JaxoImportPlugin e = (JaxoImportPlugin) chooseFileFormat.getSelectedItem();

        if (e != null) {
            e.setParentComponent(dialog);
        }

        return e;
    }

    /** Remove extension of old import from file name. */
    private void cutExtension() {
        final String extension = oldSelectedImport.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 import to file name, unless already there. */
    private void addExtension() {
        final String extension = getSelectedImport().getFileExtension();
        final String fileName = chooseFileName.getText();

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

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

    private void importButtonClicked() {
        final JaxoImportPlugin e = getSelectedImport();

        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;
            }
        }

        e.commitConfiguration();
        this.importedGraph = e.importGraph(fileName);

        dialog.dispose();
    }

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

    /**
     * Returns the graph theat was imported by this panel.
     *
     * @return the graph or null if none was imported.
     */
    public JaxoGraph getImportedGraph() {
        return importedGraph;
    }
}
