#pragma once

#include "test/cpp/jit/test_base.h"
#include "torch/csrc/jit/subgraph_matcher.h"

namespace torch {
namespace jit {
namespace {

void testTrivial1() {
  Graph graph, pattern;
  script::parseIR(
      R"IR(
graph(%0):
  %a = a::aaa(%0)
  return (%a))IR",
      &graph);
  script::parseIR(
      R"IR(
graph(%0):
  %x = a::aaa(%0)
  return (%x))IR",
      &pattern);
  AT_ASSERT(!findPatternMatches(pattern, graph).empty());
}

void testTrivial2() {
  Graph graph;
  Var i0 = Var::asNewInput(graph);
  auto o0 = i0.tanh();
  graph.registerOutput(o0.value());

  Graph pattern;
  Var mi0 = Var::asNewInput(pattern);
  auto mo0 = mi0.tanh();
  pattern.registerOutput(mo0.value());

  auto matches = findPatternMatches(pattern, graph);
  AT_ASSERT(matches.size() == 1);
  for (const Match& m : matches) {
    AT_ASSERT(m.values_map.at(mi0.value()) == i0.value());
    AT_ASSERT(m.values_map.at(mo0.value()) == o0.value());
    AT_ASSERT(m.nodes_map.at(mo0.value()->node()) == o0.value()->node());
  }
}

void testTrivial3() {
  Graph graph, pattern;
  script::parseIR(
      R"IR(
graph(%0):
  %a = a::a(%0)
  %b = a::b(%0)
  %c = a::c(%a, %b)
  return (%c))IR",
      &graph);
  script::parseIR(
      R"IR(
graph(%a, %b):
  %c = a::c(%a, %b)
  return (%c))IR",
      &pattern);
  AT_ASSERT(!findPatternMatches(pattern, graph).empty());
}

void testTrivial4() {
  Graph graph;
  Var i0 = Var::asNewInput(graph);
  Var i1 = Var::asNewInput(graph);
  auto o0 = i0 * i1;
  graph.registerOutput(o0.value());

  Graph pattern;
  auto m0 = Var::asNewInput(pattern);
  auto m1 = Var::asNewInput(pattern);
  auto m2 = m0 * m1;
  pattern.registerOutput(m2.value());

  auto matches = findPatternMatches(pattern, graph);
  AT_ASSERT(matches.size() == 1);
  for (const Match& m : matches) {
    AT_ASSERT(m.values_map.at(m0.value()) == i0.value());
    AT_ASSERT(m.values_map.at(m1.value()) == i1.value());
    AT_ASSERT(m.values_map.at(m2.value()) == o0.value());
    AT_ASSERT(m.nodes_map.at(m2.value()->node()) == o0.value()->node());
  }
}

void testLinear1() {
  Graph graph, pattern;
  script::parseIR(
      R"IR(
graph(%0):
  %a = a::aaa(%0)
  %b = b::bbb(%a)
  %c = c::ccc(%b)
  %d = d::ddd(%c)
  %a = a::aaa(%0)
  return (%d))IR",
      &graph);
  script::parseIR(
      R"IR(
graph(%0):
  %x = b::bbb(%0)
  %y = c::ccc(%x)
  return (%y))IR",
      &pattern);
  AT_ASSERT(!findPatternMatches(pattern, graph).empty());
}

void testLinear2() {
  Graph graph;
  Var i0 = Var::asNewInput(graph);
  auto o0 = i0.tanh();
  auto o1 = o0.tanh();
  graph.registerOutput(o1.value());

  Graph pattern;
  auto m0 = Var::asNewInput(pattern);
  auto m1 = m0.tanh();
  auto m2 = m1.tanh();
  pattern.registerOutput(m2.value());

  auto matches = findPatternMatches(pattern, graph);
  AT_ASSERT(matches.size() == 1);
  for (const Match& m : matches) {
    AT_ASSERT(m.values_map.at(m0.value()) == i0.value());
    AT_ASSERT(m.values_map.at(m1.value()) == o0.value());
    AT_ASSERT(m.values_map.at(m2.value()) == o1.value());
    AT_ASSERT(m.nodes_map.at(m1.value()->node()) == o0.value()->node());
    AT_ASSERT(m.nodes_map.at(m2.value()->node()) == o1.value()->node());
  }
}

/**
 * Test diamond pattern:
 *
 *     ooo
 *      |
 *     aaa
 *    /   \
 *  bbb   ccc
 *     \ /
 *     ddd
 *      |
 *     eee
 */
