/*****************************************************************************/
/*!
 * \file search_impl_base.cpp
 * 
 * Author: Clark Barrett, Vijay Ganesh (CNF Converter)
 * 
 * Created: Fri Jan 17 14:19:54 2003
 *
 * <hr>
 *
 * License to use, copy, modify, sell and/or distribute this software
 * and its documentation for any purpose is hereby granted without
 * royalty, subject to the terms and conditions defined in the \ref
 * LICENSE file provided with this distribution.
 * 
 * <hr>
 * 
 */
/*****************************************************************************/


#include "search_impl_base.h"
#include "theory.h"
#include "eval_exception.h"
#include "search_rules.h"
#include "variable.h"
#include "command_line_flags.h"
#include "statistics.h"
#include "theorem_manager.h"
#include "expr_transform.h"


using namespace std;


namespace CVC3 {


  class CoreSatAPI_implBase :public TheoryCore::CoreSatAPI {
    SearchImplBase* d_se;
  public:
    CoreSatAPI_implBase(SearchImplBase* se) : d_se(se) {}
    virtual ~CoreSatAPI_implBase() {}
    virtual void addLemma(const Theorem& thm) { d_se->addFact(thm); }
    virtual Theorem addAssumption(const Expr& assump)
      { return d_se->newIntAssumption(assump); }
    virtual void addSplitter(const Expr& e, int priority)
      { d_se->addSplitter(e, priority); }
    virtual bool check(const Expr& e);
  };

bool CoreSatAPI_implBase::check(const Expr& e)
{
  Theorem thm;
  int scope = d_se->theoryCore()->getCM()->scopeLevel();
  d_se->theoryCore()->getCM()->push();
  QueryResult res = d_se->checkValid(e, thm);
  d_se->theoryCore()->getCM()->popto(scope);
  return res == VALID;
}



}


using namespace CVC3;


// class SearchImplBase::Splitter methods

// Constructor
SearchImplBase::Splitter::Splitter(const Literal& lit): d_lit(lit) {
d_lit.count()++;
  TRACE("Splitter", "Splitter(", d_lit, ")");
}

// Copy constructor
SearchImplBase::Splitter::Splitter(const SearchImplBase::Splitter& s)
  : d_lit(s.d_lit) {
  d_lit.count()++;
  TRACE("Splitter", "Splitter[copy](", d_lit, ")");
}

// Assignment
SearchImplBase::Splitter&
SearchImplBase::Splitter::operator=(const SearchImplBase::Splitter& s) {
  if(this == &s) return *this; // Self-assignment
  d_lit.count()--;
  d_lit = s.d_lit;
  d_lit.count()++;
  TRACE("Splitter", "Splitter[assign](", d_lit, ")");
  return *this;
}

// Destructor
SearchImplBase::Splitter::~Splitter() {
  d_lit.count()--;
  TRACE("Splitter", "~Splitter(", d_lit, ")");
}



//! Constructor
SearchImplBase::SearchImplBase(TheoryCore* core)
  : SearchEngine(core),
    d_bottomScope(core->getCM()->getCurrentContext()),
    d_dpSplitters(core->getCM()->getCurrentContext()),
    d_lastValid(d_commonRules->trueTheorem()),
    d_assumptions(core->getCM()->getCurrentContext()),
    d_cnfCache(core->getCM()->getCurrentContext()),
    d_cnfVars(core->getCM()->getCurrentContext()),
    d_cnfOption(&(core->getFlags()["cnf"].getBool())),
    d_ifLiftOption(&(core->getFlags()["iflift"].getBool())),
    d_ignoreCnfVarsOption(&(core->getFlags()["ignore-cnf-vars"].getBool())),
    d_origFormulaOption(&(core->getFlags()["orig-formula"].getBool())),
    d_enqueueCNFCache(core->getCM()->getCurrentContext()),
    d_applyCNFRulesCache(core->getCM()->getCurrentContext()),
    d_replaceITECache(core->getCM()->getCurrentContext())
{
  d_vm = new VariableManager(core->getCM(), d_rules,
			     core->getFlags()["mm"].getString());
  IF_DEBUG(d_assumptions.setName("CDList[SearchImplBase::d_assumptions]"));
  d_coreSatAPI_implBase = new CoreSatAPI_implBase(this);
  core->registerCoreSatAPI(d_coreSatAPI_implBase);
}


