/**
 * 
 */
package jayhorn.hornify.encoder;

import java.math.BigInteger;
import java.util.Map;

import jayhorn.hornify.HornEncoderContext;
import jayhorn.hornify.HornHelper;
import jayhorn.solver.Prover;
import jayhorn.solver.ProverExpr;
import jayhorn.solver.ProverTupleExpr;
import jayhorn.solver.ProverTupleType;
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.variable.ClassVariable;
import soottocfg.cfg.variable.Variable;

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

	private final Prover p;
	private final HornEncoderContext hornContext;

	/**
	 * 
	 */
	public ExpressionEncoder(Prover p, HornEncoderContext hornContext) {
		this.p = p;
		this.hornContext = hornContext;
	}

	public HornEncoderContext getContext() {
		return this.hornContext;
	}

	/**
	 * TODO: this is a hack!
	 * 
	 * @param id
	 * @return
	 */
	private ProverExpr typeIdToProverExpr(int id) {
		return p.mkLiteral(id);
	}
	
	
	
	public ProverExpr exprToProverExpr(Expression e, Map<Variable, ProverExpr> varMap) {
		if (e instanceof IdentifierExpression) {
			Variable var = ((IdentifierExpression) e).getVariable();
			if (var instanceof ClassVariable) {
				return typeIdToProverExpr(hornContext.getTypeID((ClassVariable) var));
				// return p.mkLiteral(hornContext.getTypeID((ClassVariable)
				// var));
			} else {
				if (hornContext.getProgram().getGlobalVariables().contains(var)) {
					/*
					 * For globals, we just use an integer number.
					 * NOTE: The translation guarantees that this number is unique and different
					 * from Null.
					 */
					int idx = hornContext.getProgram().getGlobalVariables().indexOf(var);
					return HornHelper.hh().findOrCreateUniqueVariable(p, var, varMap, idx);

//					return p.mkLiteral(hornContext.getProgram().getGlobalVariables().indexOf(var));
				} else {
					return HornHelper.hh().findOrCreateProverVar(p, var, varMap);
				}
			}
		} else if (e instanceof TupleAccessExpression) {
			TupleAccessExpression tae = (TupleAccessExpression) e;
			ProverExpr tuple = varMap.get(tae.getVariable());
			return p.mkTupleSelect(tuple, tae.getAccessPosition());
			// return p.mkVariable("HACK_FreeVar" + HornHelper.hh().newVarNum(),
			// HornHelper.hh().getProverType(p, tae.getType()));
		} else if (e instanceof IntegerLiteral) {
			return p.mkLiteral(BigInteger.valueOf(((IntegerLiteral) e).getValue()));
		} else if (e instanceof NullLiteral) {
			return p.mkLiteral(HornHelper.NullValue);
		} else if (e instanceof BinaryExpression) {
			final BinaryExpression be = (BinaryExpression) e;
			ProverExpr left = exprToProverExpr(be.getLeft(), varMap);
			// if (left instanceof ProverTupleType) {
			// //in that case only use the projection to the first element
			// //of the tuple.
			// left = p.mkTupleSelect(left, 0);
			// }
			ProverExpr right = exprToProverExpr(be.getRight(), varMap);
			// if (right instanceof ProverTupleType) {
			// //in that case only use the projection to the first element
			// //of the tuple.
			// Verify.verify(right instanceof ProverTupleExpr, be.getRight()+"
			// becomes " +right +" of type " + right.getType().getClass());
			// right = p.mkTupleSelect(right, 0);
			// }

			if (be.getLeft() instanceof NullLiteral && be.getRight() instanceof IdentifierExpression) {
				ProverTupleType ptt = (ProverTupleType)right.getType();
				left = HornHelper.hh().mkNullExpression(p, ptt.getSubTypes());
			}
			if (be.getRight() instanceof NullLiteral && be.getLeft() instanceof IdentifierExpression) {
				ProverTupleType ptt = (ProverTupleType)left.getType();
				right = HornHelper.hh().mkNullExpression(p, ptt.getSubTypes());
			}

			// TODO: the following choices encode Java semantics
			// of various operators; need a good schema to choose
			// how precise the encoding should be (probably
			// configurable)
			switch (be.getOp()) {
			case Plus:
				return p.mkPlus(left, right);
			case Minus:
				return p.mkMinus(left, right);
			case Mul:
				return p.mkMult(left, right);
			case Div:
				return p.mkTDiv(left, right);
			case Mod:
				return p.mkTMod(left, right);
			case Eq:
		        if (left instanceof ProverTupleExpr) {
		            ProverTupleExpr tLeft = (ProverTupleExpr)left;
		            ProverTupleExpr tRight = (ProverTupleExpr)right;
		            //TODO: this is sound if we assume that the first
		            //element of a tuple is the sound identifier.
		            return p.mkEq(tLeft.getSubExpr(0), tRight.getSubExpr(0));
		        }
				return p.mkEq(left, right);
			case Ne:
				return p.mkNot(p.mkEq(left, right));
			case Gt:
				return p.mkGt(left, right);
			case Ge:
				return p.mkGeq(left, right);
			case Lt:
				return p.mkLt(left, right);
			case Le:
				return p.mkLeq(left, right);
			case PoLeq:
				if ((be.getRight() instanceof IdentifierExpression)
						&& (((IdentifierExpression) be.getRight()).getVariable() instanceof ClassVariable)) {
					final ClassVariable var = (ClassVariable) ((IdentifierExpression) be.getRight()).getVariable();

					ProverExpr disj = p.mkLiteral(false);
					for (Integer i : this.hornContext.getSubtypeIDs(var)) {
						// ProverExpr tmp =
						// p.mkTupleSelect(typeIdToProverExpr(i), 0);
						ProverExpr tmp = typeIdToProverExpr(i);
						disj = p.mkOr(disj, p.mkEq(left, tmp));
					}

					return disj;
				} else {
					throw new RuntimeException("instanceof is only supported for concrete types");
				}
			case And:
				return p.mkAnd(left, right);
			case Or:
				return p.mkOr(left, right);
			case Implies:
				return p.mkImplies(left, right);
			case Shl:
			case Shr:
			case Ushr:
			case BAnd:
			case BOr:
			case Xor:
				return p.mkVariable("HACK_FreeVar" + HornHelper.hh().newVarNum(), p.getIntType());
			// Verify.verify(left.getType()==p.getIntType() &&
			// right.getType()==p.getIntType());
			// return binopFun.mkExpr(new ProverExpr[]{left, right});
			default: {
				throw new RuntimeException("Not implemented for " + be.getOp());
			}
			}
		} else if (e instanceof UnaryExpression) {
			final UnaryExpression ue = (UnaryExpression) e;
			final ProverExpr subExpr = exprToProverExpr(ue.getExpression(), varMap);

			// TODO: the following choices encode Java semantics
			// of various operators; need a good schema to choose
			// how precise the encoding should be (probably
			// configurable)
			switch (ue.getOp()) {
			case Neg:
				return p.mkNeg(subExpr);
			case LNot:
				return p.mkNot(subExpr);
			}
		} else if (e instanceof IteExpression) {
			final IteExpression ie = (IteExpression) e;
			final ProverExpr condExpr = exprToProverExpr(ie.getCondition(), varMap);
			final ProverExpr thenExpr = exprToProverExpr(ie.getThenExpr(), varMap);
			final ProverExpr elseExpr = exprToProverExpr(ie.getElseExpr(), varMap);
			return p.mkIte(condExpr, thenExpr, elseExpr);
		} else if (e instanceof BooleanLiteral) {
			return p.mkLiteral(((BooleanLiteral) e).getValue());
		}
		throw new RuntimeException("Expression type " + e + " not implemented!");
	}

}