/**
 * 
 */
package bixie.transformation;

import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Map.Entry;

import bixie.boogie.controlflow.BasicBlock;
import bixie.boogie.controlflow.CfgProcedure;
import bixie.boogie.controlflow.CfgVariable;
import bixie.boogie.controlflow.expression.CfgArrayAccessExpression;
import bixie.boogie.controlflow.expression.CfgArrayStoreExpression;
import bixie.boogie.controlflow.expression.CfgBinaryExpression;
import bixie.boogie.controlflow.expression.CfgBitVectorAccessExpression;
import bixie.boogie.controlflow.expression.CfgExpression;
import bixie.boogie.controlflow.expression.CfgFunctionApplication;
import bixie.boogie.controlflow.expression.CfgIdentifierExpression;
import bixie.boogie.controlflow.expression.CfgIfThenElseExpression;
import bixie.boogie.controlflow.expression.CfgQuantifierExpression;
import bixie.boogie.controlflow.expression.CfgUnaryExpression;
import bixie.boogie.controlflow.statement.CfgAssertStatement;
import bixie.boogie.controlflow.statement.CfgAssignStatement;
import bixie.boogie.controlflow.statement.CfgAssumeStatement;
import bixie.boogie.controlflow.statement.CfgCallStatement;
import bixie.boogie.controlflow.statement.CfgHavocStatement;
import bixie.boogie.controlflow.statement.CfgStatement;
import bixie.util.Log;

/**
 * @author schaef
 *
 */
public class SingleStaticAssignment {
	
	public SingleStaticAssignment() {
				
	}
	
	/**
	 * computes an SSA version of p. This step also introduces blocks
	 * for frame conditions and a unified exit.
	 * @param p
	 */
	public void computeSSA(CfgProcedure p) {		
		LinkedList<BasicBlock> todo = new LinkedList<BasicBlock>();
		LinkedList<BasicBlock> done = new LinkedList<BasicBlock>();

		//generate unified exit
		{
			Log.debug("------ create unified exit");
			LinkedList<BasicBlock> sinks = new LinkedList<BasicBlock>();
			todo.add(p.getRootNode());
			while (!todo.isEmpty()) {
				BasicBlock current = todo.removeLast();
				done.add(current);
				if (current.getSuccessors().size()>0) {
					for (BasicBlock next : current.getSuccessors()) {
						if (!todo.contains(next) && !done.contains(next)) {
							todo.add(next);
						}
					}
				} else {
					sinks.add(current);
				}			
			}
			
			if (sinks.size()==0) {
				throw new RuntimeException("Every procedure must have at least one sink: "+p.getProcedureName());
			}
			
			if (sinks.size()>1) {
				BasicBlock unifiedExit = new BasicBlock(p.getLocation(), "$unifiedExit");
				unifiedExit.returns = true;
				for (BasicBlock b : sinks) {
					b.returns = false;
					b.connectToSuccessor(unifiedExit);
				}
				p.setExitNode(unifiedExit);
			} 		
		}
		//generate inbetween block if needed
		{
			Log.debug("------ create block between");
			todo = new LinkedList<BasicBlock>();
			done = new LinkedList<BasicBlock>();		
			todo.add(p.getExitNode());
			while (!todo.isEmpty()) {
				BasicBlock current = todo.removeLast();
				done.add(current);
				for (BasicBlock next : current.getPredecessors()) {
					if (!todo.contains(next) && !done.contains(next)) {
						todo.add(next);
					}
				}			
				if (current.getPredecessors().size()>1) {
					for (BasicBlock next : new HashSet<BasicBlock>(current.getPredecessors())) {
						BasicBlock between = new BasicBlock(next.getLocationTag(), next.getLabel()+"$between$"+current.getLabel());
						next.disconnectFromSuccessor(current);
						next.connectToSuccessor(between);
						between.connectToSuccessor(current);
					}							
				}			
			}		
		}

		updateBlockSSA(p);		
		
		//IMPORTANT: for some of the later analysis set we must be
		//able to assume that the Exit node does not contain any
		//statements. Thus, we create a fresh block here.
		if (!p.getExitNode().getLabel().equals("$UnifiedExit")) {
			BasicBlock finalUnifiedExit = new BasicBlock(p.getExitNode().getLocationTag(), "$UnifiedExit");
			//connect each block that does not have a successor to the unified exit
			for (BasicBlock b : done) {
				if (b.getSuccessors().size() == 0) {
					b.connectToSuccessor(finalUnifiedExit);					
				}

				{
					HashMap<CfgVariable, Integer> offset = new HashMap<CfgVariable, Integer>();
					for (BasicBlock pred : finalUnifiedExit.getPredecessors()) {
						mergeSSAOffsets(offset, pred.getLocalIncarnationMap());
					}
					for (BasicBlock pred : new HashSet<BasicBlock>(
							finalUnifiedExit.getPredecessors())) {
						addFrameCondition(pred, offset);
					}
					recomputLocalSSA(finalUnifiedExit, offset);					
				}
				
			}
			//Now do the SSA for the postcondition using the offset of
			//the unified exit.
			for (CfgExpression expr : p.getEnsures()) {
				recomputLocalSSA(expr, finalUnifiedExit.getLocalIncarnationMap());	
			}
			

			p.setExitNode(finalUnifiedExit);
		}
				
		return;
	}

