"""
Implements Tarjan's algorithm for finding strongly connected components
in a graph.
https://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm
"""
from algorithms.graph.graph import DirectedGraph


# pylint: disable=too-few-public-methods
class Tarjan:
    """
    A directed graph used for finding strongly connected components
    """
    def __init__(self, dict_graph):
        self.graph = DirectedGraph(dict_graph)
        self.index = 0
        self.stack = []

        # Runs Tarjan
        # Set all node index to None
        for vertex in self.graph.nodes:
            vertex.index = None

        self.sccs = []
        for vertex in self.graph.nodes:
            if vertex.index is None:
                self.strongconnect(vertex, self.sccs)

    def strongconnect(self, vertex, sccs):
        """
        Given a vertex, adds all successors of the given vertex to the same connected component
        """
        # Set the depth index for v to the smallest unused index
        vertex.index = self.index
        vertex.lowlink = self.index
        self.index += 1
        self.stack.append(vertex)
        vertex.on_stack = True

        # Consider successors of v
        for adjacent in self.graph.adjacency_list[vertex]:
            if adjacent.index is None:
                # Successor w has not yet been visited; recurse on it
                self.strongconnect(adjacent, sccs)
                vertex.lowlink = min(vertex.lowlink, adjacent.lowlink)
            elif adjacent.on_stack:
                # Successor w is in stack S and hence in the current SCC
                # If w is not on stack, then (v, w) is a cross-edge in the DFS
                # tree and must be ignored
                # Note: The next line may look odd - but is correct.
                # It says w.index not w.lowlink; that is deliberate and from the original paper
                vertex.lowlink = min(vertex.lowlink, adjacent.index)

        # If v is a root node, pop the stack and generate an SCC
        if vertex.lowlink == vertex.index:
            # start a new strongly connected component
            scc = []
            while True:
                adjacent = self.stack.pop()
                adjacent.on_stack = False
                scc.append(adjacent)
                if adjacent == vertex:
                    break
            scc.sort()
            sccs.append(scc)