void testDiamond1() {
  Graph graph, pattern1, pattern2;
  script::parseIR(
      R"IR(
graph(%0):
  %o = o::ooo(%0)
  %a = a::aaa(%o)
  %b = b::bbb(%a)
  %c = c::ccc(%a)
  %d = d::ddd(%b, %c)
  %e = e::eee(%d)
  return (%e))IR",
      &graph);

  script::parseIR(
      R"IR(
graph(%0):
  %a = a::aaa(%0)
  %b = b::bbb(%a)
  %c = c::ccc(%a)
  %d = d::ddd(%b, %c)
  return (%d))IR",
      &pattern1);
  AT_ASSERT(!findPatternMatches(pattern1, graph).empty());

  // Check that order of nodes inside the diamond does not affect the result
  script::parseIR(
      R"IR(
graph(%0):
  %a = a::aaa(%0)
  %c = c::ccc(%a)
  %b = b::bbb(%a)
  %d = d::ddd(%b, %c)
  return (%d))IR",
      &pattern2);
  AT_ASSERT(!findPatternMatches(pattern2, graph).empty());
}

/**
 * Test diamond pattern:
 *
 *     i0
 *      |
 *    chunk
 *    /   \
 * os[0] os[1]
 *     \ /
 *      *
 *      |
 *      o1
 */
void testDiamond2() {
  Graph graph;
  Var i0 = Var::asNewInput(graph);
  auto os = i0.chunk(2, 0);
  auto o1 = os[0] * os[1];

  Graph pattern;
  auto mi0 = Var::asNewInput(pattern);
  auto ms = mi0.chunk(2, 0);
  auto mo = ms[0] * ms[1];
  pattern.registerOutput(mo.value());

  auto matches = findPatternMatches(pattern, graph);
  AT_ASSERT(matches.size() == 1);
  for (const Match& m : matches) {
    AT_ASSERT(m.values_map.at(mi0.value()) == i0.value());
    AT_ASSERT(m.values_map.at(ms[0].value()) == os[0].value());
    AT_ASSERT(m.values_map.at(ms[1].value()) == os[1].value());
    AT_ASSERT(m.values_map.at(mo.value()) == o1.value());
    AT_ASSERT(m.nodes_map.at(mo.value()->node()) == o1.value()->node());
  }
}

void testXPattern() {
  Graph graph, pattern;
  script::parseIR(
      R"IR(
graph(%0, %1):
  %b = b::bbb(%0)
  %c = c::ccc(%1)
  %x = x::xxx(%b, %c)
  %e = e::eee(%x)
  %f = f::fff(%x)
  %g = g::ggg(%e, %f)
  return (%g))IR",
      &graph);
  script::parseIR(
      R"IR(
graph(%0, %1):
  %b = b::bbb(%0)
  %c = c::ccc(%1)
  %x = x::xxx(%b, %c)
  %e = e::eee(%x)
  %f = f::fff(%x)
  %g = g::ggg(%e, %f)
  return (%g))IR",
      &pattern);
  AT_ASSERT(!findPatternMatches(pattern, graph).empty());
}

void testMultipleMatches() {
  Graph graph, pattern;
  script::parseIR(
      R"IR(
graph(%t0):
  %t1 = a::aaa(%t0)
  %t2 = a::aaa(%t1)
  %t3 = a::aaa(%t2)
  %t4 = a::aaa(%t3)
  return (%t4))IR",
      &graph);
  script::parseIR(
      R"IR(
graph(%t0):
  %t1 = a::aaa(%t0)
  return (%t1))IR",
      &pattern);
  auto matches = findPatternMatches(pattern, graph);
  AT_ASSERT(matches.size() == 4);
}

void testOverlappingMatches() {
  Graph graph, pattern;
  script::parseIR(
      R"IR(
graph(%t0):
  %t1 = a::aaa(%t0)
  %t2 = a::aaa(%t1)
  %t3 = a::aaa(%t2)
  %t4 = a::aaa(%t3)
  return (%t4))IR",
      &graph);
  script::parseIR(
      R"IR(
graph(%t0):
  %t1 = a::aaa(%t0)
  %t2 = a::aaa(%t1)
  return (%t2))IR",
      &pattern);
  auto matches = findPatternMatches(pattern, graph);
  AT_ASSERT(matches.size() == 3);
}

void testMatchInBasicBlocks1() {
  Graph graph;
  script::parseIR(
      R"IR(
graph(%a, %b, %c):
  %d = aten::mul(%a, %b)
  %x = prim::If(%c)
    block0():
      %x1 = aten::mul(%a, %d)
      -> (%x1)
    block1():
      %x2 = aten::mul(%b, %d)
      -> (%x2)
  return (%x))IR",
      &graph);

  // Ensure the matches don't cross basic block boundaries
  Graph pattern0;
  script::parseIR(
      R"IR(
graph(%x, %y):
  %z = aten::mul(%x, %y)
  return (%z))IR",
      &pattern0);
  AT_ASSERT(findPatternMatches(pattern0, graph).size() == 3);

  Graph pattern1;
  script::parseIR(
      R"IR(
graph(%x, %y):
  %z1 = aten::mul(%x, %y)
  %z2 = aten::mul(%y, %z1)
  return (%z2))IR",
      &pattern1);
  AT_ASSERT(findPatternMatches(pattern1, graph).size() == 0);
}

