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

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

#include <pegtl/string_input.hpp>

#include <sstream>

#define CHECK_CONCAT_EXPRESSION_RESULT(data, variable_name, expected_value)   \
  {                                                                           \
    TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};                \
    auto ast = ASTBuilder::build(input);                                      \
                                                                              \
    ASTExecutionStack::create();                                              \
                                                                              \
    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{10000, 1000, 10, "fixture"};                        \
    auto [symbol, found] = symbol_table->find(variable_name, use_position);   \
                                                                              \
    auto attributes = symbol->attributes();                                   \
    auto value      = std::get<decltype(expected_value)>(attributes.value()); \
                                                                              \
    ASTExecutionStack::destroy();                                             \
                                                                              \
    REQUIRE(value == expected_value);                                         \
    ast->m_symbol_table->clearValues();                                       \
  }

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

TEST_CASE("ConcatExpressionProcessor", "[language]")
{
  SECTION("string + string")
  {
    CHECK_CONCAT_EXPRESSION_RESULT(R"(let s:string, s = "foo"; s = s+"bar";)", "s", std::string{"foobar"});
  }

  SECTION("string + N")
  {
    CHECK_CONCAT_EXPRESSION_RESULT(R"(let n:N, n = 1; let s:string, s = "foo_"; s = s+n;)", "s", std::string{"foo_1"});
  }

  SECTION("N + string")
  {
    CHECK_CONCAT_EXPRESSION_RESULT(R"(let n:N, n = 1; let s:string, s = "_foo"; s = n+s;)", "s", std::string{"1_foo"});
  }

  SECTION("string + Z")
  {
    CHECK_CONCAT_EXPRESSION_RESULT(R"(let s:string, s = "foo_"; s = s+2;)", "s", std::string{"foo_2"});
  }

  SECTION("Z + string")
  {
    CHECK_CONCAT_EXPRESSION_RESULT(R"(let s:string, s = "_foo"; s = 2+s;)", "s", std::string{"2_foo"});
  }

  SECTION("string + R")
  {
    CHECK_CONCAT_EXPRESSION_RESULT(R"(let s:string, s = "foo_"; s = s+2.4;)", "s",
                                   std::string{"foo_"} + stringify(2.4));
  }

  SECTION("R + string")
  {
    CHECK_CONCAT_EXPRESSION_RESULT(R"(let s:string, s = "_foo"; s = 2.4+s;)", "s",
                                   stringify(2.4) + std::string{"_foo"});
  }

  SECTION("string + B")
  {
    CHECK_CONCAT_EXPRESSION_RESULT(R"(let s:string, s = "foo_"; s = s+(2>1);)", "s", std::string{"foo_true"});
    CHECK_CONCAT_EXPRESSION_RESULT(R"(let s:string, s = "foo_"; s = s+(1>2);)", "s", std::string{"foo_false"});
  }

  SECTION("B + string")
  {
    CHECK_CONCAT_EXPRESSION_RESULT(R"(let s:string, s = "_foo"; s = (2>1)+s;)", "s", std::string{"true_foo"});
    CHECK_CONCAT_EXPRESSION_RESULT(R"(let s:string, s = "_foo"; s = (1>2)+s;)", "s", std::string{"false_foo"});
  }

  SECTION("string + R^1")
  {
    std::ostringstream os;
    os << "foo_" << TinyVector<1>{1};

    CHECK_CONCAT_EXPRESSION_RESULT(R"(let x:R^1, x = 1; let s:string, s = "foo_"; s = s+x;)", "s", os.str());
  }

  SECTION("R^1 + string")
  {
    std::ostringstream os;
    os << TinyVector<1>{1} << "_foo";

    CHECK_CONCAT_EXPRESSION_RESULT(R"(let x:R^1, x = 1; let s:string, s = "_foo"; s = x+s;)", "s", os.str());
  }

  SECTION("string + R^2")
  {
    std::ostringstream os;
    os << "foo_" << TinyVector<2>{1, 2};

    CHECK_CONCAT_EXPRESSION_RESULT(R"(let x:R^2, x = [1,2]; let s:string, s = "foo_"; s = s+x;)", "s", os.str());
  }

  SECTION(" R^2 + string")
  {
    std::ostringstream os;
    os << TinyVector<2>{1, 2} << "_foo";

    CHECK_CONCAT_EXPRESSION_RESULT(R"(let x:R^2, x = [1,2]; let s:string, s = "_foo"; s = x+s;)", "s", os.str());
  }

  SECTION("string + R^3")
  {
    std::ostringstream os;
    os << "foo_" << TinyVector<3>{1, 2, 3};

    CHECK_CONCAT_EXPRESSION_RESULT(R"(let s:string, s = "foo_"; s = s+[1,2,3];)", "s", os.str());
  }

  SECTION("R^3 + string")
  {
    std::ostringstream os;
    os << TinyVector<3>{1, 2, 3} << "_foo";

    CHECK_CONCAT_EXPRESSION_RESULT(R"(let x:R^3, x = [1,2,3]; let s:string, s = "_foo"; s = x+s;)", "s", os.str());
  }

  SECTION("string + R^1x1")
  {
    std::ostringstream os;
    os << "foo_" << TinyMatrix<1>{1};

    CHECK_CONCAT_EXPRESSION_RESULT(R"(let x:R^1x1, x = 1; let s:string, s = "foo_"; s = s+x;)", "s", os.str());
  }

  SECTION("R^1x1 + string")
  {
    std::ostringstream os;
    os << TinyMatrix<1>{1} << "_foo";

    CHECK_CONCAT_EXPRESSION_RESULT(R"(let x:R^1x1, x = 1; let s:string, s = "_foo"; s = x+s;)", "s", os.str());
  }

  SECTION("string + R^2x2")
  {
    std::ostringstream os;
    os << "foo_" << TinyMatrix<2>{1, 2, 3, 4};

    CHECK_CONCAT_EXPRESSION_RESULT(R"(let x:R^2x2, x = [[1,2],[3,4]]; let s:string, s = "foo_"; s = s+x;)", "s",
                                   os.str());
  }

  SECTION(" R^2x2 + string")
  {
    std::ostringstream os;
    os << TinyMatrix<2>{1, 2, 3, 4} << "_foo";

    CHECK_CONCAT_EXPRESSION_RESULT(R"(let x:R^2x2, x = [[1,2],[3,4]]; let s:string, s = "_foo"; s = x+s;)", "s",
                                   os.str());
  }

  SECTION("string + R^3x3")
  {
    std::ostringstream os;
    os << "foo_" << TinyMatrix<3>{1, 2, 3, 4, 5, 6, 7, 8, 9};

    CHECK_CONCAT_EXPRESSION_RESULT(R"(let x:R^3x3, x = [[1,2,3],[4,5,6],[7,8,9]]; let s:string, s = "foo_"; s = s+x;)",
                                   "s", os.str());
  }

  SECTION("R^3x3 + string")
  {
    std::ostringstream os;
    os << TinyMatrix<3>{1, 2, 3, 4, 5, 6, 7, 8, 9} << "_foo";

    CHECK_CONCAT_EXPRESSION_RESULT(R"(let x:R^3x3, x = [[1,2,3],[4,5,6],[7,8,9]]; let s:string, s = "_foo"; s = x+s;)",
                                   "s", os.str());
  }

  SECTION("expression type")
  {
    ASTNode node;
    REQUIRE(ConcatExpressionProcessor<std::string, std::string>{node}.type() ==
            INodeProcessor::Type::concat_expression_processor);
  }
}