# Test the creation of a CnfClause from CnfAtoms
test_that("CnfClause is created correctly from CnfAtoms", {
  u = CnfUniverse()
  X = CnfSymbol(u, "X", c("a", "b", "c"))
  Y = CnfSymbol(u, "Y", c("d", "e", "f"))

  atom1 = CnfAtom(X, c("a", "b"))
  atom2 = CnfAtom(Y, c("d", "e"))
  clause = CnfClause(list(atom1, atom2))

  expect_s3_class(clause, "CnfClause")
  expect_length(as.list(clause), 2)

  atoms = as.list(clause)
  if (identical(atoms[[1]]$symbol, "X")) {
    expect_identical(atoms[[1]], atom1)
    expect_identical(atoms[[2]], atom2)
  } else {
    expect_identical(atoms[[1]], atom2)
    expect_identical(atoms[[2]], atom1)
  }

})

test_that("CnfClause with overlapping symbols works correctly", {
  u = CnfUniverse()
  X = CnfSymbol(u, "X", c("a", "b", "c"))
  Y = CnfSymbol(u, "Y", c("d", "e", "f"))

  atom1 = CnfAtom(X, c("a", "b"))
  atom2 = CnfAtom(Y, c("d", "e"))
  atom1_sub = CnfAtom(X, c("a"))
  atom2_sub = CnfAtom(Y, c("d"))

  expect_identical(
    CnfClause(list(atom1, atom1_sub)),
    CnfClause(list(atom1))
  )

  expect_identical(CnfClause(list(atom1, atom1)), CnfClause(list(atom1)))

  combined1 = CnfClause(list(atom1, atom2, atom1_sub, atom2_sub))
  combined2 = CnfClause(list(atom1, atom2))
  combinedswitched = CnfClause(list(atom2, atom1))

  expect_true(identical(combined1, combined2) || identical(combined1, combinedswitched))

  # only one of them is identical!
  # this must be taken out if at any point we decide to sort the atoms
  expect_true(!identical(combined1, combined2) || !identical(combined1, combinedswitched))

  combined1 = CnfClause(list(atom1, atom2, atom1_sub))
  combined2 = CnfClause(list(atom1, atom2))
  combinedswitched = CnfClause(list(atom2, atom1))

  expect_true(identical(combined1, combined2) || identical(combined1, combinedswitched))

  # only one of them is identical!
  # this must be taken out if at any point we decide to sort the atoms
  expect_true(!identical(combined1, combined2) || !identical(combined1, combinedswitched))

})

test_that("CnfClause can be called with CnfClauses", {
  u = CnfUniverse()
  X = CnfSymbol(u, "X", c("a", "b", "c"))
  Y = CnfSymbol(u, "Y", c("d", "e", "f"))

  atom1 = CnfAtom(X, "a")
  atom2 = CnfAtom(Y, "d")
  atom3 = CnfAtom(X, "b")
  atom4 = CnfAtom(Y, "e")
  clause1 = CnfClause(list(atom1))
  clause2 = CnfClause(list(atom2))
  clause3 = CnfClause(list(atom3))
  clause4 = CnfClause(list(atom4))

  clause_1_3 = CnfClause(list(atom1, atom3))
  clause_2_4 = CnfClause(list(atom2, atom4))

  expect_true(all.equal(clause_1_3, CnfClause(list(clause1, clause3))))
  expect_true(all.equal(clause_1_3 | clause2, CnfClause(list(clause1, clause3, atom2))))
  expect_true(all.equal(CnfClause(list(clause_1_3, clause_2_4)), CnfClause(list(clause1, clause2, clause3, clause4))))
  expect_true(all.equal(CnfClause(list(clause_1_3, clause_2_4)), CnfClause(list(atom1, atom2, atom3, atom4))))

})

# Test the handling of tautologies and contradictions in CnfClause
test_that("CnfClause handles tautologies and contradictions correctly", {
  u = CnfUniverse()
  X = CnfSymbol(u, "X", c("a", "b", "c"))

  tautology = CnfClause(list(CnfAtom(X, c("a", "b", "c"))))
  contradiction = CnfClause(list(CnfAtom(X, character(0))))

  # Tautology should evaluate to TRUE
  expect_true(as.logical(tautology))

  # Contradiction should evaluate to FALSE
  expect_false(as.logical(contradiction))
})