	/**
	 * updates the SSA tables of each block.
	 * NOTE: use computeSSA instead if you are not sure if there is a unified exit
	 * of the frame conditions haven't been created yet.
	 * @param p
	 */
	public void updateBlockSSA(CfgProcedure p) {
		Log.debug("------ recomute the ssa");
		//Do the actual SSA
		LinkedList<BasicBlock> todo = new LinkedList<BasicBlock>();
		LinkedList<BasicBlock> done = new LinkedList<BasicBlock>();	
		if (p.getRootNode()==null) return;
		todo.add(p.getRootNode());

		//do SSA on the precondition first.
		
		
		
		for (CfgExpression expr : p.getRequires()) {
			recomputLocalSSA(expr, new HashMap<CfgVariable, Integer>());	
		}
		
		while (!todo.isEmpty()) {
			BasicBlock current = todo.removeLast();
			
			/*
			 * Check if SSA has already been computed for all predecessors. If
			 * not, add current to the bottom of the todo stack and continue
			 * with the next Block.
			 */
			{
				boolean allPredsDone = true;
				if (current.getPredecessors()!=null) {
					for (BasicBlock pred : current.getPredecessors()) {
						if (!done.contains(pred)) {
							allPredsDone = false;
							break;
						}
					}
				}
				if (allPredsDone) {
					done.add(current);
				} else {
					todo.addFirst(current);
					continue;
				}
			}
			// now compute the frame conditions for all predecessors
			{
				
				HashMap<CfgVariable, Integer> offset = new HashMap<CfgVariable, Integer>();
				if (current.getPredecessors().size() > 0) {
					for (BasicBlock pred : current.getPredecessors()) {
						mergeSSAOffsets(offset, pred.getLocalIncarnationMap());
					}
					for (BasicBlock pred : new HashSet<BasicBlock>(
							current.getPredecessors())) {
						addFrameCondition(pred, offset);
					}
				} 
				recomputLocalSSA(current, offset);
			}
			for (BasicBlock next : current.getSuccessors()) {
				if (!todo.contains(next) && !done.contains(next)) {
					todo.addLast(next);
				}
			}
		}		
	}
	
	private void mergeSSAOffsets(HashMap<CfgVariable, Integer> successorOffset,
			HashMap<CfgVariable, Integer> predecessorOffset) {
		for (Entry<CfgVariable, Integer> entry : predecessorOffset.entrySet()) {
			if (!successorOffset.containsKey(entry.getKey())) {
				successorOffset.put(entry.getKey(), entry.getValue());
			} else {
				if (successorOffset.get(entry.getKey()) < entry.getValue()) {
					successorOffset.put(entry.getKey(), entry.getValue());
				}
			}
		}
	}

	private void addFrameCondition(BasicBlock b,
			HashMap<CfgVariable, Integer> maxOffset) {
		HashMap<CfgVariable, Integer> offset = b.getLocalIncarnationMap();
		for (Entry<CfgVariable, Integer> entry : maxOffset.entrySet()) {
			if (!offset.containsKey(entry.getKey())) {
			} else {
				if (offset.get(entry.getKey()) < entry.getValue()) {
					CfgIdentifierExpression[] left = { new CfgIdentifierExpression(
							b.getLocationTag(), entry.getKey(),
							entry.getValue()) };
					CfgIdentifierExpression[] right = { new CfgIdentifierExpression(
							b.getLocationTag(), entry.getKey(),
							offset.get(entry.getKey())) };
					CfgAssignStatement assign = new CfgAssignStatement(
							b.getLocationTag(), left, right);
					b.addStatement(assign);
					b.getLocalIncarnationMap().put(entry.getKey(), entry.getValue());//TODO: experiment
				}
			}
		}

	}
	
