package lms.util

import java.util.{ArrayDeque, HashMap}


object GraphUtil {
  
  class Ref[T](init: T) {
    var value: T = init
  }
  
  /* test cases

     stronglyConnectedComponents[String](List("A"), { case "A" => List("B") case "B" => List("C") case "C" => List("A","D") case "D" => Nil})
     List(List(A, B, C), List(D))

     stronglyConnectedComponents[String](List("A","B","C"), { case "A" => List("B") case "B" => List("C") case "C" => List("A","D") case "D" => Nil})
  */

  /** 
      Returns the strongly connected components
      of the graph rooted at the first argument,
      whose edges are given by the function argument.

      The scc are returned in topological order.
      Tarjan's algorithm (linear).
   */
  def stronglyConnectedComponents[T](start: List[T], succ: T=>List[T]): List[List[T]] = {

    val id: Ref[Int] = new Ref(0)
    val stack = new ArrayDeque[T]
    val mark = new HashMap[T,Int]

    val res = new Ref[List[List[T]]](Nil)
    for (node <- start)
      visit(node,succ,id,stack,mark,res)

    res.value
  }

  def visit[T](node: T, succ: T=>List[T], id: Ref[Int], stack: ArrayDeque[T], 
            mark: HashMap[T,Int], res: Ref[List[List[T]]]): Int = {

    
    if (mark.containsKey(node)) 
      mark.get(node)
    else {
      id.value = id.value + 1

      mark.put(node, id.value)
      stack.addFirst(node)
//    println("push " + node)

      var min: Int = id.value
      for (child <- succ(node)) {
        val m = visit(child, succ, id, stack, mark, res)

        if (m < min) 
          min = m
      }

      if (min == mark.get(node)) {
        var scc: List[T] = Nil
        var loop: Boolean = true
        do {
          val element = stack.removeFirst()
//        println("appending " + element)
          scc ::= element
          mark.put(element, Integer.MAX_VALUE)
          loop = element != node
        } while (loop)
        res.value ::= scc
      }
      min
    }
  }
  
}