/**
   * Returns an array R of longs such that for each i, j in [0..R.length) i < j implies that the
   * formula identified by (int)R[i] in this.hits contributes fewer clauses to the core of the given
   * trace than the formula identified by (int)R[j].
   *
   * @return an array as described above
   */
  private long[] sortByRelevance(ResolutionTrace trace, IntSet relevantVars) {
    hits.indices().retainAll(relevantVars);

    if (hits.get(hits.indices().min()) == null) { // first call, initialize the hits
      for (IntIterator varItr = relevantVars.iterator(); varItr.hasNext(); ) {
        final int var = varItr.next();
        final IntSet varReachable = new IntBitSet(var + 1);
        varReachable.add(var);
        hits.put(var, varReachable);
      }
      for (Iterator<Clause> clauseItr = trace.reverseIterator(trace.axioms());
          clauseItr.hasNext(); ) {
        final Clause clause = clauseItr.next();
        final int maxVar = clause.maxVariable();
        for (IntSet reachableVars : hits.values()) {
          if (reachableVars.contains(maxVar)) {
            for (IntIterator lits = clause.literals(); lits.hasNext(); ) {
              reachableVars.add(StrictMath.abs(lits.next()));
            }
          }
        }
      }
    }

    final long[] counts = new long[hits.size()];

    for (Iterator<Clause> clauseItr = trace.iterator(trace.core()); clauseItr.hasNext(); ) {
      final Clause clause = clauseItr.next();
      final int maxVar = clause.maxVariable();
      int i = 0;
      for (IntSet reachableVars : hits.values()) {
        if (reachableVars.contains(maxVar)) {
          counts[i]++;
        }
        i++;
      }
    }

    int i = 0;
    for (IntIterator varItr = hits.indices().iterator(); varItr.hasNext(); ) {
      final int var = varItr.next();
      counts[i] = (counts[i] << 32) | var;
      i++;
    }

    Arrays.sort(counts);

    return counts;
  }
  /**
   * {@inheritDoc}
   *
   * @see kodkod.engine.satlab.ReductionStrategy#next(kodkod.engine.satlab.ResolutionTrace)
   */
  public IntSet next(ResolutionTrace trace) {
    if (hits.isEmpty()) return Ints.EMPTY_SET; // tried everything
    final IntSet relevantVars = StrategyUtils.coreTailUnits(trace);

    final long[] byRelevance = sortByRelevance(trace, relevantVars);
    if (DBG) printRelevant(byRelevance);
    for (int i = byRelevance.length - 1; i >= 0; i--) {
      final int var = (int) byRelevance[i];
      if (hits.remove(var) != null) {
        // remove maxVar from the set of relevant variables
        relevantVars.remove(var);
        if (relevantVars.isEmpty()) break; // there was only one root formula left
        // get all axioms and resolvents corresponding to the clauses that
        // form the translations of formulas identified by relevant vars
        final IntSet relevantClauses = clausesFor(trace, relevantVars);
        assert !relevantClauses.isEmpty() && !relevantClauses.contains(trace.size() - 1);

        if (DBG)
          System.out.println("relevant clauses: " + relevantClauses.size() + ", removed " + var);

        return relevantClauses;
      }
    }

    hits.clear();
    return Ints.EMPTY_SET;
  }
  /**
   * Returns the indices of all axioms and resolvents in the given trace that form the translations
   * of the formulas identified by the given variables. This method assumes that the axioms in the
   * given trace were generated by the Kodkod {@linkplain Translator}.
   *
   * @return let C = { c: trace.prover.clauses | c.maxVariable() in relevantVars }, T = { c1, c2: C
   *     | c2.maxVariable() in abs(c1.literals) }, A = C.*T | trace.backwardReachable(A) -
   *     trace.backwardReachable(trace.axioms() - A)
   */
  private IntSet clausesFor(ResolutionTrace trace, IntSet relevantVars) {
    final double hardness = (double) trace.size() / (double) trace.axioms().size();
    final double coreRatio = ((double) trace.core().size() / (double) trace.axioms().size());

    if (DBG)
      System.out.println(
          "trace size: "
              + trace.size()
              + ", axioms: "
              + trace.axioms().size()
              + ", core: "
              + trace.core().size()
              + ", resolvents: "
              + trace.resolvents().size());
    if (DBG) System.out.println("hardness: " + hardness + ", coreRatio: " + coreRatio);

    final IntSet relevantAxioms = StrategyUtils.clausesFor(trace, relevantVars);
    if (DBG) System.out.println("relevant axioms:  " + relevantAxioms.size());

    if (coreRatio < noRecycleRatio) {
      return relevantAxioms;
    } else if (hardness < hardnessCutOff) {
      return trace.learnable(relevantAxioms);
    } else {
      IntSet current = relevantAxioms, last;
      final int maxRelevant = (int) Math.rint(relevantAxioms.size() * recycleLimit);
      do {
        last = current;
        current = trace.directlyLearnable(current);
      } while (last.size() < current.size() && current.size() < maxRelevant);

      if (DBG)
        System.out.println(
            "last: "
                + last.size()
                + ", current: "
                + current.size()
                + ", maxRelevant: "
                + maxRelevant);

      return current.size() < maxRelevant ? current : last;
    }
  }