/**
 * 
 */
package bixie.transformation;

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

import bixie.boogie.controlflow.BasicBlock;
import bixie.boogie.controlflow.CfgProcedure;
import bixie.boogie.controlflow.CfgVariable;
import bixie.boogie.controlflow.expression.CfgExpression;
import bixie.boogie.controlflow.expression.CfgIdentifierExpression;
import bixie.boogie.controlflow.statement.CfgAssertStatement;
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 CallUnwinding {

	public CallUnwinding () {
		
	}

	
	public void unwindCalls(CfgProcedure p) {
		Log.debug("eliminateCalls for: " + p.getProcedureName());
		BasicBlock root = p.getRootNode();

		LinkedList<BasicBlock> todo = new LinkedList<BasicBlock>();
		LinkedList<BasicBlock> done = new LinkedList<BasicBlock>();
		todo.add(root);
		while (!todo.isEmpty()) {
			BasicBlock current = todo.pop();
			done.add(current);
			/*
			 * For each BasicBlock, iterate over the statements. If a statement
			 * is a call, collect all variables in the modifies clause and in
			 * the LHS of the call statement and replace the call by a Havoc for
			 * all these variables.
			 */
			LinkedList<CfgStatement> statements = current.getStatements();
			//shallow copy for iteration ... needed because we're modifying "statements"
			LinkedList<CfgStatement> iterlist = new LinkedList<CfgStatement>(statements); 

			for (CfgStatement stmt : iterlist) {
				if (stmt instanceof CfgCallStatement) {
					CfgCallStatement call = (CfgCallStatement)stmt;					
					int offset = 0;
	
					LinkedList<CfgStatement> callcontract = new LinkedList<CfgStatement>();  
					//insert the assert statements that are enforced by the "requires" clauses
					//of the callee.
					HashMap<CfgVariable, CfgExpression> substitutes = new HashMap<CfgVariable, CfgExpression>();
					for (int j=offset; j<call.getCallee().getInParams().length; j++) {
						substitutes.put(call.getCallee().getInParams()[j], call.getArguments()[j]);
					}					
					
					for (CfgExpression xp : call.getCallee().getRequires()) {						
						callcontract.add(new CfgAssertStatement(call.getLocation(), xp.substitute(substitutes)));
					}

					//create the havoc statement for the modifies clause.
					HashSet<CfgVariable> modifies = new HashSet<CfgVariable>();
					modifies.addAll(call.getCallee().getModifies());
					for (CfgIdentifierExpression lhs : call.getLeftHandSide()) {
						modifies.add(lhs.getVariable());
					}
					CfgHavocStatement havoc = new CfgHavocStatement(
							call.getLocation(),
							modifies.toArray(new CfgVariable[modifies.size()]));
					// Log.error(" call: "+ call.toString());
					// Log.error(" becomes: "+ havoc.toString());
					havoc.setReplacedStatement(call);
					callcontract.add(havoc);
					
					
					//insert the assume statements that are guaranteed by the "ensures" clauses
					//of the callee.
					substitutes = new HashMap<CfgVariable, CfgExpression>();
					for (int j=offset; j<call.getCallee().getOutParams().length; j++) {
						substitutes.put(call.getCallee().getOutParams()[j], call.getLeftHandSide()[j]);
					}					
					for (CfgExpression xp : call.getCallee().getEnsures()) {
						if (statements.indexOf(stmt)<statements.size()-1) {
							statements.add(new CfgAssumeStatement(call.getLocation(), xp.substitute(substitutes)));
						} else {
							callcontract.add(new CfgAssumeStatement(call.getLocation(), xp.substitute(substitutes)));
						}
					}
					
					//now merge callcontract back into statements at and replace the original call stmt.					
					statements.addAll(statements.indexOf(stmt), callcontract);
					//and now remove the old call statement.
					statements.remove(stmt);					
				} else {
					//TODO
				}
			}
			current.setStatements(statements);
			
			for (BasicBlock next : current.getSuccessors()) {
				if (!todo.contains(next) && !done.contains(next)) {
					todo.add(next);
				}
			}
		}
	}
	
}
