/**
 * 
 */
package jayhorn.utils;

import java.util.HashSet;
import java.util.Set;

import jayhorn.Options;
import jayhorn.hornify.encoder.S2H;
import soottocfg.cfg.Program;
import soottocfg.cfg.SourceLocation;
import soottocfg.cfg.expression.BinaryExpression;
import soottocfg.cfg.expression.BinaryExpression.BinaryOperator;
import soottocfg.cfg.expression.Expression;
import soottocfg.cfg.expression.IdentifierExpression;
import soottocfg.cfg.expression.literal.IntegerLiteral;
import soottocfg.cfg.method.CfgBlock;
import soottocfg.cfg.method.Method;
import soottocfg.cfg.statement.AssertStatement;
import soottocfg.cfg.statement.AssignStatement;
import soottocfg.cfg.statement.CallStatement;
import soottocfg.cfg.statement.NewStatement;
import soottocfg.cfg.statement.Statement;
import soottocfg.cfg.type.IntType;
import soottocfg.cfg.variable.Variable;

/**
 * @author schaef
 *
 */
public class HeapCounterTransformer {

	public static final String outHeapCounterName = "outHeapCounter";
	
	public HeapCounterTransformer() {
	}

	/**
	 * Add IDs to track the calling context. There are different
	 * strategies to do that. Currently, this only implements the
	 * approach of assigning a unqiue number to every call statement and
	 * passing that as extra parameter to all callees. The callee
	 * then adds that to all pushes and pulls.
	 * 
	 * @param p
	 */
	public void transform(Program p) {
		insertGlobalHeapCounter(p);
	}

	/**
	 * Each call statement gets a unique ID. Every time we call a method,
	 * we pass the ID of the caller as an argument. This ID is also
	 * passed into every pull and push.
	 * 
	 * @param p
	 */
	private void insertGlobalHeapCounter(Program p) {

		// start from 1 because zero is reserved for main.
		SourceLocation loc;

		for (Method m : p.getMethods()) {
			loc = m.getLocation();	
			Variable inCounter = new Variable("inHeapCounter", IntType.instance());
			//IdentifierExpression inExp = new IdentifierExpression(loc, inCounter);
			
			m.getInParams().add(inCounter);
			m.getReturnType().add(IntType.instance());
			if (m.getOutParams().isEmpty()) {
				throw new RuntimeException("unexpected");
			}
			Variable outCounter = new Variable(outHeapCounterName, IntType.instance());
			m.getOutParams().add(outCounter);
			
			if (m.isProgramEntryPoint()) {
				IdentifierExpression outExp = new IdentifierExpression(loc, outCounter);
				m.getSource().getStatements().add(0, new AssignStatement(m.getLocation(),
						outExp, new IntegerLiteral(loc, 1)));
				// Adding Heap Count bound checks
				int bound = Options.v().getHeapLimit();
				if (bound > -1) {
					//Expression diff = new BinaryExpression(loc, BinaryExpression.BinaryOperator.Minus, outExp, inExp);
					Expression assrt = new BinaryExpression(loc, BinaryExpression.BinaryOperator.Le, outExp, 
						new IntegerLiteral(loc, bound));
					m.getSink().getStatements().add(0, new AssertStatement(loc, assrt));
				}
			}else {
				m.getSource().getStatements().add(0, new AssignStatement(m.getLocation(),
						new IdentifierExpression(loc, outCounter), new IdentifierExpression(loc, inCounter)));
			}

			for (CfgBlock b : m.vertexSet()) {
				Set<NewStatement> newStmts = new HashSet<NewStatement>(); 
				for (Statement s : b.getStatements()) {
					loc = s.getSourceLocation();

					if (s instanceof CallStatement) {
						CallStatement cs = (CallStatement) s;
						cs.getArguments().add(new IdentifierExpression(loc, outCounter));
						//
						if (cs.getCallTarget().getSource()!=null) {
							cs.getReceiver().add(new IdentifierExpression(loc, outCounter));
						} else {
							cs.getReceiver().add(new IdentifierExpression(loc, new Variable("noCounter", IntType.instance())));
						}
						//cs.getReceiver().add(new IdentifierExpression(loc, outCounter));
					} else if (s instanceof NewStatement) {
						NewStatement ns = (NewStatement) s;
						ns.setCounterVar(outCounter);
						newStmts.add(ns);
					}
				}
				
				for (NewStatement ns : newStmts) {
					loc = ns.getSourceLocation();
					int idx = b.getStatements().indexOf(ns);
					BinaryExpression plusOne = new BinaryExpression(loc, BinaryOperator.Plus, new IdentifierExpression(loc, outCounter), IntegerLiteral.one());
					AssignStatement idxPlusPlus = new AssignStatement(loc,
							new IdentifierExpression(loc, outCounter), plusOne);					
					b.addStatement(idx, idxPlusPlus);
				}
				
			}
			
			// Track of in and out bound variable counters
			S2H.sh().setHeapCounter(m, inCounter, outCounter);
		}
	//System.err.println(p);

	}
}