#include "lsp/lsp.h"

static bool init_lsp(std::shared_ptr<LSPInstance> lsp) {
  int in_pipe[2];
  int out_pipe[2];
  if (pipe(in_pipe) == -1 || pipe(out_pipe) == -1) {
    perror("pipe");
    return false;
  }
  pid_t pid = fork();
  if (pid == -1) {
    perror("fork");
    return false;
  }
  if (pid == 0) {
    dup2(in_pipe[0], STDIN_FILENO);
    dup2(out_pipe[1], STDOUT_FILENO);
    int devnull = open("/dev/null", O_WRONLY);
    if (devnull >= 0) {
      dup2(devnull, STDERR_FILENO);
      close(devnull);
    }
    close(in_pipe[0]);
    close(in_pipe[1]);
    close(out_pipe[0]);
    close(out_pipe[1]);
    std::vector<char *> argv;
    argv.push_back(const_cast<char *>(lsp->lsp->command.c_str()));
    for (auto &arg : lsp->lsp->args)
      argv.push_back(const_cast<char *>(arg.c_str()));
    argv.push_back(nullptr);
    execvp(lsp->lsp->command.c_str(), argv.data());
    perror("execvp");
    _exit(127);
  }
  lsp->pid = pid;
  lsp->stdin_fd = in_pipe[1];
  lsp->stdout_fd = out_pipe[0];
  close(in_pipe[0]);
  close(out_pipe[1]);
  return true;
}

std::shared_ptr<LSPInstance> get_or_init_lsp(std::string lsp_id) {
  std::unique_lock lock(active_lsps_mtx);
  auto it = active_lsps.find(lsp_id);
  if (it == active_lsps.end()) {
    auto map_it = lsps.find(lsp_id);
    if (map_it == lsps.end())
      return nullptr;
    std::shared_ptr<LSPInstance> lsp = std::make_shared<LSPInstance>();
    lsp->lsp = &map_it->second;
    if (!init_lsp(lsp))
      return nullptr;
    LSPPending *pending = new LSPPending();
    pending->method = "initialize";
    pending->editor = nullptr;
    pending->callback = [lsp, lsp_id](Editor *, std::string, json msg) {
      if (msg.contains("result") && msg["result"].contains("capabilities")) {
        auto &caps = msg["result"]["capabilities"];
        // if (caps.contains("positionEncoding")) {
        //   std::string s = caps["positionEncoding"].get<std::string>();
        //   if (s == "utf-8")
        //     lsp->is_utf8 = true;
        //   log("Lsp name: %s, supports: %s", lsp->lsp->command.c_str(),
        //       s.c_str());
        // }
        if (caps.contains("textDocumentSync")) {
          auto &sync = caps["textDocumentSync"];
          if (sync.is_number()) {
            int change_type = sync.get<int>();
            lsp->incremental_sync = (change_type == 2);
          } else if (sync.is_object() && sync.contains("change")) {
            int change_type = sync["change"].get<int>();
            lsp->incremental_sync = (change_type == 2);
          }
        }
        lsp->allow_formatting = caps.value("documentFormattingProvider", false);
        if (lsp_id != "lua-language-server" /* Lua ls gives terrible ontype
                                               formatting so disable */
            && caps.contains("documentOnTypeFormattingProvider")) {
          auto &fmt = caps["documentOnTypeFormattingProvider"];
          if (fmt.is_object()) {
            if (fmt.contains("firstTriggerCharacter")) {
              std::string s = fmt["firstTriggerCharacter"].get<std::string>();
              if (s.size() == 1)
                lsp->format_chars.push_back(s[0]);
            }
            if (fmt.contains("moreTriggerCharacter")) {
              for (auto &c : fmt["moreTriggerCharacter"]) {
                std::string s = c.get<std::string>();
                if (s.size() == 1)
                  lsp->format_chars.push_back(s[0]);
              }
            }
            lsp->allow_formatting_on_type = true;
          } else if (fmt.is_boolean()) {
            lsp->allow_formatting_on_type = fmt.get<bool>();
          }
        }
        if (caps.contains("hoverProvider")) {
          auto &hover = caps["hoverProvider"];
          lsp->allow_hover =
              hover.is_boolean() ? hover.get<bool>() : hover.is_object();
        } else {
          lsp->allow_hover = false;
        }
        if (caps.contains("completionProvider")) {
          lsp->allow_completion = true;
          if (caps["completionProvider"].contains("resolveProvider"))
            lsp->allow_resolve =
                caps["completionProvider"]["resolveProvider"].get<bool>();
          if (caps["completionProvider"].contains("triggerCharacters")) {
            auto &chars = caps["completionProvider"]["triggerCharacters"];
            if (chars.is_array()) {
              for (auto &c : chars) {
                std::string str = c.get<std::string>();
                if (str.size() != 1)
                  continue;
                lsp->trigger_chars.push_back(str[0]);
              }
            }
          }
          if (caps["completionProvider"].contains("allCommitCharacters")) {
            auto &chars = caps["completionProvider"]["allCommitCharacters"];
            if (chars.is_array()) {
              for (auto &c : chars) {
                std::string str = c.get<std::string>();
                if (str.size() != 1)
                  continue;
                lsp->end_chars.push_back(str[0]);
              }
            }
          }
        }
      }
      lsp->initialized = true;
      json initialized = {{"jsonrpc", "2.0"},
                          {"method", "initialized"},
                          {"params", json::object()}};
      lsp_send(lsp, initialized, nullptr);
    };
    json init_message = {
        {"jsonrpc", "2.0"},
        {"method", "initialize"},
        {"params",
         {{"processId", getpid()},
          {"rootUri", "file://" + percent_encode(path_abs("."))},
          {"capabilities", client_capabilities}}}};
    lsp_send(lsp, init_message, pending);
    active_lsps[lsp_id] = lsp;
    return lsp;
  }
  return it->second;
}

