// License: GPL. Copyright 2007 by Immanuel Scholz and others
package org.openstreetmap.josm.actions;

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

import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.util.Collection;
import java.util.LinkedList;

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

import org.openstreetmap.josm.Main;
import org.openstreetmap.josm.data.osm.OsmPrimitive;
import org.openstreetmap.josm.gui.OsmPrimitivRenderer;
import org.openstreetmap.josm.gui.PleaseWaitRunnable;
import org.openstreetmap.josm.io.OsmServerWriter;
import org.openstreetmap.josm.tools.GBC;
import org.xml.sax.SAXException;

/**
 * Action that opens a connection to the osm server and upload all changes.
 *
 * An dialog is displayed asking the user to specify a rectangle to grab.
 * The url and account settings from the preferences are used.
 *
 * @author imi
 */
public class UploadAction extends JosmAction {
	
	/** Upload Hook */
	public interface UploadHook {
		/**
		 * Checks the upload.
		 * @param add The added primitives
		 * @param update The updated primitives
		 * @param delete The deleted primitives
		 * @return true, if the upload can continue
		 */
		public boolean checkUpload(Collection<OsmPrimitive> add, Collection<OsmPrimitive> update, Collection<OsmPrimitive> delete);
	}
	
	/**
	 * The list of upload hooks. These hooks will be called one after the other
	 * when the user wants to upload data. Plugins can insert their own hooks here
	 * if they want to be able to veto an upload.
	 * 
	 * Be dafault, the standard upload dialog is the only element in the list.
	 * Plugins shold normally insert their code before that, so that the upload
	 * dialog is the last thing shown before upload really starts; on occasion
	 * however, a plugin might also want to insert something after that.
	 */
	public final LinkedList<UploadHook> uploadHooks = new LinkedList<UploadHook>();

	public UploadAction() {
		super(tr("Upload to OSM"), "upload", tr("Upload all changes to the OSM server."), KeyEvent.VK_U, InputEvent.CTRL_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK, true);

		/**
		 * Displays a screen where the actions that would be taken are displayed and
		 * give the user the possibility to cancel the upload.
		 */
		uploadHooks.add(new UploadHook() {
			public boolean checkUpload(Collection<OsmPrimitive> add, Collection<OsmPrimitive> update, Collection<OsmPrimitive> delete) {

				JPanel p = new JPanel(new GridBagLayout());

				OsmPrimitivRenderer renderer = new OsmPrimitivRenderer();

				if (!add.isEmpty()) {
					p.add(new JLabel(tr("Objects to add:")), GBC.eol());
					JList l = new JList(add.toArray());
					l.setCellRenderer(renderer);
					l.setVisibleRowCount(l.getModel().getSize() < 6 ? l.getModel().getSize() : 10);
					p.add(new JScrollPane(l), GBC.eol().fill());
				}

				if (!update.isEmpty()) {
					p.add(new JLabel(tr("Objects to modify:")), GBC.eol());
					JList l = new JList(update.toArray());
					l.setCellRenderer(renderer);
					l.setVisibleRowCount(l.getModel().getSize() < 6 ? l.getModel().getSize() : 10);
					p.add(new JScrollPane(l), GBC.eol().fill());
				}

				if (!delete.isEmpty()) {
					p.add(new JLabel(tr("Objects to delete:")), GBC.eol());
					JList l = new JList(delete.toArray());
					l.setCellRenderer(renderer);
					l.setVisibleRowCount(l.getModel().getSize() < 6 ? l.getModel().getSize() : 10);
					p.add(new JScrollPane(l), GBC.eol().fill());
				}

				return JOptionPane.showConfirmDialog(Main.parent, p, tr("Upload these changes?"),
						JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION;
			}
		});
	}

	public void actionPerformed(ActionEvent e) {
		if (Main.map == null) {
			JOptionPane.showMessageDialog(Main.parent,tr("Nothing to upload. Get some data first."));
			return;
		}

		if (!Main.map.conflictDialog.conflicts.isEmpty()) {
			JOptionPane.showMessageDialog(Main.parent,tr("There are unresolved conflicts. You have to resolve these first."));
			Main.map.conflictDialog.action.button.setSelected(true);
			Main.map.conflictDialog.action.actionPerformed(null);
			return;
		}

		final LinkedList<OsmPrimitive> add = new LinkedList<OsmPrimitive>();
		final LinkedList<OsmPrimitive> update = new LinkedList<OsmPrimitive>();
		final LinkedList<OsmPrimitive> delete = new LinkedList<OsmPrimitive>();
		for (OsmPrimitive osm : Main.ds.allPrimitives()) {
			if (osm.get("josm/ignore") != null)
				continue;
			if (osm.id == 0 && !osm.deleted)
				add.addLast(osm);
			else if (osm.modified && !osm.deleted)
				update.addLast(osm);
			else if (osm.deleted && osm.id != 0)
				delete.addFirst(osm);
		}
		
		if (add.isEmpty() && update.isEmpty() && delete.isEmpty()) {
			JOptionPane.showMessageDialog(Main.parent,tr("No changes to upload."));
			return;
		}

		// Call all upload hooks in sequence. The upload confirmation dialog
		// is one of these.
		for(UploadHook hook : uploadHooks)
			if(!hook.checkUpload(add, update, delete))
				return;
		
		final OsmServerWriter server = new OsmServerWriter();
		final Collection<OsmPrimitive> all = new LinkedList<OsmPrimitive>();
		all.addAll(add);
		all.addAll(update);
		all.addAll(delete);

		PleaseWaitRunnable uploadTask = new PleaseWaitRunnable(tr("Uploading data")){
			@Override protected void realRun() throws SAXException {
				server.uploadOsm(all);
			}
			@Override protected void finish() {
				Main.main.editLayer().cleanData(server.processed, !add.isEmpty());
			}
			@Override protected void cancel() {
				server.cancel();
			}
		};
		Main.worker.execute(uploadTask);
	}
}
