#include <ASTNodeAffectationExpressionBuilder.hpp>
#include <PEGGrammar.hpp>

#include <ASTNodeNaturalConversionChecker.hpp>

#include <node_processor/AffectationProcessor.hpp>

#include <../algebra/TinyVector.hpp>

ASTNodeAffectationExpressionBuilder::ASTNodeAffectationExpressionBuilder(ASTNode& n)
{
  auto set_affectation_processor = [](ASTNode& n, const auto& operator_v) {
    auto set_affectation_processor_for_data = [&](const auto& value, const ASTNodeDataType& data_type) {
      using OperatorT = std::decay_t<decltype(operator_v)>;
      using ValueT    = std::decay_t<decltype(value)>;

      switch (data_type) {
      case ASTNodeDataType::bool_t: {
        n.m_node_processor = std::make_unique<AffectationProcessor<OperatorT, ValueT, bool>>(n);
        break;
      }
      case ASTNodeDataType::unsigned_int_t: {
        n.m_node_processor = std::make_unique<AffectationProcessor<OperatorT, ValueT, uint64_t>>(n);
        break;
      }
      case ASTNodeDataType::int_t: {
        n.m_node_processor = std::make_unique<AffectationProcessor<OperatorT, ValueT, int64_t>>(n);
        break;
      }
      case ASTNodeDataType::double_t: {
        n.m_node_processor = std::make_unique<AffectationProcessor<OperatorT, ValueT, double>>(n);
        break;
      }
        // LCOV_EXCL_START
      default: {
        throw parse_error("unexpected error: undefined operand type for affectation",
                          std::vector{n.children[1]->begin()});
      }
        // LCOV_EXCL_STOP
      }
    };

    auto set_affectation_processor_for_vector_data = [&](const auto& value, const ASTNodeDataType& data_type) {
      using OperatorT = std::decay_t<decltype(operator_v)>;
      using ValueT    = std::decay_t<decltype(value)>;

      if constexpr (std::is_same_v<OperatorT, language::eq_op>) {
        switch (data_type) {
        case ASTNodeDataType::vector_t: {
          n.m_node_processor = std::make_unique<AffectationProcessor<OperatorT, ValueT, ValueT>>(n);
          break;
        }
        case ASTNodeDataType::list_t: {
          n.m_node_processor = std::make_unique<AffectationFromListProcessor<OperatorT, ValueT>>(n);
          break;
        }
        case ASTNodeDataType::bool_t: {
          if constexpr (std::is_same_v<ValueT, TinyVector<1>>) {
            n.m_node_processor = std::make_unique<AffectationProcessor<OperatorT, ValueT, bool>>(n);
            break;
          }
        }
        case ASTNodeDataType::unsigned_int_t: {
          if constexpr (std::is_same_v<ValueT, TinyVector<1>>) {
            n.m_node_processor = std::make_unique<AffectationProcessor<OperatorT, ValueT, uint64_t>>(n);
            break;
          }
        }
        case ASTNodeDataType::int_t: {
          if constexpr (std::is_same_v<ValueT, TinyVector<1>>) {
            if (n.children[1]->is_type<language::integer>()) {
              if (std::stoi(n.children[1]->string()) == 0) {
                n.m_node_processor = std::make_unique<AffectationFromZeroProcessor<ValueT>>(n);
                break;
              }
            }
            n.m_node_processor = std::make_unique<AffectationProcessor<OperatorT, ValueT, int64_t>>(n);
            break;
          } else if (n.children[1]->is_type<language::integer>()) {
            if (std::stoi(n.children[1]->string()) == 0) {
              n.m_node_processor = std::make_unique<AffectationFromZeroProcessor<ValueT>>(n);
              break;
            }
          }
          // LCOV_EXCL_START
          throw parse_error("unexpected error: invalid integral value", std::vector{n.children[1]->begin()});
          // LCOV_EXCL_STOP
        }
        case ASTNodeDataType::double_t: {
          if constexpr (std::is_same_v<ValueT, TinyVector<1>>) {
            n.m_node_processor = std::make_unique<AffectationProcessor<OperatorT, ValueT, double>>(n);
            break;
          }
        }
          // LCOV_EXCL_START
        default: {
          throw parse_error("unexpected error: invalid operand type", std::vector{n.children[1]->begin()});
        }
          // LCOV_EXCL_STOP
        }
      } else if constexpr (std::is_same_v<OperatorT, language::pluseq_op> or
                           std::is_same_v<OperatorT, language::minuseq_op>) {
        switch (data_type) {
        case ASTNodeDataType::vector_t: {
          n.m_node_processor = std::make_unique<AffectationProcessor<OperatorT, ValueT, ValueT>>(n);
          break;
        }
          // LCOV_EXCL_START
        default: {
          throw parse_error("unexpected error: invalid operand type", std::vector{n.children[1]->begin()});
        }
          // LCOV_EXCL_STOP
        }
      } else if constexpr (std::is_same_v<OperatorT, language::multiplyeq_op>) {
        switch (data_type) {
        case ASTNodeDataType::bool_t: {
          n.m_node_processor = std::make_unique<AffectationProcessor<OperatorT, ValueT, bool>>(n);
          break;
        }
        case ASTNodeDataType::unsigned_int_t: {
          n.m_node_processor = std::make_unique<AffectationProcessor<OperatorT, ValueT, uint64_t>>(n);
          break;
        }
        case ASTNodeDataType::int_t: {
          n.m_node_processor = std::make_unique<AffectationProcessor<OperatorT, ValueT, int64_t>>(n);
          break;
        }
        case ASTNodeDataType::double_t: {
          n.m_node_processor = std::make_unique<AffectationProcessor<OperatorT, ValueT, double>>(n);
          break;
        }
        default: {
          throw parse_error("expecting scalar operand type", std::vector{n.children[1]->begin()});
        }
        }
      } else {
        throw parse_error("invalid affectation operator for " + dataTypeName(n.m_data_type), std::vector{n.begin()});
      }
    };

    auto set_affectation_processor_for_string_data = [&](const ASTNodeDataType& data_type) {
      using OperatorT = std::decay_t<decltype(operator_v)>;

      if constexpr (std::is_same_v<OperatorT, language::eq_op> or std::is_same_v<OperatorT, language::pluseq_op>) {
        switch (data_type) {
        case ASTNodeDataType::bool_t: {
          n.m_node_processor = std::make_unique<AffectationProcessor<OperatorT, std::string, bool>>(n);
          break;
        }
        case ASTNodeDataType::unsigned_int_t: {
          n.m_node_processor = std::make_unique<AffectationProcessor<OperatorT, std::string, uint64_t>>(n);
          break;
        }
        case ASTNodeDataType::int_t: {
          n.m_node_processor = std::make_unique<AffectationProcessor<OperatorT, std::string, int64_t>>(n);
          break;
        }
        case ASTNodeDataType::double_t: {
          n.m_node_processor = std::make_unique<AffectationProcessor<OperatorT, std::string, double>>(n);
          break;
        }
        case ASTNodeDataType::string_t: {
          n.m_node_processor = std::make_unique<AffectationProcessor<OperatorT, std::string, std::string>>(n);
          break;
        }
        case ASTNodeDataType::vector_t: {
          switch (data_type.dimension()) {
          case 1: {
            n.m_node_processor = std::make_unique<AffectationProcessor<OperatorT, std::string, TinyVector<1>>>(n);
            break;
          }
          case 2: {
            n.m_node_processor = std::make_unique<AffectationProcessor<OperatorT, std::string, TinyVector<2>>>(n);
            break;
          }
          case 3: {
            n.m_node_processor = std::make_unique<AffectationProcessor<OperatorT, std::string, TinyVector<3>>>(n);
            break;
          }
            // LCOV_EXCL_START
          default: {
            throw parse_error("unexpected error: invalid vector dimension for string affectation",
                              std::vector{n.children[1]->begin()});
          }
            // LCOV_EXCL_STOP
          }
          break;
        }
          // LCOV_EXCL_START
        default: {
          throw parse_error("unexpected error: undefined operand type for string affectation",
                            std::vector{n.children[1]->begin()});
        }
          // LCOV_EXCL_STOP
        }
      } else {
        throw parse_error("invalid operator for string affectation", std::vector{n.begin()});
      }
    };

    auto set_affectation_processor_for_embedded_data = [&](const ASTNodeDataType& data_type) {
      using OperatorT = std::decay_t<decltype(operator_v)>;

      if constexpr (std::is_same_v<OperatorT, language::eq_op>) {
        switch (data_type) {
        case ASTNodeDataType::type_id_t: {
          n.m_node_processor = std::make_unique<AffectationProcessor<OperatorT, EmbeddedData, EmbeddedData>>(n);
          break;
        }
          // LCOV_EXCL_START
        default: {
          throw parse_error("unexpected error: undefined operand type for string affectation",
                            std::vector{n.children[1]->begin()});
        }
          // LCOV_EXCL_STOP
        }
      } else {
        throw parse_error("invalid operator for '" + data_type.typeName() + "' affectation", std::vector{n.begin()});
      }
    };

    auto set_affectation_processor_for_value = [&](const ASTNodeDataType& value_type) {
      const ASTNodeDataType data_type = n.children[1]->m_data_type;

      switch (value_type) {
      case ASTNodeDataType::bool_t: {
        set_affectation_processor_for_data(bool{}, data_type);
        break;
      }
      case ASTNodeDataType::unsigned_int_t: {
        set_affectation_processor_for_data(uint64_t{}, data_type);
        break;
      }
      case ASTNodeDataType::int_t: {
        set_affectation_processor_for_data(int64_t{}, data_type);
        break;
      }
      case ASTNodeDataType::double_t: {
        set_affectation_processor_for_data(double{}, data_type);
        break;
      }
      case ASTNodeDataType::vector_t: {
        switch (value_type.dimension()) {
        case 1: {
          set_affectation_processor_for_vector_data(TinyVector<1>{}, data_type);
          break;
        }
        case 2: {
          set_affectation_processor_for_vector_data(TinyVector<2>{}, data_type);
          break;
        }
        case 3: {
          set_affectation_processor_for_vector_data(TinyVector<3>{}, data_type);
          break;
        }
          // LCOV_EXCL_START
        default: {
          throw parse_error("unexpected error: unexpected vector dimension", std::vector{n.begin()});
        }
          // LCOV_EXCL_STOP
        }
        break;
      }
      case ASTNodeDataType::string_t: {
        set_affectation_processor_for_string_data(data_type);
        break;
      }
      case ASTNodeDataType::type_id_t: {
        set_affectation_processor_for_embedded_data(data_type);
        break;
      }
      default: {
        throw parse_error("unexpected error: undefined value type for affectation", std::vector{n.begin()});
      }
      }
    };

    using OperatorT = std::decay_t<decltype(operator_v)>;
    // Special treatment dedicated to R^1 to be able to initialize them
    if (((n.m_data_type != n.children[1]->m_data_type) and (n.m_data_type == ASTNodeDataType::vector_t) and
         (n.m_data_type.dimension() == 1)) or
        // Special treatment for R^d vectors and operator *=
        ((n.m_data_type == ASTNodeDataType::vector_t) and (n.children[1]->m_data_type != ASTNodeDataType::vector_t) and
         std::is_same_v<OperatorT, language::multiplyeq_op>)) {
      ASTNodeNaturalConversionChecker{*n.children[1], ASTNodeDataType::double_t};
    } else {
      ASTNodeNaturalConversionChecker{*n.children[1], n.m_data_type};
    }

    set_affectation_processor_for_value(n.m_data_type);
  };

  if (n.is_type<language::eq_op>()) {
    set_affectation_processor(n, language::eq_op{});
  } else if (n.is_type<language::multiplyeq_op>()) {
    set_affectation_processor(n, language::multiplyeq_op{});
  } else if (n.is_type<language::divideeq_op>()) {
    set_affectation_processor(n, language::divideeq_op{});
  } else if (n.is_type<language::pluseq_op>()) {
    set_affectation_processor(n, language::pluseq_op{});
  } else if (n.is_type<language::minuseq_op>()) {
    set_affectation_processor(n, language::minuseq_op{});
  } else {
    throw parse_error("unexpected error: undefined affectation operator", std::vector{n.begin()});
  }
}