#include <catch2/catch_test_macros.hpp>
#include <catch2/matchers/catch_matchers_all.hpp>

#include <language/ast/ASTBuilder.hpp>
#include <language/ast/ASTModulesImporter.hpp>
#include <language/ast/ASTNodeAffectationExpressionBuilder.hpp>
#include <language/ast/ASTNodeDataTypeBuilder.hpp>
#include <language/ast/ASTNodeDeclarationToAffectationConverter.hpp>
#include <language/ast/ASTNodeExpressionBuilder.hpp>
#include <language/ast/ASTNodeTypeCleaner.hpp>
#include <language/ast/ASTSymbolTableBuilder.hpp>
#include <utils/Demangle.hpp>

#include <pegtl/string_input.hpp>

#include <sstream>

#define CHECK_WHILE_PROCESSOR_RESULT(data, variable_name, expected_value)     \
  {                                                                           \
    TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};                \
    auto ast = ASTBuilder::build(input);                                      \
                                                                              \
    ASTModulesImporter{*ast};                                                 \
    ASTNodeTypeCleaner<language::import_instruction>{*ast};                   \
                                                                              \
    ASTSymbolTableBuilder{*ast};                                              \
    ASTNodeDataTypeBuilder{*ast};                                             \
                                                                              \
    ASTNodeDeclarationToAffectationConverter{*ast};                           \
    ASTNodeTypeCleaner<language::var_declaration>{*ast};                      \
                                                                              \
    ASTNodeExpressionBuilder{*ast};                                           \
    ExecutionPolicy exec_policy;                                              \
    ast->execute(exec_policy);                                                \
                                                                              \
    auto symbol_table = ast->m_symbol_table;                                  \
                                                                              \
    using namespace TAO_PEGTL_NAMESPACE;                                      \
    position use_position{internal::iterator{"fixture"}, "fixture"};          \
    use_position.byte    = 10000;                                             \
    auto [symbol, found] = symbol_table->find(variable_name, use_position);   \
                                                                              \
    auto attributes = symbol->attributes();                                   \
    auto value      = std::get<decltype(expected_value)>(attributes.value()); \
                                                                              \
    REQUIRE(value == expected_value);                                         \
  }

#define CHECK_WHILE_PROCESSOR_THROWS_WITH(data, error_message)        \
  {                                                                   \
    TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};        \
    auto ast = ASTBuilder::build(input);                              \
                                                                      \
    ASTSymbolTableBuilder{*ast};                                      \
                                                                      \
    REQUIRE_THROWS_WITH(ASTNodeDataTypeBuilder{*ast}, error_message); \
  }

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

TEST_CASE("WhileProcessor", "[language]")
{
  SECTION("simple loop")
  {
    std::string_view data = R"(
let i:N, i = 3;
let j:N, j = 0;
while(i<10) {
  j++;
  i += j;
}
)";
    CHECK_WHILE_PROCESSOR_RESULT(data, "i", 13ul);
  }

  SECTION("simple with break")
  {
    std::string_view data = R"(
let i:N, i = 3;
let j:N, j = 0;
while(i<10) {
  j++;
  if (j==2) break;
  i += j;
}
)";
    CHECK_WHILE_PROCESSOR_RESULT(data, "i", 4ul);
  }

  SECTION("simple with continue")
  {
    std::string_view data = R"(
let i:N, i = 3;
let j:N, j = 0;
while(i<10) {
  j++;
  if (j<=3) continue;
  i += j;
}
)";
    CHECK_WHILE_PROCESSOR_RESULT(data, "i", 12ul);
  }

  SECTION("while symbol table untouched")
  {
    std::string_view data = R"(
let i:N, i = 3;
while(i != 3);
)";
    CHECK_WHILE_PROCESSOR_RESULT(data, "i", 3ul);
  }

  SECTION("single instruction while symbol table untouched")
  {
    std::string_view data = R"(
let i:N, i = 3;
while (i == 3)
  i = 2;
)";
    CHECK_WHILE_PROCESSOR_RESULT(data, "i", 2ul);
  }

  SECTION("errors")
  {
    SECTION("bad test type")
    {
      std::string_view data = R"(
while(1) {
}
)";

      CHECK_WHILE_PROCESSOR_THROWS_WITH(data, "invalid implicit conversion: Z -> B");
    }
  }
}