//! Destructor
SearchImplBase::~SearchImplBase()
{
  delete d_coreSatAPI_implBase;
  delete d_vm;
}


// Returns a new theorem if e has not already been asserted, otherwise returns
// a NULL theorem.
Theorem SearchImplBase::newUserAssumption(const Expr& e) {
  Theorem thm;
  CDMap<Expr,Theorem>::iterator i(d_assumptions.find(e));
  IF_DEBUG(if(debugger.trace("search verbose")) {
    ostream& os(debugger.getOS());
    os << "d_assumptions = [";
    for(CDMap<Expr,Theorem>::iterator i=d_assumptions.begin(),
	  iend=d_assumptions.end(); i!=iend; ++i) {
      debugger.getOS() << "(" << (*i).first << " => " << (*i).second << "), ";
    }
    os << "]" << endl;
  });
  if(i!=d_assumptions.end()) {
    TRACE("search verbose", "newUserAssumption(", e,
	  "): assumption already exists");
  } else {
    thm = d_commonRules->assumpRule(e);
    d_assumptions[e] = thm;
    e.setUserAssumption();
    TRACE("search verbose", "newUserAssumption(", thm,
	  "): created new assumption");
  }
  if (!thm.isNull()) d_core->addFact(d_core->getExprTrans()->preprocess(thm));
  return thm;
}


// Same as newUserAssertion, except it's an error to assert something that's
// already been asserted.
Theorem SearchImplBase::newIntAssumption(const Expr& e) {
  Theorem thm = d_commonRules->assumpRule(e);
  Expr atom = e.isNot() ? e[0] : e;
  thm.setQuantLevel(theoryCore()->getQuantLevelForTerm(atom));
  newIntAssumption(thm);
  return thm;
}


void SearchImplBase::newIntAssumption(const Theorem& thm) {
  DebugAssert(!d_assumptions.count(thm.getExpr()),
	      "newIntAssumption: repeated assertion: "+thm.getExpr().toString());
    d_assumptions[thm.getExpr()] = thm;
    thm.getExpr().setIntAssumption();
    TRACE("search verbose", "newIntAssumption(", thm,
	  "): new assumption");
}


void SearchImplBase::getUserAssumptions(vector<Expr>& assumptions)
{
  for(CDMap<Expr,Theorem>::orderedIterator i=d_assumptions.orderedBegin(),
	iend=d_assumptions.orderedEnd(); i!=iend; ++i)
    if ((*i).first.isUserAssumption()) assumptions.push_back((*i).first);
}


void SearchImplBase::getInternalAssumptions(std::vector<Expr>& assumptions)
{
  for(CDMap<Expr,Theorem>::orderedIterator i=d_assumptions.orderedBegin(),
	iend=d_assumptions.orderedEnd(); i!=iend; ++i)
    if ((*i).first.isIntAssumption()) assumptions.push_back((*i).first);
}


void SearchImplBase::getAssumptions(std::vector<Expr>& assumptions)
{
  for(CDMap<Expr,Theorem>::orderedIterator i=d_assumptions.orderedBegin(),
	iend=d_assumptions.orderedEnd(); i!=iend; ++i)
    assumptions.push_back((*i).first);
}


bool SearchImplBase::isAssumption(const Expr& e) {
  return d_assumptions.count(e) > 0;
}


void SearchImplBase::addCNFFact(const Theorem& thm, bool fromCore) {
  TRACE("addCNFFact", "addCNFFact(", thm.getExpr(), ") {");
  if(thm.isAbsLiteral()) {
    addLiteralFact(thm);
    // These literals are derived, and  should also be reported to the core.  
    if(!fromCore) d_core->enqueueFact(thm);
  } else {
    addNonLiteralFact(thm);
  }
  TRACE_MSG("addCNFFact", "addCNFFact => }");
}


