package org.openstreetmap.josm.gui.io;

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

import java.awt.Font;
import java.awt.GridBagLayout;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;

import org.openstreetmap.josm.Main;
import org.openstreetmap.josm.actions.downloadtasks.DownloadReferrersTask;
import org.openstreetmap.josm.data.osm.DataSet;
import org.openstreetmap.josm.data.osm.OsmPrimitive;
import org.openstreetmap.josm.data.osm.PrimitiveId;
import org.openstreetmap.josm.gui.ExtendedDialog;
import org.openstreetmap.josm.gui.PleaseWaitRunnable;
import org.openstreetmap.josm.gui.layer.OsmDataLayer;
import org.openstreetmap.josm.gui.progress.ProgressMonitor;
import org.openstreetmap.josm.gui.util.GuiHelper;
import org.openstreetmap.josm.gui.widgets.HtmlPanel;
import org.openstreetmap.josm.gui.widgets.JosmTextArea;
import org.openstreetmap.josm.io.OsmTransferException;
import org.openstreetmap.josm.tools.GBC;
import org.openstreetmap.josm.tools.Utils;
import org.xml.sax.SAXException;

/**
 * Task for downloading a set of primitives with all referrers.
 */
public class DownloadPrimitivesWithReferrersTask extends PleaseWaitRunnable {
    /** If true download into a new layer */
    private final boolean newLayer;
    /** List of primitives id to download */
    private final List<PrimitiveId> ids;
    /** If true, download members for relation */
    private final boolean full;
    /** If true, download also referrers */
    private final boolean downloadReferrers;

    /** Temporary layer where downloaded primitives are put */
    private OsmDataLayer tmpLayer;
    /** Reference to the task that download requested primitives */
    private DownloadPrimitivesTask mainTask;
    /** Flag indicated that user ask for cancel this task */
    private boolean canceled = false;
    /** Reference to the task currently running */
    private PleaseWaitRunnable currentTask = null;

    /**
     * Constructor
     *
     * @param newLayer if the data should be downloaded into a new layer
     * @param ids List of primitive id to download
     * @param downloadReferrers if the referrers of the object should be downloaded as well,
     *     i.e., parent relations, and for nodes, additionally, parent ways
     * @param full if the members of a relation should be downloaded as well
     * @param monitor ProgressMonitor to use, or null to create a new one
     */
    public DownloadPrimitivesWithReferrersTask(boolean newLayer, List<PrimitiveId> ids, boolean downloadReferrers,
            boolean full, ProgressMonitor monitor) {
        super(tr("Download objects"), monitor, false);
        this.ids = ids;
        this.downloadReferrers = downloadReferrers;
        this.full = full;
        this.newLayer = newLayer;
        // All downloaded primitives are put in a tmpLayer
        tmpLayer = new OsmDataLayer(new DataSet(), OsmDataLayer.createNewName(), null);
    }

    /**
     * Cancel recursively the task. Do not call directly
     * @see DownloadPrimitivesWithReferrersTask#operationCancel
     */
    @Override
    protected void cancel() {
        synchronized(this) {
            canceled = true;
            if(currentTask != null)
                currentTask.operationCanceled();
        }
    }

    @Override
    protected void realRun() throws SAXException, IOException, OsmTransferException {
        getProgressMonitor().setTicksCount(ids.size()+1);
        // First, download primitives
        mainTask = new DownloadPrimitivesTask(tmpLayer, ids, full, getProgressMonitor().createSubTaskMonitor(1, false));
        synchronized(this) {
            currentTask = mainTask;
            if(canceled) {
                currentTask = null;
                return;
            }
        }
        currentTask.run();
        // Then, download referrers for each primitive
        if(downloadReferrers)
            for(PrimitiveId id : ids) {
                synchronized(this) {
                    if(canceled) {
                        currentTask = null;
                        return;
                    }
                    currentTask = new DownloadReferrersTask(
                            tmpLayer, id, getProgressMonitor().createSubTaskMonitor(1, false));
                }
                currentTask.run();
            }
        currentTask = null;
    }

