/***
 * Bitwuzla: Satisfiability Modulo Theories (SMT) solver.
 *
 * Copyright (C) 2022 by the authors listed in the AUTHORS file at
 * https://github.com/bitwuzla/bitwuzla/blob/main/AUTHORS
 *
 * This file is part of Bitwuzla under the MIT license. See COPYING for more
 * information at https://github.com/bitwuzla/bitwuzla/blob/main/COPYING
 */

#include <gtest/gtest.h>

#include "option/option.h"
#include "preprocess/pass/variable_substitution.h"
#include "preprocess/preprocessor.h"
#include "sat/sat_solver_factory.h"
#include "solving_context.h"
#include "test/unit/preprocess/test_preprocess_pass.h"

namespace bzla::test {

using namespace preprocess;
using namespace backtrack;
using namespace node;

class TestPassVariableSubstitution : public TestPreprocessingPass
{
 public:
  TestPassVariableSubstitution()
      : d_sat_factory(d_options),
        d_env(d_nm, d_sat_factory),
        d_pass(d_env, &d_bm)
  {
    d_options.rewrite_level.set(0);
    d_options.pp_embedded_constr.set(false);
    d_options.pp_flatten_and.set(false);
    d_options.pp_skeleton_preproc.set(false);
    d_options.pp_normalize.set(false);
  };

