package edu.cmu.hcii.whyline.analysis;

import java.util.*;

import edu.cmu.hcii.whyline.bytecode.CodeAttribute;
import edu.cmu.hcii.whyline.bytecode.Instruction;

import gnu.trove.TIntHashSet;
import gnu.trove.TIntObjectHashMap;
import gnu.trove.TObjectIntHashMap;

/**
 * @author Andrew J. Ko
 *
 */
public final class ControlDependencies {

	private final CodeAttribute code;

	private final TIntObjectHashMap<Set<Instruction>> controlDependenciesByInstructionIndex;
	
	public ControlDependencies(CodeAttribute code) {

		this.code = code;
		
		controlDependenciesByInstructionIndex = new TIntObjectHashMap<Set<Instruction>>(code.getNumberOfInstructions() / 2);
		
		// Visit the instructions, treating them as a tree composed of branch instruction nodes.
		Instruction last = code.getInstruction(code.getNumberOfInstructions() - 1);

		ArrayList<Instruction> instructionsInPostOrderTraversalOfReverseControlFlowGraph = new ArrayList<Instruction>(code.getNumberOfInstructions());

		TObjectIntHashMap<Instruction> postOrderNumbers = visitPostOrderIterative(last, instructionsInPostOrderTraversalOfReverseControlFlowGraph);

		// Determine the post dominance frontier sets for each instruction.
		Instruction[] immediatePostDominators = determinePostDominanceFrontiers(postOrderNumbers, instructionsInPostOrderTraversalOfReverseControlFlowGraph);
		
		// For each instruction with multiple successors, add the instruction as a control dependency for its immediate post dominators.
		addControlDependencies(immediatePostDominators);
		
	}
	
	public Set<Instruction> getControlDependenciesOf(Instruction inst) {
		
		Set<Instruction> controlDependencies = controlDependenciesByInstructionIndex.get(inst.getIndex());
		return controlDependencies == null ? Collections.<Instruction>emptySet() : controlDependencies;

	}
		
	// Do a depth first traversal of the reverse control flow graph, numbering each instruction.
	// This can't be recursive, because we have some methods that are > 6000 instructions, and a typical JVM can't make it through that many method calls.
	private TObjectIntHashMap<Instruction> visitPostOrderIterative(Instruction start, ArrayList<Instruction> instructionsInPostOrderTraversalOfReverseControlFlowGraph) {
		
		TObjectIntHashMap<Instruction>postOrderNumbers = new TObjectIntHashMap<Instruction>();
		
		ArrayList<Instruction> stack = new ArrayList<Instruction>();
		TIntHashSet visitedIndices = new TIntHashSet(code.getNumberOfInstructions());
		TIntHashSet processedIndices = new TIntHashSet(code.getNumberOfInstructions());
		
		stack.add(start);
		while(stack.size() > 0) {
			Instruction top = stack.get(stack.size() - 1);
			if(visitedIndices.contains(top.getIndex())) {
				stack.remove(stack.size() - 1);
				if(!processedIndices.contains(top.getIndex())) {
					processedIndices.add(top.getIndex());
					
					instructionsInPostOrderTraversalOfReverseControlFlowGraph.add(0, top);
					postOrderNumbers.put(top, instructionsInPostOrderTraversalOfReverseControlFlowGraph.size());

				}
			}
			// Remember that we were here, then add the predecessors in reverse order to the stack.
			else {
				visitedIndices.add(top.getIndex());
				int insertionIndex = 0;
				for(Instruction predecessor : top.getOrderedPredecessors()) {
					if(!visitedIndices.contains(predecessor.getIndex())) {
						stack.add(stack.size() - insertionIndex, predecessor);
						insertionIndex++;
					}
				}
			}

		}

		return postOrderNumbers;
		
	}

