/*  Boolector: Satisfiability Modulo Theories (SMT) solver.
 *
 *  Copyright (C) 2007-2021 by the authors listed in the AUTHORS file.
 *
 *  This file is part of Boolector.
 *  See COPYING for more information on using this software.
 */

#include "test.h"

extern "C" {
#include "btorbeta.h"
#include "btorcore.h"
#include "btorexp.h"
#include "utils/btormem.h"
#include "utils/btorutil.h"
}

class TestLambda : public TestBtor
{
 protected:
  void SetUp () override
  {
    TestBtor::SetUp ();
    d_elem_sort  = btor_sort_bv (d_btor, s_elem_bw);
    d_index_sort = btor_sort_bv (d_btor, s_index_bw);
    d_array_sort = btor_sort_array (d_btor, d_index_sort, d_elem_sort);
  }

  void TearDown () override
  {
    if (d_elem_sort)
    {
      btor_sort_release (d_btor, d_elem_sort);
      d_elem_sort = 0;
    }
    if (d_index_sort)
    {
      btor_sort_release (d_btor, d_index_sort);
      d_index_sort = 0;
    }
    if (d_array_sort)
    {
      btor_sort_release (d_btor, d_array_sort);
      d_array_sort = 0;
    }
    TestBtor::TearDown ();
  }

  void assert_parameterized (int32_t argc, ...)
  {
    int32_t i;
    va_list ap;
    BtorNode *e;

    va_start (ap, argc);
    for (i = 0; i < argc; i++)
    {
      e = va_arg (ap, BtorNode *);
      ASSERT_TRUE (btor_node_real_addr (e)->parameterized);
    }
    va_end (ap);
  }

  void assert_not_parameterized (int32_t argc, ...)
  {
    int32_t i;
    va_list ap;
    BtorNode *e;

    va_start (ap, argc);
    for (i = 0; i < argc; i++)
    {
      e = va_arg (ap, BtorNode *);
      ASSERT_FALSE (btor_node_real_addr (e)->parameterized);
    }
    va_end (ap);
  }

  BtorNode *apply_and_reduce (Btor *btor,
                              BtorNode *args[],
                              int32_t argc,
                              BtorNode *lambda)
  {
    assert (btor);
    assert (argc >= 0);
    assert (argc < 1 || args);
    assert (lambda);

    int32_t i;
    BtorNode *result, *cur;
    BtorNodePtrStack unassign;
    BtorMemMgr *mm;

    mm = btor->mm;

    BTOR_INIT_STACK (mm, unassign);

    cur = lambda;
    for (i = 0; i < argc; i++)
    {
      assert (btor_node_is_regular (cur));
      assert (btor_node_is_lambda (cur));
      btor_beta_assign_param (btor, cur, args[i]);
      BTOR_PUSH_STACK (unassign, cur);
      cur = btor_node_real_addr (cur->e[1]);
    }

    result = btor_beta_reduce_full (btor, lambda, 0);

    while (!BTOR_EMPTY_STACK (unassign))
    {
      cur = BTOR_POP_STACK (unassign);
      btor_beta_unassign_params (btor, cur);
    }

    BTOR_RELEASE_STACK (unassign);

    return result;
  }

  void unary_param_exp_test (BtorNode *(*func) (Btor *, BtorNode *) )
  {
    BtorNode *result;
    BtorNode *var, *expected, *param, *param_exp, *lambda;
    BtorSortId lambda_index_sort;

    lambda_index_sort = d_elem_sort;
    var               = btor_exp_var (d_btor, d_elem_sort, "v1");
    expected          = func (d_btor, var);
    param             = btor_exp_param (d_btor, lambda_index_sort, "p1");
    param_exp         = func (d_btor, param);
    lambda            = btor_exp_lambda (d_btor, param, param_exp);
    result            = apply_and_reduce (d_btor, &var, 1, lambda);

    ASSERT_EQ (result, expected);
    assert_parameterized (2, param, param_exp);
    assert_not_parameterized (4, var, expected, lambda, result);

    btor_node_release (d_btor, result);
    btor_node_release (d_btor, lambda);
    btor_node_release (d_btor, param_exp);
    btor_node_release (d_btor, param);
    btor_node_release (d_btor, expected);
    btor_node_release (d_btor, var);
  }

  void param_extension_test (BtorNode *(*func) (Btor *, BtorNode *, uint32_t))
  {
    BtorNode *result;
    BtorNode *var, *param, *expected, *param_exp, *lambda;
    BtorSortId lower_sort, upper_sort;
    int32_t lower, upper;

    lower      = s_elem_bw / 2 + 1;
    upper      = s_elem_bw - 1;
    lower_sort = btor_sort_bv (d_btor, lower);
    upper_sort = btor_sort_bv (d_btor, upper);

    var       = btor_exp_var (d_btor, lower_sort, "v1");
    param     = btor_exp_param (d_btor, lower_sort, "p1");
    expected  = func (d_btor, var, upper_sort);
    param_exp = func (d_btor, param, upper_sort);
    lambda    = btor_exp_lambda (d_btor, param, param_exp);
    result    = apply_and_reduce (d_btor, &var, 1, lambda);

    ASSERT_EQ (result, expected);
    assert_parameterized (2, param, param_exp);
    assert_not_parameterized (4, var, expected, lambda, result);

    btor_sort_release (d_btor, lower_sort);
    btor_sort_release (d_btor, upper_sort);
    btor_node_release (d_btor, result);
    btor_node_release (d_btor, lambda);
    btor_node_release (d_btor, expected);
    btor_node_release (d_btor, param_exp);
    btor_node_release (d_btor, param);
    btor_node_release (d_btor, var);
  }

  /* (lambda x . bin_exp (x, v2)) (v1) or (lambda x . bin_exp (v1, x)) (v2) */
  void binary_param_exp_test (
      int32_t param_pos, BtorNode *(*func) (Btor *, BtorNode *, BtorNode *) )
  {
    assert (param_pos == 0 || param_pos == 1);

    BtorNode *result;
    BtorNode *param_exp, *v1, *v2, *expected, *x;
    BtorSortId v1_sort, v2_sort, x_sort;
    int32_t x_bw, v1_bw, v2_bw;

    v1_bw = s_elem_bw;
    v2_bw = s_elem_bw;

    if (func == btor_exp_implies || func == btor_exp_iff)
    {
      v1_bw = 1;
      v2_bw = 1;
    }

    x_bw = (param_pos == 0) ? v1_bw : v2_bw;

    v1_sort = btor_sort_bv (d_btor, v1_bw);
    v2_sort = btor_sort_bv (d_btor, v2_bw);
    x_sort  = btor_sort_bv (d_btor, x_bw);

    v1       = btor_exp_var (d_btor, v1_sort, "v1");
    v2       = btor_exp_var (d_btor, v2_sort, "v2");
    expected = func (d_btor, v1, v2);
    x        = btor_exp_param (d_btor, x_sort, "x");

    if (param_pos == 0)
      param_exp = func (d_btor, x, v2);
    else
      param_exp = func (d_btor, v1, x);

    BtorNode *lambda = btor_exp_lambda (d_btor, x, param_exp);

    if (param_pos == 0)
      result = apply_and_reduce (d_btor, &v1, 1, lambda);
    else
      result = apply_and_reduce (d_btor, &v2, 1, lambda);

    ASSERT_EQ (result, expected);
    assert_parameterized (2, x, param_exp);
    assert_not_parameterized (5, v1, v2, expected, lambda, result);

    btor_sort_release (d_btor, v1_sort);
    btor_sort_release (d_btor, v2_sort);
    btor_sort_release (d_btor, x_sort);
    btor_node_release (d_btor, result);
    btor_node_release (d_btor, lambda);
    btor_node_release (d_btor, param_exp);
    btor_node_release (d_btor, x);
    btor_node_release (d_btor, expected);
    btor_node_release (d_btor, v2);
    btor_node_release (d_btor, v1);
  }

  static constexpr int32_t s_index_bw = 32;
  static constexpr int32_t s_elem_bw  = 16;

