// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.gui.preferences.server;

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

import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.util.Arrays;

import javax.swing.AbstractAction;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.JTextComponent;

import org.openstreetmap.josm.Main;
import org.openstreetmap.josm.data.preferences.CollectionProperty;
import org.openstreetmap.josm.gui.help.HelpUtil;
import org.openstreetmap.josm.gui.widgets.AbstractTextComponentValidator;
import org.openstreetmap.josm.gui.widgets.HistoryComboBox;
import org.openstreetmap.josm.gui.widgets.SelectAllOnFocusGainedDecorator;
import org.openstreetmap.josm.io.OsmApi;
import org.openstreetmap.josm.io.OsmApiInitializationException;
import org.openstreetmap.josm.io.OsmTransferCanceledException;
import org.openstreetmap.josm.tools.ImageProvider;
import org.openstreetmap.josm.tools.Logging;
import org.openstreetmap.josm.tools.Utils;

/**
 * Component allowing input os OSM API URL.
 */
public class OsmApiUrlInputPanel extends JPanel {

    /**
     * OSM API URL property key.
     */
    public static final String API_URL_PROP = OsmApiUrlInputPanel.class.getName() + ".apiUrl";

    private final JLabel lblValid = new JLabel();
    private final JLabel lblApiUrl = new JLabel(tr("OSM Server URL:"));
    private final HistoryComboBox tfOsmServerUrl = new HistoryComboBox();
    private transient ApiUrlValidator valOsmServerUrl;
    private JButton btnTest;
    /** indicates whether to use the default OSM URL or not */
    private JCheckBox cbUseDefaultServerUrl;
    private final transient CollectionProperty SERVER_URL_HISTORY = new CollectionProperty("osm-server.url-history", Arrays.asList(
            "http://api06.dev.openstreetmap.org/api", "http://master.apis.dev.openstreetmap.org/api"));

    private transient ApiUrlPropagator propagator;

    /**
     * Constructs a new {@code OsmApiUrlInputPanel}.
     */
    public OsmApiUrlInputPanel() {
        build();
        HelpUtil.setHelpContext(this, HelpUtil.ht("/Preferences/Connection#ApiUrl"));
    }

    protected JComponent buildDefaultServerUrlPanel() {
        cbUseDefaultServerUrl = new JCheckBox(tr("<html>Use the default OSM server URL (<strong>{0}</strong>)</html>", OsmApi.DEFAULT_API_URL));
        cbUseDefaultServerUrl.addItemListener(new UseDefaultServerUrlChangeHandler());
        cbUseDefaultServerUrl.setFont(cbUseDefaultServerUrl.getFont().deriveFont(Font.PLAIN));
        return cbUseDefaultServerUrl;
    }

    protected final void build() {
        setLayout(new GridBagLayout());
        GridBagConstraints gc = new GridBagConstraints();

        // the checkbox for the default UL
        gc.fill = GridBagConstraints.HORIZONTAL;
        gc.anchor = GridBagConstraints.NORTHWEST;
        gc.weightx = 1.0;
        gc.insets = new Insets(0, 0, 0, 0);
        gc.gridwidth = 4;
        add(buildDefaultServerUrlPanel(), gc);


        // the input field for the URL
        gc.gridx = 0;
        gc.gridy = 1;
        gc.gridwidth = 1;
        gc.weightx = 0.0;
        gc.insets = new Insets(0, 0, 0, 3);
        add(lblApiUrl, gc);

        gc.gridx = 1;
        gc.weightx = 1.0;
        add(tfOsmServerUrl, gc);
        lblApiUrl.setLabelFor(tfOsmServerUrl);
        SelectAllOnFocusGainedDecorator.decorate(tfOsmServerUrl.getEditorComponent());
        valOsmServerUrl = new ApiUrlValidator(tfOsmServerUrl.getEditorComponent());
        valOsmServerUrl.validate();
        propagator = new ApiUrlPropagator();
        tfOsmServerUrl.addActionListener(propagator);
        tfOsmServerUrl.addFocusListener(propagator);

        gc.gridx = 2;
        gc.weightx = 0.0;
        add(lblValid, gc);

        gc.gridx = 3;
        gc.weightx = 0.0;
        ValidateApiUrlAction actTest = new ValidateApiUrlAction();
        tfOsmServerUrl.getEditorComponent().getDocument().addDocumentListener(actTest);
        btnTest = new JButton(actTest);
        add(btnTest, gc);
    }

    /**
     * Initializes the configuration panel with values from the preferences
     */
    public void initFromPreferences() {
        String url = OsmApi.getOsmApi().getServerUrl();
        tfOsmServerUrl.setPossibleItems(SERVER_URL_HISTORY.get());
        if (OsmApi.DEFAULT_API_URL.equals(url.trim())) {
            cbUseDefaultServerUrl.setSelected(true);
            propagator.propagate(OsmApi.DEFAULT_API_URL);
        } else {
            cbUseDefaultServerUrl.setSelected(false);
            tfOsmServerUrl.setText(url);
            propagator.propagate(url);
        }
    }

