#ifndef FUNCTION_ARGUMENT_CONVERTER_HPP
#define FUNCTION_ARGUMENT_CONVERTER_HPP

#include <language/node_processor/ExecutionPolicy.hpp>
#include <language/utils/DataVariant.hpp>
#include <utils/Demangle.hpp>
#include <utils/Exceptions.hpp>
#include <utils/PugsTraits.hpp>

#include <sstream>

class IFunctionArgumentConverter
{
 public:
  virtual DataVariant convert(ExecutionPolicy& exec_policy, DataVariant&& value) = 0;

  IFunctionArgumentConverter() = default;

  IFunctionArgumentConverter(const IFunctionArgumentConverter&) = delete;
  IFunctionArgumentConverter(IFunctionArgumentConverter&&)      = delete;

  virtual ~IFunctionArgumentConverter() = default;
};

class FunctionArgumentToStringConverter final : public IFunctionArgumentConverter
{
 private:
  size_t m_argument_id;

 public:
  DataVariant
  convert(ExecutionPolicy& exec_policy, DataVariant&& value)
  {
    std::visit(
      [&](auto&& v) {
        using T = std::decay_t<decltype(v)>;
        if constexpr (std::is_arithmetic_v<T>) {
          exec_policy.currentContext()[m_argument_id] = std::to_string(v);
        } else if constexpr (std::is_same_v<T, std::string>) {
          exec_policy.currentContext()[m_argument_id] = v;
        } else {
          std::ostringstream sout;
          sout << value << std::ends;
          exec_policy.currentContext()[m_argument_id] = sout.str();
        }
      },
      value);
    return {};
  }

  FunctionArgumentToStringConverter(size_t argument_id) : m_argument_id{argument_id} {}
};

template <typename ExpectedValueType, typename ProvidedValueType>
class FunctionArgumentConverter final : public IFunctionArgumentConverter
{
 private:
  size_t m_argument_id;

 public:
  DataVariant
  convert(ExecutionPolicy& exec_policy, DataVariant&& value)
  {
    static_assert(not std::is_same_v<ExpectedValueType, std::string>, "use FunctionArgumentToStringConverter");
    if constexpr (std::is_same_v<ExpectedValueType, ProvidedValueType>) {
      exec_policy.currentContext()[m_argument_id] = std::move(value);
    } else {
      exec_policy.currentContext()[m_argument_id] =
        std::move(static_cast<ExpectedValueType>(std::get<ProvidedValueType>(value)));
    }
    return {};
  }

  FunctionArgumentConverter(size_t argument_id) : m_argument_id{argument_id} {}
};

template <typename ExpectedValueType, typename ProvidedValueType>
class FunctionTinyVectorArgumentConverter final : public IFunctionArgumentConverter
{
 private:
  size_t m_argument_id;

 public:
  DataVariant
  convert(ExecutionPolicy& exec_policy, DataVariant&& value)
  {
    if constexpr (std::is_same_v<ExpectedValueType, ProvidedValueType>) {
      std::visit(
        [&](auto&& v) {
          using ValueT = std::decay_t<decltype(v)>;
          if constexpr (std::is_same_v<ValueT, ExpectedValueType>) {
            exec_policy.currentContext()[m_argument_id] = std::move(value);
          } else if constexpr (std::is_same_v<ValueT, AggregateDataVariant>) {
            ExpectedValueType vector_value{};
            for (size_t i = 0; i < vector_value.dimension(); ++i) {
              std::visit(
                [&](auto&& v_i) {
                  using Vi_T = std::decay_t<decltype(v_i)>;
                  if constexpr (std::is_arithmetic_v<Vi_T>) {
                    vector_value[i] = v_i;
                  } else {
                    throw UnexpectedError(demangle<Vi_T>() + " unexpected aggregate value type");
                  }
                },
                v[i]);
            }
            exec_policy.currentContext()[m_argument_id] = std::move(vector_value);
          }
        },
        value);
    } else if constexpr (std::is_same_v<ProvidedValueType, ZeroType>) {
      exec_policy.currentContext()[m_argument_id] = ExpectedValueType{ZeroType::zero};
    } else {
      static_assert(std::is_same_v<ExpectedValueType, TinyVector<1>>);
      exec_policy.currentContext()[m_argument_id] =
        std::move(static_cast<ExpectedValueType>(std::get<ProvidedValueType>(value)));
    }
    return {};
  }

  FunctionTinyVectorArgumentConverter(size_t argument_id) : m_argument_id{argument_id} {}
};

template <typename ContentType, typename ProvidedValueType>
class FunctionTupleArgumentConverter final : public IFunctionArgumentConverter
{
 private:
  size_t m_argument_id;