void SearchImplBase::addFact(const Theorem& thm) {
  TRACE("search addFact", "SearchImplBase::addFact(", thm.getExpr(), ") {");
  if(*d_cnfOption)
    enqueueCNF(thm);
  else
    addCNFFact(thm, true);
  TRACE_MSG("search addFact", "SearchImplBase::addFact => }");
}


void SearchImplBase::addSplitter(const Expr& e, int priority) {
  DebugAssert(e.isAbsLiteral(), "SearchImplBase::addSplitter("+e.toString()+")");
  d_dpSplitters.push_back(Splitter(newLiteral(e)));
}


void SearchImplBase::getCounterExample(vector<Expr>& assertions, bool inOrder)
{
  if(!d_core->getTM()->withAssumptions())
    throw EvalException
      ("Method getCounterExample() (or command COUNTEREXAMPLE) cannot be used\n"
       " without assumptions activated");
  if(!d_lastValid.isNull())
    throw EvalException
      ("Method getCounterExample() (or command COUNTEREXAMPLE)\n"
       " must be called only after failed QUERY");
  getInternalAssumptions(assertions);
  /*

  if(!d_lastCounterExample.empty()) {
    if (inOrder) {
      for(CDMap<Expr,Theorem>::orderedIterator i=d_assumptions.orderedBegin(),
            iend=d_assumptions.orderedEnd(); i!=iend; ++i) {
        if (d_lastCounterExample.find((*i).first) != d_lastCounterExample.end()) {
          assertions.push_back((*i).first);
        }
      }
      DebugAssert(assertions.size()==d_lastCounterExample.size(),
                  "getCounterExample: missing assertion");
    }
    else {
      ExprHashMap<bool>::iterator i=d_lastCounterExample.begin(),
        iend = d_lastCounterExample.end();
      for(; i!=iend; ++i) assertions.push_back((*i).first);
    }
  }
  */
}


Proof 
SearchImplBase::getProof()
{
  if(!d_core->getTM()->withProof())
    throw EvalException
      ("DUMP_PROOF cannot be used without proofs activated");
  if(d_lastValid.isNull())
    throw EvalException
      ("DUMP_PROOF must be called only after successful QUERY");
  // Double-check for optimized version
  if(d_lastValid.isNull()) return Proof();
  return d_lastValid.getProof();
}


const Assumptions&
SearchImplBase::getAssumptionsUsed()
{
  if(!d_core->getTM()->withAssumptions())
    throw EvalException
      ("DUMP_ASSUMPTIONS cannot be used without assumptions activated");
  if(d_lastValid.isNull())
    throw EvalException
      ("DUMP_ASSUMPTIONS must be called only after successful QUERY");
  // Double-check for optimized version
  if(d_lastValid.isNull()) return Assumptions::emptyAssump();
  return d_lastValid.getAssumptionsRef();
}

/*! 
 * Given a proof of FALSE ('res') from an assumption 'e', derive the
 * proof of the inverse of e.
 *
 * \param res is a proof of FALSE
 * \param e is the assumption used in the above proof
 */
void SearchImplBase::processResult(const Theorem& res, const Expr& e)
{
  // Result must be either Null (== SAT) or false (== unSAT)
  DebugAssert(res.isNull() || res.getExpr().isFalse(),
	      "processResult: bad input"
	      + res.toString());
  if(res.isNull()) {
    // Didn't prove valid (if CVC-lite is complete, it means invalid).
    // The assumptions for the counterexample must have been already
    // set by checkSAT().
    d_lastValid = Theorem(); // Reset last proof
    // Remove !e, keep just the counterexample
    d_lastCounterExample.erase(!e);
    if (e.isNot()) d_lastCounterExample.erase(e[0]);
  } else {
    // Proved valid
    Theorem res2 =
      d_rules->eliminateSkolemAxioms(res, d_commonRules->getSkolemAxioms());
    if(e.isNot())
      d_lastValid = d_rules->negIntro(e, res2);
    else
      d_lastValid = d_rules->proofByContradiction(e, res2);
    d_lastCounterExample.clear(); // Reset counterexample
  }
}


