@Test
  public void testToCNF() {
    Var a = new Var("a");
    Var b = new Var("b");
    Var c = new Var("c");
    Var d = new Var("d");

    Formula f = (a.and(b)).or(c.and(d));
    assertTrue("(a or c) and (b or c) and (a or d) and (b or d)".equals(f.toCNF().toString()));

    f = (a.and(b)).or(c).or(d);
    assertTrue("(a or c or d) and (b or c or d)".equals(f.toCNF().toString()));

    f = ((a.and(b)).or(c).or(d)).not();
    assertTrue("(!a or !b) and !c and !d".equals(f.toCNF().toString()));

    f = (a).not().not().not().not().not().not();
    assertTrue("a".equals(f.toCNF().toString()));

    f = (a).not().not().not().not().not();
    assertTrue("!a".equals(f.toCNF().toString()));

    f = ((a).and(b)).or((a).not().and(c)).or(((b).not()).and((c).not()));
    assertTrue(
        "(a or !a or !b) and (b or !a or !b) and (a or c or !b) and (b or c or !b) and (a or !a or !c) and (b or !a or !c) and (a or c or !c) and (b or c or !c)"
            .equals(f.toCNF().toString()));
  }
  @Test
  public void testHashCode() {
    Var a = new Var("a");
    Var b = new Var("b");

    Formula f = a.and(b);
    Formula g = a.and(b);

    assertTrue(f.hashCode() == g.hashCode()); // two distinct instances

    Formula h = b.and(a);
    assertFalse(f.hashCode() == h.hashCode()); // logically equivalent but different hash codes
  }
  @Test
  public void testEquals() {
    Var a = new Var("a");
    Var b = new Var("b");

    Formula f = a.and(b);
    Formula g = a.and(b);

    assertFalse(f == g); // two distinct instances
    assertTrue(f.equals(g)); // objects equal

    Formula h = b.and(a);
    assertFalse(f.equals(h)); // logically equivalent but objects not equal
  }
  @Test
  public void testToString() {
    Formula emptyFormula = new Formula();
    assertTrue(emptyFormula.toString().equals(StringUtils.EMPTY));

    Var a = new Var("a");
    Var b = new Var("b");
    Var c = new Var("c");
    Var d = new Var("d");

    Formula f = (a.and(b)).or(c.and(d));
    assertTrue("(a and b) or (c and d)".equals(f.toString()));

    f = (a.not().or(b)).and(c);
    assertTrue("(!a or b) and c".equals(f.toString()));
  }
  @Test
  public void testCreateFromConjunctionOfLiterals() {
    Var a = new Var("a");
    Var b = new Var("b");

    Phrase phrase = new Phrase(a.and(b));
    assertTrue("a and b".equals(phrase.toString()));
  }
  @Test
  public void testAnd() {
    Var a = new Var("a");
    Var b = new Var("b");

    Formula f = (a.and(b));
    assertTrue("a and b".equals(f.toString()));
  }
  @Test
  public void testGetRight() {
    Var a = new Var("a");
    Var b = new Var("b");

    Formula f = (a.and(b));
    assertTrue(f.getRight().equals(b));
  }
  @Test
  public void testGetOp() {
    Var a = new Var("a");
    Var b = new Var("b");

    Formula f = (a.and(b));
    assertTrue(f.getOp().equals(Formula.BOP.AND));
  }
  @Test
  public void testIsLeaf() {
    Var a = new Var("a");
    Var b = new Var("b");

    Formula f = (a.and(b));
    assertFalse(f.isLeaf());
    assertTrue(f.getLeft().isLeaf() == true && f.getRight().isLeaf() == true);
  }
  @Test
  public void testIsAtomic() {
    Var a = new Var("a");
    Var b = new Var("b");

    assertTrue(a.isAtomic()); // a literal is atomic
    assertTrue(a.not().isAtomic()); // a literal and its negation is atomic

    Formula f = (a.and(b));
    assertFalse(f.isAtomic());
    assertTrue(f.getLeft().isAtomic() == true && f.getRight().isAtomic() == true);
  }
  @Test
  public void testIsPhrase() {
    Var a = new Var("a");
    Var b = new Var("b");
    Var c = new Var("c");

    assertTrue(a.isPhrase());
    assertTrue(b.isPhrase());
    assertTrue(c.isPhrase());

    // if a and b are both phrases, then so is (a and b)
    Formula f = a.and(b);
    assertTrue(f.isPhrase());

    // if f is a phrase and c is a phrase, then so is (f and c)
    assertTrue(f.and(c).isPhrase());
    assertTrue(c.and(f).isPhrase());

    Formula g = a.or(b);
    assertFalse(g.isPhrase());

    assertFalse(g.and(c).isPhrase());
    assertFalse(c.and(g).isPhrase());
  }
  @Test
  public void testIsClause() {
    Var a = new Var("a");
    Var b = new Var("b");
    Var c = new Var("c");

    assertTrue(a.isClause());
    assertTrue(b.isClause());
    assertTrue(c.isClause());

    // if a and b are both clauses, then so is (a or b)
    Formula f = a.or(b);
    assertTrue(f.isClause());

    // if f is a literals and c is a literals, then so is (f or c)
    assertTrue(f.or(c).isClause());
    assertTrue(c.or(f).isClause());

    Formula g = a.and(b);
    assertFalse(g.isClause());

    assertFalse(g.or(c).isClause());
    assertFalse(c.or(g).isClause());
  }
  @Test
  public void testToCNFClauses() {
    Var a = new Var("a");
    Var b = new Var("b");
    Var c = new Var("c");
    Var d = new Var("d");

    Formula f = a.or(b).or(c).or(d);
    assertTrue("a or b or c or d".equals(f.toCNF().toString()));
    List<Clause> clauses = f.toCNFClauses();
    assertTrue(clauses.size() == 1);
    assertTrue("a or b or c or d".equals(clauses.get(0).toString()));

    f = (a.and(b)).or(c.and(d));
    assertTrue("(a or c) and (b or c) and (a or d) and (b or d)".equals(f.toCNF().toString()));
    clauses = f.toCNFClauses();
    assertTrue(clauses.size() == 4);
    assertTrue(clauses.contains(new Clause(a.or(c))));
    assertTrue(clauses.contains(new Clause(b.or(c))));
    assertTrue(clauses.contains(new Clause(a.or(d))));
    assertTrue(clauses.contains(new Clause(b.or(d))));

    f = (a.and(b)).or(c).or(d);
    assertTrue("(a or c or d) and (b or c or d)".equals(f.toCNF().toString()));
    clauses = f.toCNFClauses();
    assertTrue(clauses.size() == 2);
    assertTrue(clauses.contains(new Clause(a.or(c).or(d))));
    assertTrue(clauses.contains(new Clause(b.or(c).or(d))));

    f = ((a.and(b)).or(c).or(d)).not();
    assertTrue("(!a or !b) and !c and !d".equals(f.toCNF().toString()));
    clauses = f.toCNFClauses();
    assertTrue(clauses.size() == 3);
    assertTrue(clauses.contains(new Clause((a.not()).or(b.not()))));
    assertTrue(clauses.contains(new Clause(c.not())));
    assertTrue(clauses.contains(new Clause(d.not())));

    f = (a).not().not().not().not().not().not();
    assertTrue("a".equals(f.toCNF().toString()));
    clauses = f.toCNFClauses();
    assertTrue(clauses.size() == 1);
    assertTrue(clauses.contains(new Clause(a)));

    f = (a).not().not().not().not().not();
    assertTrue("!a".equals(f.toCNF().toString()));
    clauses = f.toCNFClauses();
    assertTrue(clauses.size() == 1);
    assertTrue(clauses.contains(new Clause(a.not())));

    f = ((a).and(b)).or((a).not().and(c)).or(((b).not()).and((c).not()));
    assertTrue(
        "(a or !a or !b) and (b or !a or !b) and (a or c or !b) and (b or c or !b) and (a or !a or !c) and (b or !a or !c) and (a or c or !c) and (b or c or !c)"
            .equals(f.toCNF().toString()));
    clauses = f.toCNFClauses();
    assertTrue(clauses.size() == 8);
    assertTrue(clauses.contains(new Clause(a.or(a.not()).or(b.not()))));
    assertTrue(clauses.contains(new Clause(b.or(a.not()).or(b.not()))));
    assertTrue(clauses.contains(new Clause(a.or(c).or(b.not()))));
    assertTrue(clauses.contains(new Clause(b.or(c).or(b.not()))));
    assertTrue(clauses.contains(new Clause(a.or(a.not()).or(c.not()))));
    assertTrue(clauses.contains(new Clause(b.or(a.not()).or(c.not()))));
    assertTrue(clauses.contains(new Clause(a.or(c).or(c.not()))));
    assertTrue(clauses.contains(new Clause(b.or(c).or(c.not()))));
  }