"""A graph, implemented using an array of lists. Parallel edges and self-loops are permitted."""

import sys
from AlgsSedgewickWayne.testcode.utils import adjtxtblk2OrderedDict

class Digraph(object):

  def __init__(self, a=None, **kwargs):
    if a is not None:
      if isinstance(a, int):
        self._init_empty(a)
      elif len(a) == 1:
        self._init_empty(a[0])
      else:
        self._init(a)
      self.keys = range(self._V)
    elif 'adjtxt' in kwargs:
      self._adj = adjtxtblk2OrderedDict(kwargs['adjtxt'])
      self._V = len(self._adj)
      self._E = len(set([tuple(sorted([v, w])) for v, ws in self._adj.items() for w in ws]))
      self.keys = self._adj.keys()

  def _init_empty(self, V):
    if V < 0: raise Exception("Number of vertices must be nonnegative")
    self._V = V # number of vertices 
    self._E = 0 # number of edges
    self._adj = [set() for v in range(V)]
    self._indegree = [0]*V # indegree[v] = indegree of vertex v

  def _init(self, a):
    """Initializes a graph from an input stream."""
    # The format is the number of vertices V, followed by the number of edges E,
    # followed by E pairs of vertices, with each entry separated by whitespace.
    self._init_empty(a[0]) # init V, and the empty adj list
    E = a[1]
    if E < 0: raise Exception("Number of edges must be nonnegative")
    for v, w in a[2:]:
      self.addEdge(v, w)

  def V(self): return self._V # Returns the number of vertices in self graph.
  def E(self): return self._E # Returns the number of edges in self graph.

  def addEdge(self, v, w):
    """Adds the undirected edge v-w to self graph."""
    #self._validateVertex(v)
    #self._validateVertex(w)
    self._E += 1
    self._adj[v].add(w)
    self._indegree[w] += 1

  def adj(self, v):
    """Returns the vertices adjacent to vertex v."""
    #self._validateVertex(v)
    return self._adj[v]

  def outdegree(self, v):
    """Returns the number of directed edges incident from vertex v."""
    #self._validateVertex(v)
    return self._adj[v].size()

  def indegree(self, v):
    """Returns the number of directed edges incident to vertex v."""
    #self._validateVertex(v)
    return self.indegree[v]

  def reverse(self):
    """Returns the reverse of the digraph."""
    R = Digraph(self._V)
    for v in range(self._V):
      for w in self._adj(v):
        R.addEdge(w, v)
    return R

  #def _validateVertex(self, v):
  #  """raise an IndexOutOfBoundsException unless 0 <= v < V."""
  #  if v < 0 or v >= self._V or v not in self._adj:
  #    raise Exception("vertex {} is not between 0 and {} or in {}".format(v, self._V-1, self._adj))

  def __str__(self):
    s = [(("{V} vertices, {E} edges\n").format(V=self._V, E=self._E))]
    for v in self.keys:
      s.append("{v}: ".format(v=v))
      for w in self._adj[v]:
        s.append("{w} ".format(w=w))
      s.append("\n")
    return ''.join(s)

  def __iter__(self): # Makes Graph an iterable.
    return iter(self._adj) # returns an iterator.

  def wr_png(self, fout_png="Digraph.png", prt=sys.stdout, **kwargs):
    """Make a png showing a diagram of the connected components."""
    import pydot
    # 1. Create/initialize Graph
    G = pydot.Dot(graph_type='digraph') # Undirected Graph
    # 2. Create Nodes
    nodes = [pydot.Node(v) for v in self.keys]
    # 3. Add nodes to Graph
    for node in nodes:
      G.add_node(node)
    # 4. Add Edges between Nodes to Graph
    for v, w in self.get_edges():
      if v != w: # Print only edges from one node to another (not to self)
        G.add_edge(pydot.Edge(v, w))
    # 5. Write Graph to png file
    G.write_png(fout_png)
    prt.write("  WROTE: {}\n".format(fout_png))

  def get_edges(self):
    edges = set()
    for v in self.keys:
      for w in self._adj[v]:
        edges.add(tuple(sorted([v, w]))) 
    return edges

 #*****************************************************************************/
 #  % Graph.py ../thirdparty/tinyG.txt
 #  13 vertices, 13 edges 
 #  0: 6 2 1 5 
 #  1: 0 
 #  2: 0 
 #  3: 5 4 
 #  4: 5 6 3 
 #  5: 3 4 0 
 #  6: 0 4 
 #  7: 8 
 #  8: 7 
 #  9: 11 10 12 
 #  10: 9 
 #  11: 9 12 
 #  12: 11 9 
 #  
 #*****************************************************************************/

# -----------------------------------------------------------------------------
# INTRODUCTION TO GRAPHS (9:32)


# SOME GRAPH-PROCESSING PROBLEMS 08:14
#
# PATH: Is there a path between s and t?
# SHORTEST PATH: What is the shortest path between s and t?
#
# CYCLE: Is there a cycle in the graph?
# EULER TOUR: Is there a cycle that uses each edge exactly once?
# HAMILTON TOUR: Is there a cycle that uses each vertex exactly once?
#
# CONNECTIVITY: Is there a awy to connect all of the vertices?
# MST: What is the best way to connect all of the vertices?
# BICONNECTIVITY: Is there a vertex whose removeal disconnects the graph?
#
# PLANARITY: Can you draw the graph in the plane with no crossing edges?
# GRAPH ISOMORPHISM: Do two adjacency lists represent the same graph?
# 
# CHALLENGE: Whinc of these problems are easy? difficult? intractable?



# QUESTION: A cycle that uses eachedge of a graph exactly once is called
# ANSWER: An Euler tour

#  Copyright 2002-2016, Robert Sedgewick and Kevin Wayne.
#  Copyright 2015-2019, DV Klopfenstein, Python implementation