# Test disjunction (|) of CnfClauses
test_that("Disjunction of CnfClauses works correctly", {
  u = CnfUniverse()
  X = CnfSymbol(u, "X", c("a", "b", "c"))
  Y = CnfSymbol(u, "Y", c("d", "e", "f"))

  atom1 = CnfAtom(X, c("a"))
  atom2 = CnfAtom(Y, c("d"))
  atom3 = CnfAtom(X, c("b"))
  atom4 = CnfAtom(Y, c("e"))

  clause1 = CnfClause(list(atom1, atom2))
  clause2 = CnfClause(list(atom3, atom4))

  disjunct = clause1 | clause2

  expect_s3_class(disjunct, "CnfClause")
  expect_length(as.list(disjunct), 2)

  # Check that the disjunction has unified the atoms correctly
  atoms = as.list(disjunct)
  if (identical(atoms[[1]]$symbol, "X")) {
    expect_equal(atoms[[1]]$values, c("a", "b"))
    expect_equal(atoms[[2]]$values, c("d", "e"))
  } else {
    expect_equal(atoms[[1]]$values, c("d", "e"))
    expect_equal(atoms[[2]]$values, c("a", "b"))
  }

  tautology = CnfClause(list(CnfAtom(X, c("a", "b", "c"))))
  contradiction = CnfClause(list(CnfAtom(X, character(0))))

  # Check that disjunction of a tautology with another clause is a tautology
  ttl = tautology | clause1
  expect_true(as.logical(ttl))

  # Check that disjunction of a contradiction with another clause is the other clause
  ctn = contradiction | clause1
  expect_identical(as.list(ctn), as.list(clause1))


  # tautologies that come up in the middle of a disjunction also work

  atom1_complement = CnfAtom(X, c("b", "c"))
  expect_identical(
    CnfClause(list(atom1, atom1_complement)),
    tautology
  )

})

# Test conjunction (&) of CnfClauses
test_that("Conjunction of CnfClauses works correctly", {
  u = CnfUniverse()
  X = CnfSymbol(u, "X", c("a", "b", "c"))
  Y = CnfSymbol(u, "Y", c("d", "e", "f"))
  tautology = CnfClause(list(CnfAtom(X, c("a", "b", "c"))))
  contradiction = CnfClause(list(CnfAtom(X, character(0))))


  atom1 = CnfAtom(X, c("a", "b"))
  atom2 = CnfAtom(Y, c("d", "e"))

  clause1 = CnfClause(list(atom1))
  clause2 = CnfClause(list(atom2))

  formula = clause1 & clause2

  expect_s3_class(formula, "CnfFormula")
  expect_length(as.list(formula), 2)
  if (identical(as.list(as.list(formula)[[1]])[[1]]$symbol, "X")) {
    expect_identical(lapply(as.list(formula), as.list), list(list(atom1), list(atom2)))
  } else {
    expect_identical(lapply(as.list(formula), as.list), list(list(atom2), list(atom1)))
  }

  # Check that conjunction of a tautology with another clause is the other clause
  ttl = tautology & clause2
  expect_s3_class(ttl, "CnfFormula")
  expect_identical(lapply(as.list(ttl), as.list), list(list(atom2)))

  # Check that conjunction of a contradiction with another clause is a contradiction
  ctn = contradiction & clause2
  expect_s3_class(ctn, "CnfFormula")
  expect_false(as.logical(ctn))
})

# Test negation of CnfClause
test_that("Negation of CnfClause works correctly", {
  u = CnfUniverse()
  X = CnfSymbol(u, "X", c("a", "b", "c"))
  Y = CnfSymbol(u, "Y", c("d", "e", "f"))
  tautology = CnfClause(list(CnfAtom(X, c("a", "b", "c"))))
  contradiction = CnfClause(list(CnfAtom(X, character(0))))

  clause = CnfClause(list(CnfAtom(X, c("a", "b")), CnfAtom(Y, c("d"))))
  negated_clause = !clause

  expect_s3_class(negated_clause, "CnfFormula")
  expect_length(as.list(negated_clause), 2)


  # Check that negation of a tautology is a contradiction
  expect_false(as.logical(!tautology))
  expect_s3_class(!tautology, "CnfFormula")

  # Check that negation of a contradiction is a tautology
  expect_true(as.logical(!contradiction))
  expect_s3_class(!contradiction, "CnfFormula")
})

