package commons.graph.model;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import commons.collections.MapMap;
public class Edges
{

MapMap<EdgeInfo> mm        = null;
Nodes            nodeIndex = null;

public Edges(Nodes nodes)
{
   mm = new MapMap<EdgeInfo>();
   nodeIndex = nodes;
}

public void clear ()
{
   mm.clear();
}

/**
 * Checks if this edge exists. Does find both origin-node and node-origin edges.
 * @param origin
 * @param node
 * @return
 */
public boolean contain (String origin, String node)
{
   return mm.containsMutual(origin, node);
}

/**
 * Adds an edge between two nodes and sets the distance. Note that any origin-node and node-origin
 * edge can only be added once.
 * @param origin
 * @param distance
 * @param node
 */
public void add (String origin, double distance, int type, String node)
{
   if (!mm.containsMutual(origin, node)) mm.put(origin, node, new EdgeInfo(distance, type));
   // OsUtl.trace(origin + ":" + node + "=" + mm.get(origin, node));
}

/**
 * Set the distance for a given egde.
 * @param origin
 * @param distance
 * @param node
 */
public void setDistance (String origin, String node, double distance) throws GraphModelException
{
   if (mm.contains(origin, node))
   {
      EdgeInfo ei = mm.get(origin, node);
      ei.distance = distance;
      mm.put(origin, node, ei);
   }
   else if (mm.contains(node, origin))
   {
      EdgeInfo ei = mm.get(node, origin);
      ei.distance = distance;
      mm.put(node, origin, ei);
   }
   else throw new GraphModelException("Cannot set (" + origin + ", " + distance + ", " + node + "), no such edge!");
}

/**
 * Returns the distance between two points. Result is POSITIVE_INFINITY if the edge does not exist.
 * Result is negative, if origin and node seem to be interchanged.
 * @param origin
 * @param node
 * @return double
 */
public double distance (String origin, String node)
{
   if (mm.containsMutual(origin, node))
   {
      if (mm.contains(origin, node)) return mm.get(origin, node).distance;
      else return -1 * mm.get(node, origin).distance;
   }
   return Double.POSITIVE_INFINITY;
}

/**
 * Returns the EdgeInfo associated with this edge.
 */
public EdgeInfo getInfo (String a, String b)
{
   if (mm.containsMutual(a, b))
   {
      if (mm.contains(a, b)) return mm.get(a, b);
      else mm.get(b, a);
   }
   return null;
}

/**
 * Returns -1 if the node is unknown. Otherwise counts the children.
 * @param node
 * @return
 */
public int countChildren (String node)
{
   if (!mm.contains(node)) return -1;
   return mm.size(node);
}

public List<String> getChildLabels (String node)
{
   List<String> list = new ArrayList<String>();
   for (Iterator<String> iter = mm.innerKeyIterator(node); iter.hasNext();)
      list.add(iter.next());
   return list;
}

public Nodes getChildNodes (String node)
{
   Nodes nodes = new Nodes();
   for (Iterator<String> iter = mm.innerKeyIterator(node); iter.hasNext();)
      nodes.add(nodeIndex.data.get(iter.next()));
   return nodes;
}

@SuppressWarnings("unchecked")
public List<String> getChildNodeLabelsSortedByDistance (String parentlabel)
{
   final List<String> childlabels = getChildLabels(parentlabel);
   Collections.sort(childlabels, getChildrenByDistanceSorter(this, parentlabel));
   return childlabels;
}

@SuppressWarnings("unchecked")
public List<Node> getChildNodesListSortedByDistance (String parentlabel)
{
   final List<String> childlabels = getChildLabels(parentlabel);
   Collections.sort(childlabels, getChildrenByDistanceSorter(this, parentlabel));
   final List<Node> childnodes = new ArrayList<Node>();
   for (String l : childlabels)
      childnodes.add(nodeIndex.getNode(l));
   return childnodes;
}

@SuppressWarnings("unchecked")
public static Comparator getChildrenByDistanceSorter (final Edges edges, final String parentlabel)
{
   return new Comparator()
   {

      public int compare (Object o1, Object o2)
      {
         Double d1 = edges.distance(parentlabel, ((String) o1));
         Double d2 = edges.distance(parentlabel, ((String) o2));
         return d1.compareTo(d2);
      }
   };
}

public void remove (String a, String b)
{
   mm.remove(a, b);
}

public int size ()
{
   return mm.size();
}
}
