#include <catch2/catch.hpp>

#include <language/ast/ASTBuilder.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 <language/utils/ASTPrinter.hpp>
#include <utils/Demangle.hpp>

#include <pegtl/string_input.hpp>

#include <sstream>

#define CHECK_AFFECTATION_RESULT(data, variable_name, expected_value)         \
  {                                                                           \
    string_input input{data, "test.pgs"};                                     \
    auto ast = ASTBuilder::build(input);                                      \
                                                                              \
    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);                                         \
  }

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

TEST_CASE("ASTAffectationToTupleProcessor", "[language]")
{
  SECTION("Affectations from value")
  {
    CHECK_AFFECTATION_RESULT(R"(
let s :(R); s = 2.;
)",
                             "s", (std::vector<double>{2.}));

    CHECK_AFFECTATION_RESULT(R"(
let s :(R); s = 2;
)",
                             "s", (std::vector<double>{2}));

    CHECK_AFFECTATION_RESULT(R"(
let s :(string); s = 2.;
)",
                             "s", (std::vector<std::string>{std::to_string(2.)}));

    const std::string x_string = []() -> std::string {
      std::ostringstream os;
      os << TinyVector<3, double>{1, 2, 3} << std::ends;
      return os.str();
    }();

    CHECK_AFFECTATION_RESULT(R"(
let x :R^3, x = (1,2,3);
let s :(string); s = x;
)",
                             "s", (std::vector<std::string>{x_string}));

    CHECK_AFFECTATION_RESULT(R"(
let s :(R^1); s = 1.3;
)",
                             "s", (std::vector<TinyVector<1>>{TinyVector<1>{1.3}}));
  }

  SECTION("Affectations from list")
  {
    CHECK_AFFECTATION_RESULT(R"(
let t :(R); t = (2.,3);
)",
                             "t", (std::vector<double>{2., 3}));

    CHECK_AFFECTATION_RESULT(R"(
let s :(string); s = (2.,3);
)",
                             "s", (std::vector<std::string>{std::to_string(2.), std::to_string(3)}));

    CHECK_AFFECTATION_RESULT(R"(
let s :(string); s = (2.,3,"foo");
)",
                             "s",
                             (std::vector<std::string>{std::to_string(2.), std::to_string(3), std::string{"foo"}}));

    const std::string x_string = []() -> std::string {
      std::ostringstream os;
      os << TinyVector<2, double>{1, 2} << std::ends;
      return os.str();
    }();

    CHECK_AFFECTATION_RESULT(R"(
let x : R^2, x = (1,2);
let s : (string); s = (2.,3, x);
)",
                             "s", (std::vector<std::string>{std::to_string(2.), std::to_string(3), x_string}));

    CHECK_AFFECTATION_RESULT(R"(
let x : R^2, x = (1,2);
let t :(R^2); t = (x,0);
)",
                             "t", (std::vector<TinyVector<2>>{TinyVector<2>{1, 2}, TinyVector<2>{0, 0}}));

    CHECK_AFFECTATION_RESULT(R"(
let t :(R^2); t = ((1,2),0);
)",
                             "t", (std::vector<TinyVector<2>>{TinyVector<2>{1, 2}, TinyVector<2>{0, 0}}));

    CHECK_AFFECTATION_RESULT(R"(
let t :(R^2); t = (0);
)",
                             "t", (std::vector<TinyVector<2>>{TinyVector<2>{0, 0}}));

    CHECK_AFFECTATION_RESULT(R"(
let t :(R^3); t = (0);
)",
                             "t", (std::vector<TinyVector<3>>{TinyVector<3>{0, 0, 0}}));

    CHECK_AFFECTATION_RESULT(R"(
let x : R^1, x = 1;
let t :(R^1); t = (x,2);
)",
                             "t", (std::vector<TinyVector<1>>{TinyVector<1>{1}, TinyVector<1>{2}}));
  }

  SECTION("Affectations from tuple")
  {
    const std::string x_string = []() -> std::string {
      std::ostringstream os;
      os << TinyVector<3, double>{1, 2, 3} << std::ends;
      return os.str();
    }();

    CHECK_AFFECTATION_RESULT(R"(
let x :(R^3), x = ((1,2,3));
let s :(string); s = x;
)",
                             "s", (std::vector<std::string>{x_string}));

    CHECK_AFFECTATION_RESULT(R"(
let x :(R), x = (1,2,3);
let s :(string); s = x;
)",
                             "s",
                             (std::vector<std::string>{std::to_string(1.), std::to_string(2.), std::to_string(3.)}));

    CHECK_AFFECTATION_RESULT(R"(
let n :(N), n = (1,2,3);
let t :(R); t = n;
)",
                             "t", (std::vector<double>{1, 2, 3}));

    CHECK_AFFECTATION_RESULT(R"(
let s :(N), s = (1,2,3);
let t :(N); t = s;
)",
                             "t", (std::vector<uint64_t>{1, 2, 3}));
  }
}