	public void recomputLocalSSA(BasicBlock b, HashMap<CfgVariable, Integer> offset) {
		b.localIncarnationMap = new HashMap<CfgVariable, Integer>(offset);
		for (CfgStatement stmt : b.getStatements()) {
			if (stmt instanceof CfgAssertStatement) {
				CfgAssertStatement asrt = (CfgAssertStatement) stmt;
				recomputLocalSSA(asrt.getCondition(), b.localIncarnationMap);
			} else if (stmt instanceof CfgAssumeStatement) {
				CfgAssumeStatement asum = (CfgAssumeStatement) stmt;
				recomputLocalSSA(asum.getCondition(), b.localIncarnationMap);
			} else if (stmt instanceof CfgAssignStatement) {
				CfgAssignStatement asgn = (CfgAssignStatement) stmt;
				recomputLocalSSA(asgn.getRight(), b.localIncarnationMap);
				for (CfgIdentifierExpression id : asgn.getLeft()) {
					if (!b.localIncarnationMap.containsKey(id.getVariable())) {
						b.localIncarnationMap.put(id.getVariable(), 0);
					}
					b.localIncarnationMap.put(id.getVariable(),
							b.localIncarnationMap.get(id.getVariable()) + 1);
					id.setCurrentIncarnation(b.localIncarnationMap.get(id.getVariable()));
				}
			} else if (stmt instanceof CfgCallStatement) {
				CfgCallStatement call = (CfgCallStatement) stmt;
				recomputLocalSSA(call.getArguments(), b.localIncarnationMap);
				for (CfgVariable v : call.getCallee().getModifies()) {
					if (!b.localIncarnationMap.containsKey(v)) {
						b.localIncarnationMap.put(v, 0);
					}
					b.localIncarnationMap.put(v, b.localIncarnationMap.get(v) + 1);
				}
				for (CfgIdentifierExpression id : call.getLeftHandSide()) {
					if (!b.localIncarnationMap.containsKey(id.getVariable())) {
						b.localIncarnationMap.put(id.getVariable(), 0);
					}
					b.localIncarnationMap.put(id.getVariable(),
							b.localIncarnationMap.get(id.getVariable()) + 1);
					id.setCurrentIncarnation(b.localIncarnationMap.get(id.getVariable()));
				}
			} else if (stmt instanceof CfgHavocStatement) {
				for (CfgVariable v : ((CfgHavocStatement) stmt).getVariables()) {
					if (!b.localIncarnationMap.containsKey(v)) {
						b.localIncarnationMap.put(v, 0);
					}
					b.localIncarnationMap.put(v, b.localIncarnationMap.get(v) + 1);
				}
			}
		}
	}

	private void recomputLocalSSA(CfgExpression[] exp,
			HashMap<CfgVariable, Integer> offset) {
		if (exp == null) {
			return;
		}
		for (int i = 0; i < exp.length; i++) {
			recomputLocalSSA(exp[i], offset);
		}
	}

	private void recomputLocalSSA(CfgExpression exp,
			HashMap<CfgVariable, Integer> offset) {
		if (exp instanceof CfgArrayAccessExpression) {
			CfgArrayAccessExpression aae = (CfgArrayAccessExpression) exp;
			recomputLocalSSA(aae.getIndices(), offset);
			recomputLocalSSA(aae.getBaseExpression(), offset);
		} else if (exp instanceof CfgArrayStoreExpression) {
			CfgArrayStoreExpression ase = (CfgArrayStoreExpression) exp;
			recomputLocalSSA(ase.getValueExpression(), offset);
			recomputLocalSSA(ase.getIndices(), offset);
			recomputLocalSSA(ase.getBaseExpression(), offset);
		} else if (exp instanceof CfgBinaryExpression) {
			CfgBinaryExpression bexp = (CfgBinaryExpression) exp;
			recomputLocalSSA(bexp.getLeftOp(), offset);
			recomputLocalSSA(bexp.getRightOp(), offset);
		} else if (exp instanceof CfgBitVectorAccessExpression) {
			CfgBitVectorAccessExpression bva = (CfgBitVectorAccessExpression) exp;
			recomputLocalSSA(bva.getBitvector(), offset);
		} else if (exp instanceof CfgFunctionApplication) {
			CfgFunctionApplication fa = (CfgFunctionApplication) exp;
			recomputLocalSSA(fa.getArguments(), offset);
		} else if (exp instanceof CfgIdentifierExpression) {
			CfgIdentifierExpression id = (CfgIdentifierExpression) exp;
			if (!offset.containsKey(id.getVariable())) {
				offset.put(id.getVariable(), Integer.valueOf(0));
			}
			id.setCurrentIncarnation(offset.get(id.getVariable()));
		} else if (exp instanceof CfgIfThenElseExpression) {
			CfgIfThenElseExpression ite = (CfgIfThenElseExpression) exp;
			recomputLocalSSA(ite.getCondition(), offset);
			recomputLocalSSA(ite.getThenExpression(), offset);
			recomputLocalSSA(ite.getElseExpression(), offset);
		} else if (exp instanceof CfgQuantifierExpression) {
			CfgQuantifierExpression qe = (CfgQuantifierExpression) exp;
			//TODO: does that make sense?
			for (CfgVariable v : qe.getParameters()) {
				offset.put(v, 0);
			}
			recomputLocalSSA(((CfgQuantifierExpression) exp).getSubformula(), offset);
		} else if (exp instanceof CfgUnaryExpression) {
			CfgUnaryExpression uexp = (CfgUnaryExpression) exp;
			recomputLocalSSA(uexp.getExpression(), offset);
		}
	}
	

}