  BtorIntHashTable *d_htable = nullptr;
  BtorSortId d_elem_sort     = 0;
  BtorSortId d_index_sort    = 0;
  BtorSortId d_array_sort    = 0;
};

/*---------------------------------------------------------------------------
 * constant lambda tests
 *---------------------------------------------------------------------------*/

TEST_F (TestLambda, const_lambda_const)
{
  BtorNode *result;
  BtorNode *x, *c, *i, *lambda;

  x      = btor_exp_param (d_btor, d_index_sort, "x");
  c      = btor_exp_bv_zero (d_btor, d_elem_sort);
  i      = btor_exp_var (d_btor, d_index_sort, "i");
  lambda = btor_exp_lambda (d_btor, x, c);

  /* (lambda x . 0) (i) */
  result = apply_and_reduce (d_btor, &i, 1, lambda);
  ASSERT_EQ (result, c);
  assert_not_parameterized (1, result);
  btor_node_release (d_btor, result);

  /* (lambda x . 0) () */
  result = apply_and_reduce (d_btor, 0, 0, lambda);
  ASSERT_EQ (result, c);
  assert_parameterized (1, x);
  assert_not_parameterized (4, result, c, i, lambda);

  btor_node_release (d_btor, result);
  btor_node_release (d_btor, lambda);
  btor_node_release (d_btor, i);
  btor_node_release (d_btor, c);
  btor_node_release (d_btor, x);
}

TEST_F (TestLambda, const_lambda_var)
{
  BtorNode *result;
  BtorNode *x, *a, *i, *lambda;

  x      = btor_exp_param (d_btor, d_index_sort, "x");
  a      = btor_exp_var (d_btor, d_elem_sort, "a");
  i      = btor_exp_var (d_btor, d_index_sort, "i");
  lambda = btor_exp_lambda (d_btor, x, a);

  /* (lambda x . a) (i) */
  result = apply_and_reduce (d_btor, &i, 1, lambda);
  ASSERT_EQ (result, a);
  assert_not_parameterized (1, result);
  btor_node_release (d_btor, result);

  /* (lambda x . a) () */
  result = apply_and_reduce (d_btor, 0, 0, lambda);
  ASSERT_EQ (result, a);
  assert_parameterized (1, x);
  assert_not_parameterized (4, result, lambda, i, a);

  btor_node_release (d_btor, result);
  btor_node_release (d_btor, lambda);
  btor_node_release (d_btor, a);
  btor_node_release (d_btor, x);
  btor_node_release (d_btor, i);
}

TEST_F (TestLambda, const_lambda_param)
{
  BtorNode *result;
  BtorNode *x, *a, *lambda;

  x      = btor_exp_param (d_btor, d_elem_sort, "x");
  a      = btor_exp_var (d_btor, d_elem_sort, "a");
  lambda = btor_exp_lambda (d_btor, x, x);

  /* (lambda x . x) (a) */
  result = apply_and_reduce (d_btor, &a, 1, lambda);
  ASSERT_EQ (result, a);
  assert_not_parameterized (1, result);
  btor_node_release (d_btor, result);

  /* (lambda x . x) () */
  result = apply_and_reduce (d_btor, 0, 0, lambda);
  ASSERT_EQ (result, lambda);
  assert_parameterized (1, x);
  assert_not_parameterized (3, result, lambda, a);

  btor_node_release (d_btor, result);
  btor_node_release (d_btor, lambda);
  btor_node_release (d_btor, a);
  btor_node_release (d_btor, x);
}

TEST_F (TestLambda, const_lambda_negated)
{
  BtorNode *result;
  BtorNode *a, *not_a, *x, *not_x, *lambda;

  a      = btor_exp_var (d_btor, d_elem_sort, "a");
  not_a  = btor_exp_bv_not (d_btor, a);
  x      = btor_exp_param (d_btor, d_elem_sort, "x");
  not_x  = btor_exp_bv_not (d_btor, x);
  lambda = btor_exp_lambda (d_btor, x, not_x);

  /* (lambda x . not (x)) (not (a)) */
  result = apply_and_reduce (d_btor, &not_a, 1, lambda);
  ASSERT_EQ (result, a);
  assert_not_parameterized (1, result);
  btor_node_release (d_btor, result);

  /* (lambda x . not (x)) () */
  result = apply_and_reduce (d_btor, 0, 0, lambda);
  ASSERT_EQ (result, lambda);
  assert_parameterized (2, x, not_x);
  assert_not_parameterized (4, result, lambda, not_a, a);

  btor_node_release (d_btor, result);
  btor_node_release (d_btor, lambda);
  btor_node_release (d_btor, not_x);
  btor_node_release (d_btor, x);
  btor_node_release (d_btor, not_a);
  btor_node_release (d_btor, a);
}

/* (lambda x . a) () */
TEST_F (TestLambda, unassigned_param)
{
  BtorNode *result;
  BtorNode *x, *a, *lambda;

  x      = btor_exp_param (d_btor, d_index_sort, "x");
  a      = btor_exp_var (d_btor, d_elem_sort, "a");
  lambda = btor_exp_lambda (d_btor, x, a);
  result = apply_and_reduce (d_btor, 0, 0, lambda);

  ASSERT_EQ (result, a);
  assert_parameterized (1, x);
  assert_not_parameterized (3, result, lambda, a);

  btor_node_release (d_btor, result);
  btor_node_release (d_btor, lambda);
  btor_node_release (d_btor, a);
  btor_node_release (d_btor, x);
}

/*---------------------------------------------------------------------------
 * unary expression tests
 *---------------------------------------------------------------------------*/

TEST_F (TestLambda, param_not) { unary_param_exp_test (btor_exp_bv_not); }

TEST_F (TestLambda, param_neg) { unary_param_exp_test (btor_exp_bv_neg); }

TEST_F (TestLambda, param_redor) { unary_param_exp_test (btor_exp_bv_redor); }

TEST_F (TestLambda, param_redxor) { unary_param_exp_test (btor_exp_bv_redxor); }

TEST_F (TestLambda, param_redand) { unary_param_exp_test (btor_exp_bv_redand); }

TEST_F (TestLambda, param_slice)
{
  BtorNode *result;
  BtorNode *var, *param, *expected, *slice, *lambda;
  int32_t lower, upper;

  lower = s_elem_bw / 2 + 1;
  upper = s_elem_bw - 1;

  var      = btor_exp_var (d_btor, d_elem_sort, "v1");
  param    = btor_exp_param (d_btor, d_elem_sort, "p1");
  expected = btor_exp_bv_slice (d_btor, var, upper, lower);
  slice    = btor_exp_bv_slice (d_btor, param, upper, lower);
  lambda   = btor_exp_lambda (d_btor, param, slice);
  result   = apply_and_reduce (d_btor, &var, 1, lambda);

  ASSERT_EQ (result, expected);
  assert_parameterized (2, param, slice);
  assert_not_parameterized (4, var, expected, lambda, result);

  btor_node_release (d_btor, result);
  btor_node_release (d_btor, lambda);
  btor_node_release (d_btor, expected);
  btor_node_release (d_btor, slice);
  btor_node_release (d_btor, param);
  btor_node_release (d_btor, var);
}

TEST_F (TestLambda, param_uext) { param_extension_test (btor_exp_bv_uext); }

TEST_F (TestLambda, param_sext) { param_extension_test (btor_exp_bv_sext); }

/*---------------------------------------------------------------------------
 * binary expression tests
 *---------------------------------------------------------------------------*/

TEST_F (TestLambda, param_implies)
{
  binary_param_exp_test (0, btor_exp_implies);
  binary_param_exp_test (1, btor_exp_implies);
}

TEST_F (TestLambda, param_iff)
{
  binary_param_exp_test (0, btor_exp_iff);
  binary_param_exp_test (1, btor_exp_iff);
}

TEST_F (TestLambda, param_xor)
{
  binary_param_exp_test (0, btor_exp_bv_xor);
  binary_param_exp_test (1, btor_exp_bv_xor);
}

