// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.plugins.opendata.core.gui;

import static org.openstreetmap.josm.tools.I18n.tr;

import java.awt.Component;
import java.awt.Dimension;
import java.awt.GridBagLayout;
import java.awt.event.ActionListener;

import javax.swing.BorderFactory;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JPanel;

import org.openstreetmap.josm.data.Bounds;
import org.openstreetmap.josm.data.coor.conversion.CoordinateFormatManager;
import org.openstreetmap.josm.data.coor.conversion.ICoordinateFormat;
import org.openstreetmap.josm.data.projection.Projection;
import org.openstreetmap.josm.gui.ExtendedDialog;
import org.openstreetmap.josm.gui.preferences.projection.ProjectionChoice;
import org.openstreetmap.josm.gui.preferences.projection.ProjectionPreference;
import org.openstreetmap.josm.gui.preferences.projection.SubPrefsOptions;
import org.openstreetmap.josm.tools.GBC;
import org.openstreetmap.josm.tools.Logging;

/**
 * Projection chooser, ugly copy-paste of ProjectionPreference.
 * FIXME: to be refactored in core for reuse by plugins.
 */
public class ProjectionChooser extends ExtendedDialog {

    /**
     * Combobox with all projections available
     */
    private final JComboBox<ProjectionChoice> projectionCombo = new JComboBox<>(
            ProjectionPreference.getProjectionChoices().toArray(new ProjectionChoice[0]));

    /**
     * This variable holds the JPanel with the projection's preferences. If the
     * selected projection does not implement this, it will be set to an empty
     * Panel.
     */
    private JPanel projSubPrefPanel;
    private final JPanel projSubPrefPanelWrapper = new JPanel(new GridBagLayout());

    private JLabel projectionCodeLabel;
    private Component projectionCodeGlue;
    private final JLabel projectionCode = new JLabel();
    private JLabel projectionNameLabel;
    private Component projectionNameGlue;
    private final JLabel projectionName = new JLabel();
    private final JLabel bounds = new JLabel();

    /**
     * This is the panel holding all projection preferences
     */
    private final JPanel projPanel = new JPanel(new GridBagLayout());

    /**
     * The GridBagConstraints for the Panel containing the ProjectionSubPrefs.
     * This is required twice in the code, creating it here keeps both occurrences
     * in sync
     */
    private static final GBC projSubPrefPanelGBC = GBC.std().fill(GBC.BOTH).weight(1.0, 1.0);

    public ProjectionChooser(Component parent) {
        this(parent, tr("Projection method"), new String[] {tr("OK"), tr("Cancel")});
    }

    protected ProjectionChooser(Component parent, String title, String[] buttonTexts) {
        super(parent, title, buttonTexts);
        setMinimumSize(new Dimension(600, 200));
        addGui();
    }

    public void addGui() {
        projectionCodeLabel = new JLabel(tr("Projection code"));
        projectionCodeGlue = GBC.glue(5, 0);
        projectionNameLabel = new JLabel(tr("Projection name"));
        projectionNameGlue = GBC.glue(5, 0);
        projPanel.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0));
        projPanel.setLayout(new GridBagLayout());
        projPanel.add(new JLabel(tr("Projection method")), GBC.std().insets(5, 5, 0, 5));
        projPanel.add(GBC.glue(5, 0), GBC.std().fill(GBC.HORIZONTAL));
        projPanel.add(projectionCombo, GBC.eop().fill(GBC.HORIZONTAL).insets(0, 5, 5, 5));
        projPanel.add(projectionCodeLabel, GBC.std().insets(25, 5, 0, 5));
        projPanel.add(projectionCodeGlue, GBC.std().fill(GBC.HORIZONTAL));
        projPanel.add(projectionCode, GBC.eop().fill(GBC.HORIZONTAL).insets(0, 5, 5, 5));
        projPanel.add(projectionNameLabel, GBC.std().insets(25, 5, 0, 5));
        projPanel.add(projectionNameGlue, GBC.std().fill(GBC.HORIZONTAL));
        projPanel.add(projectionName, GBC.eop().fill(GBC.HORIZONTAL).insets(0, 5, 5, 5));
        projPanel.add(new JLabel(tr("Bounds")), GBC.std().insets(25, 5, 0, 5));
        projPanel.add(GBC.glue(5, 0), GBC.std().fill(GBC.HORIZONTAL));
        projPanel.add(bounds, GBC.eop().fill(GBC.HORIZONTAL).insets(0, 5, 5, 5));
        projPanel.add(projSubPrefPanelWrapper, GBC.eol().fill(GBC.HORIZONTAL).insets(20, 5, 5, 5));

        selectedProjectionChanged((ProjectionChoice) projectionCombo.getSelectedItem());
        projectionCombo.addActionListener(e -> {
            ProjectionChoice pc = (ProjectionChoice) projectionCombo.getSelectedItem();
            selectedProjectionChanged(pc);
        });
        setContent(projPanel);
    }

    private void selectedProjectionChanged(final ProjectionChoice pc) {
        // Don't try to update if we're still starting up
        int size = projPanel.getComponentCount();
        if (size < 1)
            return;

        final ActionListener listener = e -> updateMeta(pc);

        // Replace old panel with new one
        projSubPrefPanelWrapper.removeAll();
        projSubPrefPanel = pc.getPreferencePanel(listener);
        projSubPrefPanelWrapper.add(projSubPrefPanel, projSubPrefPanelGBC);
        projPanel.revalidate();
        projSubPrefPanel.repaint();
        updateMeta(pc);
    }

    private void updateMeta(ProjectionChoice pc) {
        pc.setPreferences(pc.getPreferences(projSubPrefPanel));
        Projection proj = pc.getProjection();
        projectionCode.setText(proj.toCode());
        projectionName.setText(proj.toString());
        Bounds b = proj.getWorldBoundsLatLon();
        ICoordinateFormat cf = CoordinateFormatManager.getDefaultFormat();
        bounds.setText(cf.lonToString(b.getMin())+", "+cf.latToString(b.getMin())+" : "+
                       cf.lonToString(b.getMax())+", "+cf.latToString(b.getMax()));
        boolean showCode = true;
        boolean showName = false;
        if (pc instanceof SubPrefsOptions) {
            showCode = ((SubPrefsOptions) pc).showProjectionCode();
            showName = ((SubPrefsOptions) pc).showProjectionName();
        }
        projectionCodeLabel.setVisible(showCode);
        projectionCodeGlue.setVisible(showCode);
        projectionCode.setVisible(showCode);
        projectionNameLabel.setVisible(showName);
        projectionNameGlue.setVisible(showName);
        projectionName.setVisible(showName);
    }

    public Projection getProjection() {
        ProjectionChoice pc = (ProjectionChoice) projectionCombo.getSelectedItem();
        if (pc != null) {
            Logging.info("Chosen projection: "+pc+" ("+pc.getProjection()+")");
            return pc.getProjection();
        } else {
            return null;
        }
    }

    @Override
    public ProjectionChooser showDialog() {
        super.showDialog();
        return this;
    }
}
