#include <language/modules/CoreModule.hpp>

#include <language/modules/CoreModule.hpp>
#include <language/modules/ModuleRepository.hpp>
#include <language/utils/ASTExecutionInfo.hpp>
#include <language/utils/AffectationProcessorBuilder.hpp>
#include <language/utils/AffectationRegisterForB.hpp>
#include <language/utils/AffectationRegisterForN.hpp>
#include <language/utils/AffectationRegisterForR.hpp>
#include <language/utils/AffectationRegisterForRn.hpp>
#include <language/utils/AffectationRegisterForRnxn.hpp>
#include <language/utils/AffectationRegisterForString.hpp>
#include <language/utils/AffectationRegisterForZ.hpp>
#include <language/utils/BinaryOperatorRegisterForB.hpp>
#include <language/utils/BinaryOperatorRegisterForN.hpp>
#include <language/utils/BinaryOperatorRegisterForR.hpp>
#include <language/utils/BinaryOperatorRegisterForRn.hpp>
#include <language/utils/BinaryOperatorRegisterForRnxn.hpp>
#include <language/utils/BinaryOperatorRegisterForString.hpp>
#include <language/utils/BinaryOperatorRegisterForZ.hpp>
#include <language/utils/BuiltinFunctionEmbedder.hpp>
#include <language/utils/Exit.hpp>
#include <language/utils/IncDecOperatorRegisterForN.hpp>
#include <language/utils/IncDecOperatorRegisterForZ.hpp>
#include <language/utils/OFStream.hpp>
#include <language/utils/OStream.hpp>
#include <language/utils/UnaryOperatorRegisterForB.hpp>
#include <language/utils/UnaryOperatorRegisterForN.hpp>
#include <language/utils/UnaryOperatorRegisterForR.hpp>
#include <language/utils/UnaryOperatorRegisterForRn.hpp>
#include <language/utils/UnaryOperatorRegisterForRnxn.hpp>
#include <language/utils/UnaryOperatorRegisterForZ.hpp>
#include <utils/Messenger.hpp>
#include <utils/PugsUtils.hpp>
#include <utils/RandomEngine.hpp>

#include <random>

CoreModule::CoreModule() : BuiltinModule(true)
{
  this->_addBuiltinFunction("getPugsVersion", std::function(

                                                []() -> std::string { return pugsVersion(); }

                                                ));

  this->_addBuiltinFunction("getPugsBuildInfo", std::function(

                                                  []() -> std::string { return pugsBuildInfo(); }

                                                  ));

  this->_addBuiltinFunction("getAvailableModules", std::function(

                                                     []() -> std::string {
                                                       const ModuleRepository& repository =
                                                         ASTExecutionInfo::current().moduleRepository();

                                                       return repository.getAvailableModules();
                                                     }

                                                     ));

  this->_addBuiltinFunction("getModuleInfo", std::function(

                                               [](const std::string& module_name) -> std::string {
                                                 const ModuleRepository& repository =
                                                   ASTExecutionInfo::current().moduleRepository();

                                                 return repository.getModuleInfo(module_name);
                                               }

                                               ));

  this->_addBuiltinFunction("setRandomSeed", std::function(

                                               [](const int64_t& random_seed) -> void {
                                                 RandomEngine::instance().setRandomSeed(random_seed);
                                               }

                                               ));

  this->_addBuiltinFunction("resetRandomSeed", std::function(

                                                 []() -> void { RandomEngine::instance().resetRandomSeed(); }

                                                 ));

  this->_addTypeDescriptor(ast_node_data_type_from<std::shared_ptr<const OStream>>);

  this->_addBuiltinFunction("ofstream", std::function(

                                          [](const std::string& filename) -> std::shared_ptr<const OStream> {
                                            return std::make_shared<const OFStream>(filename);
                                          }

                                          ));

  this->_addBuiltinFunction("exit", std::function(

                                      [](const int64_t& exit_code) -> void {
                                        const auto& location = ASTBacktrace::getInstance().sourceLocation();
                                        std::cout << "\n** " << rang::fgB::yellow << "exit" << rang::fg::reset
                                                  << " explicitly called with code " << rang::fgB::cyan << exit_code
                                                  << rang::fg::reset << "\n   from " << rang::style::underline
                                                  << location.filename() << rang::style::reset << ':'
                                                  << rang::fgB::yellow << location.line() << rang::fg::reset << '\n';

                                        throw language::Exit(exit_code);
                                      }

                                      ));

  this->_addNameValue("cout", ast_node_data_type_from<std::shared_ptr<const OStream>>,
                      EmbeddedData{std::make_shared<DataHandler<const OStream>>(std::make_shared<OStream>(std::cout))});

  this->_addNameValue("cerr", ast_node_data_type_from<std::shared_ptr<const OStream>>,
                      EmbeddedData{std::make_shared<DataHandler<const OStream>>(std::make_shared<OStream>(std::cerr))});

  this->_addNameValue("clog", ast_node_data_type_from<std::shared_ptr<const OStream>>,
                      EmbeddedData{std::make_shared<DataHandler<const OStream>>(std::make_shared<OStream>(std::clog))});
}

void
CoreModule::registerOperators() const
{
  AffectationRegisterForB{};
  AffectationRegisterForN{};
  AffectationRegisterForZ{};
  AffectationRegisterForR{};
  AffectationRegisterForRn<1>{};
  AffectationRegisterForRn<2>{};
  AffectationRegisterForRn<3>{};
  AffectationRegisterForRnxn<1>{};
  AffectationRegisterForRnxn<2>{};
  AffectationRegisterForRnxn<3>{};
  AffectationRegisterForString{};

  BinaryOperatorRegisterForB{};
  BinaryOperatorRegisterForN{};
  BinaryOperatorRegisterForZ{};
  BinaryOperatorRegisterForR{};
  BinaryOperatorRegisterForRn<1>{};
  BinaryOperatorRegisterForRn<2>{};
  BinaryOperatorRegisterForRn<3>{};
  BinaryOperatorRegisterForRnxn<1>{};
  BinaryOperatorRegisterForRnxn<2>{};
  BinaryOperatorRegisterForRnxn<3>{};
  BinaryOperatorRegisterForString{};

  IncDecOperatorRegisterForN{};
  IncDecOperatorRegisterForZ{};

  UnaryOperatorRegisterForB{};
  UnaryOperatorRegisterForN{};
  UnaryOperatorRegisterForZ{};
  UnaryOperatorRegisterForR{};
  UnaryOperatorRegisterForRn<1>{};
  UnaryOperatorRegisterForRn<2>{};
  UnaryOperatorRegisterForRn<3>{};
  UnaryOperatorRegisterForRnxn<1>{};
  UnaryOperatorRegisterForRnxn<2>{};
  UnaryOperatorRegisterForRnxn<3>{};
}