TEST_F (TestLambda, param_xnor)
{
  binary_param_exp_test (0, btor_exp_bv_xnor);
  binary_param_exp_test (1, btor_exp_bv_xnor);
}

TEST_F (TestLambda, param_and)
{
  binary_param_exp_test (0, btor_exp_bv_and);
  binary_param_exp_test (1, btor_exp_bv_and);
}

TEST_F (TestLambda, param_nand)
{
  binary_param_exp_test (0, btor_exp_bv_nand);
  binary_param_exp_test (1, btor_exp_bv_nand);
}

TEST_F (TestLambda, param_or)
{
  binary_param_exp_test (0, btor_exp_bv_or);
  binary_param_exp_test (1, btor_exp_bv_or);
}

TEST_F (TestLambda, param_nor)
{
  binary_param_exp_test (0, btor_exp_bv_nor);
  binary_param_exp_test (1, btor_exp_bv_nor);
}

TEST_F (TestLambda, param_eq)
{
  binary_param_exp_test (0, btor_exp_eq);
  binary_param_exp_test (1, btor_exp_eq);
}

TEST_F (TestLambda, param_ne)
{
  binary_param_exp_test (0, btor_exp_ne);
  binary_param_exp_test (1, btor_exp_ne);
}

TEST_F (TestLambda, param_add)
{
  binary_param_exp_test (0, btor_exp_bv_add);
  binary_param_exp_test (1, btor_exp_bv_add);
}

TEST_F (TestLambda, param_uaddo)
{
  binary_param_exp_test (0, btor_exp_bv_uaddo);
  binary_param_exp_test (1, btor_exp_bv_uaddo);
}

TEST_F (TestLambda, param_saddo)
{
  binary_param_exp_test (0, btor_exp_bv_saddo);
  binary_param_exp_test (1, btor_exp_bv_saddo);
}

TEST_F (TestLambda, param_mul)
{
  binary_param_exp_test (0, btor_exp_bv_mul);
  binary_param_exp_test (1, btor_exp_bv_mul);
}

TEST_F (TestLambda, param_umulo)
{
  binary_param_exp_test (0, btor_exp_bv_umulo);
  binary_param_exp_test (1, btor_exp_bv_umulo);
}

TEST_F (TestLambda, param_smulo)
{
  binary_param_exp_test (0, btor_exp_bv_smulo);
  binary_param_exp_test (1, btor_exp_bv_smulo);
}

TEST_F (TestLambda, param_ult)
{
  binary_param_exp_test (0, btor_exp_bv_ult);
  binary_param_exp_test (1, btor_exp_bv_ult);
}

TEST_F (TestLambda, param_slt)
{
  binary_param_exp_test (0, btor_exp_bv_slt);
  binary_param_exp_test (1, btor_exp_bv_slt);
}

TEST_F (TestLambda, param_ulte)
{
  binary_param_exp_test (0, btor_exp_bv_ulte);
  binary_param_exp_test (1, btor_exp_bv_ulte);
}

TEST_F (TestLambda, param_slte)
{
  binary_param_exp_test (0, btor_exp_bv_slte);
  binary_param_exp_test (1, btor_exp_bv_slte);
}

TEST_F (TestLambda, param_ugt)
{
  binary_param_exp_test (0, btor_exp_bv_ugt);
  binary_param_exp_test (1, btor_exp_bv_ugt);
}

TEST_F (TestLambda, param_sgt)
{
  binary_param_exp_test (0, btor_exp_bv_sgt);
  binary_param_exp_test (1, btor_exp_bv_sgt);
}

TEST_F (TestLambda, param_ugte)
{
  binary_param_exp_test (0, btor_exp_bv_ugte);
  binary_param_exp_test (1, btor_exp_bv_ugte);
}

TEST_F (TestLambda, param_sgte)
{
  binary_param_exp_test (0, btor_exp_bv_sgte);
  binary_param_exp_test (1, btor_exp_bv_sgte);
}

TEST_F (TestLambda, param_sll)
{
  binary_param_exp_test (0, btor_exp_bv_sll);
  binary_param_exp_test (1, btor_exp_bv_sll);
}

TEST_F (TestLambda, param_srl)
{
  binary_param_exp_test (0, btor_exp_bv_srl);
  binary_param_exp_test (1, btor_exp_bv_srl);
}

TEST_F (TestLambda, param_sra)
{
  binary_param_exp_test (0, btor_exp_bv_sra);
  binary_param_exp_test (1, btor_exp_bv_sra);
}

TEST_F (TestLambda, param_rol)
{
  binary_param_exp_test (0, btor_exp_bv_rol);
  binary_param_exp_test (1, btor_exp_bv_rol);
}

TEST_F (TestLambda, param_ror)
{
  binary_param_exp_test (0, btor_exp_bv_ror);
  binary_param_exp_test (1, btor_exp_bv_ror);
}

TEST_F (TestLambda, param_sub)
{
  binary_param_exp_test (0, btor_exp_bv_sub);
  binary_param_exp_test (1, btor_exp_bv_sub);
}

TEST_F (TestLambda, param_usubo)
{
  binary_param_exp_test (0, btor_exp_bv_usubo);
  binary_param_exp_test (1, btor_exp_bv_usubo);
}

TEST_F (TestLambda, param_ssubo)
{
  binary_param_exp_test (0, btor_exp_bv_ssubo);
  binary_param_exp_test (1, btor_exp_bv_ssubo);
}

TEST_F (TestLambda, param_udiv)
{
  binary_param_exp_test (0, btor_exp_bv_udiv);
  binary_param_exp_test (1, btor_exp_bv_udiv);
}

TEST_F (TestLambda, param_sdiv)
{
  binary_param_exp_test (0, btor_exp_bv_sdiv);
  binary_param_exp_test (1, btor_exp_bv_sdiv);
}

TEST_F (TestLambda, param_sdivo)
{
  binary_param_exp_test (0, btor_exp_bv_sdivo);
  binary_param_exp_test (1, btor_exp_bv_sdivo);
}

TEST_F (TestLambda, param_urem)
{
  binary_param_exp_test (0, btor_exp_bv_urem);
  binary_param_exp_test (1, btor_exp_bv_urem);
}

TEST_F (TestLambda, param_srem)
{
  binary_param_exp_test (0, btor_exp_bv_srem);
  binary_param_exp_test (1, btor_exp_bv_srem);
}

TEST_F (TestLambda, param_smod)
{
  binary_param_exp_test (0, btor_exp_bv_smod);
  binary_param_exp_test (1, btor_exp_bv_smod);
}

TEST_F (TestLambda, param_concat)
{
  binary_param_exp_test (0, btor_exp_bv_concat);
  binary_param_exp_test (1, btor_exp_bv_concat);
}

/* (lambda x . read(a, x)) (i) */
TEST_F (TestLambda, param_read)
{
  BtorNode *result;
  BtorNode *x, *i, *a, *expected, *read, *lambda;

  x        = btor_exp_param (d_btor, d_index_sort, "x");
  i        = btor_exp_var (d_btor, d_index_sort, "i");
  a        = btor_exp_array (d_btor, d_array_sort, "a");
  expected = btor_exp_read (d_btor, a, i);
  read     = btor_exp_read (d_btor, a, x);
  lambda   = btor_exp_lambda (d_btor, x, read);
  result   = apply_and_reduce (d_btor, &i, 1, lambda);

  ASSERT_EQ (result, expected);
  assert_parameterized (2, x, read);
  assert_not_parameterized (5, result, lambda, expected, a, i);

  btor_node_release (d_btor, result);
  btor_node_release (d_btor, lambda);
  btor_node_release (d_btor, read);
  btor_node_release (d_btor, expected);
  btor_node_release (d_btor, a);
  btor_node_release (d_btor, i);
  btor_node_release (d_btor, x);
}

/*---------------------------------------------------------------------------
 * ternary expression tests
 *---------------------------------------------------------------------------*/