    @Override
    protected void finish() {
        synchronized(this) {
            if(canceled)
                return;
        }

        // Append downloaded data to JOSM
        OsmDataLayer layer = Main.main.getEditLayer();
        if(layer == null || this.newLayer)
            Main.main.addLayer(tmpLayer);
        else
            layer.mergeFrom(tmpLayer);

        // Warm about missing primitives
        final Set<PrimitiveId> errs = mainTask.getMissingPrimitives();
        if (errs != null && !errs.isEmpty())
            GuiHelper.runInEDTAndWait(new Runnable() {
                @Override
                public void run() {
                    reportProblemDialog(errs,
                            trn("Object could not be downloaded", "Some objects could not be downloaded", errs.size()),
                            trn("One object could not be downloaded.<br>",
                                    "{0} objects could not be downloaded.<br>",
                                    errs.size(),
                                    errs.size())
                                    + tr("The server replied with response code 404.<br>"
                                         + "This usually means, the server does not know an object with the requested id."),
                            tr("missing objects:"),
                            JOptionPane.ERROR_MESSAGE
                            ).showDialog();
                }
            });

        // Warm about deleted primitives
        final Set<PrimitiveId> del = new HashSet<>();
        DataSet ds = Main.main.getCurrentDataSet();
        for (PrimitiveId id : ids) {
            OsmPrimitive osm = ds.getPrimitiveById(id);
            if (osm != null && osm.isDeleted()) {
                del.add(id);
            }
        }
        if (!del.isEmpty())
            GuiHelper.runInEDTAndWait(new Runnable() {
                @Override
                public void run() {
                    reportProblemDialog(del,
                            trn("Object deleted", "Objects deleted", del.size()),
                            trn(
                                "One downloaded object is deleted.",
                                "{0} downloaded objects are deleted.",
                                del.size(),
                                del.size()),
                            null,
                            JOptionPane.WARNING_MESSAGE
                    ).showDialog();
                }
            });
    }

    /**
     * Return id of really downloaded primitives.
     * @return List of primitives id or null if no primitives was downloaded
     */
    public List<PrimitiveId> getDownloadedId() {
        synchronized(this) {
            if(canceled)
                return null;
        }
        ArrayList<PrimitiveId> downloaded = new ArrayList<>(ids);
        downloaded.removeAll(mainTask.getMissingPrimitives());
        return downloaded;
    }

    /**
     * Dialog for report a problem during download.
     * @param errs Primitives involved
     * @param title Title of dialog
     * @param text Detail message
     * @param listLabel List of primitives description
     * @param msgType Type of message, see {@link JOptionPane}
     * @return The Dialog object
     */
    private static ExtendedDialog reportProblemDialog(Set<PrimitiveId> errs,
            String title, String text, String listLabel, int msgType) {
        JPanel p = new JPanel(new GridBagLayout());
        p.add(new HtmlPanel(text), GBC.eop());
        if (listLabel != null) {
            JLabel missing = new JLabel(listLabel);
            missing.setFont(missing.getFont().deriveFont(Font.PLAIN));
            p.add(missing, GBC.eol());
        }
        JosmTextArea txt = new JosmTextArea();
        txt.setFont(new Font("Monospaced", txt.getFont().getStyle(), txt.getFont().getSize()));
        txt.setEditable(false);
        txt.setBackground(p.getBackground());
        txt.setColumns(40);
        txt.setRows(1);
        txt.setText(Utils.join(", ", errs));
        JScrollPane scroll = new JScrollPane(txt);
        p.add(scroll, GBC.eop().weight(1.0, 0.0).fill(GBC.HORIZONTAL));

        return new ExtendedDialog(
                Main.parent,
                title,
                new String[] { tr("Ok") })
        .setButtonIcons(new String[] { "ok" })
        .setIcon(msgType)
        .setContent(p, false);
    }
}
