// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.plugins.turnlanes.model;

import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

import org.openstreetmap.josm.command.Command;
import org.openstreetmap.josm.data.osm.AbstractPrimitive;
import org.openstreetmap.josm.data.osm.DataSet;
import org.openstreetmap.josm.data.osm.OsmPrimitive;
import org.openstreetmap.josm.data.osm.PrimitiveData;

public class GenericCommand extends Command {
    private static final class BeforeAfter {
        final PrimitiveData before;
        final AbstractPrimitive after;

        BeforeAfter(PrimitiveData before, AbstractPrimitive after) {
            this.before = before;
            this.after = after;
        }

        public OsmPrimitive afterPrimitive() {
            return (OsmPrimitive) after;
        }

        public PrimitiveData afterData() {
            return (PrimitiveData) after;
        }
    }

    private final String description;
    private final Map<OsmPrimitive, BeforeAfter> beforeAfters = new HashMap<>();

    public GenericCommand(DataSet dataSet, String description) {
        super(dataSet);
        this.description = description;
    }

    void add(OsmPrimitive p) {
        beforeAfters.put(p, new BeforeAfter(null, p));
    }

    AbstractPrimitive backup(OsmPrimitive p) {
        final BeforeAfter ba = beforeAfters.get(p);

        if (ba == null) {
            final BeforeAfter newBa = new BeforeAfter(p.save(), p.save());
            beforeAfters.put(p, newBa);
            return newBa.after;
        } else {
            return ba.after;
        }
    }

    @Override
    public void fillModifiedData(Collection<OsmPrimitive> modified, Collection<OsmPrimitive> deleted,
            Collection<OsmPrimitive> added) {}

    @Override
    public String getDescriptionText() {
        return description;
    }

    @Override
    public boolean executeCommand() {
        for (Entry<OsmPrimitive, BeforeAfter> e : beforeAfters.entrySet()) {
            if (e.getValue().before == null) {
                getAffectedDataSet().addPrimitive(e.getValue().afterPrimitive());
            } else {
                e.getKey().load(e.getValue().afterData());
            }
        }
        return true;
    }

    @Override
    public void undoCommand() {
        for (Entry<OsmPrimitive, BeforeAfter> e : beforeAfters.entrySet()) {
            if (e.getValue().before == null) {
                getAffectedDataSet().removePrimitive(e.getValue().afterPrimitive().getPrimitiveId());
            } else {
                e.getKey().load(e.getValue().before);
            }
        }
    }

    @Override
    public PrimitiveData getOrig(OsmPrimitive osm) {
        return beforeAfters.get(osm).before;
    }

    @Override
    public Collection<? extends OsmPrimitive> getParticipatingPrimitives() {
        return Collections.unmodifiableSet(beforeAfters.keySet());
    }
}