// !!! currently broken as we do not support lambda hashing yet
/* (lambda x . write (a, i, x)) (e) */
TEST_F (TestLambda, DISABLED_param_write1)
{
  BtorNode *result;
  BtorNode *i, *e, *a, *expected, *x, *param_exp, *lambda;

  i         = btor_exp_var (d_btor, d_index_sort, "i");
  e         = btor_exp_var (d_btor, d_elem_sort, "e");
  a         = btor_exp_array (d_btor, d_array_sort, "a");
  expected  = btor_exp_lambda_write (d_btor, a, i, e);
  x         = btor_exp_param (d_btor, d_elem_sort, "x");
  param_exp = btor_exp_lambda_write (d_btor, a, i, x);
  lambda    = btor_exp_lambda (d_btor, x, param_exp);
  result    = apply_and_reduce (d_btor, &e, 1, lambda);

  ASSERT_EQ (result, expected);
  assert_parameterized (2, x, param_exp);
  assert_not_parameterized (6, result, lambda, expected, a, e, i);

  btor_node_release (d_btor, result);
  btor_node_release (d_btor, lambda);
  btor_node_release (d_btor, param_exp);
  btor_node_release (d_btor, x);
  btor_node_release (d_btor, expected);
  btor_node_release (d_btor, a);
  btor_node_release (d_btor, e);
  btor_node_release (d_btor, i);
}

// !!! currently broken as we do not support lambda hashing yet
/* (lambda x . write (a, x, e)) (i) */
TEST_F (TestLambda, DISABLED_param_write2)
{
  BtorNode *result;
  BtorNode *i, *e, *a, *expected, *x, *param_exp, *lambda;

  i         = btor_exp_var (d_btor, d_index_sort, "i");
  e         = btor_exp_var (d_btor, d_elem_sort, "e");
  a         = btor_exp_array (d_btor, d_array_sort, "a");
  expected  = btor_exp_lambda_write (d_btor, a, i, e);
  x         = btor_exp_param (d_btor, d_index_sort, "p");
  param_exp = btor_exp_lambda_write (d_btor, a, x, e);
  lambda    = btor_exp_lambda (d_btor, x, param_exp);
  result    = apply_and_reduce (d_btor, &i, 1, lambda);

  ASSERT_EQ (result, expected);
  assert_parameterized (2, x, param_exp);
  assert_not_parameterized (6, result, lambda, expected, a, e, i);

  btor_node_release (d_btor, result);
  btor_node_release (d_btor, lambda);
  btor_node_release (d_btor, param_exp);
  btor_node_release (d_btor, x);
  btor_node_release (d_btor, expected);
  btor_node_release (d_btor, a);
  btor_node_release (d_btor, e);
  btor_node_release (d_btor, i);
}

/* (lambda x . x ? v2 : v3) (v1) */
TEST_F (TestLambda, param_bcond1)
{
  BtorNode *result;
  BtorNode *v1, *v2, *v3, *x, *expected, *param_exp, *lambda;
  BtorSortId sort;

  sort      = btor_sort_bv (d_btor, 1);
  v1        = btor_exp_var (d_btor, sort, "v1");
  x         = btor_exp_param (d_btor, sort, "x");
  v2        = btor_exp_var (d_btor, d_elem_sort, "v2");
  v3        = btor_exp_var (d_btor, d_elem_sort, "v3");
  expected  = btor_exp_cond (d_btor, v1, v2, v3);
  param_exp = btor_exp_cond (d_btor, x, v2, v3);
  lambda    = btor_exp_lambda (d_btor, x, param_exp);
  result    = apply_and_reduce (d_btor, &v1, 1, lambda);

  ASSERT_EQ (result, expected);
  assert_parameterized (2, x, param_exp);
  assert_not_parameterized (6, result, lambda, expected, v3, v2, v1);

  btor_sort_release (d_btor, sort);
  btor_node_release (d_btor, result);
  btor_node_release (d_btor, lambda);
  btor_node_release (d_btor, param_exp);
  btor_node_release (d_btor, expected);
  btor_node_release (d_btor, v3);
  btor_node_release (d_btor, v2);
  btor_node_release (d_btor, x);
  btor_node_release (d_btor, v1);
}

/* (lambda x . v1 ? x : v3) (v2) */
TEST_F (TestLambda, param_bcond2)
{
  BtorNode *result;
  BtorNode *v1, *v2, *v3, *x, *expected, *param_exp, *lambda;
  BtorSortId sort;

  sort      = btor_sort_bv (d_btor, 1);
  v1        = btor_exp_var (d_btor, sort, "v1");
  x         = btor_exp_param (d_btor, d_elem_sort, "x");
  v2        = btor_exp_var (d_btor, d_elem_sort, "v2");
  v3        = btor_exp_var (d_btor, d_elem_sort, "v3");
  expected  = btor_exp_cond (d_btor, v1, v2, v3);
  param_exp = btor_exp_cond (d_btor, v1, x, v3);
  lambda    = btor_exp_lambda (d_btor, x, param_exp);
  result    = apply_and_reduce (d_btor, &v2, 1, lambda);

  ASSERT_EQ (result, expected);
  assert_parameterized (2, x, param_exp);
  assert_not_parameterized (6, result, lambda, expected, v3, v2, v1);

  btor_sort_release (d_btor, sort);
  btor_node_release (d_btor, result);
  btor_node_release (d_btor, lambda);
  btor_node_release (d_btor, param_exp);
  btor_node_release (d_btor, expected);
  btor_node_release (d_btor, v3);
  btor_node_release (d_btor, v2);
  btor_node_release (d_btor, x);
  btor_node_release (d_btor, v1);
}

/* (lambda x . v1 ? v2 : x) (v3) */
TEST_F (TestLambda, param_bcond3)
{
  BtorNode *result;
  BtorNode *v1, *v2, *v3, *x, *expected, *param_exp, *lambda;
  BtorSortId sort;

  sort      = btor_sort_bv (d_btor, 1);
  v1        = btor_exp_var (d_btor, sort, "v1");
  x         = btor_exp_param (d_btor, d_elem_sort, "x");
  v2        = btor_exp_var (d_btor, d_elem_sort, "v2");
  v3        = btor_exp_var (d_btor, d_elem_sort, "v3");
  expected  = btor_exp_cond (d_btor, v1, v2, v3);
  param_exp = btor_exp_cond (d_btor, v1, v2, x);
  lambda    = btor_exp_lambda (d_btor, x, param_exp);
  result    = apply_and_reduce (d_btor, &v3, 1, lambda);

  ASSERT_EQ (result, expected);
  assert_parameterized (2, x, param_exp);
  assert_not_parameterized (6, result, lambda, expected, v3, v2, v1);

  btor_sort_release (d_btor, sort);
  btor_node_release (d_btor, result);
  btor_node_release (d_btor, lambda);
  btor_node_release (d_btor, param_exp);
  btor_node_release (d_btor, expected);
  btor_node_release (d_btor, v3);
  btor_node_release (d_btor, v2);
  btor_node_release (d_btor, x);
  btor_node_release (d_btor, v1);
}

/* NOTE: right now, we have to use a read on the array condition as lambdas
 * return bit-vectors only. */