# Test subset operations on CnfClause
test_that("Subsetting of CnfClause works correctly", {
  u = CnfUniverse()
  X = CnfSymbol(u, "X", c("a", "b", "c"))
  Y = CnfSymbol(u, "Y", c("d", "e", "f"))

  clause = CnfClause(list(CnfAtom(X, c("a", "b")), CnfAtom(Y, c("d", "e"))))

  subset_clause = clause["X"]

  expect_s3_class(subset_clause, "CnfClause")
  expect_length(as.list(subset_clause), 1)
  expect_identical(as.list(subset_clause)[[1]]$symbol, "X")
  expect_identical(subset_clause, CnfClause(list(CnfAtom(X, c("a", "b")))))

  expect_identical(clause[0], CnfClause(list(CnfAtom(X, character(0)))))
  expect_identical(clause[integer(0)], CnfClause(list(CnfAtom(X, character(0)))))
  expect_identical(clause[c(FALSE, FALSE)], CnfClause(list(CnfAtom(X, character(0)))))

  expect_error(clause["Z"], "Must be a subset of \\{'X','Y'\\}")

  expect_identical(clause[c(1, 2)], clause)
  expect_identical(clause[c(TRUE, TRUE)], clause)

  expect_identical(clause[c(1, 2, 1, 2)], clause)
  expect_identical(clause[c(1, 0, 2, 0)], clause)

})

# Test print and format methods for CnfClause
test_that("print and format methods for CnfClause work correctly", {
  u = CnfUniverse()
  X = CnfSymbol(u, "X", c("a", "b", "c"))
  Y = CnfSymbol(u, "Y", c("d", "e", "f"))

  clause = CnfClause(list(CnfAtom(X, c("a", "b")), CnfAtom(Y, c("d"))))

  # Test print method
  expect_output(print(clause), "CnfClause:\n  X . \\{a, b\\} \\| Y . \\{d\\}|CnfClause:\n  Y . \\{d\\} \\| X . \\{a, b\\}")

  # Test format method
  expect_equal(format(clause), "CnfClause(2)")
})

# Test conversion between logical and CnfClause
test_that("as.logical and as.CnfClause conversions work correctly", {
  u = CnfUniverse()
  X = CnfSymbol(u, "X", c("a", "b", "c"))

  atom_true = CnfClause(list(CnfAtom(X, c("a", "b", "c"))))
  atom_false = CnfClause(list(CnfAtom(X, character(0))))

  # Convert CnfClause to logical
  expect_true(as.logical(atom_true))
  expect_false(as.logical(atom_false))

  # Convert logical to CnfClause
  expect_s3_class(as.CnfClause(TRUE), "CnfClause")
  expect_true(as.logical(as.CnfClause(TRUE)))

  expect_s3_class(as.CnfClause(FALSE), "CnfClause")
  expect_false(as.logical(as.CnfClause(FALSE)))

  atom_neither = CnfClause(list(CnfAtom(X, c("a", "b"))))
  expect_identical(as.logical(atom_neither), NA)
})

# Test invalid creation of CnfClause with clauses from different universes
test_that("CnfClause throws error when clauses are from different universes", {
  u1 = CnfUniverse()
  u2 = CnfUniverse()
  X1 = CnfSymbol(u1, "X", c("a", "b", "c"))
  X2 = CnfSymbol(u2, "X", c("a", "b", "c"))

  expect_error(CnfClause(list(CnfAtom(X1, c("a", "b")), CnfAtom(X2, c("a", "b")))), "All symbols must be in the same universe")
})


