// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.actions.downloadtasks;

import java.net.URL;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.Future;
import java.util.stream.Collectors;

import org.openstreetmap.josm.data.Bounds;
import org.openstreetmap.josm.gui.progress.NullProgressMonitor;
import org.openstreetmap.josm.gui.progress.ProgressMonitor;
import org.openstreetmap.josm.io.XmlWriter;
import org.openstreetmap.josm.tools.ExceptionUtil;

/**
 * Interface defining a general download task used to download geographic data (OSM data, GPX tracks, etc.) for a given URL or geographic area.
 */
public interface DownloadTask {

    /**
     * Asynchronously launches the download task for a given bounding box.
     * <p>
     * Set <code>progressMonitor</code> to null, if the task should create, open, and close a progress monitor.
     * Set progressMonitor to {@link NullProgressMonitor#INSTANCE} if progress information is to
     * be discarded.
     * <p>
     * You can wait for the asynchronous download task to finish by synchronizing on the returned
     * {@link Future}, but make sure not to freeze up JOSM. Example:
     * <pre>
     *    Future&lt;?&gt; future = task.download(...);
     *    // DON'T run this on the Swing EDT or JOSM will freeze
     *    future.get(); // waits for the dowload task to complete
     * </pre>
     *
     * The following example uses a pattern which is better suited if a task is launched from
     * the Swing EDT:
     * <pre>
     *    final Future&lt;?&gt; future = task.download(...);
     *    Runnable runAfterTask = new Runnable() {
     *       public void run() {
     *           // this is not strictly necessary because of the type of executor service
     *           // Main.worker is initialized with, but it doesn't harm either
     *           //
     *           future.get(); // wait for the download task to complete
     *           doSomethingAfterTheTaskCompleted();
     *       }
     *    }
     *    MainApplication.worker.submit(runAfterTask);
     * </pre>
     *
     * @param settings download settings
     *
     * @param downloadArea the area to download
     * @param progressMonitor the progressMonitor
     * @return the future representing the asynchronous task
     * @since 13927
     */
    Future<?> download(DownloadParams settings, Bounds downloadArea, ProgressMonitor progressMonitor);

    /**
     * Asynchronously launches the download task for a given bounding URL.
     * <p>
     * Set progressMonitor to null, if the task should create, open, and close a progress monitor.
     * Set progressMonitor to {@link NullProgressMonitor#INSTANCE} if progress information is to
     * be discarded.

     * @param settings download settings
     * @param url the url to download from
     * @param progressMonitor the progressMonitor
     * @return the future representing the asynchronous task
     *
     * @see #download(DownloadParams, Bounds, ProgressMonitor)
     * @since 13927
     */
    Future<?> loadUrl(DownloadParams settings, String url, ProgressMonitor progressMonitor);

    /**
     * Returns true if the task is able to open the given URL, false otherwise.
     * @param url the url to download from
     * @param isRemotecontrol True if download request comes from remotecontrol.
     * @return True if the task is able to open the given URL, false otherwise.
     * Return false, if the request comes from remotecontrol, but the task is not
     * safe for remotecontrol.
     * A task is not safe for remotecontrol if it is possible to prepare a file
     * for download which does something unintended, e.g. gain access to the
     * local file system.
     */
    boolean acceptsUrl(String url, boolean isRemotecontrol);

    /**
     * Returns a short HTML documentation string, describing acceptable URLs.
     * @return The HTML documentation
     * @since 6031
     */
    default String acceptsDocumentationSummary() {
        StringBuilder buff = new StringBuilder(128)
                .append("<tr><td>")
                .append(getTitle())
                .append(":</td><td>");
        String[] patterns = getPatterns();
        if (patterns.length > 0) {
            buff.append("<ul>");
            for (String pattern: patterns) {
                buff.append("<li>")
                        .append(XmlWriter.encode(pattern))
                        .append("</li>");
            }
            buff.append("</ul>");
        }
        buff.append("</td></tr>");
        return buff.toString();
    }

    /**
     * Returns human-readable description of the task
     * @return The task description
     * @since 6031
     */
    String getTitle();

    /**
     * Returns regular expressions that match the URLs
     * @return The array of accepted URL patterns
     * @since 6031
     */
    String[] getPatterns();

    /**
     * Replies the error objects of the task. Empty list, if no error messages are available.
     * <p>
     * Error objects are either {@link String}s with error messages or {@link Exception}s.
     *
     * @return the list of error objects
     */
    List<Object> getErrorObjects();

    /**
     * Replies the error messages of the task. Empty list, if no error messages are available.
     *
     * @return the list of error messages
     * @since 17330
     */
    default List<String> getErrorMessages() {
        return getErrorObjects().stream().map(o -> {
            if (o instanceof String) {
                return (String) o;
            } else if (o instanceof Exception) {
                return ExceptionUtil.explainException((Exception) o).replace("<html>", "").replace("</html>", "");
            } else {
                return null;
            }
        }).filter(Objects::nonNull).collect(Collectors.toList());
    }

    /**
     * If this task provides potentially old data, this should return {@code true}. If so, it would be a good decision
     * to prompt users to verify if they want the data to be downloaded to the current layer.
     * @return {@code true} if the data could be old.
     * @since 19550
     */
    default boolean providesOldData() {
        return false;
    }

    /**
     * Cancels the asynchronous download task.
     *
     */
    void cancel();

    /**
     * Replies the HTML-formatted confirmation message to be shown to user when the given URL needs to be confirmed before loading.
     * @param url The URL to be confirmed
     * @return The HTML-formatted confirmation message to be shown to user
     * @since 5691
     */
    String getConfirmationMessage(URL url);

    /**
     * Sets whether the map view will zoom to download area after download
     * @param zoomAfterDownload if true, the map view will zoom to download area after download
     * @since 13261
     */
    void setZoomAfterDownload(boolean zoomAfterDownload);
}