TEST_F (TestLambda, param_acond)
{
  BtorNode *result;
  BtorNode *var, *index, *e_if, *e_else, *expected_acond, *expected,
      *expected_cond;
  BtorNode *param, *param_cond, *param_acond, *param_exp, *lambda;

  var            = btor_exp_var (d_btor, d_index_sort, "v1");
  index          = btor_exp_var (d_btor, d_index_sort, "i");
  expected_cond  = btor_exp_eq (d_btor, var, index);
  e_if           = btor_exp_array (d_btor, d_array_sort, "a1");
  e_else         = btor_exp_array (d_btor, d_array_sort, "a2");
  expected_acond = btor_exp_cond (d_btor, expected_cond, e_if, e_else);
  expected       = btor_exp_read (d_btor, expected_acond, var);

  param       = btor_exp_param (d_btor, d_index_sort, "p");
  param_cond  = btor_exp_eq (d_btor, param, index);
  param_acond = btor_exp_cond (d_btor, param_cond, e_if, e_else);
  param_exp   = btor_exp_read (d_btor, param_acond, param);
  lambda      = btor_exp_lambda (d_btor, param, param_exp);
  result      = apply_and_reduce (d_btor, &var, 1, lambda);

  ASSERT_EQ (result, expected);
  assert_parameterized (4, param, param_cond, param_acond, param_exp);
  assert_not_parameterized (4, result, lambda, expected, expected_acond);
  assert_not_parameterized (5, e_else, e_if, expected_cond, index, var);

  btor_node_release (d_btor, result);
  btor_node_release (d_btor, lambda);
  btor_node_release (d_btor, param_exp);
  btor_node_release (d_btor, param_acond);
  btor_node_release (d_btor, param_cond);
  btor_node_release (d_btor, param);
  btor_node_release (d_btor, expected);
  btor_node_release (d_btor, expected_acond);
  btor_node_release (d_btor, e_else);
  btor_node_release (d_btor, e_if);
  btor_node_release (d_btor, expected_cond);
  btor_node_release (d_btor, index);
  btor_node_release (d_btor, var);
}

/*---------------------------------------------------------------------------
 * reduction tests (with reduced expressions)
 *---------------------------------------------------------------------------*/

/* (lambda x . read ((lambda y . y), x)) */
TEST_F (TestLambda, bounded_reduce1)
{
  BtorNode *result;
  BtorNode *x, *y, *l2, *r, *l1, *v, *expected;

  x  = btor_exp_param (d_btor, d_index_sort, "x");
  y  = btor_exp_param (d_btor, d_index_sort, "y");
  l2 = btor_exp_lambda (d_btor, y, y);
  r  = btor_exp_apply_n (d_btor, l2, &x, 1);
  l1 = btor_exp_lambda (d_btor, x, r);
  v  = btor_exp_var (d_btor, d_index_sort, "v");

  expected = btor_exp_apply_n (d_btor, l2, &v, 1);

  /* bound 2: stop at second lambda */
  btor_beta_assign_param (d_btor, l1, v);
  result = btor_beta_reduce_bounded (d_btor, l1, 2);
  btor_beta_unassign_params (d_btor, l1);

  ASSERT_EQ (result, expected);
  btor_node_release (d_btor, result);
  btor_node_release (d_btor, expected);

  /* bound 3: stop at third lambda */
  btor_beta_assign_param (d_btor, l1, v);
  result = btor_beta_reduce_bounded (d_btor, l1, 3);
  btor_beta_unassign_params (d_btor, l1);

  ASSERT_EQ (result, v);

  btor_node_release (d_btor, result);
  btor_node_release (d_btor, v);
  btor_node_release (d_btor, l1);
  btor_node_release (d_btor, r);
  btor_node_release (d_btor, l2);
  btor_node_release (d_btor, y);
  btor_node_release (d_btor, x);
}

TEST_F (TestLambda, bounded_reduce2)
{
  BtorNode *result;
  BtorNode *x, *i, *eq, *l, *j, *expected;

  x        = btor_exp_param (d_btor, d_index_sort, "x");
  i        = btor_exp_var (d_btor, d_index_sort, "i");
  eq       = btor_exp_eq (d_btor, x, i);
  l        = btor_exp_lambda (d_btor, x, eq);
  j        = btor_exp_var (d_btor, d_index_sort, "j");
  expected = btor_exp_eq (d_btor, i, j);

  btor_beta_assign_param (d_btor, l, j);
  result = btor_beta_reduce_bounded (d_btor, l, 0);
  ASSERT_EQ (result, expected);
  btor_node_release (d_btor, result);

  result = btor_beta_reduce_bounded (d_btor, l, 1);
  ASSERT_EQ (result, l);
  btor_node_release (d_btor, result);

  result = btor_beta_reduce_bounded (d_btor, eq, 1);
  ASSERT_EQ (result, expected);
  btor_node_release (d_btor, result);

  result = btor_beta_reduce_bounded (d_btor, l, 2);
  ASSERT_EQ (result, expected);
  btor_beta_unassign_params (d_btor, l);

  btor_node_release (d_btor, result);
  btor_node_release (d_btor, expected);
  btor_node_release (d_btor, j);
  btor_node_release (d_btor, l);
  btor_node_release (d_btor, eq);
  btor_node_release (d_btor, i);
  btor_node_release (d_btor, x);
}

TEST_F (TestLambda, bounded_reduce3)
{
  BtorNode *result;
  BtorNode *x, *y, *l1, *a, *l2, *i, *expected;

  x        = btor_exp_param (d_btor, d_index_sort, "x");
  y        = btor_exp_param (d_btor, d_index_sort, "y");
  l1       = btor_exp_lambda (d_btor, x, x);
  a        = btor_exp_apply_n (d_btor, l1, &y, 1);
  l2       = btor_exp_lambda (d_btor, y, a);
  i        = btor_exp_var (d_btor, d_index_sort, "i");
  expected = btor_exp_apply_n (d_btor, l1, &i, 1);

  btor_beta_assign_param (d_btor, l2, i);
  result = btor_beta_reduce_bounded (d_btor, l2, 1);
  ASSERT_EQ (result, l2);
  btor_node_release (d_btor, result);

  result = btor_beta_reduce_bounded (d_btor, l2, 2);
  ASSERT_EQ (result, expected);
  btor_node_release (d_btor, result);

  result = btor_beta_reduce_bounded (d_btor, l2, 3);
  ASSERT_EQ (result, i);
  btor_beta_unassign_params (d_btor, l2);

  btor_node_release (d_btor, result);
  btor_node_release (d_btor, expected);
  btor_node_release (d_btor, i);
  btor_node_release (d_btor, a);
  btor_node_release (d_btor, l2);
  btor_node_release (d_btor, l1);
  btor_node_release (d_btor, y);
  btor_node_release (d_btor, x);
}

/*---------------------------------------------------------------------------
 * reduction tests (with reduced expressions)
 *---------------------------------------------------------------------------*/

TEST_F (TestLambda, reduce_write1)
{
  BtorNode *result;
  BtorNode *a, *i, *e, *param, *read, *eq, *cond, *lambda;

  a      = btor_exp_array (d_btor, d_array_sort, "a");
  i      = btor_exp_var (d_btor, d_index_sort, "i");
  e      = btor_exp_var (d_btor, d_elem_sort, "e");
  param  = btor_exp_param (d_btor, d_index_sort, "p");
  read   = btor_exp_read (d_btor, a, param);
  eq     = btor_exp_eq (d_btor, param, i);
  cond   = btor_exp_cond (d_btor, eq, e, read);
  lambda = btor_exp_lambda (d_btor, param, cond);
  result = apply_and_reduce (d_btor, &i, 1, lambda);

  ASSERT_EQ (result, e);

  btor_node_release (d_btor, result);
  btor_node_release (d_btor, lambda);
  btor_node_release (d_btor, cond);
  btor_node_release (d_btor, eq);
  btor_node_release (d_btor, read);
  btor_node_release (d_btor, param);
  btor_node_release (d_btor, e);
  btor_node_release (d_btor, i);
  btor_node_release (d_btor, a);
}

TEST_F (TestLambda, reduce_write2)
{
  BtorNode *result;
  BtorNode *a, *i, *e, *param, *read, *expected, *eq, *cond, *lambda;

  a        = btor_exp_array (d_btor, d_array_sort, "a");
  i        = btor_exp_var (d_btor, d_index_sort, "i");
  e        = btor_exp_var (d_btor, d_elem_sort, "e");
  param    = btor_exp_param (d_btor, d_index_sort, "p");
  read     = btor_exp_read (d_btor, a, param);
  expected = btor_exp_read (d_btor, a, i);
  eq       = btor_exp_ne (d_btor, param, i);
  cond     = btor_exp_cond (d_btor, eq, e, read);
  lambda   = btor_exp_lambda (d_btor, param, cond);
  result   = apply_and_reduce (d_btor, &i, 1, lambda);

  ASSERT_EQ (result, expected);

  btor_node_release (d_btor, result);
  btor_node_release (d_btor, lambda);
  btor_node_release (d_btor, cond);
  btor_node_release (d_btor, eq);
  btor_node_release (d_btor, expected);
  btor_node_release (d_btor, read);
  btor_node_release (d_btor, param);
  btor_node_release (d_btor, e);
  btor_node_release (d_btor, i);
  btor_node_release (d_btor, a);
}