test_that("all.equal recognizes (in)equality for CnfClause", {
  u = CnfUniverse()
  X = CnfSymbol(u, "X", c("a", "b", "c"))
  Y = CnfSymbol(u, "Y", c("d", "e", "f"))

  # Test equality between identical CnfClauses
  atom1 = CnfAtom(X, c("a", "b"))
  atom2 = CnfAtom(Y, c("d", "e"))
  clause1 = CnfClause(list(atom1, atom2))
  clause2 = CnfClause(list(atom1, atom2))

  expect_true(all.equal(clause1, clause2))

  # Test equality with the same atoms but in different order
  clause2 = CnfClause(list(atom2, atom1))

  expect_true(all.equal(clause1, clause2))

  # Test inequality for different CnfClauses with the same symbols but different values
  atom3 = CnfAtom(X, c("b", "c"))
  clause2 = CnfClause(list(atom3, atom2))

  expect_string(all.equal(clause1, clause2), pattern = "string mismatches")

  # Test equality for CnfClauses where the values of one symbol are in a different order
  atom4 = CnfAtom(X, c("b", "a"))  # same values, different order
  clause2 = CnfClause(list(atom4, atom2))

  expect_true(all.equal(clause1, clause2))

  # Test inequality when symbols in CnfClauses differ
  Z = CnfSymbol(u, "Z", c("g", "h", "i"))
  atom5 = CnfAtom(Z, c("g"))
  clause2 = CnfClause(list(atom1, atom5))

  expect_string(paste(all.equal(clause1, clause2), collapse = "\n"), pattern = "string mismatch")

  # Test equality for logical CnfClause (TRUE and FALSE) vs a clause
  clause_tautology = CnfClause(list(CnfAtom(X, c("a", "b", "c"))))  # tautology (TRUE)
  clause_contradiction = CnfClause(list(CnfAtom(X, character(0))))  # contradiction (FALSE)

  expect_string(all.equal(clause1, clause_tautology), pattern = "not both logicals")
  expect_string(all.equal(clause1, clause_contradiction), pattern = "not both logicals")
  expect_string(all.equal(clause_tautology, clause_contradiction), pattern = "logicals but not equal")

  # Test equality for a logical TRUE clause and another logical TRUE clause
  atom_ttl1 = CnfAtom(X, c("a", "b", "c"))
  atom_ttl2 = CnfAtom(Y, c("d", "e", "f"))
  clause_ttl1 = CnfClause(list(atom_ttl1))
  clause_ttl2 = CnfClause(list(atom_ttl2))

  expect_true(all.equal(clause_ttl1, clause_ttl2))
  expect_true(all.equal(clause_ttl1, as.CnfClause(TRUE)))

  # Test equality for a logical FALSE clause and another logical FALSE clause
  clause_false1 = CnfClause(list(CnfAtom(X, character(0))))
  clause_false2 = CnfClause(list(CnfAtom(Y, character(0))))

  expect_true(all.equal(clause_false1, clause_false2))
  expect_true(all.equal(clause_false1, as.CnfClause(FALSE)))

  # Test inequality between a CnfClause and a non-CnfClause object
  expect_string(all.equal(clause1, "not a CnfClause"), pattern = "current is not a CnfClause")

  # Test equality between CnfClauses in different universes
  u1 = CnfUniverse()
  u2 = CnfUniverse()
  X1 = CnfSymbol(u1, "X", c("a", "b", "c"))
  X2 = CnfSymbol(u2, "X", c("a", "b", "c"))

  atom_u1 = CnfAtom(X1, c("a", "b"))
  atom_u2 = CnfAtom(X2, c("a", "b"))
  clause_u1 = CnfClause(list(atom_u1))
  clause_u2 = CnfClause(list(atom_u2))

  expect_true(all.equal(clause_u1, clause_u2))

  # Test inequality when universes differ in length
  u3 = CnfUniverse()
  X3 = CnfSymbol(u3, "X", c("a", "b", "c", "d"))
  atom_u3 = CnfAtom(X3, c("a", "b"))
  clause_u3 = CnfClause(list(atom_u3))

  expect_string(all.equal(clause_u1, clause_u3), pattern = "Lengths \\(3, 4\\) differ")

  # Explicitly constructed object all.equals test
  u = CnfUniverse()
  X = CnfSymbol(u, "X", c("a", "b", "c"))
  Y = CnfSymbol(u, "Y", c("d", "e", "f"))
  Z = CnfSymbol(u, "Z", c("g", "h", "i"))

  c1 = structure(list(
    X = c("a", "b"), Y = c("d")
  ), universe = u, class = "CnfClause")
  c2 = structure(list(
    X = c("b", "a"), Y = c("d")
  ), universe = u, class = "CnfClause")
  c3 = structure(list(
    Y = c("d"), X = c("a", "b")
  ), universe = u, class = "CnfClause")
  c4 = structure(list(
    Y = c("d"), X = c("b", "a")
  ), universe = u, class = "CnfClause")
  c_unequal = structure(list(
    X = c("a", "b"), Z = c("g")
  ), universe = u, class = "CnfClause")


  for (cx in list(c1, c2, c3, c4)) {
    for (cy in list(c1, c2, c3, c4)) {
      expect_true(all.equal(cx, cy))
    }
  }

  for (cx in list(c1, c2, c3, c4)) {
    expect_string(paste(all.equal(cx, c_unequal), collapse = "\n"), pattern = "string mismatch|[Ll]ength mismatch")
  }


})
