// 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.Component;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;

import javax.swing.JOptionPane;
import javax.xml.parsers.ParserConfigurationException;

import org.openstreetmap.josm.Main;
import org.openstreetmap.josm.gui.HelpAwareOptionPane;
import org.openstreetmap.josm.gui.PleaseWaitRunnable;
import org.openstreetmap.josm.gui.help.HelpUtil;
import org.openstreetmap.josm.io.Capabilities;
import org.openstreetmap.josm.io.OsmTransferException;
import org.openstreetmap.josm.tools.CheckParameterUtil;
import org.openstreetmap.josm.tools.Utils;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

/**
 * This is an asynchronous task for testing whether an URL points to an OSM API server.
 * It tries to retrieve capabilities from the given URL. If it succeeds, the method
 * {@link #isSuccess()} replies true, otherwise false.
 * @since 2745
 */
public class ApiUrlTestTask extends PleaseWaitRunnable {

    private final String url;
    private boolean canceled;
    private boolean success;
    private final Component parent;
    private HttpURLConnection connection;

    /**
     * Constructs a new {@code ApiUrlTestTask}.
     *
     * @param parent the parent component relative to which the {@link PleaseWaitRunnable}-Dialog is displayed
     * @param url the url. Must not be null.
     * @throws IllegalArgumentException if url is null.
     */
    public ApiUrlTestTask(Component parent, String url) {
        super(parent, tr("Testing OSM API URL ''{0}''", url), false /* don't ignore exceptions */);
        CheckParameterUtil.ensureParameterNotNull(url, "url");
        this.parent = parent;
        this.url = url;
    }

    protected void alertInvalidUrl(String url) {
        HelpAwareOptionPane.showMessageDialogInEDT(
                parent,
                tr("<html>"
                        + "''{0}'' is not a valid OSM API URL.<br>"
                        + "Please check the spelling and validate again."
                        + "</html>",
                        url
                ),
                tr("Invalid API URL"),
                JOptionPane.ERROR_MESSAGE,
                HelpUtil.ht("/Preferences/Connection#InvalidAPIUrl")
        );
    }

    protected void alertInvalidCapabilitiesUrl(String url) {
        HelpAwareOptionPane.showMessageDialogInEDT(
                parent,
                tr("<html>"
                        + "Failed to build URL ''{0}'' for validating the OSM API server.<br>"
                        + "Please check the spelling of ''{1}'' and validate again."
                        +"</html>",
                        url,
                        getNormalizedApiUrl()
                ),
                tr("Invalid API URL"),
                JOptionPane.ERROR_MESSAGE,
                HelpUtil.ht("/Preferences/Connection#InvalidAPIGetChangesetsUrl")
        );
    }

    protected void alertConnectionFailed() {
        HelpAwareOptionPane.showMessageDialogInEDT(
                parent,
                tr("<html>"
                        + "Failed to connect to the URL ''{0}''.<br>"
                        + "Please check the spelling of ''{1}'' and your Internet connection and validate again."
                        +"</html>",
                        url,
                        getNormalizedApiUrl()
                ),
                tr("Connection to API failed"),
                JOptionPane.ERROR_MESSAGE,
                HelpUtil.ht("/Preferences/Connection#ConnectionToAPIFailed")
        );
    }

    protected void alertInvalidServerResult(int retCode) {
        HelpAwareOptionPane.showMessageDialogInEDT(
                parent,
                tr("<html>"
                        + "Failed to retrieve a list of changesets from the OSM API server at<br>"
                        + "''{1}''. The server responded with the return code {0} instead of 200.<br>"
                        + "Please check the spelling of ''{1}'' and validate again."
                        + "</html>",
                        retCode,
                        getNormalizedApiUrl()
                ),
                tr("Connection to API failed"),
                JOptionPane.ERROR_MESSAGE,
                HelpUtil.ht("/Preferences/Connection#InvalidServerResult")
        );
    }

    protected void alertInvalidCapabilities() {
        HelpAwareOptionPane.showMessageDialogInEDT(
                parent,
                tr("<html>"
                        + "The OSM API server at ''{0}'' did not return a valid response.<br>"
                        + "It is likely that ''{0}'' is not an OSM API server.<br>"
                        + "Please check the spelling of ''{0}'' and validate again."
                        + "</html>",
                        getNormalizedApiUrl()
                ),
                tr("Connection to API failed"),
                JOptionPane.ERROR_MESSAGE,
                HelpUtil.ht("/Preferences/Connection#InvalidSettings")
        );
    }

    @Override
    protected void cancel() {
        canceled = true;
        synchronized (this) {
            if (connection != null) {
                connection.disconnect();
            }
        }
    }

    @Override
    protected void finish() {}

    /**
     * Removes leading and trailing whitespace from the API URL and removes trailing '/'.
     *
     * @return the normalized API URL
     */
    protected String getNormalizedApiUrl() {
        String apiUrl = url.trim();
        while (apiUrl.endsWith("/")) {
            apiUrl = apiUrl.substring(0, apiUrl.lastIndexOf('/'));
        }
        return apiUrl;
    }

    @Override
    protected void realRun() throws SAXException, IOException, OsmTransferException {
        try {
            try {
                new URL(getNormalizedApiUrl());
            } catch (MalformedURLException e) {
                alertInvalidUrl(getNormalizedApiUrl());
                return;
            }
            URL capabilitiesUrl;
            String getCapabilitiesUrl = getNormalizedApiUrl() + "/0.6/capabilities";
            try {
                capabilitiesUrl = new URL(getCapabilitiesUrl);
            } catch (MalformedURLException e) {
                alertInvalidCapabilitiesUrl(getCapabilitiesUrl);
                return;
            }

            synchronized (this) {
                connection = Utils.openHttpConnection(capabilitiesUrl);
            }
            connection.setDoInput(true);
            connection.setDoOutput(false);
            connection.setRequestMethod("GET");
            connection.connect();

            if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) {
                alertInvalidServerResult(connection.getResponseCode());
                return;
            }

            try {
                Capabilities.CapabilitiesParser.parse(new InputSource(connection.getInputStream()));
            } catch (SAXException | ParserConfigurationException e) {
                Main.warn(e.getMessage());
                alertInvalidCapabilities();
                return;
            }
            success = true;
        } catch (IOException e) {
            if (canceled)
                // ignore exceptions
                return;
            Main.error(e);
            alertConnectionFailed();
            return;
        }
    }

    /**
     * Determines if the test has been canceled.
     * @return {@code true} if canceled, {@code false} otherwise
     */
    public boolean isCanceled() {
        return canceled;
    }

    /**
     * Determines if the test has succeeded.
     * @return {@code true} if success, {@code false} otherwise
     */
    public boolean isSuccess() {
        return success;
    }
}