TEST_F (TestLambda, reduce_nested_writes)
{
  BtorNode *result;
  BtorNode *i, *a, *e2, *w2, *e1, *w1;

  i = btor_exp_var (d_btor, d_index_sort, "i");
  /* w2 = write (a, i, e2) */
  a  = btor_exp_array (d_btor, d_array_sort, "a");
  e2 = btor_exp_var (d_btor, d_elem_sort, "e2");
  w2 = btor_exp_lambda_write (d_btor, a, i, e2);
  /* w1 = write (w1, not i, e1) */
  e1     = btor_exp_var (d_btor, d_elem_sort, "e1");
  w1     = btor_exp_lambda_write (d_btor, w2, btor_node_invert (i), e1);
  result = apply_and_reduce (d_btor, &i, 1, w1);

  ASSERT_EQ (result, e2);

  btor_node_release (d_btor, result);
  btor_node_release (d_btor, w1);
  btor_node_release (d_btor, e1);
  btor_node_release (d_btor, w2);
  btor_node_release (d_btor, e2);
  btor_node_release (d_btor, a);
  btor_node_release (d_btor, i);
}

/* (lambda x . (lambda y . (x + y))) (a b) */
TEST_F (TestLambda, reduce_nested_lambdas_add1)
{
  BtorNode *result;
  BtorNode *a, *b, *expected, *x, *y, *add, *fun;

  a                   = btor_exp_var (d_btor, d_elem_sort, "a");
  b                   = btor_exp_var (d_btor, d_elem_sort, "b");
  BtorNode *args[2]   = {a, b};
  expected            = btor_exp_bv_add (d_btor, a, b);
  x                   = btor_exp_param (d_btor, d_elem_sort, "x");
  y                   = btor_exp_param (d_btor, d_elem_sort, "y");
  BtorNode *params[2] = {x, y};
  add                 = btor_exp_bv_add (d_btor, x, y);
  fun                 = btor_exp_fun (d_btor, params, 2, add);

  result = apply_and_reduce (d_btor, args, 2, fun);
  ASSERT_EQ (result, expected);
  btor_node_release (d_btor, result);

  BtorNode *apply = btor_exp_apply_n (d_btor, fun, args, 2);
  result          = btor_beta_reduce_full (d_btor, apply, 0);
  ASSERT_EQ (result, expected);

  btor_node_release (d_btor, apply);
  btor_node_release (d_btor, result);
  btor_node_release (d_btor, fun);
  btor_node_release (d_btor, add);
  btor_node_release (d_btor, y);
  btor_node_release (d_btor, x);
  btor_node_release (d_btor, expected);
  btor_node_release (d_btor, b);
  btor_node_release (d_btor, a);
}

/* (lambda x . (x + read(lambda y . y, b))) (a) */
TEST_F (TestLambda, reduce_nested_lambdas_add2)
{
  BtorNode *result;
  BtorNode *a, *b, *expected, *x, *y, *lambda1, *lambda2, *app, *add;
  BtorSortId lambda_index_sort;

  lambda_index_sort = d_elem_sort;
  a                 = btor_exp_var (d_btor, d_elem_sort, "a");
  b                 = btor_exp_var (d_btor, d_elem_sort, "b");
  expected          = btor_exp_bv_add (d_btor, a, b);
  x                 = btor_exp_param (d_btor, lambda_index_sort, "x");
  y                 = btor_exp_param (d_btor, lambda_index_sort, "y");
  lambda2           = btor_exp_lambda (d_btor, y, y);
  app               = btor_exp_apply_n (d_btor, lambda2, &b, 1);
  add               = btor_exp_bv_add (d_btor, x, app);
  lambda1           = btor_exp_lambda (d_btor, x, add);
  result            = apply_and_reduce (d_btor, &a, 1, lambda1);

  ASSERT_EQ (result, expected);

  btor_node_release (d_btor, result);
  btor_node_release (d_btor, lambda1);
  btor_node_release (d_btor, add);
  btor_node_release (d_btor, app);
  btor_node_release (d_btor, lambda2);
  btor_node_release (d_btor, y);
  btor_node_release (d_btor, x);
  btor_node_release (d_btor, expected);
  btor_node_release (d_btor, b);
  btor_node_release (d_btor, a);
}

/* (lambda x . not (read (lambda y . y, x + var))) (a) */
TEST_F (TestLambda, reduce_nested_lambdas_read)
{
  BtorNode *result;
  BtorNode *a, *var, *y, *lambda1, *lambda2, *x, *add, *app, *napp;
  BtorNode *expected, *expected_add;

  var     = btor_exp_var (d_btor, d_elem_sort, "var");
  y       = btor_exp_param (d_btor, d_elem_sort, "y");
  lambda2 = btor_exp_lambda (d_btor, y, y);
  x       = btor_exp_param (d_btor, d_elem_sort, "x");
  add     = btor_exp_bv_add (d_btor, x, var);
  app     = btor_exp_apply_n (d_btor, lambda2, &add, 1);
  napp    = btor_exp_bv_not (d_btor, app);
  lambda1 = btor_exp_lambda (d_btor, x, napp);
  a       = btor_exp_var (d_btor, d_elem_sort, "a");
  /* exptected not (a + var) */
  expected_add = btor_exp_bv_add (d_btor, a, var);
  expected     = btor_exp_bv_not (d_btor, expected_add);
  result       = apply_and_reduce (d_btor, &a, 1, lambda1);

  ASSERT_EQ (result, expected);

  btor_node_release (d_btor, result);
  btor_node_release (d_btor, expected);
  btor_node_release (d_btor, expected_add);
  btor_node_release (d_btor, a);
  btor_node_release (d_btor, lambda1);
  btor_node_release (d_btor, napp);
  btor_node_release (d_btor, app);
  btor_node_release (d_btor, add);
  btor_node_release (d_btor, x);
  btor_node_release (d_btor, lambda2);
  btor_node_release (d_btor, y);
  btor_node_release (d_btor, var);
}

/* (lambda x1 . (lambda x2 . ... (lambda x1000 . var))) (i1 ... i1000) */
TEST_F (TestLambda, reduce_nested_lambdas_const_n1000)
{
  BtorNode *result;
  BtorNode **params, **indices, *var, *fun;
  int32_t i, nesting_lvl;
  size_t size;

  nesting_lvl = 1000;
  size        = nesting_lvl * sizeof (BtorNode *);
  var         = btor_exp_var (d_btor, d_elem_sort, 0);

  params  = (BtorNode **) btor_mem_malloc (d_btor->mm, size);
  indices = (BtorNode **) btor_mem_malloc (d_btor->mm, size);

  for (i = nesting_lvl - 1; i >= 0; i--)
  {
    indices[i] = btor_exp_var (d_btor, d_index_sort, 0);
    params[i]  = btor_exp_param (d_btor, d_index_sort, 0);
  }
  fun = btor_exp_fun (d_btor, params, nesting_lvl, var);

  result = apply_and_reduce (d_btor, indices, nesting_lvl, fun);
  ASSERT_EQ (result, var);
  btor_node_release (d_btor, result);

  BtorNode *apply = btor_exp_apply_n (d_btor, fun, indices, nesting_lvl);
  result          = btor_beta_reduce_full (d_btor, apply, 0);
  ASSERT_EQ (result, var);

  for (i = 0; i < nesting_lvl; i++)
  {
    btor_node_release (d_btor, params[i]);
    btor_node_release (d_btor, indices[i]);
  }

  btor_mem_free (d_btor->mm, params, size);
  btor_mem_free (d_btor->mm, indices, size);

  btor_node_release (d_btor, fun);
  btor_node_release (d_btor, apply);
  btor_node_release (d_btor, result);
  btor_node_release (d_btor, var);
}

