// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.gui.conflict.properties;

import static org.openstreetmap.josm.tools.I18n.tr;

import java.awt.Color;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.text.DecimalFormat;
import java.util.Observable;
import java.util.Observer;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

import org.openstreetmap.josm.Main;
import org.openstreetmap.josm.data.coor.LatLon;
import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
import org.openstreetmap.josm.gui.conflict.MergeDecisionType;
import org.openstreetmap.josm.tools.ImageProvider;

/**
 * This class represents a UI component for resolving conflicts in some properties
 * of {@see OsmPrimitive}.
 * 
 */
public class PropertiesMerger extends JPanel implements Observer {
    private static DecimalFormat COORD_FORMATTER = new DecimalFormat("###0.0000");

    public final static Color BGCOLOR_NO_CONFLICT = new Color(234,234,234);
    public final static Color BGCOLOR_UNDECIDED = new Color(255,197,197);
    public final static Color BGCOLOR_DECIDED = new Color(217,255,217);

    private  JLabel lblMyVersion;
    private  JLabel lblMergedVersion;
    private  JLabel lblTheirVersion;

    private JLabel lblMyCoordinates;
    private JLabel lblMergedCoordinates;
    private JLabel lblTheirCoordinates;

    private JLabel lblMyDeletedState;
    private JLabel lblMergedDeletedState;
    private JLabel lblTheirDeletedState;

    private JLabel lblMyVisibleState;
    private JLabel lblMergedVisibleState;
    private JLabel lblTheirVisibleState;

    private final PropertiesMergeModel model;

    protected JLabel buildValueLabel(String name) {
        JLabel lbl = new JLabel();
        lbl.setName(name);
        lbl.setHorizontalAlignment(JLabel.CENTER);
        lbl.setOpaque(true);
        lbl.setBorder(BorderFactory.createLoweredBevelBorder());
        return lbl;
    }

    protected void buildHeaderRow() {
        GridBagConstraints gc = new GridBagConstraints();

        gc.gridx = 1;
        gc.gridy = 0;
        gc.gridwidth = 1;
        gc.gridheight = 1;
        gc.fill = GridBagConstraints.NONE;
        gc.anchor = GridBagConstraints.CENTER;
        gc.weightx = 0.0;
        gc.weighty = 0.0;
        gc.insets = new Insets(10,0,10,0);
        lblMyVersion = new JLabel(tr("My version"));
        lblMyVersion.setToolTipText(tr("Properties in my dataset, i.e. the local dataset"));
        add(lblMyVersion, gc);

        gc.gridx = 3;
        gc.gridy = 0;
        lblMergedVersion = new JLabel(tr("Merged version"));
        lblMergedVersion.setToolTipText(tr("Properties in the merged element. They will replace properties in my elements when merge decisions are applied."));
        add(lblMergedVersion, gc);

        gc.gridx = 5;
        gc.gridy = 0;
        lblTheirVersion = new JLabel(tr("Their version"));
        lblTheirVersion.setToolTipText(tr("Properties in their dataset, i.e. the server dataset"));
        add(lblTheirVersion, gc);
    }