void testMatchInBasicBlocks2() {
  Graph graph;
  script::parseIR(
      R"IR(
graph(%a, %b):
  %x = my::mul(%a, %b)
  %y = my::node_with_subblock()
    block0():
      %z = my::mul(%b, %x)
      -> (%z)
  return (%y))IR",
      &graph);

  // Check that we can match both mul ops
  Graph pattern0;
  script::parseIR(
      R"IR(
graph(%x, %y):
  %z = my::mul(%x, %y)
  return (%z))IR",
      &pattern0);
  AT_ASSERT(findPatternMatches(pattern0, graph).size() == 2);

  // Ensure the matches don't cross basic block boundaries
  Graph pattern1;
  script::parseIR(
      R"IR(
graph(%x, %y):
  %u = my::mul(%x, %y)
  %v = my::mul(%y, %u)
  return (%v))IR",
      &pattern1);
  AT_ASSERT(findPatternMatches(pattern1, graph).size() == 0);
}

void testAttributes() {
  Graph graph;
  script::parseIR(
      R"IR(
graph(%0):
  %a = a::a[isattr=[1,2]](%0)
  %b = a::b[intattr=10, floatattr=3.14](%0)
  %c = a::c[myattr="qqq"](%a, %b)
  return (%c))IR",
      &graph);

  {
    Graph pattern;
    script::parseIR(
        R"IR(
graph(%a, %b):
  %c = a::c[myattr="qqq"](%a, %b)
  return (%c))IR",
        &pattern);
    AT_ASSERT(!findPatternMatches(pattern, graph).empty());
  }
  {
    Graph pattern;
    script::parseIR(
        R"IR(
graph(%a, %b):
  %c = a::c[myattr="zzz"](%a, %b)
  return (%c))IR",
        &pattern);
    AT_ASSERT(findPatternMatches(pattern, graph).empty());
  }
  {
    Graph pattern;
    script::parseIR(
        R"IR(
graph(%0):
  %b = a::b[extraattr=10](%0)
  return (%b))IR",
        &pattern);
    AT_ASSERT(findPatternMatches(pattern, graph).empty());
  }
  {
    Graph pattern;
    script::parseIR(
        R"IR(
graph(%0):
  %b = a::b[intattr=10, floatattr=3.14](%0)
  return (%b))IR",
        &pattern);
    AT_ASSERT(!findPatternMatches(pattern, graph).empty());
  }
  {
    Graph pattern;
    script::parseIR(
        R"IR(
graph(%0):
  %b = a::b[intattr=10, floatattr=3.14, strattr="rrr"](%0)
  return (%b))IR",
        &pattern);
    AT_ASSERT(findPatternMatches(pattern, graph).empty());
  }
  {
    Graph pattern;
    script::parseIR(
        R"IR(
graph(%0):
  %a = a::a[isattr=[1,2]](%0)
  return (%a))IR",
        &pattern);
    // Lists are not supported yet, thus we shouldn't match for now.
    AT_ASSERT(findPatternMatches(pattern, graph).empty());
  }
}

void testBadPattern() {
  Graph graph, pattern1, pattern2;
  script::parseIR(
      R"IR(
graph(%0):
  %a = a::aaa(%0)
  return (%a))IR",
      &graph);

  script::parseIR(
      R"IR(
graph(%x):
  %y = my::node_with_subblock()
    block0():
      %z = my::op(%x)
      -> (%z)
  return (%y))IR",
      &pattern1);
  ASSERT_ANY_THROW(findPatternMatches(pattern1, graph));

  script::parseIR(
      R"IR(
graph(%x):
  %y = my::op1(%x)
  %z = my::op2(%x)
  return (%y, %z))IR",
      &pattern2);
  ASSERT_ANY_THROW(findPatternMatches(pattern2, graph));
}

void testSubgraphMatching() {
  testTrivial1();
  testTrivial2();
  testTrivial3();
  testTrivial4();
  testLinear1();
  testLinear2();
  testDiamond1();
  testDiamond2();
  testXPattern();
  testMultipleMatches();
  testOverlappingMatches();
  testMatchInBasicBlocks1();
  testMatchInBasicBlocks2();
  testAttributes();
  testBadPattern();
}

} // namespace
} // namespace jit
} // namespace torch