TEST_F (TestLambda, hashing1)
{
  BtorNode *w0, *w1, *i, *e, *a;
  BtorSortId array_sort, sort;

  sort       = btor_sort_bv (d_btor, 32);
  array_sort = btor_sort_array (d_btor, sort, sort);

  a  = btor_exp_array (d_btor, array_sort, 0);
  i  = btor_exp_var (d_btor, sort, 0);
  e  = btor_exp_var (d_btor, sort, 0);
  w0 = btor_exp_lambda_write (d_btor, a, i, e);
  w1 = btor_exp_lambda_write (d_btor, a, i, e);
  ASSERT_EQ (w0, w1);

  btor_sort_release (d_btor, array_sort);
  btor_sort_release (d_btor, sort);
  btor_node_release (d_btor, a);
  btor_node_release (d_btor, i);
  btor_node_release (d_btor, e);
  btor_node_release (d_btor, w0);
  btor_node_release (d_btor, w1);
}

TEST_F (TestLambda, hashing2)
{
  BtorNode *ite0, *ite1, *i, *e, *a0, *a1, *eq;
  BtorSortId array_sort, sort;

  sort       = btor_sort_bv (d_btor, 32);
  array_sort = btor_sort_array (d_btor, sort, sort);

  a0   = btor_exp_array (d_btor, array_sort, 0);
  a1   = btor_exp_array (d_btor, array_sort, 0);
  i    = btor_exp_var (d_btor, sort, 0);
  e    = btor_exp_var (d_btor, sort, 0);
  eq   = btor_exp_eq (d_btor, i, e);
  ite0 = btor_exp_cond (d_btor, eq, a0, a1);
  ite1 = btor_exp_cond (d_btor, eq, a0, a1);
  ASSERT_EQ (ite0, ite1);

  btor_node_release (d_btor, ite1);
  ite1 = btor_exp_cond (d_btor, btor_node_invert (eq), a1, a0);
  ASSERT_EQ (ite0, ite1);

  btor_sort_release (d_btor, array_sort);
  btor_sort_release (d_btor, sort);
  btor_node_release (d_btor, a0);
  btor_node_release (d_btor, a1);
  btor_node_release (d_btor, i);
  btor_node_release (d_btor, e);
  btor_node_release (d_btor, eq);
  btor_node_release (d_btor, ite0);
  btor_node_release (d_btor, ite1);
}

/* check if hashing considers commutativity */
TEST_F (TestLambda, hashing3)
{
  BtorNode *l0, *l1, *v, *p0, *p1, *eq0, *eq1;
  BtorSortId sort;

  sort = btor_sort_bv (d_btor, 32);

  /* NOTE: order p0, v, p1 is important here */
  p0 = btor_exp_param (d_btor, sort, 0);
  v  = btor_exp_var (d_btor, sort, 0);
  p1 = btor_exp_param (d_btor, sort, 0);

  eq0 = btor_exp_eq (d_btor, p0, v);
  eq1 = btor_exp_eq (d_btor, v, p1);

  l0 = btor_exp_lambda (d_btor, p0, eq0);
  l1 = btor_exp_lambda (d_btor, p1, eq1);
  ASSERT_EQ (l0, l1);

  btor_sort_release (d_btor, sort);
  btor_node_release (d_btor, p0);
  btor_node_release (d_btor, p1);
  btor_node_release (d_btor, v);
  btor_node_release (d_btor, eq0);
  btor_node_release (d_btor, eq1);
  btor_node_release (d_btor, l0);
  btor_node_release (d_btor, l1);
}

TEST_F (TestLambda, hashing4)
{
  BtorNode *f0, *f1, *p0[2], *p1[2], *eq0, *eq1;
  BtorSortId sort;

  sort = btor_sort_bv (d_btor, 32);

  p0[0] = btor_exp_param (d_btor, sort, 0);
  p0[1] = btor_exp_param (d_btor, sort, 0);
  p1[0] = btor_exp_param (d_btor, sort, 0);
  p1[1] = btor_exp_param (d_btor, sort, 0);

  eq0 = btor_exp_eq (d_btor, p0[0], p0[1]);
  eq1 = btor_exp_eq (d_btor, p1[0], p1[1]);

  f0 = btor_exp_fun (d_btor, p0, 2, eq0);
  f1 = btor_exp_fun (d_btor, p1, 2, eq1);
  ASSERT_EQ (f0, f1);

  btor_sort_release (d_btor, sort);
  btor_node_release (d_btor, p0[0]);
  btor_node_release (d_btor, p0[1]);
  btor_node_release (d_btor, p1[0]);
  btor_node_release (d_btor, p1[1]);
  btor_node_release (d_btor, eq0);
  btor_node_release (d_btor, eq1);
  btor_node_release (d_btor, f0);
  btor_node_release (d_btor, f1);
}

TEST_F (TestLambda, quantifier_hashing1)
{
  BtorNode *x0, *y0, *eq0, *f0, *e0;
  BtorNode *x1, *y1, *eq1, *f1, *e1;
  BtorSortId sort;

  sort = btor_sort_bv (d_btor, 32);
  x0   = btor_exp_param (d_btor, sort, 0);
  y0   = btor_exp_param (d_btor, sort, 0);
  eq0  = btor_exp_eq (d_btor, x0, y0);
  f0   = btor_exp_forall (d_btor, y0, eq0);
  e0   = btor_exp_exists (d_btor, x0, f0);

  x1  = btor_exp_param (d_btor, sort, 0);
  y1  = btor_exp_param (d_btor, sort, 0);
  eq1 = btor_exp_eq (d_btor, x1, y1);
  f1  = btor_exp_forall (d_btor, y1, eq1);
  e1  = btor_exp_exists (d_btor, x1, f1);
  ASSERT_EQ (e0, e1);

  btor_node_release (d_btor, x0);
  btor_node_release (d_btor, y0);
  btor_node_release (d_btor, eq0);
  btor_node_release (d_btor, f0);
  btor_node_release (d_btor, e0);
  btor_node_release (d_btor, x1);
  btor_node_release (d_btor, y1);
  btor_node_release (d_btor, eq1);
  btor_node_release (d_btor, f1);
  btor_node_release (d_btor, e1);
  btor_sort_release (d_btor, sort);
}

