// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.gui.dialogs.relation;

import static org.openstreetmap.josm.gui.dialogs.relation.WayConnectionType.Direction.NONE;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.TreeSet;

import org.openstreetmap.josm.data.osm.Node;
import org.openstreetmap.josm.data.osm.RelationMember;
import org.openstreetmap.josm.data.osm.Way;

/**
 * Auxiliary class for relation sorting.
 *
 * Constructs two mappings: One that maps each way to its nodes and the inverse mapping that
 * maps each node to all ways that have this node.
 * After construction both maps are consistent, but later on objects that are no longer needed
 * are removed from the value sets.
 * However the corresponding keys are not deleted even if they map to an empty set.
 * Note that normal ways have 2 nodes (beginning and end) but roundabouts can have less or more
 * (that are shared by other members).
 *
 * @author Christiaan Welvaart <cjw@time4t.net>
 *
 */
public class RelationNodeMap {
    /*
     * the maps. (Need TreeMap for efficiency.)
     */
    private TreeMap<Node, TreeSet<Integer>> nodesMap;
    private TreeMap<Integer, TreeSet<Node>> waysMap;
    /*
     * Used to keep track of what members are done.
     */
    private TreeSet<Integer> remaining;

    /**
     * All members that are incomplete or not a way
     */
    private List<Integer> notSortable = new ArrayList<Integer>();

    RelationNodeMap(List<RelationMember> members) {
        nodesMap = new TreeMap<Node, TreeSet<Integer>>();
        waysMap = new TreeMap<Integer, TreeSet<Node>>();

        for (int i = 0; i < members.size(); ++i) {
            RelationMember m = members.get(i);
            if (m.getMember().isIncomplete() || !m.isWay())
            {
                notSortable.add(i);
            }
            else {
                Way w = m.getWay();
                if (MemberTableModel.roundaboutType(w) != NONE) {
                    for (Node nd : w.getNodes()) {
                        addPair(nd, i);
                    }
                } else {
                    addPair(w.firstNode(), i);
                    addPair(w.lastNode(), i);
                }
            }
        }

        remaining = new TreeSet<Integer>();
        for (Integer k : waysMap.keySet()) {
            remaining.add(k);
        }

        /*
         * Clean up the maps, i.e. remove nodes from roundabouts and dead ends that
         * cannot be used in future. (only for performance)
         */
        Iterator<Map.Entry<Node,TreeSet<Integer>>> it = nodesMap.entrySet().iterator();
        while (it.hasNext()) {
            Map.Entry<Node,TreeSet<Integer>> nodeLinks = it.next();

            if (nodeLinks.getValue().size() < 2) {
                if (nodeLinks.getValue().size() != 1) throw new AssertionError();

                Integer d_way = nodeLinks.getValue().iterator().next();
                TreeSet<Node> d_way_nodes = waysMap.get(d_way);
                d_way_nodes.remove(nodeLinks.getKey());

                it.remove();
                continue;
            }
        }
    }

    private void addPair(Node n, int i) {
        TreeSet<Integer> ts = nodesMap.get(n);
        if (ts == null) {
            ts = new TreeSet<Integer>();
            nodesMap.put(n, ts);
        }
        ts.add(i);

        TreeSet<Node> ts2 = waysMap.get(i);
        if (ts2 == null) {
            ts2 = new TreeSet<Node>();
            waysMap.put(i, ts2);
        }
        ts2.add(n);
    }

    /**
     * Return a relation member that is linked to the
     * member 'i', but has not been popped jet.
     * Return null if there is no such member left.
     */
    public Integer popAdjacent(Integer i) {
        TreeSet<Node> nodes = waysMap.get(i);
        for (Node n : nodes) {
            TreeSet<Integer> adj = nodesMap.get(n);
            if (!adj.isEmpty()) {
                Integer j = adj.iterator().next();
                done(j);
                waysMap.get(j).remove(n);
                return j;
            }
        }
        return null;
    }

    /**
     * Returns some remaining member or null if
     * every sortable member has been processed.
     */
    public Integer pop() {
        if (remaining.isEmpty()) return null;
        Integer i = remaining.iterator().next();
        done(i);
        return i;
    }

    /**
     * This relation member has been processed.
     * Remove references in the nodesMap.
     */
    private void done(Integer i) {
        remaining.remove(i);
        TreeSet<Node> nodes = waysMap.get(i);
        for (Node n : nodes) {
            boolean result = nodesMap.get(n).remove(i);
            if (!result) throw new AssertionError();
        }
    }

    public List<Integer> getNotSortableMembers() {
        return notSortable;
    }
}