QueryResult SearchImplBase::checkValid(const Expr& e, Theorem& result) {
  d_commonRules->clearSkolemAxioms();
  QueryResult qres = checkValidInternal(e);
  result = d_lastValid;
  return qres;
}


QueryResult SearchImplBase::restart(const Expr& e, Theorem& result) {
  QueryResult qres = restartInternal(e);
  result = d_lastValid;
  return qres;
}


void 
SearchImplBase::enqueueCNF(const Theorem& beta) {
  TRACE("mycnf", "enqueueCNF(", beta, ") {");
  if(*d_origFormulaOption)
    addCNFFact(beta);

  enqueueCNFrec(beta);
  TRACE_MSG("mycnf", "enqueueCNF => }");
}


void 
SearchImplBase::enqueueCNFrec(const Theorem& beta) {
  Theorem theta = beta;

  TRACE("mycnf","enqueueCNFrec(", theta.getExpr(), ") { ");
  TRACE("cnf-clauses", "enqueueCNF call", theta.getExpr(), "");
  // The theorem scope shouldn't be higher than current
  DebugAssert(theta.getScope() <= scopeLevel(),
	      "\n theta.getScope()="+int2string(theta.getScope())
	      +"\n scopeLevel="+int2string(scopeLevel())
	      +"\n e = "+theta.getExpr().toString());

  Expr thetaExpr = theta.getExpr();

  if(d_enqueueCNFCache.count(thetaExpr) > 0) {
    TRACE_MSG("mycnf", "enqueueCNFrec[cached] => }");
    return;
  }

  d_enqueueCNFCache[thetaExpr] = true;

  //   // Strip double negations first
  while(thetaExpr.isNot() && thetaExpr[0].isNot()) {
    theta = d_commonRules->notNotElim(theta);
    thetaExpr = theta.getExpr();
    // Cache all the derived theorems too
    d_enqueueCNFCache[thetaExpr] = true;
  }

  // Check for a propositional literal
  if(thetaExpr.isPropLiteral()) {
    theta = d_commonRules->iffMP(theta, replaceITE(thetaExpr));
    DebugAssert(!*d_ifLiftOption || theta.isAbsLiteral(),
		"Must be a literal: theta = "
		+theta.getExpr().toString());
    addCNFFact(theta);
    TRACE_MSG("mycnf", "enqueueCNFrec[literal] => }");
    TRACE("cnf-clauses", "literal with proofs(", theta.getExpr(), ")");
    return;
  }
  
  thetaExpr = theta.getExpr();
  // Obvious optimizations for AND and OR  
  switch(thetaExpr.getKind()) {
  case AND:
    // Split up the top-level conjunction and translate individually
    for(int i=0; i<thetaExpr.arity(); i++)
      enqueueCNFrec(d_commonRules->andElim(theta, i));
    TRACE_MSG("mycnf", "enqueueCNFrec[AND] => }");
    return;
  case OR: {
    // Check if we are already in CNF (ignoring ITEs in the terms),
    // and if we are, translate term ITEs on the way
    bool cnf(true);
    TRACE("bv mycnf", "enqueueCNFrec[OR] ( ", theta.getExpr().toString(), ")");
    for(Expr::iterator i=thetaExpr.begin(),iend=thetaExpr.end();
	i!=iend && cnf; i++) {
      if(!(*i).isPropLiteral())
	cnf = false;
    }
    if(cnf) {
      vector<Theorem> thms;
      vector<unsigned int> changed;
      unsigned int cc=0;
      for(Expr::iterator i=thetaExpr.begin(),iend=thetaExpr.end();
	  i!=iend; i++,cc++) {
	Theorem thm = replaceITE(*i);	
	if(thm.getLHS() != thm.getRHS()) {
	  thms.push_back(thm);
	  changed.push_back(cc);
	}
      }
      if(changed.size() > 0) {
	Theorem rewrite =
	  d_commonRules->substitutivityRule(theta.getExpr(), changed, thms);
	theta = d_commonRules->iffMP(theta, rewrite);
      }
      addCNFFact(theta);
      TRACE_MSG("mycnf", "enqueueCNFrec[cnf] => }");
      return;           
    }
    break;
  }
  case IFF: { // Check for "var <=> phi" and "phi <=> var"
    const Expr& t0 = thetaExpr[0];
    const Expr& t1 = thetaExpr[1];
    if(t1.isPropLiteral()) {
      if(!t1.isAbsLiteral())
	theta = d_commonRules->transitivityRule(theta, replaceITE(t1));
      applyCNFRules(theta);
      return;
    } else if(thetaExpr[0].isPropLiteral()) {
      theta = d_commonRules->symmetryRule(theta);
      if(!t0.isAbsLiteral())
	theta = d_commonRules->transitivityRule(theta, replaceITE(t0));
      applyCNFRules(theta);
      return;
    }
    break;
  }
  case ITE:
    if(thetaExpr[0].isPropLiteral() && thetaExpr[1].isPropLiteral()
       && thetaExpr[2].isPropLiteral()) {
      // Replace ITEs
      vector<Theorem> thms;
      vector<unsigned int> changed;
      for(int c=0, cend=thetaExpr.arity(); c<cend; ++c) {
	Theorem thm = replaceITE(thetaExpr[c]);
	if(thm.getLHS() != thm.getRHS()) {
	  DebugAssert(!*d_ifLiftOption || thm.getRHS().isAbsLiteral(),
		      "thm = "+thm.getExpr().toString());
	  thms.push_back(thm);
	  changed.push_back(c);
	}
      }
      if(changed.size() > 0) {
	Theorem rewrite =
	  d_commonRules->substitutivityRule(theta.getExpr(), changed, thms);
	theta = d_commonRules->iffMP(theta, rewrite);
      }
      // Convert to clauses
      Theorem thm = d_rules->iteToClauses(theta);
      DebugAssert(thm.getExpr().isAnd() && thm.getExpr().arity()==2,
		  "enqueueCNFrec [ITE over literals]\n thm = "
		  +thm.getExpr().toString());
      addCNFFact(d_commonRules->andElim(thm, 0));
      addCNFFact(d_commonRules->andElim(thm, 1));
      return;
    }
    break;
  default: 
    break;
  }

  // Now do the real work
  Theorem res = findInCNFCache(theta.getExpr());
  if(!res.isNull()) {
    Theorem thm = d_commonRules->iffMP(theta, res);
    DebugAssert(thm.isAbsLiteral(), "thm = "+thm.getExpr().toString());
    addCNFFact(thm);
    TRACE("cnf-clauses", "enqueueCNFrec call[cache hit]:(",
	  thm.getExpr(), ")");
    applyCNFRules(res);
    TRACE_MSG("mycnf", "enqueueCNFrec[cached] => }");
    return;
  }

  //  std::vector<Theorem> clauses;
  Theorem result;
  // (\phi <==> v)
  result = d_commonRules->varIntroSkolem(theta.getExpr());

  TRACE("mycnf", "enqueueCNFrec: varIntroSkolem => ", result.getExpr(),
	" @ "+int2string(result.getScope())
	+" (current="+int2string(scopeLevel())+")");

  //result = skolemize(result, false);

  TRACE("mycnf", "enqueueCNFrec: skolemize => ", result.getExpr(),
	" @ "+int2string(result.getScope())
	+" (current="+int2string(scopeLevel())+")");
  DebugAssert(result.isRewrite(),
	      "SearchImplBase::CNF(): result = "+result.toString());
  DebugAssert(!result.getLHS().isPropLiteral() && 
	      result.getRHS().isPropLiteral(),
	      "SearchImplBase::CNF(): result = "+result.toString());
  DebugAssert(result.getLHS() == theta.getExpr(),
	      "SearchImplBase::CNF(): result = "+result.toString()
	      +"\n theta = "+theta.toString());
  
  //enqueue v
  Theorem var(d_commonRules->iffMP(theta, result));
  // Add variable to the set of CNF vars
  d_cnfVars[var.getExpr()] = true;
  TRACE("mycnf", "enqueueCNFrec: theta = ", theta.getExpr(),
	" @ "+int2string(theta.getScope())
	+" (current="+int2string(scopeLevel())+")");
  TRACE("mycnf", "enqueueCNFrec: var = ", var.getExpr(),
	" @ "+int2string(var.getScope())
	+" (current="+int2string(scopeLevel())+")");
  DebugAssert(var.isAbsLiteral(), "var = "+var.getExpr().toString());
  addCNFFact(var);
  // phi <=> v
  addToCNFCache(result);
  applyCNFRules(result);
  TRACE_MSG("mycnf", "enqueueCNFrec => }");
}