TEST_F (TestLambda, quantifier_hashing2)
{
  BtorNode *x0, *x1, *x2, *x3, *y0, *y1, *y2, *y3;
  BtorNode *a0, *a1, *a2, *a3, *a4, *a5, *a6, *r0;
  BtorNode *f0, *e0, *f1, *e1, *f2, *e2, *f3, *e3;
  BtorNode *x10, *x11, *x12, *x13, *y10, *y11, *y12, *y13;
  BtorNode *a10, *a11, *a12, *a13, *a14, *a15, *a16, *r10;
  BtorNode *f10, *e10, *f11, *e11, *f12, *e12, *f13, *e13;
  BtorSortId sort;

  sort = btor_sort_bv (d_btor, 32);
  x0   = btor_exp_param (d_btor, sort, 0);
  x1   = btor_exp_param (d_btor, sort, 0);
  x2   = btor_exp_param (d_btor, sort, 0);
  x3   = btor_exp_param (d_btor, sort, 0);
  y0   = btor_exp_param (d_btor, sort, 0);
  y1   = btor_exp_param (d_btor, sort, 0);
  y2   = btor_exp_param (d_btor, sort, 0);
  y3   = btor_exp_param (d_btor, sort, 0);

  a0 = btor_exp_bv_and (d_btor, x0, y0);
  a1 = btor_exp_bv_and (d_btor, a0, x1);
  a2 = btor_exp_bv_and (d_btor, a1, y1);
  a3 = btor_exp_bv_and (d_btor, a2, x2);
  a4 = btor_exp_bv_and (d_btor, a3, y2);
  a5 = btor_exp_bv_and (d_btor, a4, x3);
  a6 = btor_exp_bv_and (d_btor, a5, y3);
  r0 = btor_exp_bv_redor (d_btor, a6);
  f0 = btor_exp_forall (d_btor, x0, r0);
  e0 = btor_exp_exists (d_btor, y0, f0);
  e1 = btor_exp_exists (d_btor, y1, e0);
  f1 = btor_exp_forall (d_btor, x1, e1);
  f2 = btor_exp_forall (d_btor, x2, f1);
  e2 = btor_exp_exists (d_btor, y2, f2);
  f3 = btor_exp_forall (d_btor, x3, e2);
  e3 = btor_exp_exists (d_btor, y3, f3);

  x10 = btor_exp_param (d_btor, sort, 0);
  x11 = btor_exp_param (d_btor, sort, 0);
  x12 = btor_exp_param (d_btor, sort, 0);
  x13 = btor_exp_param (d_btor, sort, 0);
  y10 = btor_exp_param (d_btor, sort, 0);
  y11 = btor_exp_param (d_btor, sort, 0);
  y12 = btor_exp_param (d_btor, sort, 0);
  y13 = btor_exp_param (d_btor, sort, 0);

  a10 = btor_exp_bv_and (d_btor, x10, y10);
  a11 = btor_exp_bv_and (d_btor, a10, x11);
  a12 = btor_exp_bv_and (d_btor, a11, y11);
  a13 = btor_exp_bv_and (d_btor, a12, x12);
  a14 = btor_exp_bv_and (d_btor, a13, y12);
  a15 = btor_exp_bv_and (d_btor, a14, x13);
  a16 = btor_exp_bv_and (d_btor, a15, y13);
  r10 = btor_exp_bv_redor (d_btor, a16);
  f10 = btor_exp_forall (d_btor, x10, r10);
  e10 = btor_exp_exists (d_btor, y10, f10);
  e11 = btor_exp_exists (d_btor, y11, e10);
  f11 = btor_exp_forall (d_btor, x11, e11);
  f12 = btor_exp_forall (d_btor, x12, f11);
  e12 = btor_exp_exists (d_btor, y12, f12);
  f13 = btor_exp_forall (d_btor, x13, e12);
  e13 = btor_exp_exists (d_btor, y13, f13);

  ASSERT_EQ (e3, e13);

  btor_node_release (d_btor, x0);
  btor_node_release (d_btor, x1);
  btor_node_release (d_btor, x2);
  btor_node_release (d_btor, x3);
  btor_node_release (d_btor, y0);
  btor_node_release (d_btor, y1);
  btor_node_release (d_btor, y2);
  btor_node_release (d_btor, y3);
  btor_node_release (d_btor, a0);
  btor_node_release (d_btor, a1);
  btor_node_release (d_btor, a2);
  btor_node_release (d_btor, a3);
  btor_node_release (d_btor, a4);
  btor_node_release (d_btor, a5);
  btor_node_release (d_btor, a6);
  btor_node_release (d_btor, r0);
  btor_node_release (d_btor, f0);
  btor_node_release (d_btor, e0);
  btor_node_release (d_btor, e1);
  btor_node_release (d_btor, f1);
  btor_node_release (d_btor, f2);
  btor_node_release (d_btor, e2);
  btor_node_release (d_btor, f3);
  btor_node_release (d_btor, e3);
  btor_node_release (d_btor, x10);
  btor_node_release (d_btor, x11);
  btor_node_release (d_btor, x12);
  btor_node_release (d_btor, x13);
  btor_node_release (d_btor, y10);
  btor_node_release (d_btor, y11);
  btor_node_release (d_btor, y12);
  btor_node_release (d_btor, y13);
  btor_node_release (d_btor, a10);
  btor_node_release (d_btor, a11);
  btor_node_release (d_btor, a12);
  btor_node_release (d_btor, a13);
  btor_node_release (d_btor, a14);
  btor_node_release (d_btor, a15);
  btor_node_release (d_btor, a16);
  btor_node_release (d_btor, r10);
  btor_node_release (d_btor, f10);
  btor_node_release (d_btor, e10);
  btor_node_release (d_btor, e11);
  btor_node_release (d_btor, f11);
  btor_node_release (d_btor, f12);
  btor_node_release (d_btor, e12);
  btor_node_release (d_btor, f13);
  btor_node_release (d_btor, e13);
  btor_sort_release (d_btor, sort);
}

#if 0
/* (lambda x . (lambda y . (x + y))) (a) */
TEST_F (TestLambda, partial_reduce_nested_lambdas_add1)
{
  BtorNode *result;
  BtorNode *a, *x, *y, *add, *params[2] = {x, y}, *fun;

  a = btor_exp_var (d_btor, d_elem_sort, "a");
  x = btor_exp_param (d_btor, d_elem_sort, "x");
  y = btor_exp_param (d_btor, d_elem_sort, "y");
  add = btor_exp_bv_add (d_btor, x, y);
  fun = btor_exp_fun (d_btor, params, 2, add); 
  result = apply_and_reduce (d_btor, 1, &a, fun);

  /* expected: lambda y' . (a + y') */
  ASSERT_TRUE (btor_node_is_lambda (result));
  ASSERT_NE (result, fun->e[1]);
  ASSERT_NE (result->e[0], fun->e[1]->e[0]);
  ASSERT_EQ (btor_node_real_addr (result->e[1])->kind, BTOR_BV_ADD_NODE);
  ASSERT_EQ (btor_node_real_addr (result->e[1])->e[0], a);
  ASSERT_EQ (btor_node_real_addr (result->e[1])->e[1], result->e[0]);

  btor_node_release (d_btor, result);
  btor_node_release (d_btor, fun);
  btor_node_release (d_btor, add);
  btor_node_release (d_btor, y);
  btor_node_release (d_btor, x);
  btor_node_release (d_btor, a);
}
#endif

/*---------------------------------------------------------------------------
 * additional tests
 *---------------------------------------------------------------------------*/

TEST_F (TestLambda, define_fun)
{
  int32_t i;
  int32_t nesting_lvl = 1000;
  size_t size;
  BtorNode **params, **lambdas, **ands, *left, *right, *expected, *result;

  size    = nesting_lvl * sizeof (BtorNode *);
  params  = (BtorNode **) btor_mem_malloc (d_btor->mm, size);
  lambdas = (BtorNode **) btor_mem_malloc (d_btor->mm, size);
  ands = (BtorNode **) btor_mem_malloc (d_btor->mm, size - sizeof (BtorNode *));

  for (i = 0; i < nesting_lvl; i++)
    params[i] = btor_exp_param (d_btor, d_elem_sort, 0);

  ASSERT_GT (nesting_lvl, 1);
  left  = params[0];
  right = params[1];
  for (i = 0; i < nesting_lvl - 1; i++)
  {
    ands[i] = btor_exp_bv_and (d_btor, left, right);

    if (i + 2 < nesting_lvl)
    {
      left  = params[i + 2];
      right = ands[i];
    }
  }

  /* build expected */
  expected = ands[nesting_lvl - 2];
  for (i = nesting_lvl - 1; i >= 0; i--)
  {
    ASSERT_NE (expected, nullptr);
    lambdas[i] = btor_exp_lambda (d_btor, params[i], expected);
    expected   = lambdas[i];
  }

  result = btor_exp_fun (d_btor, params, nesting_lvl, ands[nesting_lvl - 2]);

  ASSERT_EQ (result, expected);

  for (i = 0; i < nesting_lvl; i++)
  {
    btor_node_release (d_btor, lambdas[i]);
    btor_node_release (d_btor, params[i]);

    if (i < nesting_lvl - 1) btor_node_release (d_btor, ands[i]);
  }

  btor_mem_free (d_btor->mm, params, size);
  btor_mem_free (d_btor->mm, lambdas, size);
  btor_mem_free (d_btor->mm, ands, size - sizeof (BtorNode *));
  btor_node_release (d_btor, result);
}
