package soottocfg.cfg.optimization.dataflow;

import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import soottocfg.cfg.LiveVars;
import soottocfg.cfg.expression.Expression;
import soottocfg.cfg.expression.literal.BooleanLiteral;
import soottocfg.cfg.method.CfgBlock;
import soottocfg.cfg.method.CfgEdge;
import soottocfg.cfg.method.Method;
import soottocfg.cfg.optimization.ExpressionEvaluator;
import soottocfg.cfg.optimization.UnreachableNodeRemover;
import soottocfg.cfg.statement.AssignStatement;
import soottocfg.cfg.statement.PullStatement;
import soottocfg.cfg.statement.Statement;
import soottocfg.util.SetOperations;

public class DeadCodeElimination  {
	// Given a method, eliminate the dead code in it
	public DeadCodeElimination() {
	}

	public static boolean eliminateDeadCode(Method m) {		
		LiveVars<CfgBlock> blockLiveVars = m.computeBlockLiveVariables();
		boolean changed = false;
		for (CfgBlock block : m.vertexSet()) {
			changed = eliminateDeadStatements(m, block, blockLiveVars) ? true : changed; 
			//TODO: this currently doesn't affect the value of changed 
			//because its not finished yet.
			eliminateDeadConditions(m, block);
		}
		UnreachableNodeRemover.pruneUnreachableNodes(m, m.getSource());
		return changed;
	}

	protected static boolean isDead(Statement stmt, LiveVars<Statement> liveVars) {
		if (!(stmt instanceof AssignStatement) && !(stmt instanceof PullStatement)) {
			// only assignments and pulls can be dead.
			return false;
		}
		// If a statement writes to only variables that are not live, we can
		// remove it!
		// I.e. if intersection s.lvals, s.live is empty
		return SetOperations.intersect(stmt.getDefVariables(), liveVars.liveOut.get(stmt)).isEmpty();
	}

	protected static boolean eliminateDeadStatements(Method method, CfgBlock block, LiveVars<CfgBlock> blockLiveVars) {
		boolean changed = false;
		List<Statement> rval = new LinkedList<Statement>();
		LiveVars<Statement> stmtLiveVars = block.computeLiveVariables(blockLiveVars);
		for (Statement s : block.getStatements()) {
			if (isDead(s, stmtLiveVars)) {
				// If the statements is dead, just remove it from the list
				changed = true;
			} else {
				// otherwise, it stays in the list
				rval.add(s.deepCopy());
			}
		}
		if (changed) {
			//only replace statements if something changed.
			block.setStatements(rval);
		}
		return changed;
	}

	/**
	 * Simplify the expressions on the edge labels. If an expression simplifies
	 * to True, we can remove the lable. If it simplifies to false, we can remove
	 * the edge.
	 * @param method
	 * @param block
	 * @return
	 */
	protected static boolean eliminateDeadConditions(Method method, CfgBlock block) {
		boolean changed = false;
		Set<CfgEdge> toRemove = new HashSet<CfgEdge>();

		for (CfgEdge edge : method.outgoingEdgesOf(block)) {
			if (edge.getLabel().isPresent()) {
				Expression simpleLabel = ExpressionEvaluator.simplify(edge.getLabel().get());
				if (simpleLabel instanceof BooleanLiteral) {
					if (((BooleanLiteral)simpleLabel).getValue()) {
						//then we can remove the label.
						edge.removeLabel();
					} else {
						//then we can remove the edge.
						toRemove.add(edge);
					}
				} else {
					//TODO : do not set the label for now because it causes
					//princess to fail during type checking.
//					edge.setLabel(simpleLabel);
				}
//				
//				Optional<Object> res = ExpressionEvaluator.eval(edge.getLabel().get());
//				if (res.isPresent()) {
//					if (!(Boolean)res.get()) {
//						// condition false, remove edge
//						toRemove.add(edge);
//					} else {
//						// condition true, remove all other edges?
//					}
//				} else {
//					// TODO?
//				}
			}
		}
				
		if (!toRemove.isEmpty()) {
			method.removeAllEdges(toRemove);
			changed = true;
		}
		return changed;
	}
}