/*!
 * \param thm is the input of the form phi <=> v
 */
void
SearchImplBase::applyCNFRules(const Theorem& thm) {
  TRACE("mycnf", "applyCNFRules(", thm.getExpr(), ") { ");
  
  Theorem result = thm;
  DebugAssert(result.isRewrite(),
	      "SearchImplBase::applyCNFRules: input must be an iff: " + 
	      result.getExpr().toString());
  Expr lhs = result.getLHS();
  Expr rhs = result.getRHS();
  DebugAssert(rhs.isAbsLiteral(),
	      "SearchImplBase::applyCNFRules: rhs of input must be literal: " +
	      result.getExpr().toString());

  // Eliminate negation first
  while(result.getLHS().isNot())
    result = d_commonRules->iffContrapositive(result);
  lhs = result.getLHS();
  rhs = result.getRHS();
  
  CDMap<Expr,bool>::iterator it = d_applyCNFRulesCache.find(lhs);
  if(it == d_applyCNFRulesCache.end())
    d_applyCNFRulesCache[lhs] = true;
  else {
    TRACE_MSG("mycnf","applyCNFRules[temp cache] => }");
    return;
  }
  
  // Catch the trivial case v1 <=> v2
  if(lhs.isPropLiteral()) {
    if(!lhs.isAbsLiteral()) {
      Theorem replaced = d_commonRules->symmetryRule(replaceITE(lhs));
      result = d_commonRules->transitivityRule(replaced, result);
      lhs = result.getLHS();
      DebugAssert(rhs == result.getRHS(),
		  "applyCNFRules [literal lhs]\n result = "
    		  +result.getExpr().toString()
    		  +"\n rhs = "+rhs.toString());
    }
    Theorem thm = d_rules->iffToClauses(result);
    DebugAssert(thm.getExpr().isAnd() && thm.getExpr().arity()==2,
		"applyCNFRules [literal lhs]\n thm = "
		+thm.getExpr().toString());
    addCNFFact(d_commonRules->andElim(thm, 0));
    addCNFFact(d_commonRules->andElim(thm, 1));
    return;
  }
  
  // Check the kids in e[0], replace them with cache[e[0][i]], rebuild thm
  vector<unsigned> changed;
  vector<Theorem> substitutions;
  int c=0;
  for(Expr::iterator j = lhs.begin(), jend = lhs.end(); j!=jend; ++c,++j) {
    const Expr& phi = *j;
    if(!phi.isPropLiteral()) {
      Theorem phiIffVar = findInCNFCache(phi);
      if(phiIffVar.isNull()) {
	// Create a new variable for this child
	phiIffVar = d_commonRules->varIntroSkolem(phi);
	addToCNFCache(phiIffVar);
      }
      DebugAssert(phiIffVar.isRewrite(),
		  "SearchImplBase::applyCNFRules: result = " +
		  result.toString());
      DebugAssert(phi == phiIffVar.getLHS(),
		  "SearchImplBase::applyCNFRules:\n phi = " + 
		  phi.toString()
		  + "\n\n phiIffVar = " + phiIffVar.toString());
      DebugAssert(phiIffVar.getRHS().isAbsLiteral(),
		  "SearchImplBase::applyCNFRules: phiIffVar = " +
		  phiIffVar.toString());
      changed.push_back(c);
      substitutions.push_back(phiIffVar);
      applyCNFRules(phiIffVar);
    }
  }
  if(changed.size() > 0) {
    Theorem subst = 
      d_commonRules->substitutivityRule(lhs, changed, substitutions);
    subst = d_commonRules->symmetryRule(subst);
    result = d_commonRules->transitivityRule(subst, result);
  }

  switch(result.getLHS().getKind()) {
  case AND:
    result = d_rules->andCNFRule(result);
    break;
  case OR:
    result = d_rules->orCNFRule(result);
    break;
  case IFF:
    result = d_rules->iffCNFRule(result);
    break;
  case IMPLIES:
    result = d_rules->impCNFRule(result);
    break;
  case ITE:
    result = d_rules->iteCNFRule(result);
    break;
  default:
    DebugAssert(false,
		"SearchImplBase::applyCNFRules: "
		"the input operator must be and|or|iff|imp|ite\n result = " + 
		result.getLHS().toString());
    break;
  }

  // FIXME: eliminate this once debugged
  Theorem clauses(result);

  // Enqueue the clauses
  DebugAssert(!clauses.isNull(), "Oops!..");
  DebugAssert(clauses.getExpr().isAnd(), "clauses = "
	      +clauses.getExpr().toString());

  // The clauses may containt term ITEs, and we need to translate those
  
  for(int i=0, iend=clauses.getExpr().arity(); i<iend; ++i) {
    Theorem clause(d_commonRules->andElim(clauses,i));
    addCNFFact(clause);
  }
  TRACE_MSG("mycnf","applyCNFRules => }");
}


