#include <ASTNodeListAffectationExpressionBuilder.hpp>
#include <PEGGrammar.hpp>

#include <node_processor/AffectationProcessor.hpp>

#include <ASTNodeDataTypeFlattener.hpp>

template <typename OperatorT>
void
ASTNodeListAffectationExpressionBuilder::_buildAffectationProcessor(
  const ASTNodeSubDataType& rhs_node_sub_data_type,
  ASTNode& value_node,
  std::unique_ptr<ListAffectationProcessor<OperatorT>>& list_affectation_processor)
{
  auto add_affectation_processor_for_data = [&](const auto& value, const ASTNodeSubDataType& node_sub_data_type) {
    using ValueT = std::decay_t<decltype(value)>;
    switch (node_sub_data_type.m_data_type) {
    case ASTNodeDataType::bool_t: {
      list_affectation_processor->template add<ValueT, bool>(value_node);
      break;
    }
    case ASTNodeDataType::unsigned_int_t: {
      list_affectation_processor->template add<ValueT, uint64_t>(value_node);
      break;
    }
    case ASTNodeDataType::int_t: {
      list_affectation_processor->template add<ValueT, int64_t>(value_node);
      break;
    }
    case ASTNodeDataType::double_t: {
      list_affectation_processor->template add<ValueT, double>(value_node);
      break;
    }
    default: {
      throw parse_error("invalid operand type for affectation", std::vector{node_sub_data_type.m_parent_node.begin()});
    }
    }
  };

  auto add_affectation_processor_for_string_data = [&](const ASTNodeSubDataType& node_sub_data_type) {
    if constexpr (std::is_same_v<OperatorT, language::eq_op> or std::is_same_v<OperatorT, language::pluseq_op>) {
      switch (node_sub_data_type.m_data_type) {
      case ASTNodeDataType::bool_t: {
        list_affectation_processor->template add<std::string, bool>(value_node);
        break;
      }
      case ASTNodeDataType::unsigned_int_t: {
        list_affectation_processor->template add<std::string, uint64_t>(value_node);
        break;
      }
      case ASTNodeDataType::int_t: {
        list_affectation_processor->template add<std::string, int64_t>(value_node);
        break;
      }
      case ASTNodeDataType::double_t: {
        list_affectation_processor->template add<std::string, double>(value_node);
        break;
      }
      case ASTNodeDataType::string_t: {
        list_affectation_processor->template add<std::string, std::string>(value_node);
        break;
      }
      default: {
        throw parse_error("invalid operand type for string affectation",
                          std::vector{node_sub_data_type.m_parent_node.begin()});
      }
      }
    } else {
      throw parse_error("unexpected error: undefined operator type for string affectation",
                        std::vector{m_node.begin()});
    }
  };

  auto add_affectation_processor_for_value = [&](const ASTNodeDataType& value_type,
                                                 const ASTNodeSubDataType& node_sub_data_type) {
    switch (value_type) {
    case ASTNodeDataType::bool_t: {
      add_affectation_processor_for_data(bool{}, node_sub_data_type);
      break;
    }
    case ASTNodeDataType::unsigned_int_t: {
      add_affectation_processor_for_data(uint64_t{}, node_sub_data_type);
      break;
    }
    case ASTNodeDataType::int_t: {
      add_affectation_processor_for_data(int64_t{}, node_sub_data_type);
      break;
    }
    case ASTNodeDataType::double_t: {
      add_affectation_processor_for_data(double{}, node_sub_data_type);
      break;
    }
    case ASTNodeDataType::string_t: {
      add_affectation_processor_for_string_data(node_sub_data_type);
      break;
    }
    default: {
      throw parse_error("undefined value type for tuple affectation", std::vector{value_node.begin()});
    }
    }
  };

  add_affectation_processor_for_value(value_node.m_data_type, rhs_node_sub_data_type);
}

template <typename OperatorT>
void
ASTNodeListAffectationExpressionBuilder::_buildListAffectationProcessor()
{
  Assert(m_node.children[1]->is_type<language::expression_list>() or
         m_node.children[1]->is_type<language::function_evaluation>());

  ASTNodeDataTypeFlattener::FlattenedDataTypeList flattened_rhs_data_type_list;
  ASTNodeDataTypeFlattener{*m_node.children[1], flattened_rhs_data_type_list};

  ASTNode& name_list_node = *m_node.children[0];

  if (name_list_node.children.size() != flattened_rhs_data_type_list.size()) {
    throw parse_error("incompatible list sizes in affectation", std::vector{m_node.begin()});
  }

  using ListAffectationProcessorT = ListAffectationProcessor<OperatorT>;

  std::unique_ptr list_affectation_processor = std::make_unique<ListAffectationProcessorT>(m_node);

  for (size_t i = 0; i < name_list_node.children.size(); ++i) {
    ASTNode& name_node = *name_list_node.children[i];
    this->_buildAffectationProcessor(flattened_rhs_data_type_list[i], name_node, list_affectation_processor);
  }

  m_node.m_node_processor = std::move(list_affectation_processor);
}

ASTNodeListAffectationExpressionBuilder::ASTNodeListAffectationExpressionBuilder(ASTNode& node) : m_node(node)
{
  if (node.children[1]->is_type<language::expression_list>() or
      node.children[1]->is_type<language::function_evaluation>()) {
    if (node.is_type<language::eq_op>()) {
      this->_buildListAffectationProcessor<language::eq_op>();
    } else {
      throw parse_error("undefined affectation operator for tuples", std::vector{node.begin()});
    }
  } else {
    throw parse_error("invalid right hand side in tuple affectation", std::vector{node.children[1]->begin()});
  }
}