/**
 * Compose auxiliary functions recursively into a BDD.
 */
void BddTrAttachment::composeAuxiliaryFunctions(
  BddAttachment const * bat,
  BDD & f,
  unordered_map<int, ID> const & index2id)
{
  vector<unsigned int> support = f.SupportIndices();
  queue< vector<unsigned int> > q;
  q.push(support);
  while (!q.empty()) {
    vector<unsigned int> supp = q.front();
    q.pop();
    for (vector<unsigned int>::const_iterator j = supp.begin();
         j != supp.end(); ++j) {
      unordered_map<int,ID>::const_iterator cit = index2id.find(*j);
      if (cit != index2id.end()) {
        BDD g = bat->bdd(cit->second);
        BDD a = bddManager().bddVar(*j);
        f = f.AndAbstract(a.Xnor(g),a);
        vector<unsigned int> addSupp = g.SupportIndices();
        q.push(addSupp);
      }
    }
  }
}
/**
 * Build BDDs for the transition relation of the model.
 */
void BddTrAttachment::build()
{
  Options::Verbosity verbosity = _model.verbosity();
  if (verbosity > Options::Silent)
    cout << "Computing transition relation for model " << _model.name() << endl;
  BddAttachment const * bat = 
    (BddAttachment const *) _model.constAttachment(Key::BDD);
  assert(bat != 0);
  // The BDD attachment may not have BDDs because of a timeout.
  if (bat->hasBdds() == false) {
    _model.constRelease(bat);
    return;
  }
  if (hasBdds())
    return;
  ExprAttachment const * eat =
    (ExprAttachment *) _model.constAttachment(Key::EXPR);
  Expr::Manager::View *v = _model.newView();
  resetBddManager("bdd_tr_timeout");

  try {
    // Gather the conjuncts of the transition relation and initial condition.
    const vector<ID> sv = eat->stateVars();
    const vector<ID> iv = eat->inputs();
    const unordered_map<ID, int>& auxVar = bat->auxiliaryVars();
    int nvars = 2 * sv.size() + iv.size() + auxVar.size();
    if (verbosity > Options::Terse)
      cout << "number of variables = " << nvars << endl;
    BDD inputCube = bddManager().bddOne();
    for (vector<ID>::const_iterator i = iv.begin(); i != iv.end(); ++i) {
      BDD w = bat->bdd(*i);
      inputCube &= w;
    }
    BDD fwAbsCube = bddManager().bddOne();
    BDD bwAbsCube = bddManager().bddOne();
    vector<BDD> conjuncts;
    for (vector<ID>::const_iterator i = sv.begin(); i != sv.end(); ++i) {
      BDD x = bat->bdd(*i);
      _xvars.push_back(x);
      fwAbsCube &= x;
      ID nsv = v->prime(*i);
      BDD y = bat->bdd(nsv);
      _yvars.push_back(y);
      bwAbsCube &= y;
      ID nsf = eat->nextStateFnOf(*i);
      BDD f = bat->bdd(nsf);
      if (verbosity > Options::Informative)
        if (f.IsVar())
          cout << "Found a variable next-state function" << endl; 
      BDD t = y.Xnor(f);
      if (verbosity > Options::Verbose)
        reportBDD(stringOf(*v, nsv) + " <-> " + stringOf(*v, nsf),
                  t, nvars, verbosity, Options::Verbose);
      conjuncts.push_back(t);
    }

    // Process auxiliary variables.
    vector<BDD> conjAux;
    for (unordered_map<ID, int>::const_iterator it = auxVar.begin();
         it != auxVar.end(); ++it) {
      BDD f = bat->bdd(it->first);
      BDD v = bddManager().bddVar(it->second);
      BDD t = v.Xnor(f);
      conjAux.push_back(t);
      inputCube &= v;
    }
    conjuncts.insert(conjuncts.end(), conjAux.begin(), conjAux.end());
    // Build map from BDD variable indices to expression IDs.
    unordered_map<int, ID> index2id;
    for (unordered_map<ID, int>::const_iterator it = auxVar.begin();
         it != auxVar.end(); ++it) {
      index2id[it->second] = it->first;
    }

    // Add invariant constraints.
    const vector<ID> constr = eat->constraintFns();
    for (vector<ID>::const_iterator i = constr.begin(); 
         i != constr.end(); ++i) {
      BDD cn = bat->bdd(*i);
      composeAuxiliaryFunctions(bat, cn, index2id);
      _constr.push_back(cn);
      BDD cny = cn.ExistAbstract(inputCube);
      cny = cny.SwapVariables(_yvars, _xvars);
      reportBDD("Invariant constraint", cn, nvars, verbosity, Options::Terse);
      conjuncts.push_back(cn);
      conjuncts.push_back(cny);
    }

    unsigned int clusterLimit = 2500;
    if (_model.options().count("bdd_tr_cluster")) {
      clusterLimit = _model.options()["bdd_tr_cluster"].as<unsigned int>();
    }

    // Collect output functions applying invariant constraints and substituting
    // auxiliary variables.
    const vector<ID> outf = eat->outputs();
    for (vector<ID>::const_iterator i = outf.begin(); i != outf.end(); ++i) {
      BDD of = bat->bdd(eat->outputFnOf(*i));
      // Apply invariant constraints.
      for (vector<BDD>::iterator i = _constr.begin(); i != _constr.end(); ++i)
        of &= *i;
      composeAuxiliaryFunctions(bat, of, index2id);
      // Finally, remove primary inputs.
      if (_model.defaultMode() != Model::mFAIR) {
        of = of.ExistAbstract(inputCube);
      }
      _inv.push_back(of);
      reportBDD("Output function", of, _xvars.size(), verbosity,
                Options::Terse);
    }

    // Build the transition relation from the conjuncts.
    vector<BDD> sortedConjuncts = linearArrangement(conjuncts);
    assert(sortedConjuncts.size() == conjuncts.size());
    conjuncts.clear();
    vector<BDD> clusteredConjuncts =
      clusterConjuncts(sortedConjuncts, inputCube, clusterLimit, verbosity);
    assert(sortedConjuncts.size() == 0);
    inputCube = quantifyLocalInputs(clusteredConjuncts, inputCube,
                                    clusterLimit, verbosity);
    computeSchedule(clusteredConjuncts, inputCube);

    // Build initial condition.
    const vector<ID> ini = eat->initialConditions();
    _init = bddManager().bddOne();
    for (vector<ID>::const_iterator i = ini.begin(); i != ini.end(); ++i) {
      BDD ic = bat->bdd(*i);
      _init &= ic;
    }
    reportBDD("Initial condition", _init, _xvars.size(), verbosity,
              Options::Terse);
  } catch (Timeout& e) {
    bddManager().ClearErrorCode();
    bddManager().UnsetTimeLimit();
    bddManager().ResetStartTime();
    if (verbosity > Options::Silent)
      std::cout << e.what() << std::endl;
    _tr.clear();
    _xvars.clear();
    _yvars.clear();
    _inv.clear();
    _prequantx = bddManager().bddOne();
    _prequanty = bddManager().bddOne();
    _init = bddManager().bddZero();
  }
  delete v;
  _model.constRelease(eat);
  _model.constRelease(bat);

  bddManager().UpdateTimeLimit();

} // BddTrAttachment::build