/**
   * translate boogie statement to prover expression
   *
   * @param s
   * @return
   */
  protected ProverExpr statement2ProverExpr(Statement s) {
    if (s instanceof AssignStatement) {
      AssignStatement st = (AssignStatement) s;
      if (st.getLeft() instanceof SimpleHeapAccess || st.getLeft() instanceof ArrayReadExpression) {
        return arrayWrite2ProverExpr(st.getLeft(), expression2ProverExpr(st.getRight()));
      } else {

        return theoremProver.mkEq(
            expression2ProverExpr(st.getLeft()), expression2ProverExpr(st.getRight()));
      }
    } else if (s instanceof AssumeStatement) {
      AssumeStatement st = (AssumeStatement) s;
      return guardExpression2ProverExpr(st.getExpression());
    } else if (s instanceof AssertStatement) {
      AssertStatement st = (AssertStatement) s;
      return guardExpression2ProverExpr(st.getExpression());
    } else if (s instanceof InvokeStatement) {
      InvokeStatement st = (InvokeStatement) s;
      if (st.getReturnTargets().size() > 1) {
        Log.error("A method with too many return parameters! Should have been removed in SSA");
        return null;
      }
      ProverFun fun = procedure2ProverExpr(st.getInvokedProcedure());

      Log.error(
          "This should not happen as calls are removed by SSA: "
              + fun.toString()); // TODO: proper error handling?
      // This can never happen.
      LinkedList<ProverExpr> args = new LinkedList<ProverExpr>();
      for (Expression e : st.getArguments()) {
        args.add(expression2ProverExpr(e));
      }
      ProverExpr ret = fun.mkExpr((ProverExpr[]) args.toArray(new ProverExpr[args.size()]));

      if (st.getReturnTargets().size() > 0) {
        ProverExpr lhs = expression2ProverExpr(st.getReturnTargets().get(0));
        if (st.getReturnTargets().get(0) instanceof SimpleHeapAccess
            || st.getReturnTargets().get(0) instanceof ArrayReadExpression) {
          ret = arrayWrite2ProverExpr(st.getReturnTargets().get(0), ret);
          // ((NAryExpression)lhs).addArgument(ret);
          // ret = VCExpressionGenerator.binOp(TmpConstants.Eq,
          // ((NAryExpression)lhs).getArgument(0),lhs);
        } else {
          ret = theoremProver.mkEq(lhs, ret);
        }
      }
      return ret;
    } else if (s instanceof ExpressionStatement) {
      ExpressionStatement es = (ExpressionStatement) s;
      return expression2ProverExpr(es.getExpression());
    } else {
      Log.error("Unmatched Statement " + s.toString());
      return null;
    }
  }
  /**
   * generates a prover expression from an expression e
   *
   * @param e
   * @param boundvars
   * @return
   */
  private ProverExpr expression2ProverExpr(Expression e, HashMap<Variable, ProverExpr> boundvars) {
    try {
      if (e instanceof ArrayReadExpression) {
        ArrayReadExpression exp = (ArrayReadExpression) e;

        return arrayExpression2ProverExpr(
            exp.getBaseExpression(),
            new LinkedList<Expression>(Arrays.asList(new Expression[] {exp.getIndexExpression()})),
            null,
            boundvars);
      } else if (e instanceof InvokeExpression) {
        InvokeExpression exp = (InvokeExpression) e;
        ProverFun fun = procedure2ProverExpr(exp.getInvokedProcedure());
        LinkedList<ProverExpr> args = new LinkedList<ProverExpr>();
        for (Expression e2 : exp.getArguments()) {
          args.add(expression2ProverExpr(e2, boundvars));
        }
        return fun.mkExpr((ProverExpr[]) args.toArray(new ProverExpr[args.size()]));
      } else if (e instanceof SimpleHeapAccess) {
        SimpleHeapAccess exp = (SimpleHeapAccess) e;
        return arrayExpression2ProverExpr(
            exp.getHeapVariable(),
            new LinkedList<Expression>(
                Arrays.asList(
                    new Expression[] {exp.getBaseExpression(), exp.getFieldExpression()})),
            null,
            boundvars);
      } else if (e instanceof TypeExpression) {
        TypeExpression exp = (TypeExpression) e;
        return expression2ProverExpr(exp.getTypeVariable(), boundvars);
      } else if (e instanceof Variable) {
        Variable exp = (Variable) e;
        if (boundvars.containsKey(exp)) {
          return boundvars.get(exp);
        }
        if (!usedVariable.containsKey(exp)) {
          ProverExpr pe =
              theoremProver.mkVariable(
                  getProverFriendlyName(exp.getName()), type2ProverSort(exp.getType()));
          usedVariable.put(exp, pe);
        }
        return usedVariable.get(exp);
      } else if (e instanceof UboundedIntConstant) {
        UboundedIntConstant exp = (UboundedIntConstant) e;
        return theoremProver.mkLiteral(BigInteger.valueOf(exp.getValue()));
      } else if (e instanceof Constant) {
        Log.error("DEBUG-ERROR, there should not be any constant but Integers! VC will be wrong!");
        return expression2ProverExpr(BoogieProgram.v().getNullReference(), boundvars);
      } else if (e instanceof QuantifiedExpression) {
        // TODO: right now, Quantifiers can only be used in Axioms
        QuantifiedExpression qe = (QuantifiedExpression) e;
        HashMap<Variable, ProverExpr> bvars = new HashMap<Variable, ProverExpr>(boundvars);
        // LinkedList<ProverExpr> localBound = new
        // LinkedList<ProverExpr>();
        int idx = 0;
        for (Variable v : qe.getBoundVariables()) {
          ProverExpr boundvar = theoremProver.mkBoundVariable(idx++, type2ProverSort(v.getType()));
          bvars.put(v, boundvar);
          // localBound.add(boundvar);
        }
        ProverExpr boundExp = expression2ProverExpr(qe.getExpression(), bvars);
        if (qe.getQuantifier() == Quantifier.ForAll) {
          return theoremProver.mkAll(boundExp, theoremProver.getBooleanType());
        } else {
          return theoremProver.mkEx(boundExp, theoremProver.getBooleanType());
        }
      } else if (e instanceof BinOpExpression) {
        BinOpExpression be = (BinOpExpression) e;
        ProverExpr left = (expression2ProverExpr(be.getLhs(), boundvars));
        ProverExpr right = (expression2ProverExpr(be.getRhs(), boundvars));

        switch (be.getOp()) {
          case Eq:
            {
              return theoremProver.mkEq(left, right);
            }
          case Lt:
            {
              return theoremProver.mkLt(left, right);
            }
          case Le:
            {
              return theoremProver.mkLeq(left, right);
            }
          case Gt:
            {
              return theoremProver.mkGt(left, right);
            }
          case Ge:
            {
              return theoremProver.mkGeq(left, right);
            }
          case Neq:
            {
              return theoremProver.mkNot(theoremProver.mkEq(left, right));
            }
          case Implies:
            {
              return theoremProver.mkOr(theoremProver.mkNot(left), right);
            }
          case LAnd:
            {
              return theoremProver.mkAnd(left, right);
            }
          case LOr:
            {
              return theoremProver.mkOr(left, right);
            }
          case Plus:
            {
              return theoremProver.mkPlus(left, right);
            }
          case Minus:
            {
              return theoremProver.mkMinus(left, right);
            }
          case Mul:
            {
              return theoremProver.mkMult(left, right);
            }
          default:
            {
              throw new RuntimeException("Unmatched binop: " + be.getOp().toString());
              // TODO: a proper exception might be nice.
              // However, this case can only occur if we screw up the
              // implementation
            }
        }
      } else if (e instanceof IteExpression) {
        IteExpression ite = (IteExpression) e;
        ProverExpr condE = expression2ProverExpr(ite.getCond(), boundvars);
        ProverExpr thenE = expression2ProverExpr(ite.getThen(), boundvars);
        ProverExpr elseE = expression2ProverExpr(ite.getElse(), boundvars);
        return theoremProver.mkIte(condE, thenE, elseE);
      } else {
        Log.error("Unmatched Expression " + e.toString());
        return null;
      }
    } catch (Exception exc) {
      Log.error("expression2ProverExpr failed: " + exc.toString());
      Log.error("Called on expression: " + e.toString());
      if (e instanceof BinOpExpression) {
        BinOpExpression boe = (BinOpExpression) e;
        Log.error(boe.getLhs().getType().toString() + " and " + boe.getRhs().getType());
      }
      return null;
    }
  }