    protected void buildCoordinateConflictRows() {
        GridBagConstraints gc = new GridBagConstraints();

        gc.gridx = 0;
        gc.gridy = 1;
        gc.gridwidth = 1;
        gc.gridheight = 1;
        gc.fill = GridBagConstraints.HORIZONTAL;
        gc.anchor = GridBagConstraints.LINE_START;
        gc.weightx = 0.0;
        gc.weighty = 0.0;
        gc.insets = new Insets(0,5,0,5);
        add(new JLabel(tr("Coordinates:")), gc);

        gc.gridx = 1;
        gc.gridy = 1;
        gc.fill = GridBagConstraints.BOTH;
        gc.anchor = GridBagConstraints.CENTER;
        gc.weightx = 0.33;
        gc.weighty = 0.0;
        add(lblMyCoordinates = buildValueLabel("label.mycoordinates"), gc);

        gc.gridx = 2;
        gc.gridy = 1;
        gc.fill = GridBagConstraints.NONE;
        gc.anchor = GridBagConstraints.CENTER;
        gc.weightx = 0.0;
        gc.weighty = 0.0;
        KeepMyCoordinatesAction actKeepMyCoordinates = new KeepMyCoordinatesAction();
        model.addObserver(actKeepMyCoordinates);
        JButton btnKeepMyCoordinates = new JButton(actKeepMyCoordinates);
        btnKeepMyCoordinates.setName("button.keepmycoordinates");
        add(btnKeepMyCoordinates, gc);


        gc.gridx = 3;
        gc.gridy = 1;
        gc.fill = GridBagConstraints.BOTH;
        gc.anchor = GridBagConstraints.CENTER;
        gc.weightx = 0.33;
        gc.weighty = 0.0;
        add(lblMergedCoordinates = buildValueLabel("label.mergedcoordinates"), gc);

        gc.gridx = 4;
        gc.gridy = 1;
        gc.fill = GridBagConstraints.NONE;
        gc.anchor = GridBagConstraints.CENTER;
        gc.weightx = 0.0;
        gc.weighty = 0.0;
        KeepTheirCoordinatesAction actKeepTheirCoordinates = new KeepTheirCoordinatesAction();
        model.addObserver(actKeepTheirCoordinates);
        JButton btnKeepTheirCoordinates = new JButton(actKeepTheirCoordinates);
        add(btnKeepTheirCoordinates, gc);

        gc.gridx = 5;
        gc.gridy = 1;
        gc.fill = GridBagConstraints.BOTH;
        gc.anchor = GridBagConstraints.CENTER;
        gc.weightx = 0.33;
        gc.weighty = 0.0;
        add(lblTheirCoordinates = buildValueLabel("label.theircoordinates"), gc);

        // ---------------------------------------------------
        gc.gridx = 3;
        gc.gridy = 2;
        gc.fill = GridBagConstraints.NONE;
        gc.anchor = GridBagConstraints.CENTER;
        gc.weightx = 0.0;
        gc.weighty = 0.0;
        gc.insets = new Insets(0,5,20,5);
        UndecideCoordinateConflictAction actUndecideCoordinates = new UndecideCoordinateConflictAction();
        model.addObserver(actUndecideCoordinates);
        JButton btnUndecideCoordinates = new JButton(actUndecideCoordinates);
        add(btnUndecideCoordinates, gc);
    }

    protected void buildDeletedStateConflictRows() {
        GridBagConstraints gc = new GridBagConstraints();

        gc.gridx = 0;
        gc.gridy = 3;
        gc.gridwidth = 1;
        gc.gridheight = 1;
        gc.fill = GridBagConstraints.BOTH;
        gc.anchor = GridBagConstraints.LINE_START;
        gc.weightx = 0.0;
        gc.weighty = 0.0;
        gc.insets = new Insets(0,5,0,5);
        add(new JLabel(tr("Deleted State:")), gc);

        gc.gridx = 1;
        gc.gridy = 3;
        gc.fill = GridBagConstraints.BOTH;
        gc.anchor = GridBagConstraints.CENTER;
        gc.weightx = 0.33;
        gc.weighty = 0.0;
        add(lblMyDeletedState = buildValueLabel("label.mydeletedstate"), gc);

        gc.gridx = 2;
        gc.gridy = 3;
        gc.fill = GridBagConstraints.NONE;
        gc.anchor = GridBagConstraints.CENTER;
        gc.weightx = 0.0;
        gc.weighty = 0.0;
        KeepMyDeletedStateAction actKeepMyDeletedState = new KeepMyDeletedStateAction();
        model.addObserver(actKeepMyDeletedState);
        JButton btnKeepMyDeletedState = new JButton(actKeepMyDeletedState);
        btnKeepMyDeletedState.setName("button.keepmydeletedstate");
        add(btnKeepMyDeletedState, gc);


        gc.gridx = 3;
        gc.gridy = 3;
        gc.fill = GridBagConstraints.BOTH;
        gc.anchor = GridBagConstraints.CENTER;
        gc.weightx = 0.33;
        gc.weighty = 0.0;
        add(lblMergedDeletedState = buildValueLabel("label.mergeddeletedstate"), gc);

        gc.gridx = 4;
        gc.gridy = 3;
        gc.fill = GridBagConstraints.NONE;
        gc.anchor = GridBagConstraints.CENTER;
        gc.weightx = 0.0;
        gc.weighty = 0.0;
        KeepTheirDeletedStateAction actKeepTheirDeletedState = new KeepTheirDeletedStateAction();
        model.addObserver(actKeepTheirDeletedState);
        JButton btnKeepTheirDeletedState = new JButton(actKeepTheirDeletedState);
        btnKeepTheirDeletedState.setName("button.keeptheirdeletedstate");
        add(btnKeepTheirDeletedState, gc);

        gc.gridx = 5;
        gc.gridy = 3;
        gc.fill = GridBagConstraints.BOTH;
        gc.anchor = GridBagConstraints.CENTER;
        gc.weightx = 0.33;
        gc.weighty = 0.0;
        add(lblTheirDeletedState = buildValueLabel("label.theirdeletedstate"), gc);

        // ---------------------------------------------------
        gc.gridx = 3;
        gc.gridy = 4;
        gc.fill = GridBagConstraints.NONE;
        gc.anchor = GridBagConstraints.CENTER;
        gc.weightx = 0.0;
        gc.weighty = 0.0;
        UndecideDeletedStateConflictAction actUndecideDeletedState = new UndecideDeletedStateConflictAction();
        model.addObserver(actUndecideDeletedState);
        JButton btnUndecideDeletedState = new JButton(actUndecideDeletedState);
        btnUndecideDeletedState.setName("button.undecidedeletedstate");
        add(btnUndecideDeletedState, gc);
    }

