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

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.swing.AbstractListModel;
import javax.swing.DefaultListSelectionModel;

import org.openstreetmap.josm.data.osm.DefaultNameFormatter;
import org.openstreetmap.josm.data.osm.NameFormatter;
import org.openstreetmap.josm.data.osm.OsmPrimitive;
import org.openstreetmap.josm.data.osm.Relation;

/**
 * This is a list model for a list of turn restrictions.
 *
 */
public class TurnRestrictionsListModel extends AbstractListModel<Relation> {
    private final ArrayList<Relation> turnrestrictions = new ArrayList<>();
    private DefaultListSelectionModel selectionModel;

    /**
     * Creates the model
     *
     * @param selectionModel the selection model used in the turn restriction list
     */
    public TurnRestrictionsListModel(DefaultListSelectionModel selectionModel) {
        this.selectionModel = selectionModel;
    }

    /**
     * Replies the turn restriction at position {@code idx} in the list.
     *
     * @param idx the index
     * @return the turn restriction at position {@code idx} in the list.
     */
    public Relation getTurnRestriction(int idx) {
        return turnrestrictions.get(idx);
    }

    /**
     * Sorts the turn restrictions in this model
     */
    public void sort() {
        Collections.sort(
                turnrestrictions,
                new Comparator<Relation>() {
                    NameFormatter formatter = DefaultNameFormatter.getInstance();

                    @Override
                    public int compare(Relation r1, Relation r2) {
                        return r1.getDisplayName(formatter).compareTo(r2.getDisplayName(formatter));
                    }
                }
        );
    }

    protected boolean isValid(Relation r) {
        return !r.isDeleted() && r.isVisible() && !r.isIncomplete();
    }

    /**
     * Replies true if the primitive {@code primitive} represents
     * an OSM turn restriction.
     *
     * @param primitive the primitive
     * @return true if the primitive {@code primitive} represents
     * an OSM turn restriction; false, otherwise
     */
    protected boolean isTurnRestriction(OsmPrimitive primitive) {
        if (primitive == null) return false;
        if (!(primitive instanceof Relation)) return false;
        String type = primitive.get("type");
        if (type == null || !type.equals("restriction")) return false;
        return true;
    }

    /**
     * Populates the model with the turn restrictions in {@code turnrestrictions}.
     *
     * @param turnrestrictions the turn restrictions
     */
    public void setTurnRestrictions(Collection<Relation> turnrestrictions) {
        List<Relation> sel = getSelectedTurnRestrictions();
        this.turnrestrictions.clear();
        if (turnrestrictions == null) {
            selectionModel.clearSelection();
            fireContentsChanged(this, 0, getSize());
            return;
        }
        for (Relation r: turnrestrictions) {
            if (isValid(r) && isTurnRestriction(r)) {
                this.turnrestrictions.add(r);
            }
        }
        sort();
        fireIntervalAdded(this, 0, getSize());
        setSelectedTurnRestrictions(sel);
    }

    /**
     * Add all turn restrictions in <code>addedPrimitives</code> to the model for the
     * relation list dialog
     *
     * @param addedPrimitives the collection of added primitives. May include nodes,
     * ways, and relations.
     */
    public void addTurnRestrictions(Collection<? extends OsmPrimitive> addedPrimitives) {
        boolean added = false;
        for (OsmPrimitive p: addedPrimitives) {
            if (!isTurnRestriction(p)) {
                continue;
            }

            Relation r = (Relation) p;
            if (!isValid(r)) continue;
            if (turnrestrictions.contains(r)) {
                continue;
            }
            turnrestrictions.add(r);
            added = true;
        }
        if (added) {
            List<Relation> sel = getSelectedTurnRestrictions();
            sort();
            fireIntervalAdded(this, 0, getSize());
            setSelectedTurnRestrictions(sel);
        }
    }

    /**
     * Removes all turn restrictions in <code>removedPrimitives</code> from the model
     *
     * @param removedPrimitives the removed primitives. May include nodes, ways,
     *   and relations
     */
    public void removeTurnRestrictions(Collection<? extends OsmPrimitive> removedPrimitives) {
        if (removedPrimitives == null) return;
        Set<Relation> removedTurnRestrictions = new HashSet<>();
        for (OsmPrimitive p: removedPrimitives) {
            if (!isTurnRestriction(p)) continue;
            removedTurnRestrictions.add((Relation) p);
        }
        if (removedTurnRestrictions.isEmpty()) return;
        int size = turnrestrictions.size();
        turnrestrictions.removeAll(removedTurnRestrictions);
        if (size != turnrestrictions.size()) {
            List<Relation> sel = getSelectedTurnRestrictions();
            sort();
            fireContentsChanged(this, 0, getSize());
            setSelectedTurnRestrictions(sel);
        }
    }

    @Override
    public Relation getElementAt(int index) {
        return turnrestrictions.get(index);
    }

    @Override
    public int getSize() {
        return turnrestrictions.size();
    }

    /**
     * Replies the list of selected, non-new relations. Empty list,
     * if there are no selected, non-new relations.
     *
     * @return the list of selected, non-new relations.
     */
    public List<Relation> getSelectedNonNewRelations() {
        ArrayList<Relation> ret = new ArrayList<>();
        for (int i = 0; i < getSize(); i++) {
            if (!selectionModel.isSelectedIndex(i)) {
                continue;
            }
            if (turnrestrictions.get(i).isNew()) {
                continue;
            }
            ret.add(turnrestrictions.get(i));
        }
        return ret;
    }

    /**
     * Replies the list of selected turn restrictions. Empty list,
     * if there are no selected turn restrictions.
     *
     * @return the list of selected turn restrictions
     */
    public List<Relation> getSelectedTurnRestrictions() {
        ArrayList<Relation> ret = new ArrayList<>();
        for (int i = 0; i < getSize(); i++) {
            if (!selectionModel.isSelectedIndex(i)) {
                continue;
            }
            ret.add(turnrestrictions.get(i));
        }
        return ret;
    }

    /**
     * Sets the selected turn restrictions
     *
     * @param sel the list of selected turn restrictions
     */
    public void setSelectedTurnRestrictions(List<Relation> sel) {
        selectionModel.clearSelection();
        if (sel == null || sel.isEmpty())
            return;
        for (Relation r: sel) {
            int i = turnrestrictions.indexOf(r);
            if (i < 0) {
                continue;
            }
            selectionModel.addSelectionInterval(i, i);
        }
    }

    /**
     * Returns the index of a turn restriction
     *
     * @param tr Turn restriction relation to check
     * @return index of relation (-1, if not found)
     */
    public int getTurnRestrictionIndex(Relation tr) {
        int i = turnrestrictions.indexOf(tr);
        if (i < 0) return -1;
        return i;
    }
}
