// Copyright (C) 2022-2023 Exaloop Inc. <https://exaloop.io>

#include "jupyter.h"

#include <dirent.h>
#include <fcntl.h>
#include <iostream>
#include <locale>
#include <nlohmann/json.hpp>
#include <unistd.h>
#include <xeus-zmq/xserver_zmq.hpp>
#include <xeus/xhelper.hpp>
#include <xeus/xkernel.hpp>
#include <xeus/xkernel_configuration.hpp>

#include "codon/compiler/compiler.h"
#include "codon/compiler/error.h"
#include "codon/compiler/jit.h"
#include "codon/config/config.h"
#include "codon/parser/common.h"
#include "codon/util/common.h"

using std::move;
using std::string;

namespace nl = nlohmann;
namespace codon {

CodonJupyter::CodonJupyter(const std::string &argv0,
                           const std::vector<std::string> &plugins)
    : argv0(argv0), plugins(plugins) {}

nl::json CodonJupyter::execute_request_impl(int execution_counter, const string &code,
                                            bool silent, bool store_history,
                                            nl::json user_expressions,
                                            bool allow_stdin) {
  LOG("[codon-jupyter] execute_request_impl");
  auto result = jit->execute(code);
  string failed;
  llvm::handleAllErrors(
      result.takeError(),
      [&](const codon::error::ParserErrorInfo &e) {
        std::vector<string> backtrace;
        for (auto &msg : e)
          for (auto &s : msg)
            backtrace.push_back(s.getMessage());
        string err = backtrace[0];
        backtrace.erase(backtrace.begin());
        failed = fmt::format("Compile error: {}\nBacktrace:\n{}", err,
                             ast::join(backtrace, "  \n"));
      },
      [&](const codon::error::RuntimeErrorInfo &e) {
        auto backtrace = e.getBacktrace();
        failed = fmt::format("Runtime error: {}\nBacktrace:\n{}", e.getMessage(),
                             ast::join(backtrace, "  \n"));
      });
  if (failed.empty()) {
    std::string out = *result;
    nl::json pub_data;
    using std::string_literals::operator""s;
    std::string codonMimeMagic = "\x00\x00__codon/mime__\x00"s;
    if (ast::startswith(out, codonMimeMagic)) {
      std::string mime = "";
      int i = codonMimeMagic.size();
      for (; i < out.size() && out[i]; i++)
        mime += out[i];
      if (i < out.size() && !out[i]) {
        i += 1;
      } else {
        mime = "text/plain";
        i = 0;
      }
      pub_data[mime] = out.substr(i);
      LOG("> {}: {}", mime, out.substr(i));
    } else {
      pub_data["text/plain"] = out;
    }
    if (!out.empty())
      publish_execution_result(execution_counter, move(pub_data), nl::json::object());
    return nl::json{{"status", "ok"},
                    {"payload", nl::json::array()},
                    {"user_expressions", nl::json::object()}};
  } else {
    publish_stream("stderr", failed);
    return nl::json{{"status", "error"}};
  }
}

void CodonJupyter::configure_impl() {
  jit = std::make_unique<codon::jit::JIT>(argv0, "jupyter");
  jit->getCompiler()->getLLVMVisitor()->setCapture();

  for (const auto &plugin : plugins) {
    // TODO: error handling on plugin init
    bool failed = false;
    llvm::handleAllErrors(jit->getCompiler()->load(plugin),
                          [&failed](const codon::error::PluginErrorInfo &e) {
                            codon::compilationError(e.getMessage(), /*file=*/"",
                                                    /*line=*/0, /*col=*/0,
                                                    /*terminate=*/false);
                            failed = true;
                          });
  }
  llvm::cantFail(jit->init());
}

nl::json CodonJupyter::complete_request_impl(const string &code, int cursor_pos) {
  LOG("[codon-jupyter] complete_request_impl");
  return nl::json{{"status", "ok"}};
}

nl::json CodonJupyter::inspect_request_impl(const string &code, int cursor_pos,
                                            int detail_level) {
  LOG("[codon-jupyter] inspect_request_impl");
  return nl::json{{"status", "ok"}};
}

nl::json CodonJupyter::is_complete_request_impl(const string &code) {
  LOG("[codon-jupyter] is_complete_request_impl");
  return nl::json{{"status", "complete"}};
}

nl::json CodonJupyter::kernel_info_request_impl() {
  LOG("[codon-jupyter] kernel_info_request_impl");
  return xeus::create_info_reply("", "codon_kernel", CODON_VERSION, "python", "3.7",
                                 "text/x-python", ".codon", "python", "", "",
                                 "Codon Kernel");
}

void CodonJupyter::shutdown_request_impl() {
  LOG("[codon-jupyter] shutdown_request_impl");
}

int startJupyterKernel(const std::string &argv0,
                       const std::vector<std::string> &plugins,
                       const std::string &configPath) {
  xeus::xconfiguration config = xeus::load_configuration(configPath);

  auto context = xeus::make_context<zmq::context_t>();

  LOG("[codon-jupyter] startJupyterKernel");
  auto interpreter = std::make_unique<CodonJupyter>(argv0, plugins);
  xeus::xkernel kernel(config, xeus::get_user_name(), move(context), move(interpreter),
                       xeus::make_xserver_zmq);
  kernel.start();

  return 0;
}

} // namespace codon