/*! @brief From T|- QUANTIFIER (vars): e we get T|-QUANTIFIER(vars') e
 * where vars' is obtained from vars by removing all bound variables
 *  not used in e. If vars' is empty the produced theorem is just T|-e
 */
Theorem QuantTheoremProducer::boundVarElim(const Theorem& t1)
{
  const Expr e=t1.getExpr();
  const Expr body = e.getBody();
  if(CHECK_PROOFS) {
      CHECK_SOUND(e.isForall() || e.isExists(),
		"bound var elimination: "
		+e.toString());
  }
  ExprMap<bool> boundVars; //a mapping of bound variables in the body to true
  ExprMap<bool> visited; //to make sure expressions aren't traversed
			  //multiple times
  recFindBoundVars(body, boundVars, visited);
  vector<Expr> quantVars;
  const vector<Expr>& origVars = e.getVars();
  for(vector<Expr>::const_iterator it = origVars.begin(),
	iend=origVars.end(); it!=iend; ++it)
    {
    if(boundVars.count(*it) > 0)
      quantVars.push_back(*it);
    }

  // If all variables are used, just return the original theorem
  if(quantVars.size() == origVars.size())
    return t1;

  Proof pf;
  if(withProof()) {
    vector<Expr> es;
    vector<Proof> pfs;
    es.push_back(e);
    es.insert(es.end(), quantVars.begin(), quantVars.end());
    pfs.push_back(t1.getProof());
    pf= newPf("bound_variable_elimination", es, pfs);
  }
  if(quantVars.size() == 0)
    return(newTheorem(e.getBody(), t1.getAssumptionsRef(), pf));

  Expr newQuantExpr;
  if(e.isForall())
    newQuantExpr = d_theoryQuant->getEM()->newClosureExpr(FORALL, quantVars, body);
  else
    newQuantExpr = d_theoryQuant->getEM()->newClosureExpr(EXISTS, quantVars, body);

  return(newTheorem(newQuantExpr, t1.getAssumptionsRef(), pf));
}
//!adjust the order of bound vars, newBvs begin first
Theorem QuantTheoremProducer::adjustVarUniv(const Theorem& t1, const std::vector<Expr>& newBvs){
  const Expr e=t1.getExpr();
  const Expr body = e.getBody();
  if(CHECK_PROOFS) {
      CHECK_SOUND(e.isForall(),
		"adjustVarUniv: "
		+e.toString());
  }

  const vector<Expr>& origVars = e.getVars();


  ExprMap<bool> oldmap;
  for(vector<Expr>::const_iterator it = origVars.begin(),
	iend=origVars.end(); it!=iend; ++it)    {
    oldmap[*it]=true;
  }

  vector<Expr> quantVars;
  for(vector<Expr>::const_iterator it = newBvs.begin(),
	iend=newBvs.end(); it!=iend; ++it)    {
    if(oldmap.count(*it) > 0)
      quantVars.push_back(*it);
  }

  if(quantVars.size() == origVars.size())
    return t1;

  ExprMap<bool> newmap;
  for(vector<Expr>::const_iterator it = newBvs.begin(),
	iend=newBvs.end(); it!=iend; ++it)    {
    newmap[*it]=true;
  }

  for(vector<Expr>::const_iterator it = origVars.begin(),
	iend=origVars.end(); it!=iend; ++it)    {
    if(newmap.count(*it)<=0){
      quantVars.push_back(*it);
    };
  }

  Proof pf;
  if(withProof()) {
    vector<Expr> es;
    vector<Proof> pfs;
    es.push_back(e);
    es.insert(es.end(), quantVars.begin(), quantVars.end());
    pfs.push_back(t1.getProof());
    pf= newPf("adjustVarUniv", es, pfs);
  }

  Expr newQuantExpr;
  newQuantExpr = d_theoryQuant->getEM()->newClosureExpr(FORALL, quantVars, body);

  return(newTheorem(newQuantExpr, t1.getAssumptionsRef(), pf));
}
Theorem
QuantTheoremProducer::normalizeQuant(const Expr& quant) {
  if(CHECK_PROOFS) {
    CHECK_SOUND(quant.isForall()||quant.isExists(),
		"normalizeQuant: expr must be FORALL or EXISTS\n"
		+quant.toString());
  }


  std::map<Expr,int>::iterator typeIter;
  std::string base("_BD");
  int counter(0);

  vector<Expr> newVars;
  const std::vector<Expr>& cur_vars = quant.getVars();
  for(size_t j =0; j<cur_vars.size(); j++){
    Type t = cur_vars[j].getType();
    int typeIndex ;

    typeIter = d_typeFound.find(t.getExpr());

    if(d_typeFound.end() ==  typeIter){
      typeIndex = d_typeFound.size();
      d_typeFound[t.getExpr()] = typeIndex;
    }
    else{
      typeIndex = typeIter->second;
    }

    counter++;
    std::stringstream stringType;
    stringType << counter << "TY" << typeIndex ;
    std::string out_str = base + stringType.str();
    Expr newExpr = d_theoryQuant->getEM()->newBoundVarExpr(out_str, int2string(counter));
    newExpr.setType(t);
    newVars.push_back(newExpr);
  }

  vector<vector<Expr> > trigs = quant.getTriggers();
  for(size_t i = 0 ; i < trigs.size(); i++){
    for(size_t j = 0 ; j < trigs[i].size(); j++){
      trigs[i][j] = trigs[i][j].substExpr(cur_vars,newVars);
    }
  }

  Expr normBody = quant.getBody().substExpr(cur_vars,newVars);
  Expr normQuant = d_theoryQuant->getEM()->newClosureExpr(quant.isForall()?FORALL:EXISTS, newVars, normBody, trigs);

  Proof pf;
  if(withProof())
    pf = newPf("normalizeQuant", quant, normQuant);

  return newRWTheorem(quant, normQuant, Assumptions::emptyAssump(), pf);

}
Expr VCCmd::skolemizeAx(const Expr& e)
{
  vector<Expr>vars;
  const vector<Expr>boundVars = e.getVars();
  for(unsigned int i=0; i<boundVars.size(); i++) {
    Expr skolV(e.skolemExpr(i));
    vars.push_back(skolV);
  }
  Expr sub = e.getBody().substExpr(boundVars, vars);
  return e.iffExpr(sub);
}
//! find all bound variables in e and maps them to true in boundVars
void QuantTheoremProducer::recFindBoundVars(const Expr& e,
		           ExprMap<bool> & boundVars, ExprMap<bool> &visited)
{
  if(visited.count(e)>0)
    return;
  else
    visited[e] = true;
  if(e.getKind() == BOUND_VAR)
    boundVars[e] = true;
  if(e.getKind() == EXISTS || e.getKind() == FORALL)
    recFindBoundVars(e.getBody(), boundVars, visited);
  for(Expr::iterator it = e.begin(); it!=e.end(); ++it)
    recFindBoundVars(*it, boundVars, visited);

}
void VCCmd::findAxioms(const Expr& e,  ExprMap<bool>& skolemAxioms,
		       ExprMap<bool>& visited) {
  if(visited.count(e)>0)
    return;
  else visited[e] = true;
  if(e.isSkolem()) {
    skolemAxioms.insert(e.getExistential(), true);
    return;
  }
  if(e.isClosure()) {
    findAxioms(e.getBody(), skolemAxioms, visited);
  }
  if(e.arity()>0) {
    Expr::iterator end = e.end();
    for(Expr::iterator i = e.begin(); i!=end; ++i)
      findAxioms(*i, skolemAxioms, visited);
  }

}
//! convert (forall (x) ... forall (y)) into (forall (x y)...)
//! convert (exists (x) ... exists (y)) into (exists (x y)...)
Theorem QuantTheoremProducer::pullVarOut(const Theorem& t1){
  const Expr thm_expr=t1.getExpr();
  
  if(CHECK_PROOFS) {
    CHECK_SOUND(thm_expr.isForall() || thm_expr.isExists(),
		"pullVarOut: "
		+thm_expr.toString());
  }

  const Expr outBody = thm_expr.getBody();

//   if(((outBody.isAnd() && outBody[1].isForall()) ||
//        (outBody.isImpl() && outBody[1].isForall()) ||
//        (outBody.isNot() && outBody[0].isAnd() && outBody[0][1].isExists()) )){
//     return t1;
//   }

  if (thm_expr.isForall()){
    if((outBody.isNot() && outBody[0].isAnd() && outBody[0][1].isExists())){
      
      vector<Expr> bVarsOut = thm_expr.getVars();
      
      const Expr innerExists =outBody[0][1];
      const Expr innerBody = innerExists.getBody();
      vector<Expr> bVarsIn = innerExists.getVars();
      
      for(vector<Expr>::iterator i=bVarsIn.begin(), iend=bVarsIn.end(); i!=iend; i++){
	bVarsOut.push_back(*i);
      }
      
      Proof pf;
      if(withProof()) {
	vector<Expr> es;
	vector<Proof> pfs;
	es.push_back(thm_expr);
	es.insert(es.end(), bVarsIn.begin(), bVarsIn.end());
	pfs.push_back(t1.getProof());
	pf= newPf("pullVarOut", es, pfs);
      }
      
      Expr newbody;
      
      newbody=(outBody[0][0].notExpr()).orExpr(innerBody.notExpr());
      
      Expr newQuantExpr;
      newQuantExpr = d_theoryQuant->getEM()->newClosureExpr(FORALL, bVarsOut, newbody);
      
      return(newTheorem(newQuantExpr, t1.getAssumptionsRef(), pf));
    }
    
    else if ((outBody.isAnd() && outBody[1].isForall()) ||
	     (outBody.isImpl() && outBody[1].isForall())){
      
      vector<Expr> bVarsOut = thm_expr.getVars();
      
      const Expr innerForall=outBody[1];
      const Expr innerBody = innerForall.getBody();
      vector<Expr> bVarsIn = innerForall.getVars();
      
      for(vector<Expr>::iterator i=bVarsIn.begin(), iend=bVarsIn.end(); i!=iend; i++){
	bVarsOut.push_back(*i);
      }
      
      Proof pf;
      if(withProof()) {
	vector<Expr> es;
	vector<Proof> pfs;
	es.push_back(thm_expr);
	es.insert(es.end(), bVarsIn.begin(), bVarsIn.end());
	pfs.push_back(t1.getProof());
	pf= newPf("pullVarOut", es, pfs);
      }
      
      Expr newbody;
      if(outBody.isAnd()){
	newbody=outBody[0].andExpr(innerBody);
      }
      else if(outBody.isImpl()){
	newbody=outBody[0].impExpr(innerBody);
      }
      
      Expr newQuantExpr;
      newQuantExpr = d_theoryQuant->getEM()->newClosureExpr(FORALL, bVarsOut, newbody);
      
      return(newTheorem(newQuantExpr, t1.getAssumptionsRef(), pf));
    }
    return t1; // case cannot be handled now. 
  }
  
  else if (thm_expr.isExists()){
    if ((outBody.isAnd() && outBody[1].isExists()) ||
	(outBody.isImpl() && outBody[1].isExists())){
      
      vector<Expr> bVarsOut = thm_expr.getVars();
      
      const Expr innerExists = outBody[1];
      const Expr innerBody = innerExists.getBody();
      vector<Expr> bVarsIn = innerExists.getVars();
      
      for(vector<Expr>::iterator i=bVarsIn.begin(), iend=bVarsIn.end(); i!=iend; i++){
	bVarsOut.push_back(*i);
      }
      
      Proof pf;
      if(withProof()) {
	vector<Expr> es;
	vector<Proof> pfs;
	es.push_back(thm_expr);
	es.insert(es.end(), bVarsIn.begin(), bVarsIn.end());
	pfs.push_back(t1.getProof());
	pf= newPf("pullVarOut", es, pfs);
      }
      
      Expr newbody;
      if(outBody.isAnd()){
	newbody=outBody[0].andExpr(innerBody);
      }
      else if(outBody.isImpl()){
	newbody=outBody[0].impExpr(innerBody);
      }
      
      Expr newQuantExpr;
      newQuantExpr = d_theoryQuant->getEM()->newClosureExpr(EXISTS, bVarsOut, newbody);
      
      return(newTheorem(newQuantExpr, t1.getAssumptionsRef(), pf));
    }
  }
  return t1; 
}
Theorem QuantTheoremProducer::partialUniversalInst(const Theorem& t1, const vector<Expr>& terms, int quantLevel){
  cout<<"error in partial inst" << endl;
  Expr e = t1.getExpr();
  const vector<Expr>& boundVars = e.getVars();
  if(CHECK_PROOFS) {
    CHECK_SOUND(boundVars.size() >= terms.size(),
		"Universal instantiation: size of terms array does "
		"not match quanitfied variables array size");
    CHECK_SOUND(e.isForall(),
		"universal instantiation: expr must be FORALL:\n"
		+e.toString());
    for(unsigned int i=0; i<terms.size(); i++){
      CHECK_SOUND(d_theoryQuant->getBaseType(boundVars[i]) ==
                  d_theoryQuant->getBaseType(terms[i]),
	      "partial Universal instantiation: type mismatch");
    }
  }

  //build up a conjunction of type predicates for expression
  Expr tr = e.getEM()->trueExpr();
  Expr typePred = tr;
  for(unsigned int i=0; i<terms.size(); i++) {
    Expr p = d_theoryQuant->getTypePred(boundVars[i].getType(),terms[i]);
    if(p!=tr) {
      if(typePred==tr)
	typePred = p;
      else
	typePred = typePred.andExpr(p);
    }
  }
  Proof pf;
  if(withProof()) {
    vector<Proof> pfs;
    vector<Expr> es;
    pfs.push_back(t1.getProof());
    es.push_back(e);
    es.insert(es.end(), terms.begin(), terms.end());
    pf= newPf("partial_universal_instantiation", es, pfs);
  }


  if(terms.size() == boundVars.size()){
    Expr inst = e.getBody().substExpr(e.getVars(), terms);
    Expr imp;
    if(typePred == tr)
      imp = inst;
    else
      imp = typePred.impExpr(inst);
    return(newTheorem(imp, t1.getAssumptionsRef(), pf));
  }
  else{
    vector<Expr> newBoundVars;
    for(size_t i=0; i<terms.size(); i++) {
      newBoundVars.push_back(boundVars[i]);
    }

    vector<Expr>leftBoundVars;
    for(size_t i=terms.size(); i<boundVars.size(); i++) {
      leftBoundVars.push_back(boundVars[i]);
    }

    Expr tempinst = e.getBody().substExpr(newBoundVars, terms);
    Expr inst = d_theoryQuant->getEM()->newClosureExpr(FORALL, leftBoundVars, tempinst);

    Expr imp;
    if(typePred == tr)
      imp = inst;
    else
      imp = typePred.impExpr(inst);

    Theorem res = (newTheorem(imp, t1.getAssumptionsRef(), pf));
    int thmLevel = t1.getQuantLevel();
    if(quantLevel >= thmLevel) {
      res.setQuantLevel(quantLevel+1);
    }
    else{
      //k      ret.setQuantLevel(thmLevel+1);
      res.setQuantLevel(thmLevel);
    }
    return res;

  }
}
Theorem QuantTheoremProducer::universalInst(const Theorem& t1, const  vector<Expr>& terms){

  Expr e = t1.getExpr();
  const vector<Expr>& boundVars = e.getVars();
  if(CHECK_PROOFS) {
    CHECK_SOUND(boundVars.size() == terms.size(),
		"Universal instantiation: size of terms array does "
		"not match quanitfied variables array size");
    CHECK_SOUND(e.isForall(),
		"universal instantiation: expr must be FORALL:\n"
		+e.toString());
    for(unsigned int i=0; i<terms.size(); i++)
      CHECK_SOUND(d_theoryQuant->getBaseType(boundVars[i]) ==
                  d_theoryQuant->getBaseType(terms[i]),
	      "Universal instantiation: type mismatch");
  }

  //build up a conjunction of type predicates for expression
  Expr tr = e.getEM()->trueExpr();
  Expr typePred = tr;
  unsigned qlevel=0, qlevelMax = 0;
  for(unsigned int i=0; i<terms.size(); i++) {
    Expr p = d_theoryQuant->getTypePred(boundVars[i].getType(),terms[i]);
    if(p!=tr) {
      if(typePred==tr)
	typePred = p;
      else
	typePred = typePred.andExpr(p);
    }
    qlevel = d_theoryQuant->theoryCore()->getQuantLevelForTerm(terms[i]);
    if (qlevel > qlevelMax) qlevel = qlevelMax;
  }

  Expr inst = e.getBody().substExpr(e.getVars(), terms);
  //  Expr inst = e.getBody().substExprQuant(e.getVars(), terms);


  //  Expr inst = e.getBody().substExpr(e.getVars(), terms);

  Proof pf;
  if(withProof()) {
    vector<Proof> pfs;
    vector<Expr> es;
    pfs.push_back(t1.getProof());
    es.push_back(e);
    es.push_back(Expr(RAW_LIST,terms));
    //    es.insert(es.end(), terms.begin(), terms.end());
    es.push_back(inst);
    pf= newPf("universal_elimination3", es, pfs);
  }

  //   Expr inst = e.getBody().substExpr(e.getVars(), terms);

   Expr imp;
   if( typePred == tr ) //just for easy life, yeting, change this asap
     imp = inst;
   else
     imp = typePred.impExpr(inst);
   Theorem ret = newTheorem(imp, t1.getAssumptionsRef(), pf);


   unsigned thmLevel = t1.getQuantLevel();
   if(qlevel >= thmLevel) {
      ret.setQuantLevel(qlevel+1);
    }
    else{
      //      ret.setQuantLevel(thmLevel+1);
      ret.setQuantLevel(thmLevel+1);
    }


   //   ret.setQuantLevel(qlevel+1);
   return ret;
}