    protected void buildVisibleStateRows() {
        GridBagConstraints gc = new GridBagConstraints();

        gc.gridx = 0;
        gc.gridy = 5;
        gc.gridwidth = 1;
        gc.gridheight = 1;
        gc.fill = GridBagConstraints.BOTH;
        gc.anchor = GridBagConstraints.LINE_START;
        gc.weightx = 0.0;
        gc.weighty = 0.0;
        gc.insets = new Insets(0,5,0,5);
        add(new JLabel(tr("Visible State:")), gc);

        gc.gridx = 1;
        gc.gridy = 5;
        gc.fill = GridBagConstraints.BOTH;
        gc.anchor = GridBagConstraints.CENTER;
        gc.weightx = 0.33;
        gc.weighty = 0.0;
        add(lblMyVisibleState = buildValueLabel("label.myvisiblestate"), gc);

        gc.gridx = 2;
        gc.gridy = 5;
        gc.fill = GridBagConstraints.NONE;
        gc.anchor = GridBagConstraints.CENTER;
        gc.weightx = 0.0;
        gc.weighty = 0.0;
        KeepMyVisibleStateAction actKeepMyVisibleState = new KeepMyVisibleStateAction();
        model.addObserver(actKeepMyVisibleState);
        JButton btnKeepMyVisibleState = new JButton(actKeepMyVisibleState);
        btnKeepMyVisibleState.setName("button.keepmyvisiblestate");
        add(btnKeepMyVisibleState, gc);

        gc.gridx = 3;
        gc.gridy = 5;
        gc.fill = GridBagConstraints.BOTH;
        gc.anchor = GridBagConstraints.CENTER;
        gc.weightx = 0.33;
        gc.weighty = 0.0;
        add(lblMergedVisibleState = buildValueLabel("label.mergedvisiblestate"), gc);

        gc.gridx = 4;
        gc.gridy = 5;
        gc.fill = GridBagConstraints.NONE;
        gc.anchor = GridBagConstraints.CENTER;
        gc.weightx = 0.0;
        gc.weighty = 0.0;
        KeepTheirVisibleStateAction actKeepTheirVisibleState = new KeepTheirVisibleStateAction();
        model.addObserver(actKeepTheirVisibleState);
        JButton btnKeepTheirVisibleState = new JButton(actKeepTheirVisibleState);
        btnKeepTheirVisibleState.setName("button.keeptheirvisiblestate");
        add(btnKeepTheirVisibleState, gc);

        gc.gridx = 5;
        gc.gridy = 5;
        gc.fill = GridBagConstraints.BOTH;
        gc.anchor = GridBagConstraints.CENTER;
        gc.weightx = 0.33;
        gc.weighty = 0.0;
        add(lblTheirVisibleState = buildValueLabel("label.theirvisiblestate"), gc);

        // ---------------------------------------------------
        gc.gridx = 3;
        gc.gridy = 6;
        gc.fill = GridBagConstraints.NONE;
        gc.anchor = GridBagConstraints.CENTER;
        gc.weightx = 0.0;
        gc.weighty = 0.0;
        UndecideVisibleStateConflictAction actUndecideVisibleState = new UndecideVisibleStateConflictAction();
        model.addObserver(actUndecideVisibleState);
        JButton btnUndecideVisibleState = new JButton(actUndecideVisibleState);
        btnUndecideVisibleState.setName("button.undecidevisiblestate");
        add(btnUndecideVisibleState, gc);
    }