 protected:
  option::Options d_options;
  sat::SatSolverFactory d_sat_factory;
  Env d_env;
  preprocess::pass::PassVariableSubstitution d_pass;
};

TEST_F(TestPassVariableSubstitution, subst1)
{
  Node x  = d_nm.mk_const(d_nm.mk_bool_type(), "x");
  Node t  = d_nm.mk_const(d_nm.mk_bool_type(), "t");
  Node eq = d_nm.mk_node(Kind::EQUAL, {x, t});

  d_as.push_back(eq);

  preprocess::AssertionVector assertions(d_as.view());
  d_pass.apply(assertions);

  ASSERT_EQ(d_as.size(), 1);
  ASSERT_EQ(d_as[0], d_nm.mk_value(true));
  ASSERT_EQ(d_pass.process(eq), d_nm.mk_value(true));
}

TEST_F(TestPassVariableSubstitution, subst2)
{
  Node x  = d_nm.mk_const(d_nm.mk_bool_type());
  Node y  = d_nm.mk_const(d_nm.mk_bool_type());
  Node t  = d_nm.mk_const(d_nm.mk_bool_type());
  Node eq = d_nm.mk_node(Kind::EQUAL, {x, y});
  Node di = d_nm.mk_node(Kind::NOT, {d_nm.mk_node(Kind::EQUAL, {x, t})});

  d_as.push_back(eq);
  d_as.push_back(di);

  preprocess::AssertionVector assertions(d_as.view());
  d_pass.apply(assertions);

  Node expected = d_nm.mk_node(Kind::NOT, {d_nm.mk_node(Kind::EQUAL, {y, t})});
  ASSERT_EQ(d_as.size(), 2);
  ASSERT_EQ(d_as[0], d_nm.mk_value(true));
  ASSERT_EQ(d_as[1], expected);
  ASSERT_EQ(d_pass.process(eq), d_nm.mk_value(true));
  ASSERT_EQ(d_pass.process(di), expected);
}

TEST_F(TestPassVariableSubstitution, cycle1)
{
  Node x  = d_nm.mk_const(d_nm.mk_bool_type(), "x");
  Node eq = d_nm.mk_node(Kind::EQUAL, {x, x});

  d_as.push_back(eq);

  preprocess::AssertionVector assertions(d_as.view());
  d_pass.apply(assertions);
  ASSERT_EQ(assertions[0], eq);
}

TEST_F(TestPassVariableSubstitution, cycle2)
{
  Node x       = d_nm.mk_const(d_nm.mk_bool_type(), "x");
  Node y       = d_nm.mk_const(d_nm.mk_bool_type(), "y");
  Node eq1     = d_nm.mk_node(Kind::EQUAL, {x, y});
  Node x_and_y = d_nm.mk_node(Kind::AND, {x, y});
  Node eq2     = d_nm.mk_node(Kind::EQUAL, {y, x_and_y});

  d_as.push_back(eq1);
  d_as.push_back(eq2);
  d_as.push_back(x_and_y);

  preprocess::AssertionVector assertions(d_as.view());
  d_pass.apply(assertions);
  ASSERT_EQ(d_as[0], d_nm.mk_value(true));
  ASSERT_EQ(d_as[1], d_nm.mk_value(true));
  ASSERT_EQ(d_as[2], y);
}

TEST_F(TestPassVariableSubstitution, cycle3)
{
  Node x       = d_nm.mk_const(d_nm.mk_bool_type(), "x");
  Node y       = d_nm.mk_const(d_nm.mk_bool_type(), "y");
  Node z       = d_nm.mk_const(d_nm.mk_bool_type(), "z");
  Node x_and_z = d_nm.mk_node(Kind::AND, {x, z});
  Node eq1     = d_nm.mk_node(Kind::EQUAL, {x, y});
  Node eq2     = d_nm.mk_node(Kind::EQUAL, {y, x_and_z});

  d_as.push_back(x_and_z);
  d_as.push_back(eq2);
  d_as.push_back(eq1);

  preprocess::AssertionVector assertions(d_as.view());
  d_pass.apply(assertions);

  Node y_and_z = d_nm.mk_node(Kind::AND, {y, z});
  ASSERT_EQ(d_as[0], y_and_z);
  ASSERT_EQ(d_as[1], d_nm.mk_node(Kind::EQUAL, {y, y_and_z}));
  ASSERT_EQ(d_as[2], d_nm.mk_value(true));
}

TEST_F(TestPassVariableSubstitution, cycle4)
{
  Node x   = d_nm.mk_const(d_nm.mk_bool_type(), "x");
  Node y   = d_nm.mk_const(d_nm.mk_bool_type(), "y");
  Node z   = d_nm.mk_const(d_nm.mk_bool_type(), "z");
  Node eq1 = d_nm.mk_node(Kind::EQUAL, {x, y});
  Node eq2 = d_nm.mk_node(Kind::EQUAL, {y, z});
  Node eq3 = d_nm.mk_node(Kind::EQUAL, {z, x});

  d_as.push_back(eq1);
  d_as.push_back(eq2);
  d_as.push_back(eq3);

  preprocess::AssertionVector assertions(d_as.view());
  d_pass.apply(assertions);

  ASSERT_EQ(d_as[0], d_nm.mk_value(true));
  ASSERT_EQ(d_as[1], d_nm.mk_value(true));
  ASSERT_EQ(d_as[2], d_nm.mk_value(true));
}

TEST_F(TestPassVariableSubstitution, cycle5)
{
  Node x   = d_nm.mk_const(d_nm.mk_bool_type(), "x");
  Node y   = d_nm.mk_const(d_nm.mk_bool_type(), "y");
  Node z   = d_nm.mk_const(d_nm.mk_bool_type(), "z");
  Node eq1 = d_nm.mk_node(Kind::EQUAL, {x, y});
  Node eq2 = d_nm.mk_node(Kind::EQUAL, {y, z});
  Node eq3 = d_nm.mk_node(Kind::EQUAL, {z, x});

  d_as.push_back(eq1);
  d_as.push_back(eq2);

  preprocess::AssertionVector assertions(d_as.view());
  d_pass.apply(assertions);
  ASSERT_EQ(d_as[0], d_nm.mk_value(true));
  ASSERT_EQ(d_as[1], d_nm.mk_value(true));

  d_as.push_back(eq3);
  preprocess::AssertionVector assertions2(d_as.view());
  d_pass.apply(assertions);
  ASSERT_EQ(d_as[2], d_nm.mk_value(true));
}

/* --- Incremental tests ---------------------------------------------------- */

TEST_F(TestPassVariableSubstitution, inc1)
{
  SolvingContext ctx(d_nm, d_options, d_sat_factory);
  Preprocessor& pp = ctx.preprocessor();

  Node x  = d_nm.mk_const(d_nm.mk_bool_type(), "x");
  Node y  = d_nm.mk_const(d_nm.mk_bool_type(), "y");
  Node t  = d_nm.mk_const(d_nm.mk_bool_type(), "t");
  Node eq = d_nm.mk_node(Kind::EQUAL, {x, y});
  Node di = d_nm.mk_node(Kind::NOT, {d_nm.mk_node(Kind::EQUAL, {x, t})});

  ctx.assert_formula(eq);
  ctx.push();
  ctx.assert_formula(di);

  ctx.preprocess();
  auto as = ctx.assertions();

  if (d_options.pp_variable_subst_norm_diseq())
  {
    Node not_t = d_nm.invert_node(t);
    ASSERT_EQ(as.size(), 2);
    ASSERT_EQ(as[0], d_nm.mk_node(Kind::EQUAL, {y, y}));
    ASSERT_EQ(as[1], d_nm.mk_node(Kind::EQUAL, {y, not_t}));
    ASSERT_EQ(pp.process(eq), d_nm.mk_node(Kind::EQUAL, {not_t, not_t}));
    ASSERT_EQ(pp.process(di),
              d_nm.invert_node(d_nm.mk_node(Kind::EQUAL, {not_t, t})));

    ctx.pop();
    ctx.preprocess();
    ASSERT_EQ(as.size(), 1);
    ASSERT_EQ(as[0], d_nm.mk_node(Kind::EQUAL, {y, y}));
    ASSERT_EQ(pp.process(eq), d_nm.mk_node(Kind::EQUAL, {y, y}));
    ASSERT_EQ(pp.process(di),
              d_nm.mk_node(Kind::NOT, {d_nm.mk_node(Kind::EQUAL, {y, t})}));
  }
  else
  {
    Node expected =
        d_nm.mk_node(Kind::NOT, {d_nm.mk_node(Kind::EQUAL, {y, t})});
    ASSERT_EQ(as.size(), 2);
    ASSERT_EQ(as[0], d_nm.mk_node(Kind::EQUAL, {y, y}));
    ASSERT_EQ(as[1], expected);
    ASSERT_EQ(pp.process(eq), d_nm.mk_node(Kind::EQUAL, {y, y}));
    ASSERT_EQ(pp.process(di), expected);

    ctx.pop();
    ctx.preprocess();
    ASSERT_EQ(as.size(), 1);
    ASSERT_EQ(as[0], d_nm.mk_node(Kind::EQUAL, {y, y}));
    ASSERT_EQ(pp.process(eq), d_nm.mk_node(Kind::EQUAL, {y, y}));
    ASSERT_EQ(pp.process(di), expected);
  }
}

TEST_F(TestPassVariableSubstitution, inc2)
{
  SolvingContext ctx(d_nm, d_options, d_sat_factory);
  Preprocessor& pp = ctx.preprocessor();

  Node x       = d_nm.mk_const(d_nm.mk_bool_type(), "x");
  Node y       = d_nm.mk_const(d_nm.mk_bool_type(), "y");
  Node eq      = d_nm.mk_node(Kind::EQUAL, {x, y});
  Node di      = d_nm.mk_node(Kind::NOT, {d_nm.mk_node(Kind::EQUAL, {x, y})});
  Node x_and_y = d_nm.mk_node(Kind::AND, {x, y});

  ctx.assert_formula(di);
  ctx.push();
  ctx.assert_formula(eq);
  ctx.assert_formula(x_and_y);

  ctx.preprocess();
  auto as = ctx.assertions();

  if (d_options.pp_variable_subst_norm_diseq())
  {
    Node not_y       = d_nm.invert_node(y);
    Node not_y_and_y = d_nm.mk_node(Kind::AND, {not_y, y});
    ASSERT_EQ(as[0], d_nm.invert_node(d_nm.mk_node(Kind::EQUAL, {not_y, y})));
    ASSERT_EQ(as[1], d_nm.mk_node(Kind::EQUAL, {not_y, y}));
    ASSERT_EQ(as[2], not_y_and_y);
    ASSERT_EQ(pp.process(eq), d_nm.mk_node(Kind::EQUAL, {not_y, y}));
    ASSERT_EQ(pp.process(x_and_y), not_y_and_y);

    ctx.pop();
    ctx.preprocess();
    ASSERT_EQ(pp.process(eq), d_nm.mk_node(Kind::EQUAL, {not_y, y}));
    ASSERT_EQ(pp.process(x_and_y), not_y_and_y);
  }
  else
  {
    Node y_and_y = d_nm.mk_node(Kind::AND, {y, y});
    ASSERT_EQ(as[0], di);
    ASSERT_EQ(as[1], eq);
    ASSERT_EQ(as[2], y_and_y);
    ASSERT_EQ(pp.process(eq), d_nm.mk_node(Kind::EQUAL, {y, y}));
    ASSERT_EQ(pp.process(x_and_y), y_and_y);

    ctx.pop();
    ctx.preprocess();
    ASSERT_EQ(pp.process(eq), eq);
    ASSERT_EQ(pp.process(x_and_y), x_and_y);
  }
}

TEST_F(TestPassVariableSubstitution, inc3)
{
  SolvingContext ctx(d_nm, d_options, d_sat_factory);
  Preprocessor& pp = ctx.preprocessor();

  Node x       = d_nm.mk_const(d_nm.mk_bool_type(), "x");
  Node y       = d_nm.mk_const(d_nm.mk_bool_type(), "y");
  Node z       = d_nm.mk_const(d_nm.mk_bool_type(), "y");
  Node eq      = d_nm.mk_node(Kind::EQUAL, {x, y});
  Node di      = d_nm.mk_node(Kind::NOT, {d_nm.mk_node(Kind::EQUAL, {x, y})});
  Node x_and_y = d_nm.mk_node(Kind::AND, {x, y});
  Node x_and_z = d_nm.mk_node(Kind::AND, {x, z});

  ctx.assert_formula(di);
  ctx.push();
  ctx.assert_formula(eq);
  ctx.assert_formula(x_and_y);
  ctx.push();
  ctx.push();
  ctx.assert_formula(x_and_z);

  ctx.preprocess();
  auto as = ctx.assertions();

  if (d_options.pp_variable_subst_norm_diseq())
  {
    Node not_y       = d_nm.invert_node(y);
    Node not_y_eq_y  = d_nm.mk_node(Kind::EQUAL, {not_y, y});
    Node not_y_and_z = d_nm.mk_node(Kind::AND, {not_y, z});
    Node not_y_and_y = d_nm.mk_node(Kind::AND, {not_y, y});
    ASSERT_EQ(as[0], d_nm.invert_node(not_y_eq_y));
    ASSERT_EQ(as[1], not_y_eq_y);
    ASSERT_EQ(as[2], not_y_and_y);
    ASSERT_EQ(as[3], not_y_and_z);
    ASSERT_EQ(pp.process(eq), not_y_eq_y);
    ASSERT_EQ(pp.process(x_and_y), not_y_and_y);
    ASSERT_EQ(pp.process(x_and_z), not_y_and_z);

    ctx.pop();
    ASSERT_EQ(pp.process(eq), not_y_eq_y);
    ASSERT_EQ(pp.process(x_and_y), not_y_and_y);
    ASSERT_EQ(pp.process(x_and_z), not_y_and_z);

    ctx.pop();
    ctx.pop();
    ASSERT_EQ(pp.process(eq), not_y_eq_y);
    ASSERT_EQ(pp.process(x_and_y), not_y_and_y);
    ASSERT_EQ(pp.process(x_and_z), not_y_and_z);
  }
  else
  {
    Node y_and_z = d_nm.mk_node(Kind::AND, {y, z});
    Node y_and_y = d_nm.mk_node(Kind::AND, {y, y});
    ASSERT_EQ(as[0], di);
    ASSERT_EQ(as[1], eq);
    ASSERT_EQ(as[2], y_and_y);
    ASSERT_EQ(as[3], y_and_z);
    ASSERT_EQ(pp.process(eq), d_nm.mk_node(Kind::EQUAL, {y, y}));
    ASSERT_EQ(pp.process(x_and_y), y_and_y);
    ASSERT_EQ(pp.process(x_and_z), y_and_z);

    ctx.pop();
    ASSERT_EQ(pp.process(eq), d_nm.mk_node(Kind::EQUAL, {y, y}));
    ASSERT_EQ(pp.process(x_and_y), y_and_y);
    ASSERT_EQ(pp.process(x_and_z), y_and_z);

    ctx.pop();
    ctx.pop();
    ASSERT_EQ(pp.process(eq), eq);
    ASSERT_EQ(pp.process(x_and_y), x_and_y);
    ASSERT_EQ(pp.process(x_and_z), x_and_z);
  }
}

}  // namespace bzla::test