bool SearchImplBase::isClause(const Expr& e) {
  if(e.isAbsLiteral()) return true;
  if(!e.isOr()) return false;

  bool cnf(true);
  for(Expr::iterator i=e.begin(), iend=e.end(); i!=iend && cnf; ++i)
    cnf = (*i).isAbsLiteral();
  return cnf;
}


bool
SearchImplBase::isPropClause(const Expr& e) {
  if(e.isPropLiteral()) return true;
  if(!e.isOr()) return false;

  bool cnf(true);
  for(Expr::iterator i=e.begin(), iend=e.end(); i!=iend && cnf; ++i)
    cnf = (*i).isPropLiteral();
  return cnf;
}


bool
SearchImplBase::isGoodSplitter(const Expr& e) {
  if(!*d_ignoreCnfVarsOption) return true;
  TRACE("isGoodSplitter", "isGoodSplitter(", e, ") {");
  // Check for var being an auxiliary CNF variable
  const Expr& var = e.isNot()? e[0] : e;
  bool res(!isCNFVar(var));
  TRACE("isGoodSplitter", "isGoodSplitter => ", res? "true" : "false", " }");
  return res;
}


void
SearchImplBase::addToCNFCache(const Theorem& thm) {
  TRACE("mycnf", "addToCNFCache(", thm.getExpr(), ")");

  d_core->getStatistics().counter("CNF New Vars")++;

  Theorem result = thm;
  DebugAssert(result.isRewrite(),
	      "SearchImplBase::addToCNFCache: input must be an iff: " + 
	      result.getExpr().toString());
  // Record the CNF variable
  d_cnfVars[thm.getRHS()] = true;

  Theorem t(thm);
  Expr phi = thm.getLHS();
  while(phi.isNot()) {
    t = d_commonRules->iffContrapositive(thm);
    phi = phi[0];
  }
  DebugAssert(d_cnfCache.count(phi) == 0, 
	      "thm = "+thm.getExpr().toString() + 
	      "\n t = "+ t.getExpr().toString());
  //d_cnfCache.insert(phi, t);
  d_cnfCache.insert(phi, t, d_bottomScope);
}