    protected void build() {
        setLayout(new GridBagLayout());
        buildHeaderRow();
        buildCoordinateConflictRows();
        buildDeletedStateConflictRows();
        buildVisibleStateRows();
    }

    public PropertiesMerger() {
        model = new PropertiesMergeModel();
        model.addObserver(this);
        build();
    }

    public String coordToString(LatLon coord) {
        if (coord == null)
            return tr("(none)");
        StringBuilder sb = new StringBuilder();
        sb.append("(")
        .append(COORD_FORMATTER.format(coord.lat()))
        .append(",")
        .append(COORD_FORMATTER.format(coord.lon()))
        .append(")");
        return sb.toString();
    }

    public String deletedStateToString(Boolean deleted) {
        if (deleted == null)
            return tr("(none)");
        if (deleted)
            return tr("deleted");
        else
            return tr("not deleted");
    }

    public String visibleStateToString(Boolean visible) {
        if (visible == null)
            return tr("(none)");
        if (visible)
            return tr("visible (on the server)");
        else
            return tr("not visible (on the server)");
    }

    public String visibleStateToStringMerged(Boolean visible) {
        if (visible == null)
            return tr("(none)");
        if (visible)
            return tr("Keep a clone of the local version");
        else
            return tr("Physically delete from local dataset");
    }

    protected void updateCoordinates() {
        lblMyCoordinates.setText(coordToString(model.getMyCoords()));
        lblMergedCoordinates.setText(coordToString(model.getMergedCoords()));
        lblTheirCoordinates.setText(coordToString(model.getTheirCoords()));
        if (! model.hasCoordConflict()) {
            lblMyCoordinates.setBackground(BGCOLOR_NO_CONFLICT);
            lblMergedCoordinates.setBackground(BGCOLOR_NO_CONFLICT);
            lblTheirCoordinates.setBackground(BGCOLOR_NO_CONFLICT);
        } else {
            if (!model.isDecidedCoord()) {
                lblMyCoordinates.setBackground(BGCOLOR_UNDECIDED);
                lblMergedCoordinates.setBackground(BGCOLOR_NO_CONFLICT);
                lblTheirCoordinates.setBackground(BGCOLOR_UNDECIDED);
            } else {
                lblMyCoordinates.setBackground(
                        model.isCoordMergeDecision(MergeDecisionType.KEEP_MINE)
                        ? BGCOLOR_DECIDED : BGCOLOR_NO_CONFLICT
                );
                lblMergedCoordinates.setBackground(BGCOLOR_DECIDED);
                lblTheirCoordinates.setBackground(
                        model.isCoordMergeDecision(MergeDecisionType.KEEP_THEIR)
                        ? BGCOLOR_DECIDED : BGCOLOR_NO_CONFLICT
                );
            }
        }
    }

    protected void updateDeletedState() {
        lblMyDeletedState.setText(deletedStateToString(model.getMyDeletedState()));
        lblMergedDeletedState.setText(deletedStateToString(model.getMergedDeletedState()));
        lblTheirDeletedState.setText(deletedStateToString(model.getTheirDeletedState()));

        if (! model.hasDeletedStateConflict()) {
            lblMyDeletedState.setBackground(BGCOLOR_NO_CONFLICT);
            lblMergedDeletedState.setBackground(BGCOLOR_NO_CONFLICT);
            lblTheirDeletedState.setBackground(BGCOLOR_NO_CONFLICT);
        } else {
            if (!model.isDecidedDeletedState()) {
                lblMyDeletedState.setBackground(BGCOLOR_UNDECIDED);
                lblMergedDeletedState.setBackground(BGCOLOR_NO_CONFLICT);
                lblTheirDeletedState.setBackground(BGCOLOR_UNDECIDED);
            } else {
                lblMyDeletedState.setBackground(
                        model.isDeletedStateDecision(MergeDecisionType.KEEP_MINE)
                        ? BGCOLOR_DECIDED : BGCOLOR_NO_CONFLICT
                );
                lblMergedDeletedState.setBackground(BGCOLOR_DECIDED);
                lblTheirDeletedState.setBackground(
                        model.isDeletedStateDecision(MergeDecisionType.KEEP_THEIR)
                        ? BGCOLOR_DECIDED : BGCOLOR_NO_CONFLICT
                );
            }
        }
    }