void close_lsp(std::string lsp_id) {
  std::shared_lock active_lsps_lock(active_lsps_mtx);
  auto it = active_lsps.find(lsp_id);
  if (it == active_lsps.end())
    return;
  std::shared_ptr<LSPInstance> lsp = it->second;
  active_lsps_lock.unlock();
  lsp->exited = true;
  lsp->initialized = false;
  LSPPending *shutdown_pending = new LSPPending();
  shutdown_pending->method = "shutdown";
  shutdown_pending->callback = [lsp](Editor *, std::string, json) {
    json exit = {{"jsonrpc", "2.0"}, {"method", "exit"}};
    lsp_send(lsp, exit, nullptr);
  };
  json shutdown = {{"jsonrpc", "2.0"}, {"method", "shutdown"}};
  lsp_send(lsp, shutdown, shutdown_pending);
  std::thread t([lsp, lsp_id] {
    std::this_thread::sleep_for(100ms);
    std::unique_lock active_lsps_lock(active_lsps_mtx);
    std::unique_lock lock(lsp->mtx);
    if (lsp->pid != -1 && kill(lsp->pid, 0) == 0)
      kill(lsp->pid, SIGKILL);
    waitpid(lsp->pid, nullptr, 0);
    close(lsp->stdin_fd);
    close(lsp->stdout_fd);
    for (auto &kv : lsp->pending)
      delete kv.second;
    for (auto &editor : lsp->editors) {
      std::unique_lock editor_lock(editor->lsp_mtx);
      editor->lsp = nullptr;
    }
    active_lsps.erase(lsp_id);
  });
  t.detach();
}

void clean_lsp(std::shared_ptr<LSPInstance> lsp, std::string lsp_id) {
  for (auto &kv : lsp->pending)
    delete kv.second;
  lsp->pid = -1;
  close(lsp->stdin_fd);
  close(lsp->stdout_fd);
  for (auto &editor : lsp->editors) {
    std::unique_lock editor_lock(editor->lsp_mtx);
    editor->lsp = nullptr;
  }
  active_lsps.erase(lsp_id);
}