    /**
     * Saves the values to the preferences
     */
    public void saveToPreferences() {
        String oldUrl = OsmApi.getOsmApi().getServerUrl();
        String hmiUrl = getStrippedApiUrl();
        if (cbUseDefaultServerUrl.isSelected() || OsmApi.DEFAULT_API_URL.equals(hmiUrl)) {
            Main.pref.put("osm-server.url", null);
        } else {
            Main.pref.put("osm-server.url", hmiUrl);
            tfOsmServerUrl.addCurrentItemToHistory();
            SERVER_URL_HISTORY.put(tfOsmServerUrl.getHistory());
        }
        String newUrl = OsmApi.getOsmApi().getServerUrl();

        // When API URL changes, re-initialize API connection so we may adjust server-dependent settings.
        if (!oldUrl.equals(newUrl)) {
            try {
                OsmApi.getOsmApi().initialize(null);
            } catch (OsmTransferCanceledException | OsmApiInitializationException ex) {
                Logging.warn(ex);
            }
        }
    }

    /**
     * Returns the entered API URL, stripped of leading and trailing white characters.
     * @return the entered API URL, stripped of leading and trailing white characters.
     *         May be an empty string if nothing has been entered. In this case, it means the user wants to use {@link OsmApi#DEFAULT_API_URL}.
     * @see Utils#strip(String)
     * @since 6602
     */
    public final String getStrippedApiUrl() {
        return Utils.strip(tfOsmServerUrl.getText());
    }

    class ValidateApiUrlAction extends AbstractAction implements DocumentListener {
        private String lastTestedUrl;

        ValidateApiUrlAction() {
            putValue(NAME, tr("Validate"));
            putValue(SHORT_DESCRIPTION, tr("Test the API URL"));
            updateEnabledState();
        }

        @Override
        public void actionPerformed(ActionEvent arg0) {
            final String url = getStrippedApiUrl();
            final ApiUrlTestTask task = new ApiUrlTestTask(OsmApiUrlInputPanel.this, url);
            Main.worker.submit(task);
            Runnable r = () -> {
                if (task.isCanceled())
                    return;
                Runnable r1 = () -> {
                    if (task.isSuccess()) {
                        lblValid.setIcon(ImageProvider.get("dialogs", "valid"));
                        lblValid.setToolTipText(tr("The API URL is valid."));
                        lastTestedUrl = url;
                        updateEnabledState();
                    } else {
                        lblValid.setIcon(ImageProvider.get("warning-small"));
                        lblValid.setToolTipText(tr("Validation failed. The API URL seems to be invalid."));
                    }
                };
                SwingUtilities.invokeLater(r1);
            };
            Main.worker.submit(r);
        }

        protected final void updateEnabledState() {
            String url = getStrippedApiUrl();
            boolean enabled = !url.isEmpty() && !url.equals(lastTestedUrl);
            if (enabled) {
                lblValid.setIcon(null);
            }
            setEnabled(enabled);
        }

        @Override
        public void changedUpdate(DocumentEvent arg0) {
            updateEnabledState();
        }

        @Override
        public void insertUpdate(DocumentEvent arg0) {
            updateEnabledState();
        }

        @Override
        public void removeUpdate(DocumentEvent arg0) {
            updateEnabledState();
        }
    }

    /**
     * Enables or disables the API URL input.
     * @param enabled {@code true} to enable input, {@code false} otherwise
     */
    public void setApiUrlInputEnabled(boolean enabled) {
        lblApiUrl.setEnabled(enabled);
        tfOsmServerUrl.setEnabled(enabled);
        lblValid.setEnabled(enabled);
        btnTest.setEnabled(enabled);
    }

    private static class ApiUrlValidator extends AbstractTextComponentValidator {
        ApiUrlValidator(JTextComponent tc) {
            super(tc);
        }

        @Override
        public boolean isValid() {
            if (getComponent().getText().trim().isEmpty())
                return false;
            return Utils.isValidUrl(getComponent().getText().trim());
        }

        @Override
        public void validate() {
            if (getComponent().getText().trim().isEmpty()) {
                feedbackInvalid(tr("OSM API URL must not be empty. Please enter the OSM API URL."));
                return;
            }
            if (!isValid()) {
                feedbackInvalid(tr("The current value is not a valid URL"));
            } else {
                feedbackValid(tr("Please enter the OSM API URL."));
            }
        }
    }

    /**
     * Handles changes in the default URL
     */
    class UseDefaultServerUrlChangeHandler implements ItemListener {
        @Override
        public void itemStateChanged(ItemEvent e) {
            switch(e.getStateChange()) {
            case ItemEvent.SELECTED:
                setApiUrlInputEnabled(false);
                propagator.propagate(OsmApi.DEFAULT_API_URL);
                break;
            case ItemEvent.DESELECTED:
                setApiUrlInputEnabled(true);
                valOsmServerUrl.validate();
                tfOsmServerUrl.requestFocusInWindow();
                propagator.propagate();
                break;
            default: // Do nothing
            }
        }
    }

    class ApiUrlPropagator extends FocusAdapter implements ActionListener {
        protected void propagate() {
            propagate(getStrippedApiUrl());
        }

        protected void propagate(String url) {
            firePropertyChange(API_URL_PROP, null, url);
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            propagate();
        }

        @Override
        public void focusLost(FocusEvent arg0) {
            propagate();
        }
    }
}