Theorem 
SearchImplBase::findInCNFCache(const Expr& e) { 
  TRACE("mycnf", "findInCNFCache(", e, ") { ");
  Expr phi(e);

  int numNegs(0);
  // Strip all the top-level negations from e
  while(phi.isNot()) {
    phi = phi[0]; numNegs++;
  }
  CDMap<Expr,Theorem>::iterator it = d_cnfCache.find(phi);
  if(it != d_cnfCache.end()) {
    // IF_DEBUG(debugger.counter("CNF cache hits")++);
    d_core->getStatistics().counter("CNF cache hits")++;
    Theorem thm = (*it).second;
    
    DebugAssert(thm.isRewrite() && thm.getLHS() == phi,
		"SearchImplBase::findInCNFCache: thm must be an iff: " + 
		thm.getExpr().toString());
    
    // Now put all the negations back.  If the number of negations is
    // odd, first transform phi<=>v into !phi<=>!v.  Then apply
    // !!phi<=>phi rewrite as many times as needed.
    if(numNegs % 2 != 0) {
      thm = d_commonRules->iffContrapositive(thm); 
      numNegs--;
    }
    for(; numNegs > 0; numNegs-=2) {
      Theorem t = d_commonRules->rewriteNotNot(!!(thm.getLHS()));
      thm = d_commonRules->transitivityRule(t,thm);
    }

    DebugAssert(numNegs == 0, "numNegs = "+int2string(numNegs));
    TRACE("mycnf", "findInCNFCache => ", thm.getExpr(), " }");
    return thm;
  }
  TRACE_MSG("mycnf", "findInCNFCache => null  }");
  return Theorem();
}

