package soottocfg.cfg.optimization;

import java.util.LinkedList;
import java.util.List;

import com.google.common.base.Verify;

import soottocfg.cfg.expression.BinaryExpression;
import soottocfg.cfg.expression.Expression;
import soottocfg.cfg.expression.IdentifierExpression;
import soottocfg.cfg.expression.IteExpression;
import soottocfg.cfg.expression.TupleAccessExpression;
import soottocfg.cfg.expression.UnaryExpression;
import soottocfg.cfg.expression.literal.BooleanLiteral;
import soottocfg.cfg.expression.literal.IntegerLiteral;
import soottocfg.cfg.expression.literal.NullLiteral;
import soottocfg.cfg.method.CfgBlock;
import soottocfg.cfg.method.CfgEdge;
import soottocfg.cfg.method.Method;
import soottocfg.cfg.statement.AssertStatement;
import soottocfg.cfg.statement.AssignStatement;
import soottocfg.cfg.statement.AssumeStatement;
import soottocfg.cfg.statement.CallStatement;
import soottocfg.cfg.statement.NewStatement;
import soottocfg.cfg.statement.PullStatement;
import soottocfg.cfg.statement.PushStatement;
import soottocfg.cfg.statement.Statement;
import soottocfg.cfg.util.CfgVisitor;

//DSN should this be an abstract class
//DSN There might be value in tracking whether an expression actually changed.  Possible optimization
public class CfgUpdater extends CfgVisitor {

	protected Boolean changed = false;

	public boolean updateMethod(Method m) {
		changed = false;
		processMethod(m);
		return changed;
	}

	public boolean runFixpt(Method m) {
		boolean everChanged = false;
		while (updateMethod(m)) {
			everChanged = true;
		}
		return everChanged;
	}

	public CfgUpdater() {
	}

	@Override
	protected void processCfgBlock(CfgBlock block) {
		setCurrentCfgBlock(block);
		// We need to update the statements
		block.setStatements(processStatementList(block.getStatements()));
		// and also the graph conditions, in case those have been changed by the
		// analysis
		for (CfgEdge edge : block.getMethod().outgoingEdgesOf(block)) {
			if (edge.getLabel().isPresent()) {
				edge.setLabel(processExpression(edge.getLabel().get()));
			}
		}
		setCurrentCfgBlock(null);
	}

	@Override
	protected List<Statement> processStatementList(List<Statement> sl) {
		List<Statement> rval = new LinkedList<Statement>();
		for (Statement s : sl) {
			rval.add(processStatement(s));
		}
		return rval;
	}

	@Override
	protected Statement processStatement(AssertStatement s) {
		Expression e = processExpression(s.getExpression());
		return new AssertStatement(s.getSourceLocation(), e);
	}

	@Override
	protected Statement processStatement(PullStatement s) {
		List<IdentifierExpression> lhs = new LinkedList<IdentifierExpression>(); 
		for (IdentifierExpression l : s.getLeft()) {
			lhs.add((IdentifierExpression)processExpression(l));
		}
		IdentifierExpression obj = (IdentifierExpression) processExpression(s.getObject());
		return new PullStatement(s.getSourceLocation(), s.getClassSignature(), obj, lhs);
	}

	@Override
	protected Statement processStatement(PushStatement s) {
		List<Expression> rhs = new LinkedList<Expression>(); 
		for (Expression r : s.getRight()) {
			rhs.add(processExpression(r));
		}		
		IdentifierExpression obj = (IdentifierExpression) processExpression(s.getObject());
		return new PushStatement(s.getSourceLocation(), s.getClassSignature(), obj, rhs);
	}
	
	@Override
	protected Statement processStatement(AssignStatement s) {
		Expression lhs = processExpression(s.getLeft());
		Expression rhs = processExpression(s.getRight());
		return new AssignStatement(s.getSourceLocation(), lhs, rhs);
	}

	@Override
	protected Statement processStatement(AssumeStatement s) {
		Expression e = processExpression(s.getExpression());
		return new AssumeStatement(s.getSourceLocation(), e);
	}

	@Override
	protected Statement processStatement(CallStatement s) {
		List<Expression> args = processExpressionList(s.getArguments());
		List<Expression> rec = new LinkedList<Expression>();
		for (Expression e : s.getReceiver()) {
			rec.add(processExpression(e));
		}
		return new CallStatement(s.getSourceLocation(), s.getCallTarget(), args, rec);
	}
	
	@Override
	protected Statement processStatement(NewStatement s) {
		Expression e = processExpression(s.getLeft());
		Verify.verify(e instanceof IdentifierExpression);
		return new NewStatement(s.getSourceLocation(), (IdentifierExpression) e, s.getClassVariable());
	}
	
	/// Expressions
	@Override
	protected List<Expression> processExpressionList(List<Expression> el) {
		List<Expression> rval = new LinkedList<Expression>();
		for (Expression e : el) {
			rval.add(processExpression(e));
		}
		return rval;
	}

	@Override
	protected Expression processExpression(BinaryExpression e) {
		Expression left = processExpression(e.getLeft());
		Expression right = processExpression(e.getRight());
		return new BinaryExpression(e.getSourceLocation(), e.getOp(), left, right);
	}

	@Override
	protected Expression processExpression(BooleanLiteral e) {
		return e.deepCopy();
	}

	@Override
	protected Expression processExpression(NullLiteral e) {
		return e.deepCopy();
	}

	
	@Override
	protected Expression processExpression(IdentifierExpression e) {
		return e.deepCopy();
	}

	@Override
	protected Expression processExpression(IntegerLiteral e) {
		return e.deepCopy();
	}

	@Override
	protected Expression processExpression(TupleAccessExpression e) {
		return e.deepCopy();
	}

	@Override
	protected Expression processExpression(IteExpression ite) {
		Expression i = processExpression(ite.getCondition());
		Expression t = processExpression(ite.getThenExpr());
		Expression e = processExpression(ite.getElseExpr());
		return new IteExpression(ite.getSourceLocation(), i, t, e);
	}

	@Override
	protected Expression processExpression(UnaryExpression e) {
		Expression exp = processExpression(e.getExpression());
		return new UnaryExpression(e.getSourceLocation(), e.getOp(), exp);
	}
}
