/*
  Name: dfs.java
  Purpose: demo for DFS and graph scanning
  Author: Leo Liberti
  Source: Java
  History: 27/9/2011 work started (from an INF431 tutorial)
*/

import java.io.*;
import java.util.*;
import java.lang.*;

// implements an (unweighted) arc
class Arc {
    public Integer source;
    public Integer dest;
    public Arc(Integer s, Integer d) {
	source = s;
	dest = d;
    }
}

// implements a data structure which is either a stack or a queue
class myDataStructure {
    Stack<Integer> theStack;
    Queue<Integer> theQueue;
    Boolean isStack;
    public myDataStructure(Boolean st) {
        isStack = st;
	if (isStack == Boolean.TRUE) {
	    theStack = new Stack<Integer>();
	} else {
	    theQueue = new ArrayDeque<Integer>();
	}
    }
    
    public void push(Integer item) {
	if (isStack == Boolean.TRUE) {
	    theStack.push(item);
	} else {
	    theQueue.add(item);
	}
    }

    public Integer pop() {
	Integer ret;
	if (isStack == Boolean.TRUE) {
	    ret = theStack.pop();
	} else {
	    ret = theQueue.remove();
	}
	return ret;
    }

    public Boolean isEmpty() {
	Boolean ret;
	if (isStack == Boolean.TRUE) {
	    ret = theStack.isEmpty();
	} else {
	    ret = theQueue.isEmpty();
	}
	return ret;
    }
}


// the main class
public class dfs {
    public static final double INFINITY = Double.MAX_VALUE;

    // adjacency lists
    private Map<Integer, List<Integer> > N;

    // arc list
    private Map<Arc, Boolean> L;

    // visited nodes
    private Map<Integer, Boolean> visited;

    // set of nodes
    private Set<Integer> V;

    // constructor
    public dfs() {
	N = new HashMap<Integer, List<Integer> >();
	L = new HashMap<Arc, Boolean>();
	visited = new HashMap<Integer, Boolean>();
	V = new TreeSet<Integer>();
    }

    // store arc information and make vertices unvisited
    public void storeArc(Integer source, Integer dest) {
	Arc a = new Arc(source, dest);
	L.put(a, Boolean.TRUE);
	V.add(source);
	V.add(dest);
	if (!N.containsKey(source)) {
	    N.put(source, new ArrayList<Integer>());
	}
	List<Integer> Nv = N.get(source);
	Nv.add(dest);
	visited.put(source, Boolean.FALSE);
	visited.put(dest, Boolean.FALSE);
    }

    // print out adjacency lists
    public void printAdjList() {
	// loop on vertex keys
	for(Map.Entry<Integer, List<Integer> > pairs : N.entrySet()) {
	    System.out.print(pairs.getKey());
	    System.out.print(":");
	    List<Integer> Nv = pairs.getValue(); 
	    for(Integer u : Nv) {
		// loop on vertex star
		System.out.print(" " + u);
	    }
	    System.out.print("\n");
	}
    }

    // graph scanning algorithm (lecture 7)
    public void graphScanning(Integer s, Boolean isDFS) {
    	// Line "Require":

	// notice: tree operations are O(log n), so R.contains(w) at 
	//   line 3 is O(log n). Could have been more efficient using a 
	//   hash map, as in DFS variants above. My choice here just goes 
	//   to show how close code and pseudocode can be!
    	Set<Integer> R = new TreeSet<Integer>();
    	R.add(s);

	myDataStructure Q = new myDataStructure(isDFS);
    	Q.push(s);

    	// line 1: while Q \not= \emptyset do
    	while(!Q.isEmpty()) {

    	    // line 2: choose v\in Q
	    // line 3: remove v from Q
    	    Integer v = Q.pop();
    	    System.out.println(v);

    	    // (not in pseudocode: make sure v is connected to rest of graph)
    	    if (N.containsKey(v)) {

    		// define N^+(v)
    		List<Integer> Nv = N.get(v);

		// line 4: for all w in N^+(v)...
		for(Integer w : Nv) {
		    // line 4: ...such that w is not in R
		    if (!R.contains(w)) {

			// line 5
			R.add(w);
			
			// line 6
			Q.push(w);
		    }
    		} // line 7: end for

    	    }

    	} // line 8: end while
    }


    // a recursive DFS
    public void depthFirstSearch(Integer v, String spaces) {
    	System.out.println(spaces + v);
     	if (N.containsKey(v)) {
     	    List<Integer> Nv = N.get(v);
	    for(Integer u : Nv) {
 		if (!visited.get(u)) {
		    visited.put(u, Boolean.TRUE);
		    String s = spaces + "  ";
     		    depthFirstSearch(u, s);
     		}
     	    }
     	}
    }

