#ifndef BUILTIN_MODULE_HPP
#define BUILTIN_MODULE_HPP

#include <language/modules/IModule.hpp>
#include <language/utils/ASTNodeDataType.hpp>

#include <utils/Exceptions.hpp>

#include <sstream>

class IBuiltinFunctionEmbedder;
class TypeDescriptor;
class ValueDescriptor;

template <typename FX, typename... Args>
class BuiltinFunctionEmbedder;

class BuiltinModule : public IModule
{
 protected:
  NameBuiltinFunctionMap m_name_builtin_function_map;
  NameTypeMap m_name_type_map;
  NameValueMap m_name_value_map;

  template <typename FX, typename... Args>
  void
  _addBuiltinFunction(const std::string& name, std::function<FX(Args...)>&& f)
  {
    try {
      this->_addBuiltinFunction(name, std::make_shared<BuiltinFunctionEmbedder<FX(Args...)>>(
                                        std::forward<std::function<FX(Args...)>>(f)));
    }
    catch (std::invalid_argument& e) {
      std::ostringstream os;
      os << "while defining builtin function '" << rang::fgB::yellow << name << rang::fg::reset << "'\n";
      os << e.what();
      throw UnexpectedError(os.str());
    }
  }

 private:
  void _addBuiltinFunction(const std::string& name,
                           std::shared_ptr<IBuiltinFunctionEmbedder> builtin_function_embedder);

 protected:
  void _addTypeDescriptor(const ASTNodeDataType& type);

  void _addNameValue(const std::string& name, const ASTNodeDataType& type, const DataVariant& data);

  const bool m_is_mandatory;

 public:
  bool
  isMandatory() const final
  {
    return m_is_mandatory;
  }

  const NameBuiltinFunctionMap&
  getNameBuiltinFunctionMap() const final
  {
    return m_name_builtin_function_map;
  }

  const NameTypeMap&
  getNameTypeMap() const final
  {
    return m_name_type_map;
  }

  const NameValueMap&
  getNameValueMap() const final
  {
    return m_name_value_map;
  }

  BuiltinModule(bool is_mandatory = false);

  ~BuiltinModule() = default;
};

#endif   // BUILTIN_MODULE_HPP