	// From Cooper, Harvey, and Kennedy, "A Simple Fast Dominance Algorithm", but instead of using
	// a CFG and immediate dominators, it uses a reverse CFG (using predecessors as edges) and
	// immediate post dominators. This allows us to determine control dependencies.
	private Instruction[] determinePostDominanceFrontiers(TObjectIntHashMap<Instruction> postOrderNumbers, ArrayList<Instruction> instructionsInPostOrderTraversalOfReverseControlFlowGraph) {
				
		// Initialize the dominance array, with null represents the end node of the control flow graph.
		Instruction[] immediatePostDominators = new Instruction[code.getNumberOfInstructions()];

		// Keep an array tracking with instructions have post dominators set. We need this because
		// "null" represents "end node" and not "undefined" in the immediate post dominators array.
		// All of this is because we don't have an end node to point to.
		final boolean[] postDominatorIsSet = new boolean[code.getNumberOfInstructions()];

		boolean changed = true;
		while(changed) {
		
			changed = false;
			for(Instruction currentInstruction : instructionsInPostOrderTraversalOfReverseControlFlowGraph) {
			
				Set<Instruction> successors = currentInstruction.getOrderedSuccessors();
				
				// If this has no successor, then we still mark it as set so that it can be chosen as a post dominator.
				// All of this yuckiness is because we don't have an end node!
				if(successors.isEmpty()) {
					
					postDominatorIsSet[currentInstruction.getIndex()] = true;
					
				}
				else {
					
					// Pick a successor to start with, and then search for other successors that are a better choice.
					Iterator<Instruction> succIterator = successors.iterator();
					Instruction newImmediatePostDominator = succIterator.next();
					
					while(succIterator.hasNext()) {
						
						Instruction successor = succIterator.next();
						// As long as we've assigned an immediate post dominator for this successor, intersect the successor and the current selection for post dominator
						// The trick here is that null could also mean "end node" and not "undefined".
						if(postDominatorIsSet[successor.getIndex()]) {
							
							Instruction finger1 = successor;
							Instruction finger2 = newImmediatePostDominator;

							while(finger1 != null && finger2 != null && finger1 != finger2) {

								while(finger1 != null && finger2 != null && postOrderNumbers.get(finger1) < postOrderNumbers.get(finger2))
									finger1 = immediatePostDominators[finger1.getIndex()];

								while(finger1 != null && finger2 != null && postOrderNumbers.get(finger2) < postOrderNumbers.get(finger1))
									finger2 = immediatePostDominators[finger2.getIndex()];

							}
						
							newImmediatePostDominator = finger1;
							
						}
	
					}
						
					if(immediatePostDominators[currentInstruction.getIndex()] != newImmediatePostDominator) {

						immediatePostDominators[currentInstruction.getIndex()] = newImmediatePostDominator;
						postDominatorIsSet[currentInstruction.getIndex()] = true;
						changed = true;
						
					}
					
				}
				
			}
						
		}
		
		return immediatePostDominators;
				
	}
	
	// For each instruction with more than one possible successor, add the instruction as a control dependency
	// for each successor and its immediate post dominators. Here's the psuedocode given in Cooper (note that
	// we're going the opposite direction from this algorithm, so we're looking at successors).
	//
	//		for all nodes, b 
	//			if the number of predecessors of b >= 2 
	//				for all predecessors, p, of b 
	//					runner = p 
	//					while runner != doms[b] 
	//						add b to runner�s dominance frontier set 
	//						runner = doms[runner]
	//
	private void addControlDependencies(Instruction[] immediatePostDominators) {
		
		for(Instruction branch : code.getInstructions()) {
						
			Set<Instruction> successors = branch.getOrderedSuccessors();
			if(successors.size() >= 2) {

				for(Instruction successor : successors) {
					
					Instruction runner = successor;
					while(runner != null && runner != immediatePostDominators[branch.getIndex()]) {
						
						boolean wasNew = addControlDependency(runner, branch, successor);
						runner = immediatePostDominators[runner.getIndex()];
						
						if(!wasNew) break;
						
					}

				}

			}
			
		}

	}

	// Returns true if the dependency was a new one for this instruction.
	private boolean addControlDependency(Instruction inst, Instruction dependency, Instruction target) {
		
		Set<Instruction> dependencies = controlDependenciesByInstructionIndex.get(inst.getIndex());
		if(dependencies == null) {
			dependencies = new HashSet<Instruction>(1);
			controlDependenciesByInstructionIndex.put(inst.getIndex(), dependencies);
		}
		
		// If the dependencies already had this pair, return true.
		return dependencies.add(dependency);
		
	}
	
}