    // recursive DFS with pre/post orders for both nodes and arcs
    public void dfsOrder(Integer v, int order) {
    	if (order == 1) {
    	    // prenode
    	    System.out.println(v);
    	}
    	if (N.containsKey(v)) {
    	    List<Integer> Nv = N.get(v);
	    for(Integer u : Nv) {
    		if (!visited.get(u)) {
    		    visited.put(u, Boolean.TRUE);
    		    if (order == 2) {
    			// prearc
    			System.out.println("(" + v + "," + u + ")");
    		    }
    		    dfsOrder(u, order);
    		    if (order == 3) {
    			// postarc
    			System.out.println("(" + v + "," + u + ")");
    		    }
    		}
    	    }
    	}
    	if (order == 4) {
    	    // postnode
    	    System.out.println(v);
    	}
    }

    // non-recursive DFS (lecture 6)
    public void depthFirstSearchNonRecursive(Integer v) {
    	Map<Integer, String> spaces = new HashMap<Integer, String>();
    	Stack<Integer> Q = new Stack<Integer>();
    	Q.push(v);
    	spaces.put(v, "");
    	Integer w;
    	while(!Q.isEmpty()) {
    	    w = Q.pop();
    	    if (!visited.get(w)) {
    		System.out.println(spaces.get(w) + w);
    		visited.put(w, Boolean.TRUE);
    		if (N.containsKey(w)) {
    		    List<Integer> Nw = N.get(w);
		    for(ListIterator<Integer> it = Nw.listIterator(Nw.size()); 
			it.hasPrevious(); ) {
			Integer u = it.previous();
    			if (!visited.get(u)) {
    			    Q.push(u);
    			    spaces.put(u, spaces.get(w) + "  ");
    			}
    		    }
    		}
    	    }
    	}
    }


    public static void main( String [ ] args ) {

	// if not enough cmd line args, abort
	if (args.length < 3) {
	    System.err.println("error: syntax is java dfs cmd file vertex");
	    System.err.println("  cmd in {print, dfs, dfsNonRecursive, scan, order}"); 
	    System.exit(1);
	}

	// read cmd line
	String cmd = args[0];
	Integer startv = Integer.parseInt(args[2]);

	// create the graph
	dfs g = new dfs();
	Boolean directed = Boolean.FALSE;

	try {
	    // open file on cmd line
	    FileReader fin = new FileReader( args[1] );
	    BufferedReader graphFile = new BufferedReader( fin );

	    String line;
	    while( ( line = graphFile.readLine( ) ) != null ) {
		// read and interpret line
		if (line.charAt(0) == '#') {
		    continue;
		} else if (line.contains("Directed")) {
		    directed = Boolean.TRUE;
		} else if (line.contains("Undirected")) {
		    directed = Boolean.FALSE;
		}
		// parse it if arc line
		StringTokenizer st = new StringTokenizer( line );
		if( st.countTokens( ) < 3 ) {
		    continue;
		}
		Integer source  = Integer.parseInt(st.nextToken());
		Integer dest  = Integer.parseInt(st.nextToken());
		// will ignore the cost
		Double cost = Double.parseDouble(st.nextToken());
		// store arc in the graph
		g.storeArc(source, dest);
		if (!directed) {
		    g.storeArc(dest, source);
		}
	    }
	} catch (IOException e) {
	    System.err.println(e);
	}
	if (cmd.contentEquals("print")) {
	    g.printAdjList();
	} else if (cmd.contentEquals("dfs")) {
	    String sp = "";
	    g.visited.put(startv, Boolean.TRUE);
	    g.depthFirstSearch(startv, sp);
	} else if (cmd.contentEquals("dfsNonRecursive")) {
	    g.depthFirstSearchNonRecursive(startv);
	} else if (cmd.contentEquals("scan")) {
	    if (args.length < 4) {
		System.err.println("error: scan requires further arg");
		System.err.println("  1=dfs, 2=bfs");
		System.exit(1);
	    }		
	    int method = (int) Integer.parseInt(args[3]);
	    if (method == 1) {
		g.graphScanning(startv, Boolean.TRUE);
	    } else {
		g.graphScanning(startv, Boolean.FALSE);
	    }
	} else if (cmd.contentEquals("order")) {
	    if (args.length < 4) {
		System.err.println("error: order requires further arg");
		System.err.println("  1=prenode, 2=prearc, 3=postarc, 4=postnode");
		System.exit(1);
	    }		
	    int order = (int) Integer.parseInt(args[3]);
	    g.visited.put(startv, Boolean.TRUE);
	    g.dfsOrder(startv, order);
	}
    }
}