    protected void updateVisibleState() {
        lblMyVisibleState.setText(visibleStateToString(model.getMyVisibleState()));
        lblMergedVisibleState.setText(visibleStateToStringMerged(model.getMergedVisibleState()));
        lblTheirVisibleState.setText(visibleStateToString(model.getTheirVisibleState()));

        if (! model.hasVisibleStateConflict()) {
            lblMyVisibleState.setBackground(BGCOLOR_NO_CONFLICT);
            lblMergedVisibleState.setBackground(BGCOLOR_NO_CONFLICT);
            lblTheirVisibleState.setBackground(BGCOLOR_NO_CONFLICT);
        } else {
            if (!model.isDecidedVisibleState()) {
                lblMyVisibleState.setBackground(BGCOLOR_UNDECIDED);
                lblMergedVisibleState.setBackground(BGCOLOR_NO_CONFLICT);
                lblTheirVisibleState.setBackground(BGCOLOR_UNDECIDED);
            } else {
                lblMyVisibleState.setBackground(
                        model.isVisibleStateDecision(MergeDecisionType.KEEP_MINE)
                        ? BGCOLOR_DECIDED : BGCOLOR_NO_CONFLICT
                );
                lblMergedVisibleState.setBackground(BGCOLOR_DECIDED);
                lblTheirVisibleState.setBackground(
                        model.isVisibleStateDecision(MergeDecisionType.KEEP_THEIR)
                        ? BGCOLOR_DECIDED : BGCOLOR_NO_CONFLICT
                );
            }
        }
    }

    public void update(Observable o, Object arg) {
        updateCoordinates();
        updateDeletedState();
        updateVisibleState();
    }

    public PropertiesMergeModel getModel() {
        return model;
    }

    class KeepMyCoordinatesAction extends AbstractAction implements Observer {
        public KeepMyCoordinatesAction() {
            putValue(Action.SMALL_ICON, ImageProvider.get("dialogs/conflict", "tagkeepmine"));
            putValue(Action.SHORT_DESCRIPTION, tr("Keep my coordiates"));
        }

        public void actionPerformed(ActionEvent e) {
            model.decideCoordsConflict(MergeDecisionType.KEEP_MINE);
        }

        public void update(Observable o, Object arg) {
            setEnabled(model.hasCoordConflict() && ! model.isDecidedCoord());
        }
    }

    class KeepTheirCoordinatesAction extends AbstractAction implements Observer {
        public KeepTheirCoordinatesAction() {
            putValue(Action.SMALL_ICON, ImageProvider.get("dialogs/conflict", "tagkeeptheir"));
            putValue(Action.SHORT_DESCRIPTION, tr("Keep their coordiates"));
        }

        public void actionPerformed(ActionEvent e) {
            model.decideCoordsConflict(MergeDecisionType.KEEP_THEIR);
        }

        public void update(Observable o, Object arg) {
            setEnabled(model.hasCoordConflict() && ! model.isDecidedCoord());
        }
    }

    class UndecideCoordinateConflictAction extends AbstractAction implements Observer {
        public UndecideCoordinateConflictAction() {
            putValue(Action.SMALL_ICON, ImageProvider.get("dialogs/conflict", "tagundecide"));
            putValue(Action.SHORT_DESCRIPTION, tr("Undecide conflict between different coordinates"));
        }

        public void actionPerformed(ActionEvent e) {
            model.decideCoordsConflict(MergeDecisionType.UNDECIDED);
        }

        public void update(Observable o, Object arg) {
            setEnabled(model.hasCoordConflict() && model.isDecidedCoord());
        }
    }

    class KeepMyDeletedStateAction extends AbstractAction implements Observer {
        public KeepMyDeletedStateAction() {
            putValue(Action.SMALL_ICON, ImageProvider.get("dialogs/conflict", "tagkeepmine"));
            putValue(Action.SHORT_DESCRIPTION, tr("Keep my deleted state"));
        }

        public void actionPerformed(ActionEvent e) {
            model.decideDeletedStateConflict(MergeDecisionType.KEEP_MINE);
        }

        public void update(Observable o, Object arg) {
            setEnabled(model.hasDeletedStateConflict() && ! model.isDecidedDeletedState());
        }
    }

    class KeepTheirDeletedStateAction extends AbstractAction implements Observer {
        public KeepTheirDeletedStateAction() {
            putValue(Action.SMALL_ICON, ImageProvider.get("dialogs/conflict", "tagkeeptheir"));
            putValue(Action.SHORT_DESCRIPTION, tr("Keep their deleted state"));
        }

