/**
   * Returns an instance of this class which is the simplified representation of the formula
   * represented by this object. May return this object if the formula is already simplified.
   *
   * <p>This method uses the Quine-McCluskey algorithm and code described at
   * http://en.literateprograms.org/Quine-McCluskey_algorithm_%28Java%29
   *
   * @return The simplified representation of this formula, or this object, if already simplified.
   */
  public BooleanFormula simplify() {
    if (isSimplified) {
      return this;
    }
    if (booleanTerms == null || booleanTerms.size() < 2) {
      isSimplified = true;
      return this;
    }

    // Remove true and false terms.  False terms are discarded.  True terms
    // make the entire formula evaluate to true.
    Set<BooleanTerm> trimmed = new HashSet<BooleanTerm>();
    for (BooleanTerm term : booleanTerms) {
      if (term.isTrue()) {
        booleanTerms = null;
        isSimplified = true;
        return this;
      } else if (term.isFalse()) {
        continue; // omitting a false term
      } else {
        trimmed.add(term);
      }
    }
    if (trimmed.size() != booleanTerms.size()) {
      booleanTerms = trimmed;
    }

    // Determine number of unique variable names in the formula
    Map<String, Integer> names = new TreeMap<String, Integer>();
    for (Set<BooleanVar> term : booleanTerms) {
      for (BooleanVar state : term) {
        names.put(state.name, -1);
      }
    }
    int i = 0;
    for (Map.Entry<String, Integer> entry : names.entrySet()) {
      entry.setValue(i++);
    }
    int count = names.size();
    if (count >= Integer.SIZE) {
      throw new IllegalArgumentException(Integer.toString(count));
    }
    List<Term> terms = new ArrayList<Term>(booleanTerms.size());
    for (Set<BooleanVar> term : booleanTerms) {
      byte[] bytes = new byte[count];
      for (i = 0; i < count; i++) bytes[i] = Term.DontCare;
      boolean ignoreTerm = false;
      for (BooleanVar state : term) {
        int index = names.get(state.name);
        byte val = state.state ? (byte) 1 : (byte) 0;
        if (bytes[index] == Term.DontCare) bytes[index] = val;
        else if (bytes[index] != val) {
          // Mutually exclusive states.  Don't consider this term
          ignoreTerm = true;
          break;
        }
      }
      if (!ignoreTerm) {
        terms.add(new Term(bytes));
      }
    }
    Set<BooleanTerm> result = new HashSet<BooleanTerm>();
    if (terms.size() == 0) {
      return new BooleanFormula(false);
    }

    terms = expandDontCares(count, terms);

    Formula formula = new Formula(terms);
    formula.reduceToPrimeImplicants();
    formula.reducePrimeImplicantsToSubset();

    // now convert back to featureExpression form
    List<Term> termList = formula.getTermList();
    if (termList.size() == 0) {
      return new BooleanFormula(false);
    }
    for (Term term : termList) {
      byte[] varValues = term.getVarValues();
      Set<BooleanVar> states = new HashSet<BooleanVar>();
      for (Map.Entry<String, Integer> entry : names.entrySet()) {
        if (varValues[entry.getValue()] == (byte) 1) {
          states.add(new BooleanVar(entry.getKey(), true));
        } else if (varValues[entry.getValue()] == (byte) 0) {
          states.add(new BooleanVar(entry.getKey(), false));
        }
      }
      if (states.size() > 0) {
        result.add(new BooleanTerm(states));
      }
    }
    if (result.size() == 0) {
      return new BooleanFormula(true);
    }
    BooleanFormula newFormula = new BooleanFormula();
    newFormula.booleanTerms = result;
    newFormula.isSimplified = true;
    return newFormula;
  }