/*!
 * Strategy:
 *
 * For a given atomic formula phi(ite(c, t1, t2)) which depends on a
 * term ITE, generate an equisatisfiable formula:
 *
 * phi(x) & ite(c, t1=x, t2=x),
 *
 * where x is a new variable.
 *
 * The phi(x) part will be generated by the caller, and our job is to
 * assert the 'ite(c, t1=x, t2=x)', and return a rewrite theorem
 * phi(ite(c, t1, t2)) <=> phi(x).
 */
Theorem
SearchImplBase::replaceITE(const Expr& e) {
  TRACE("replaceITE","replaceITE(", e, ") { ");
  Theorem res;

  CDMap<Expr,Theorem>::iterator i=d_replaceITECache.find(e),
    iend=d_replaceITECache.end();
  if(i!=iend) {
    TRACE("replaceITE", "replaceITE[cached] => ", (*i).second, " }");
    return (*i).second;
  }
  
  if(e.isAbsLiteral())
    res = d_core->rewriteLiteral(e);
  else
    res = d_commonRules->reflexivityRule(e);


  // If 'res' is e<=>phi, and the resulting formula phi is not a
  // literal, introduce a new variable x, enqueue phi<=>x, and return
  // e<=>x.
  if(!res.getRHS().isPropLiteral()) {
    Theorem varThm(findInCNFCache(res.getRHS()));
    if(varThm.isNull()) {
      varThm = d_commonRules->varIntroSkolem(res.getRHS());
      Theorem var;
      if(res.isRewrite())
	var = d_commonRules->transitivityRule(res,varThm);
      else 
	var = d_commonRules->iffMP(res,varThm);
      //d_cnfVars[var.getExpr()] = true;
      //addCNFFact(var);
      addToCNFCache(varThm);
    }
    applyCNFRules(varThm);
    //enqueueCNFrec(varThm);
    res = d_commonRules->transitivityRule(res, varThm);
  }
  
  d_replaceITECache[e] = res;
  
  TRACE("replaceITE", "replaceITE => ", res, " }");
  return res;
}


syntax highlighted by Code2HTML, v. 0.9.1