#include "inputinjector.h"

#include <spdlog/spdlog.h>

#include <cstring>

void InputInjector::Destroy() {
  // The uinput device must be destroyed before freeing the libevdev device.
  if (uinput_dev_ != nullptr) {
    libevdev_uinput_destroy(uinput_dev_);
  }

  if (dev_ != nullptr) {
    libevdev_free(dev_);
  }
}

void InputInjector::Initialize(const std::unordered_set<uint32_t> key_codes) {
  spdlog::info("Initializing InputInjector");

  dev_ = libevdev_new();
  libevdev_set_name(dev_, "Slither Device");

  spdlog::info("Enabling event type EV_KEY");
  auto err = libevdev_enable_event_type(dev_, EV_KEY);
  if (err == -1) {
    spdlog::error("Failed to enable event type EV_KEY");
    return;
  }

  spdlog::info("Enabling event key codes");
  for (auto key_code : key_codes) {
    spdlog::debug("key_code = {}", key_code);

    if (err = libevdev_enable_event_code(dev_, EV_KEY, key_code, nullptr);
        err == -1) {
      spdlog::error("Failed to enable event key code: {}", key_code);
    }
  }

  spdlog::info("Creating uinput device based on given libevdev device");
  if (err = libevdev_uinput_create_from_device(
        dev_, LIBEVDEV_UINPUT_OPEN_MANAGED, &uinput_dev_);
      err != 0) {
    spdlog::error(
      "Failed to create uinput device; elevated privileges required to access "
      "/dev/uinput (errno: {}, strerror: {})",
      -err, std::strerror(-err));
    return;
  }
}

void InputInjector::Inject(const std::vector<uint32_t>& key_codes) {
  // A time delay does not appear to be necessary in between key presses and
  // key releases.
  Write(true, uinput_dev_, key_codes);
  Write(false, uinput_dev_, key_codes);
}

void InputInjector::Write(bool press, libevdev_uinput* uinput_dev,
                          const std::vector<uint32_t>& key_codes) {
  auto err = int32_t{0};

  if (press) {
    // Simulates key presses.
    spdlog::info("Simulating key presses");
    for (size_t i = 0; i < key_codes.size(); ++i) {
      auto key_code = key_codes[i];
      spdlog::debug("key_code = {}", key_code);
      err = libevdev_uinput_write_event(uinput_dev, EV_KEY, key_code, 1);

      if (err != 0) {
        spdlog::error(
          "Failed to simulate key release for key code: {} (errno: {}, "
          "strerror: "
          "{})",
          key_code, -err, std::strerror(-err));
      }
    }
  } else {
    // Simulates key releases.
    spdlog::info("Simulating key releases");
    for (int32_t i = key_codes.size() - 1; i >= 0; --i) {
      auto key_code = key_codes[i];
      spdlog::debug("key_code = {}", key_code);
      err = libevdev_uinput_write_event(uinput_dev, EV_KEY, key_code, 0);

      if (err != 0) {
        spdlog::error(
          "Failed to simulate key release for key code: {} (errno: {}, "
          "strerror: "
          "{})",
          key_code, -err, std::strerror(-err));
      }
    }
  }

  // Syncs keyboard events.
  err = libevdev_uinput_write_event(uinput_dev, EV_SYN, SYN_REPORT, 0);
  if (err != 0) {
    spdlog::error("Failed to sync key event (errno: {}, strerror: {})", -err,
                  std::strerror(-err));
  }
}

libevdev* InputInjector::dev_{nullptr};

libevdev_uinput* InputInjector::uinput_dev_{nullptr};