        public void actionPerformed(ActionEvent e) {
            model.decideDeletedStateConflict(MergeDecisionType.KEEP_THEIR);
        }

        public void update(Observable o, Object arg) {
            setEnabled(model.hasDeletedStateConflict() && ! model.isDecidedDeletedState());
        }
    }

    class UndecideDeletedStateConflictAction extends AbstractAction implements Observer {
        public UndecideDeletedStateConflictAction() {
            putValue(Action.SMALL_ICON, ImageProvider.get("dialogs/conflict", "tagundecide"));
            putValue(Action.SHORT_DESCRIPTION, tr("Undecide conflict between deleted state"));
        }

        public void actionPerformed(ActionEvent e) {
            model.decideDeletedStateConflict(MergeDecisionType.UNDECIDED);
        }

        public void update(Observable o, Object arg) {
            setEnabled(model.hasDeletedStateConflict() && model.isDecidedDeletedState());
        }
    }

    class KeepMyVisibleStateAction extends AbstractAction implements Observer {
        public KeepMyVisibleStateAction() {
            putValue(Action.SMALL_ICON, ImageProvider.get("dialogs/conflict", "tagkeepmine"));
            putValue(Action.SHORT_DESCRIPTION, tr("Keep my visible state"));
        }

        public void actionPerformed(ActionEvent e) {
            if (confirmKeepMine()) {
                model.decideVisibleStateConflict(MergeDecisionType.KEEP_MINE);
            }
        }

        public void update(Observable o, Object arg) {
            setEnabled(model.hasVisibleStateConflict() && ! model.isDecidedVisibleState());
        }

        protected boolean confirmKeepMine() {
            String [] options = {
                    tr("Yes, reset the id"),
                    tr("No, abort")
            };
            int ret = JOptionPane.showOptionDialog(
                    null,
                    tr("<html>To keep your local version, JOSM<br>"
                            + "has to reset the id of {0} {1} to 0.<br>"
                            + "On the next upload the server will assign<br>"
                            + "it a new id.<br>"
                            + "Do yo agree?</html>",
                            OsmPrimitiveType.from(model.getMyPrimitive()).getLocalizedDisplayNamePlural(),
                            model.getMyPrimitive().id
                    ),
                    tr("Reset id to 0"),
                    JOptionPane.YES_NO_OPTION,
                    JOptionPane.QUESTION_MESSAGE,
                    null,
                    options,
                    options[1]
            );
            return ret == JOptionPane.YES_OPTION;
        }
    }

    class KeepTheirVisibleStateAction extends AbstractAction implements Observer {
        public KeepTheirVisibleStateAction() {
            putValue(Action.SMALL_ICON, ImageProvider.get("dialogs/conflict", "tagkeeptheir"));
            putValue(Action.SHORT_DESCRIPTION, tr("Keep their visible state"));
        }

        public void actionPerformed(ActionEvent e) {
            if (confirmKeepTheir()){
                model.decideVisibleStateConflict(MergeDecisionType.KEEP_THEIR);
            }
        }

        public void update(Observable o, Object arg) {
            setEnabled(model.hasVisibleStateConflict() && ! model.isDecidedVisibleState());
        }

        protected boolean confirmKeepTheir() {
            String [] options = {
                    tr("Yes, purge it"),
                    tr("No, abort")
            };
            int ret = JOptionPane.showOptionDialog(
                    null,
                    tr("<html>JOSM will have to remove your local primitive with id {0}<br>"
                            + "from the dataset.<br>"
                            + "Do you agree?</html>",
                            model.getMyPrimitive().id
                    ),
                    tr("Remove from dataset"),
                    JOptionPane.YES_NO_OPTION,
                    JOptionPane.QUESTION_MESSAGE,
                    null,
                    options,
                    options[1]
            );
            return ret == JOptionPane.YES_OPTION;
        }
    }

    class UndecideVisibleStateConflictAction extends AbstractAction implements Observer {
        public UndecideVisibleStateConflictAction() {
            putValue(Action.SMALL_ICON, ImageProvider.get("dialogs/conflict", "tagundecide"));
            putValue(Action.SHORT_DESCRIPTION, tr("Undecide conflict between visible state"));
        }

        public void actionPerformed(ActionEvent e) {
            model.decideVisibleStateConflict(MergeDecisionType.UNDECIDED);
        }

        public void update(Observable o, Object arg) {
            setEnabled(model.hasVisibleStateConflict() && model.isDecidedVisibleState());
        }
    }
}
