#include <cassert>
#include <iostream>

#include <ir/utils.h>

using namespace wasm;

#define assertEqual(left, right)                                               \
  assert(ExpressionAnalyzer::hash(&left) == ExpressionAnalyzer::hash(&right));

#define assertNotEqual(left, right)                                            \
  assert(ExpressionAnalyzer::hash(&left) != ExpressionAnalyzer::hash(&right));

#define assertShallowEqual(left, right)                                        \
  assert(ExpressionAnalyzer::shallowHash(&left) ==                             \
         ExpressionAnalyzer::shallowHash(&right));

#define assertShallowNotEqual(left, right)                                     \
  assert(ExpressionAnalyzer::shallowHash(&left) !=                             \
         ExpressionAnalyzer::shallowHash(&right));

int main() {
  {
    Const x, y;
    x.set(Literal(int32_t(10)));
    y.set(Literal(int32_t(10)));
    assertEqual(x, y);
  }
  {
    // The value matters (with extremely high probability...)
    Const x, y;
    x.set(Literal(int32_t(10)));
    y.set(Literal(int32_t(11)));
    assertNotEqual(x, y);
  }
  {
    // The type matters.
    Const x, y;
    x.set(Literal(int32_t(10)));
    y.set(Literal(int64_t(10)));
    assertNotEqual(x, y);
  }
  {
    // Nested identical child.
    Drop dx, dy;
    Const x, y;
    x.set(Literal(int32_t(10)));
    y.set(Literal(int32_t(10)));
    dx.value = &x;
    dy.value = &y;
    assertEqual(dx, dy);
  }
  {
    // Nested identical child, checked shallowly.
    Drop dx, dy;
    Const x, y;
    x.set(Literal(int32_t(10)));
    y.set(Literal(int32_t(10)));
    dx.value = &x;
    dy.value = &y;
    assertShallowEqual(dx, dy);
  }
  {
    // Nested different child.
    Drop dx, dy;
    Const x, y;
    x.set(Literal(int32_t(10)));
    y.set(Literal(int32_t(11)));
    dx.value = &x;
    dy.value = &y;
    assertNotEqual(dx, dy);
  }
  {
    // Nested different child, checked shallowly (so we ignore the difference,
    // and return equal).
    Drop dx, dy;
    Const x, y;
    x.set(Literal(int32_t(10)));
    y.set(Literal(int32_t(11)));
    dx.value = &x;
    dy.value = &y;
    assertShallowEqual(dx, dy);
  }
  MixedArena arena;
  {
    // Blocks
    Block x(arena);
    Block y(arena);
    assertEqual(x, y);
  }
  {
    // Blocks with contents.
    Block x(arena);
    Block y(arena);
    Nop n;
    y.list.push_back(&n);
    assertNotEqual(x, y);
  }
  {
    // Blocks with names.
    Block x(arena);
    x.name = "foo";
    Block y(arena);
    y.name = "foo";
    assertEqual(x, y);
  }
  {
    // Different block names hash equally - we ignore internal name differences
    // intentionally.
    Block x(arena);
    x.name = "foo";
    Block y(arena);
    y.name = "bar";
    assertEqual(x, y);
  }
  {
    // Different br names are checked relatively as well.
    Break x;
    x.name = "foo";
    Break y;
    y.name = "bar";
    Block z(arena);
    z.name = "foo";
    z.list.push_back(&x);
    Block w(arena);
    w.name = "bar";
    w.list.push_back(&y);
    Block outer1(arena);
    outer1.name = "outer1";
    outer1.list.push_back(&z);
    Block outer2(arena);
    outer2.name = "outer2";
    outer2.list.push_back(&w);
    assertEqual(outer1, outer2);
  }
  {
    // But referring to different relative names leads to a difference.
    Break x;
    x.name = "outer1"; // instead of x, go to the outer label this time
    Break y;
    y.name = "bar";
    Block z(arena);
    z.name = "foo";
    z.list.push_back(&x);
    Block w(arena);
    w.name = "bar";
    w.list.push_back(&y);
    Block outer1(arena);
    outer1.name = "outer1";
    outer1.list.push_back(&z);
    Block outer2(arena);
    outer2.name = "outer2";
    outer2.list.push_back(&w);
    assertNotEqual(outer1, outer2);
  }
  {
    // Indexes.
    LocalGet x, y;
    x.index = 10;
    y.index = 10;
    assertEqual(x, y);
  }
  {
    // Indexes.
    LocalGet x, y;
    x.index = 10;
    y.index = 11;
    assertNotEqual(x, y);
  }
  {
    // It is ok to hash something that refers to an unknown name, like a break
    // without the outside context that it branches to. And different names
    // should have different hashes.
    Break x;
    x.name = "foo";
    Break y;
    y.name = "bar";
    assertNotEqual(x, y);
  }
  std::cout << "success.\n";
}