 public:
  DataVariant
  convert(ExecutionPolicy& exec_policy, DataVariant&& value)
  {
    using TupleType = std::vector<ContentType>;
    if constexpr (std::is_convertible_v<ProvidedValueType, ContentType>) {
      std::visit(
        [&](auto&& v) {
          using ValueT = std::decay_t<decltype(v)>;
          if constexpr (std::is_same_v<ValueT, ContentType>) {
            exec_policy.currentContext()[m_argument_id] = std::move(TupleType{std::move(v)});
          } else if constexpr (is_std_vector_v<ValueT>) {
            using ContentT = typename ValueT::value_type;
            if constexpr (std::is_same_v<ContentT, ContentType>) {
              TupleType list_value;
              list_value.reserve(v.size());
              for (size_t i = 0; i < v.size(); ++i) {
                list_value.emplace_back(std::move(v[i]));
              }
              exec_policy.currentContext()[m_argument_id] = std::move(list_value);
            } else if constexpr ((std::is_convertible_v<ContentT, ContentType>)and not is_tiny_vector_v<ContentType>) {
              TupleType list_value;
              list_value.reserve(v.size());
              for (size_t i = 0; i < v.size(); ++i) {
                list_value.push_back(static_cast<ContentType>(v[i]));
              }
              exec_policy.currentContext()[m_argument_id] = std::move(list_value);
            } else {
              // LCOV_EXCL_START
              throw UnexpectedError(std::string{"cannot convert '"} + demangle<ValueT>() + "' to '" +
                                    demangle<ContentType>() + "'");
              // LCOV_EXCL_STOP
            }
          } else if constexpr (std::is_convertible_v<ValueT, ContentType> and not is_tiny_vector_v<ContentType>) {
            exec_policy.currentContext()[m_argument_id] = std::move(TupleType{static_cast<ContentType>(v)});
          } else {
            throw UnexpectedError(std::string{"cannot convert '"} + demangle<ValueT>() + "' to '" +
                                  demangle<ContentType>() + "'");
          }
        },
        value);

    } else {
      // LCOV_EXCL_START
      throw UnexpectedError(std::string{"cannot convert '"} + demangle<ProvidedValueType>() + "' to '" +
                            demangle<ContentType>() + "'");
      // LCOV_EXCL_STOP
    }
    return {};
  }

  FunctionTupleArgumentConverter(size_t argument_id) : m_argument_id{argument_id} {}
};

template <typename ContentType, typename ProvidedValueType>
class FunctionListArgumentConverter final : public IFunctionArgumentConverter
{
 private:
  size_t m_argument_id;

 public:
  DataVariant
  convert(ExecutionPolicy& exec_policy, DataVariant&& value)
  {
    using TupleType = std::vector<ContentType>;
    if constexpr (std::is_same_v<ContentType, ProvidedValueType>) {
      std::visit(
        [&](auto&& v) {
          using ValueT = std::decay_t<decltype(v)>;
          if constexpr (std::is_same_v<ValueT, AggregateDataVariant>) {
            TupleType list_value;
            list_value.reserve(v.size());
            for (size_t i = 0; i < v.size(); ++i) {
              std::visit(
                [&](auto&& vi) {
                  using Vi_T = std::decay_t<decltype(vi)>;
                  if constexpr (std::is_same_v<Vi_T, ContentType>) {
                    list_value.emplace_back(vi);
                  } else if constexpr (is_tiny_vector_v<ContentType>) {
                    // LCOV_EXCL_START
                    throw UnexpectedError(std::string{"invalid conversion of '"} + demangle<Vi_T>() + "' to '" +
                                          demangle<ContentType>() + "'");
                    // LCOV_EXCL_STOP
                  } else if constexpr (std::is_convertible_v<Vi_T, ContentType>) {
                    list_value.emplace_back(vi);
                  } else {
                    // LCOV_EXCL_START
                    throw UnexpectedError("unexpected types");
                    // LCOV_EXCL_STOP
                  }
                },
                (v[i]));
            }
            exec_policy.currentContext()[m_argument_id] = std::move(list_value);
          } else if constexpr (is_std_vector_v<ValueT>) {
            using ContentT = typename ValueT::value_type;
            if constexpr (std::is_same_v<ContentT, ContentType>) {
              exec_policy.currentContext()[m_argument_id] = v;
            } else {
              // LCOV_EXCL_START
              throw UnexpectedError(std::string{"invalid conversion of '"} + demangle<ContentT>() + "' to '" +
                                    demangle<ContentType>() + "'");
              // LCOV_EXCL_STOP
            }
          } else if constexpr (std::is_same_v<ValueT, ContentType>) {
            exec_policy.currentContext()[m_argument_id] = std::move(TupleType{v});
          } else if constexpr (std::is_convertible_v<ValueT, ContentType> and not is_tiny_vector_v<ValueT> and
                               not is_tiny_vector_v<ContentType>) {
            exec_policy.currentContext()[m_argument_id] = std::move(TupleType{static_cast<ContentType>(v)});
          } else {
            // LCOV_EXCL_START
            throw UnexpectedError(demangle<ValueT>() + " unexpected value type");
            // LCOV_EXCL_STOP
          }
        },
        value);
    }
    static_assert(std::is_same_v<ContentType, ProvidedValueType>, "conversion is not implemented");

    return {};
  }

  FunctionListArgumentConverter(size_t argument_id) : m_argument_id{argument_id} {}
};

class FunctionArgumentToFunctionSymbolIdConverter final : public IFunctionArgumentConverter
{
 private:
  size_t m_argument_id;
  std::shared_ptr<SymbolTable> m_symbol_table;

 public:
  DataVariant
  convert(ExecutionPolicy& exec_policy, DataVariant&& value)
  {
    exec_policy.currentContext()[m_argument_id] = FunctionSymbolId{std::get<uint64_t>(value), m_symbol_table};

    return {};
  }

  FunctionArgumentToFunctionSymbolIdConverter(size_t argument_id, const std::shared_ptr<SymbolTable>& symbol_table)
    : m_argument_id{argument_id}, m_symbol_table{symbol_table}
  {}
};

#endif   // FUNCTION_ARGUMENT_CONVERTER_HPP