#include <catch2/catch.hpp>

#include <language/ast/ASTBuilder.hpp>
#include <language/ast/ASTSymbolInitializationChecker.hpp>
#include <language/ast/ASTSymbolTableBuilder.hpp>

#include <pegtl/string_input.hpp>

// clazy:excludeall=non-pod-global-static

TEST_CASE("ASTSymbolInitializationChecker", "[language]")
{
  SECTION("Declarative initialization")
  {
    std::string_view data = R"(
let m:N, m = 2;
let n:N, n = m ;
let p:N;
)";

    string_input input{data, "test.pgs"};
    auto ast = ASTBuilder::build(input);

    ASTSymbolTableBuilder{*ast};
    ASTSymbolInitializationChecker{*ast};

    position position{internal::iterator{"fixture"}, "fixture"};
    position.byte = data.size();   // ensure that variables are declared at this point

    auto [symbol_m, found_m] = ast->m_symbol_table->find("m", position);
    REQUIRE(found_m);
    REQUIRE(symbol_m->attributes().isInitialized());

    auto [symbol_n, found_n] = ast->m_symbol_table->find("n", position);
    REQUIRE(found_n);
    REQUIRE(symbol_n->attributes().isInitialized());

    auto [symbol_p, found_p] = ast->m_symbol_table->find("p", position);
    REQUIRE(found_p);
    REQUIRE(not symbol_p->attributes().isInitialized());
  }

  SECTION("Array subscript initialization")
  {
    std::string_view data = R"(
let x:R^3;
x[0] = 1;
)";

    string_input input{data, "test.pgs"};
    auto ast = ASTBuilder::build(input);

    ASTSymbolTableBuilder{*ast};
    ASTSymbolInitializationChecker{*ast};

    position position{internal::iterator{"fixture"}, "fixture"};
    position.byte = data.size();   // ensure that variables are declared at this point

    auto [symbol_m, found_m] = ast->m_symbol_table->find("x", position);
    REQUIRE(found_m);
    REQUIRE(symbol_m->attributes().isInitialized());
  }

  SECTION("Declaration plus affectation")
  {
    std::string_view data = R"(
let z:Z;
let m:N;
let n:N;
n = 2;
m = n;
)";

    string_input input{data, "test.pgs"};
    auto ast = ASTBuilder::build(input);

    ASTSymbolTableBuilder{*ast};
    ASTSymbolInitializationChecker{*ast};

    position position{internal::iterator{"fixture"}, "fixture"};
    position.byte = data.size();   // ensure that variables are declared at this point

    auto [symbol_m, found_m] = ast->m_symbol_table->find("m", position);
    REQUIRE(found_m);
    REQUIRE(symbol_m->attributes().isInitialized());

    auto [symbol_n, found_n] = ast->m_symbol_table->find("n", position);
    REQUIRE(found_n);
    REQUIRE(symbol_n->attributes().isInitialized());

    auto [symbol_z, found_z] = ast->m_symbol_table->find("z", position);
    REQUIRE(found_z);
    REQUIRE(not symbol_z->attributes().isInitialized());
  }

  SECTION("Declarative function initialization")
  {
    std::string_view data = R"(
let f: R->R, x->x+1;
)";

    string_input input{data, "test.pgs"};
    auto ast = ASTBuilder::build(input);

    ASTSymbolTableBuilder{*ast};
    ASTSymbolInitializationChecker{*ast};

    position position{internal::iterator{"fixture"}, "fixture"};
    position.byte = data.size();   // ensure that variables are declared at this point

    auto [symbol_m, found_m] = ast->m_symbol_table->find("f", position);
    REQUIRE(found_m);
    REQUIRE(symbol_m->attributes().isInitialized());
  }

  SECTION("Lists")
  {
    SECTION("Declarative initialization")
    {
      std::string_view data = R"(
let (x,y):R*R, (x,y) = (2.3, 4.1);
)";

      string_input input{data, "test.pgs"};
      auto ast = ASTBuilder::build(input);

      ASTSymbolTableBuilder{*ast};
      ASTSymbolInitializationChecker{*ast};

      position position{internal::iterator{"fixture"}, "fixture"};
      position.byte = data.size();   // ensure that variables are declared at this point

      auto [symbol_x, found_x] = ast->m_symbol_table->find("x", position);
      REQUIRE(found_x);
      REQUIRE(symbol_x->attributes().isInitialized());

      auto [symbol_y, found_y] = ast->m_symbol_table->find("y", position);
      REQUIRE(found_y);
      REQUIRE(symbol_y->attributes().isInitialized());
    }

    SECTION("Declarative initialization")
    {
      std::string_view data = R"(
let x:R^2, x = (2.3, 4.1);
)";

      string_input input{data, "test.pgs"};
      auto ast = ASTBuilder::build(input);

      ASTSymbolTableBuilder{*ast};
      ASTSymbolInitializationChecker{*ast};

      position position{internal::iterator{"fixture"}, "fixture"};
      position.byte = data.size();   // ensure that variables are declared at this point

      auto [symbol_x, found_x] = ast->m_symbol_table->find("x", position);
      REQUIRE(found_x);
      REQUIRE(symbol_x->attributes().isInitialized());
    }

    SECTION("Not initialized")
    {
      std::string_view data = R"(
let(x,y):R*R;
y = 3;
)";

      string_input input{data, "test.pgs"};
      auto ast = ASTBuilder::build(input);

      ASTSymbolTableBuilder{*ast};
      ASTSymbolInitializationChecker{*ast};

      position position{internal::iterator{"fixture"}, "fixture"};
      position.byte = data.size();   // ensure that variables are declared at this point

      auto [symbol_x, found_x] = ast->m_symbol_table->find("x", position);
      REQUIRE(found_x);
      REQUIRE(not symbol_x->attributes().isInitialized());

      auto [symbol_y, found_y] = ast->m_symbol_table->find("y", position);
      REQUIRE(found_y);
      REQUIRE(symbol_y->attributes().isInitialized());
    }

    SECTION("Affectation")
    {
      std::string_view data = R"(
let (x,y):R*R;
(x,y) = (2.3, 4.1);
)";

      string_input input{data, "test.pgs"};
      auto ast = ASTBuilder::build(input);

      ASTSymbolTableBuilder{*ast};
      ASTSymbolInitializationChecker{*ast};

      position position{internal::iterator{"fixture"}, "fixture"};
      position.byte = data.size();   // ensure that variables are declared at this point

      auto [symbol_x, found_x] = ast->m_symbol_table->find("x", position);
      REQUIRE(found_x);
      REQUIRE(symbol_x->attributes().isInitialized());

      auto [symbol_y, found_y] = ast->m_symbol_table->find("y", position);
      REQUIRE(found_y);
      REQUIRE(symbol_y->attributes().isInitialized());
    }

    SECTION("Affectation")
    {
      std::string_view data = R"(
let (x,y):R^3*R;
(x,y) = ((2.3, 2, 5), 4.1);
)";

      string_input input{data, "test.pgs"};
      auto ast = ASTBuilder::build(input);

      ASTSymbolTableBuilder{*ast};
      ASTSymbolInitializationChecker{*ast};

      position position{internal::iterator{"fixture"}, "fixture"};
      position.byte = data.size();   // ensure that variables are declared at this point

      auto [symbol_x, found_x] = ast->m_symbol_table->find("x", position);
      REQUIRE(found_x);
      REQUIRE(symbol_x->attributes().isInitialized());

      auto [symbol_y, found_y] = ast->m_symbol_table->find("y", position);
      REQUIRE(found_y);
      REQUIRE(symbol_y->attributes().isInitialized());
    }
  }

  SECTION("Affectation")
  {
    std::string_view data = R"(
let x:R^3;
(x[2], x[1], x[0]) = (1, 2, 3);
)";

    string_input input{data, "test.pgs"};
    auto ast = ASTBuilder::build(input);

    ASTSymbolTableBuilder{*ast};
    ASTSymbolInitializationChecker{*ast};

    position position{internal::iterator{"fixture"}, "fixture"};
    position.byte = data.size();   // ensure that variables are declared at this point

    auto [symbol_x, found_x] = ast->m_symbol_table->find("x", position);
    REQUIRE(found_x);
    REQUIRE(symbol_x->attributes().isInitialized());
  }

  SECTION("errors")
  {
    SECTION("used uninitialized")
    {
      std::string_view data = R"(
let n:N;
let m:N, m = n;
)";

      string_input input{data, "test.pgs"};
      auto ast = ASTBuilder::build(input);

      ASTSymbolTableBuilder{*ast};
      REQUIRE_THROWS_WITH(ASTSymbolInitializationChecker{*ast}, std::string{"uninitialized symbol 'n'"});
    }

    SECTION("used uninitialized in list affectation")
    {
      std::string_view data = R"(
let k:N;
let (l, x) : N*R;

(k, x) = (l, 3.2);
)";

      string_input input{data, "test.pgs"};
      auto ast = ASTBuilder::build(input);

      ASTSymbolTableBuilder{*ast};
      REQUIRE_THROWS_WITH(ASTSymbolInitializationChecker{*ast}, std::string{"uninitialized symbol 'l'"});
    }

    SECTION("used uninitialized in function")
    {
      std::string_view data = R"(
let y:R;
let f : R->R, x->x+y;
)";

      string_input input{data, "test.pgs"};
      auto ast = ASTBuilder::build(input);

      ASTSymbolTableBuilder{*ast};
      REQUIRE_THROWS_WITH(ASTSymbolInitializationChecker{*ast}, std::string{"uninitialized symbol 'y'"});
    }

    SECTION("expecting a list of identifiers")
    {
      std::string_view data = R"(
let (x,y,z):R*R*R, x = 3;
)";

      string_input input{data, "test.pgs"};
      auto ast = ASTBuilder::build(input);

      ASTSymbolTableBuilder{*ast};
      REQUIRE_THROWS_WITH(ASTSymbolInitializationChecker{*ast}, std::string{"expecting a list of identifiers"});
    }
  }
}