/*
 * mouse_events.cc: conky support for mouse events
 *
 * Copyright (C) 2020 Tin Svagelj tin.svagelj@live.com
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 */

#include "mouse-events.h"

#include <array>
#include <cstring>
#include <ctime>
#include <optional>
#include <string>
#include <type_traits>
#include <variant>

extern "C" {
#include <lua.h>

#ifdef BUILD_X11
#include <X11/Xatom.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>

#include <X11/extensions/XI2.h>
#include <X11/extensions/XInput2.h>
#endif /* BUILD_X11 */
}

#include "logging.h"
#include "lua/setting.hh"

namespace conky {

#ifdef BUILD_MOUSE_EVENTS
/* Lua helper functions */
void push_table_value(lua_State *L, std::string key, std::string value) {
  lua_pushstring(L, key.c_str());
  lua_pushstring(L, value.c_str());
  lua_settable(L, -3);
}

template <typename T>
typename std::enable_if<std::is_integral<T>::value>::type push_table_value(
    lua_State *L, std::string key, T value) {
  lua_pushstring(L, key.c_str());
  lua_pushinteger(L, value);
  lua_settable(L, -3);
}

template <typename T>
typename std::enable_if<std::is_floating_point<T>::value>::type
push_table_value(lua_State *L, std::string key, T value) {
  lua_pushstring(L, key.c_str());
  lua_pushnumber(L, value);
  lua_settable(L, -3);
}

void push_table_value(lua_State *L, std::string key, bool value) {
  lua_pushstring(L, key.c_str());
  lua_pushboolean(L, value);
  lua_settable(L, -3);
}

template <std::size_t N>
void push_bitset(lua_State *L, std::bitset<N> it,
                 std::array<std::string, N> labels) {
  lua_newtable(L);
  for (std::size_t i = 0; i < N; i++)
    push_table_value(L, labels[i], it.test(i));
}

const std::array<std::string, 6> mod_names = {{
    "shift",
    "control",
    "alt",
    "super",
    "caps_lock",
    "num_lock",
}};

void push_mods(lua_State *L, modifier_state_t mods) {
  lua_pushstring(L, "mods");
  push_bitset(L, mods, mod_names);
  lua_settable(L, -3);
}

// Returns ms since Epoch.
inline std::size_t current_time_ms() {
  struct timespec spec;
  clock_gettime(CLOCK_REALTIME, &spec);
  return static_cast<std::size_t>(static_cast<std::uint64_t>(spec.tv_sec) *
                                      1'000 +
                                  spec.tv_nsec / 1'000'000);
}

void push_table_value(lua_State *L, std::string key, mouse_event_t type) {
  lua_pushstring(L, key.c_str());
  switch (type) {
    case mouse_event_t::PRESS:
      lua_pushstring(L, "button_down");
      break;
    case mouse_event_t::RELEASE:
      lua_pushstring(L, "button_up");
      break;
    case mouse_event_t::SCROLL:
      lua_pushstring(L, "mouse_scroll");
      break;
    case mouse_event_t::MOVE:
      lua_pushstring(L, "mouse_move");
      break;
    case mouse_event_t::AREA_ENTER:
      lua_pushstring(L, "mouse_enter");
      break;
    case mouse_event_t::AREA_LEAVE:
      lua_pushstring(L, "mouse_leave");
      break;
    default:
      lua_pushnil(L);
      break;
  }
  lua_settable(L, -3);
}

void push_table_value(lua_State *L, std::string key,
                      scroll_direction_t direction) {
  lua_pushstring(L, key.c_str());
  switch (direction) {
    case scroll_direction_t::DOWN:
      lua_pushstring(L, "down");
      break;
    case scroll_direction_t::UP:
      lua_pushstring(L, "up");
      break;
    case scroll_direction_t::LEFT:
      lua_pushstring(L, "left");
      break;
    case scroll_direction_t::RIGHT:
      lua_pushstring(L, "right");
      break;
    default:
      lua_pushnil(L);
      break;
  }
  lua_settable(L, -3);
}

void push_table_value(lua_State *L, std::string key, mouse_button_t button) {
  lua_pushstring(L, key.c_str());
  switch (button) {
    case mouse_button_t::LEFT:
      lua_pushstring(L, "left");
      break;
    case mouse_button_t::RIGHT:
      lua_pushstring(L, "right");
      break;
    case mouse_button_t::MIDDLE:
      lua_pushstring(L, "middle");
      break;
    case mouse_button_t::BACK:
      lua_pushstring(L, "back");
      break;
    case mouse_button_t::FORWARD:
      lua_pushstring(L, "forward");
      break;
    default:
      lua_pushnil(L);
      break;
  }
  lua_settable(L, -3);
}

/* Class methods */
mouse_event::mouse_event(mouse_event_t type)
    : type(type), time(current_time_ms()) {};

void mouse_event::push_lua_table(lua_State *L) const {
  lua_newtable(L);
  push_table_value(L, "type", this->type);
  push_table_value(L, "time", this->time);
  push_lua_data(L);
}

void mouse_positioned_event::push_lua_data(lua_State *L) const {
  push_table_value(L, "x", this->pos.x());
  push_table_value(L, "y", this->pos.y());
  push_table_value(L, "x_abs", this->pos_absolute.x());
  push_table_value(L, "y_abs", this->pos_absolute.y());
}

void mouse_move_event::push_lua_data(lua_State *L) const {
  mouse_positioned_event::push_lua_data(L);
  push_mods(L, this->mods);
}

void mouse_scroll_event::push_lua_data(lua_State *L) const {
  mouse_positioned_event::push_lua_data(L);
  push_table_value(L, "direction", this->direction);
  push_mods(L, this->mods);
}

void mouse_button_event::push_lua_data(lua_State *L) const {
  mouse_positioned_event::push_lua_data(L);
  push_table_value(L, "button_code", static_cast<std::uint32_t>(this->button));
  push_table_value(L, "button", this->button);
  push_mods(L, this->mods);
}
#endif /* BUILD_MOUSE_EVENTS */

#ifdef BUILD_X11
/// Last global device id.
size_t last_device_id = 0;

static std::map<size_t, device_info> device_info_cache{};
static std::map<xi_device_id, size_t> xi_id_mapping{};

device_info *device_info::from_xi_id(xi_device_id device_id, Display *display) {
  if (xi_id_mapping.count(device_id) > 0) {
    return &device_info_cache[xi_id_mapping[device_id]];
  }
  if (display == nullptr) return nullptr;

  int num_devices;
  XIDeviceInfo *device = XIQueryDevice(display, device_id, &num_devices);
  if (num_devices == 0) return nullptr;

  int master;

  if (device->use == XIMasterPointer) {
    master = device->deviceid;
  } else {
    master = device->attachment;
  }

  device_info info = device_info{device_id, master, std::string(device->name)};

  size_t id = last_device_id++;
  info.init_xi_device(display, device);
  XIFreeDeviceInfo(device);

  device_info_cache[id] = info;
  xi_id_mapping[device_id] = id;

  return &device_info_cache[id];
}

void handle_xi_device_change(const XIHierarchyEvent *event) {
  if ((event->flags & XISlaveRemoved) != 0) {
    for (int i = 0; i < event->num_info; i++) {
      auto info = event->info[i];
      if ((info.flags & XISlaveRemoved) == 0) continue;
      if (xi_id_mapping.count(info.deviceid) == 0) continue;

      size_t id = xi_id_mapping[info.deviceid];
      xi_id_mapping.erase(info.deviceid);
      device_info_cache.erase(id);
    }
  }

  if ((event->flags & XISlaveAdded) != 0) {
    for (int i = 0; i < event->num_info; i++) {
      auto info = event->info[i];
      if ((info.flags & XISlaveAdded) == 0) continue;
      if (info.use == IsXPointer || info.use == IsXExtensionPointer)
        conky::device_info::from_xi_id(info.deviceid, event->display);
    }
  }
}

/// Allows override of valuator indices in `xorg.conf` in case they're wrong for
/// some device (unlikely).
size_t fixed_valuator_index(Display *display, XIDeviceInfo *device,
                            valuator_t valuator) {
  const std::array<const char *, VALUATOR_COUNT> atom_names = {
      "ConkyValuatorMoveX", "ConkyValuatorMoveY", "ConkyValuatorScrollX",
      "ConkyValuatorScrollY"};
  Atom override_atom = XInternAtom(display, atom_names[*valuator], False);
  unsigned char *value;
  Atom type_return;
  int format_return;
  unsigned long num_items;
  unsigned long bytes_after;
  do {
    if (XIGetProperty(display, device->deviceid, override_atom, 0, 1, False,
                      XA_INTEGER, &type_return, &format_return, &num_items,
                      &bytes_after,
                      reinterpret_cast<unsigned char **>(&value)) == 0) {
      if (num_items == 0) break;
      if (type_return != XA_INTEGER) {
        LOG_WARNING(
            "invalid '{}' option value, expected a single integer; value will "
            "be ignored",
            atom_names[*valuator]);
        XFree(value);
        break;
      }
      uint32_t result = *reinterpret_cast<uint32_t *>(value);
      XFree(value);
      return static_cast<size_t>(result);
    }
  } while (true);
  return *valuator;
}

/// Allows override of valuator value type via X device properties in case
/// they're wrong for some device (happens with VMs and some devices/setups).
///
/// Scroll valuators always accumulate regardless of XIValuatorClassInfo::mode,
/// so they default to absolute.
/// See: https://www.x.org/releases/X11R7.7/doc/inputproto/XI2proto.txt
/// (section "XIScrollClass")
bool fixed_valuator_relative(Display *display, XIDeviceInfo *device,
                             valuator_t valuator,
                             XIValuatorClassInfo *class_info, bool is_scroll) {
  const std::array<const char *, 2> atom_names = {
      "ConkyValuatorMoveMode",
      "ConkyValuatorScrollMode",
  };

  Atom override_atom = XInternAtom(display, atom_names[*valuator >> 1], False);
  unsigned char *value_return;
  Atom type_return;
  int format_return;
  unsigned long num_items;
  unsigned long bytes_after;

  do {
    if (XIGetProperty(display, device->deviceid, override_atom, 0, 9, False,
                      XA_ATOM, &type_return, &format_return, &num_items,
                      &bytes_after,
                      reinterpret_cast<unsigned char **>(&value_return)) == 0) {
      if (num_items == 0) break;
      if (type_return != XA_ATOM) {
        LOG_WARNING(
            "invalid '{}' option value, expected an atom (string); value will "
            "be ignored",
            atom_names[*valuator >> 1]);
        XFree(value_return);
        break;
      }
      Atom return_atom = *reinterpret_cast<Atom *>(value_return);
      XFree(value_return);
      char *value = XGetAtomName(display, return_atom);

      // lowercase value
      for (auto c = value; *c; ++c) *c = tolower(*c);

      bool relative = false;
      if (strcmp(reinterpret_cast<char *>(value), "relative") == 0) {
        relative = true;
      } else if (strcmp(reinterpret_cast<char *>(value), "absolute") != 0) {
        LOG_WARNING(
            "unknown '{}' option value: '{}', expected 'absolute' or "
            "'relative'; value will be ignored",
            atom_names[*valuator >> 1], value);
        XFree(value);
        break;
      }
      XFree(value);
      return relative;
    }
  } while (true);

  // Scroll valuators accumulate; XIValuatorClassInfo::mode is unreliable
  // for them (libinput reports XIModeRelative despite absolute values).
  // This is because X11 was designed from ground up to make relative sense.
  if (is_scroll) return false;

  return class_info->mode == XIModeRelative;
}

void device_info::init_xi_device(
    Display *display, std::variant<xi_device_id, XIDeviceInfo *> source) {
  XIDeviceInfo *device = nullptr;
  if (std::holds_alternative<XIDeviceInfo *>(source)) {
    device = std::get<XIDeviceInfo *>(source);
  } else if (std::holds_alternative<xi_device_id>(source)) {
    int num_devices;
    device =
        XIQueryDevice(display, std::get<xi_device_id>(source), &num_devices);
    if (num_devices == 0) return;
  }
  if (device == nullptr) return;

  std::array<size_t, VALUATOR_COUNT> valuator_indices;
  for (size_t i = 0; i < VALUATOR_COUNT; i++) {
    valuator_indices[i] =
        fixed_valuator_index(display, device, static_cast<valuator_t>(i));
  }

  // XIScrollClass is the authoritative source for scroll valuator mapping.
  // Per the spec, each XIScrollClassInfo::number corresponds to an
  // XIValuatorClassInfo with the same number. Don't guess from ordinals.
  // Devices without XIScrollClass entries have no scroll valuators; scroll
  // is handled via button 4-7 events instead.
  std::map<size_t, double> scroll_increments;
  // Invalidate ordinal defaults; keep user overrides from fixed_valuator_index.
  for (auto sv : {valuator_t::SCROLL_X, valuator_t::SCROLL_Y}) {
    if (valuator_indices[*sv] == *sv) { valuator_indices[*sv] = SIZE_MAX; }
  }
  for (int i = 0; i < device->num_classes; i++) {
    if (device->classes[i]->type != XIScrollClass) continue;
    auto *si = reinterpret_cast<XIScrollClassInfo *>(device->classes[i]);
    scroll_increments[si->number] = si->increment;

    valuator_t target = valuator_t::UNKNOWN;
    if (si->scroll_type == XIScrollTypeVertical)
      target = valuator_t::SCROLL_Y;
    else if (si->scroll_type == XIScrollTypeHorizontal)
      target = valuator_t::SCROLL_X;

    LOG_DEBUG(
        "xi scroll class: number={} type={} increment={}", si->number,
        si->scroll_type == XIScrollTypeVertical ? "vertical" : "horizontal",
        si->increment);

    // Only set if not already claimed and doesn't conflict with a move axis.
    // Some devices (e.g. Xephyr) alias scroll and move to the same valuator;
    // movement deltas would be misinterpreted as scroll.
    if (target != valuator_t::UNKNOWN &&
        valuator_indices[*target] == SIZE_MAX &&
        si->number != valuator_indices[*valuator_t::MOVE_X] &&
        si->number != valuator_indices[*valuator_t::MOVE_Y]) {
      valuator_indices[*target] = si->number;
    }
  }

  // class order is undefined!
  for (int i = 0; i < device->num_classes; i++) {
    if (device->classes[i]->type != XIValuatorClass) continue;
    XIValuatorClassInfo *class_info = (XIValuatorClassInfo *)device->classes[i];

    // check if one of used (mapped) valuators
    valuator_t valuator = valuator_t::UNKNOWN;
    for (size_t i = 0; i < VALUATOR_COUNT; i++) {
      if (valuator_indices[i] == class_info->number) {
        valuator = static_cast<valuator_t>(i);
        break;
      }
    }
    if (valuator == valuator_t::UNKNOWN) { continue; }

    // Scroll valuators with XIScrollClassInfo always accumulate — scroll
    // is derived from value deltas divided by increment.
    // See: https://www.x.org/releases/X11R7.7/doc/inputproto/XI2proto.txt
    // (section "XIScrollClass")
    // Only consult fixed_valuator_relative for non-scroll axes.
    auto it = scroll_increments.find(class_info->number);
    bool is_scroll = it != scroll_increments.end();

    auto info = conky_valuator_info{
        .index = static_cast<size_t>(class_info->number),
        .min = class_info->min,
        .max = class_info->max,
        .value = class_info->value,
        .relative = fixed_valuator_relative(display, device, valuator,
                                            class_info, is_scroll),
    };

    if (is_scroll) { info.increment = it->second; }

    this->valuators[*valuator] = info;
  }

  LOG_DEBUG(
      "xi device '{}' valuators: move_x={} move_y={} scroll_x={} "
      "scroll_y={} scroll_y_increment={}",
      device->name, this->valuators[*valuator_t::MOVE_X].index,
      this->valuators[*valuator_t::MOVE_Y].index,
      this->valuators[*valuator_t::SCROLL_X].index,
      this->valuators[*valuator_t::SCROLL_Y].index,
      this->valuators[*valuator_t::SCROLL_Y].increment);

  if (std::holds_alternative<xi_device_id>(source)) {
    XIFreeDeviceInfo(device);
  }
}
conky_valuator_info &device_info::valuator(valuator_t valuator) {
  return this->valuators[*valuator];
}

bool xi_event_base::read_from_cookie(Display *display, const void *data) {
  const XIEvent *source = reinterpret_cast<const XIEvent *>(data);
  if (source->type != GenericEvent) { return false; }
  this->evtype = static_cast<xi_event_type>(source->evtype);
  this->serial = source->serial;
  this->send_event = source->send_event;
  this->display = source->display;
  this->extension = source->extension;
  this->time = source->time;
  return true;
}

bool xi_pointer_event::read_from_cookie(Display *display, const void *data) {
  if (!xi_event_base::read_from_cookie(display, data)) { return false; }

  // XInput event hierarchy is less granular than ours. `handler` copies common
  // data so we don't have to individually copy all duplicate fields
  // all `xi_pointer_event` based events provide.
  auto handler = [&](auto *source) {
    auto device = device_info::from_xi_id(source->deviceid, source->display);
    if (device == nullptr) return false;  // shouldn't happen

    this->device = device;
    this->sourceid = source->sourceid;
    this->detail = source->detail;
    this->root = source->root;
    this->event = source->event;
    this->child = source->child;
    this->pos_absolute = vec2d{source->root_x, source->root_y};
    this->pos = vec2d{source->event_x, source->event_y};
    return true;
  };

  // Technically, we could reinterpret_cast data into any single struct, it
  // just feels less error prone to pick a single one. API won't change, but
  // it feels icky to write code that completely ignores upstream semantics
  // when having 4 if blocks is all that's needed to future proof.
  if (evtype == XI_ButtonPress || evtype == XI_ButtonRelease ||
      evtype == XI_Motion) {
    const XIDeviceEvent *source = reinterpret_cast<const XIDeviceEvent *>(data);
    return handler(source);
  }
  if (evtype == XI_Enter || evtype == XI_Leave || evtype == XI_FocusIn ||
      evtype == XI_FocusOut) {
    const XIEnterEvent *source = reinterpret_cast<const XIEnterEvent *>(data);
    return handler(source);
  }
  if (evtype == XI_GesturePinchBegin || evtype == XI_GesturePinchUpdate ||
      evtype == XI_GesturePinchEnd) {
    const XIGesturePinchEvent *source =
        reinterpret_cast<const XIGesturePinchEvent *>(data);
    return handler(source);
  }
  if (evtype == XI_GestureSwipeBegin || evtype == XI_GestureSwipeUpdate ||
      evtype == XI_GestureSwipeEnd) {
    const XIGestureSwipeEvent *source =
        reinterpret_cast<const XIGestureSwipeEvent *>(data);
    return handler(source);
  }
  return false;
}

bool xi_pointer_interact_event::read_from_cookie(Display *display,
                                                 const void *data) {
  if (!xi_pointer_event::read_from_cookie(display, data)) { return false; }
  if (!(evtype == XI_ButtonPress || evtype == XI_ButtonRelease ||
        evtype == XI_Motion)) {
    return false;
  }

  const XIDeviceEvent *source = reinterpret_cast<const XIDeviceEvent *>(data);

  std::bitset<32> buttons;
  for (size_t bi = 1; bi < source->buttons.mask_len * 8; bi++) {
    if (XIMaskIsSet(source->buttons.mask, bi)) buttons[bi] = true;
  }

  std::map<size_t, double> valuators{};
  double *values = source->valuators.values;
  for (size_t vi = 0; vi < source->valuators.mask_len * 8; vi++) {
    if (XIMaskIsSet(source->valuators.mask, vi)) valuators[vi] = *values++;
  }

  this->flags = source->flags;
  this->buttons = buttons;
  this->valuators = valuators;
  this->mods = source->mods;
  this->group = source->group;

  for (size_t v = 0; v < VALUATOR_COUNT; v++) {
    valuator_t valuator = static_cast<valuator_t>(v);
    auto &valuator_info = device->valuator(valuator);

    if (this->valuators.count(valuator_info.index) == 0) { continue; }
    auto current = this->valuators[valuator_info.index];

    if (valuator_info.relative) {
      this->valuators_relative[v] = current;
    } else {
      // Source int width is driver-dependent; wraparound is theoretically
      // possible but unrealistic with 32-bit values.
      this->valuators_relative[v] = current - valuator_info.value;
    }
    LOG_TRACE_WITH(
        ({"index", valuator_info.index}, {"current", current},
         {"prev", valuator_info.value}, {"relative", valuator_info.relative}),
        "xi valuator[{}] delta={}", v, this->valuators_relative[v]);
    valuator_info.value = current;
  }

  return true;
}

bool xi_pointer_crossing_event::read_from_cookie(Display *display,
                                                 const void *data) {
  if (!xi_pointer_event::read_from_cookie(display, data)) { return false; }
  if (!(evtype == XI_Enter || evtype == XI_Leave || evtype == XI_FocusIn ||
        evtype == XI_FocusOut)) {
    return false;
  }

  const XIEnterEvent *source = reinterpret_cast<const XIEnterEvent *>(data);

  std::bitset<32> buttons;
  for (size_t bi = 1; bi < source->buttons.mask_len * 8; bi++) {
    if (XIMaskIsSet(source->buttons.mask, bi)) buttons[bi] = true;
  }

  this->mode = source->mode;
  this->focus = source->focus;
  this->same_screen = source->same_screen;
  this->buttons = buttons;
  this->mods = source->mods;
  this->group = source->group;

  return true;
}

bool xi_pointer_interact_event::test_valuator(valuator_t valuator) const {
  return this->valuators.count(this->device->valuator(valuator).index) > 0;
}
conky_valuator_info *xi_pointer_interact_event::valuator_info(
    valuator_t valuator) const {
  return &this->device->valuator(valuator);
}
std::optional<double> xi_pointer_interact_event::valuator_value(
    valuator_t valuator) const {
  auto info = this->valuator_info(valuator);
  if (info == nullptr) return std::nullopt;
  size_t index = info->index;
  if (this->valuators.count(index) == 0) return std::nullopt;
  return this->valuators.at(index);
}

std::optional<double> xi_pointer_interact_event::valuator_relative_value(
    valuator_t valuator) const {
  auto &info = this->device->valuator(valuator);
  if (this->valuators.count(info.index) == 0) return std::nullopt;
  return this->valuators_relative.at(*valuator);
}

std::vector<std::tuple<int, XEvent *>> xi_pointer_move::generate_events(
    Window target, Window child, conky::vec2d target_pos) const {
  std::vector<std::tuple<int, XEvent *>> result{};

  auto device_info = this->device;

  bool is_move = this->test_valuator(valuator_t::MOVE_X) ||
                 this->test_valuator(valuator_t::MOVE_Y);
  bool is_scroll = this->test_valuator(valuator_t::SCROLL_X) ||
                   this->test_valuator(valuator_t::SCROLL_Y);

  if (is_move) {
    XEvent *produced = new XEvent;
    std::memset(produced, 0, sizeof(XEvent));

    XMotionEvent *e = &produced->xmotion;
    e->type = MotionNotify;
    e->display = this->display;
    e->root = this->root;
    e->window = target;
    e->subwindow = child;
    e->time = CurrentTime;
    e->x = static_cast<int>(target_pos.x());
    e->y = static_cast<int>(target_pos.y());
    e->x_root = static_cast<int>(this->pos_absolute.x());
    e->y_root = static_cast<int>(this->pos_absolute.y());
    e->state = this->mods.effective;
    e->is_hint = NotifyNormal;
    e->same_screen = True;
    result.emplace_back(std::make_tuple(PointerMotionMask, produced));
  }
  if (is_scroll) {
    uint scroll_direction = 0;
    auto vertical = this->valuator_relative_value(valuator_t::SCROLL_Y);
    double vertical_value = vertical.value_or(0.0);

    if (vertical_value != 0.0) {
      double increment = this->device->valuator(valuator_t::SCROLL_Y).increment;
      scroll_direction = (vertical_value * increment) < 0.0 ? Button4 : Button5;
    } else {
      auto horizontal = this->valuator_relative_value(valuator_t::SCROLL_X);
      double horizontal_value = horizontal.value_or(0.0);
      if (horizontal_value != 0.0) {
        double increment =
            this->device->valuator(valuator_t::SCROLL_X).increment;
        scroll_direction = (horizontal_value * increment) < 0.0 ? 6 : 7;
      }
    }

    if (scroll_direction != 0) {
      XEvent *produced = new XEvent;
      std::memset(produced, 0, sizeof(XEvent));

      XButtonEvent *e = &produced->xbutton;
      e->display = display;
      e->root = this->root;
      e->window = target;
      e->subwindow = child;
      e->time = CurrentTime;
      e->x = static_cast<int>(target_pos.x());
      e->y = static_cast<int>(target_pos.y());
      e->x_root = static_cast<int>(this->pos_absolute.x());
      e->y_root = static_cast<int>(this->pos_absolute.y());
      e->state = this->mods.effective;
      e->button = scroll_direction;
      e->same_screen = True;

      XEvent *press = new XEvent;
      e->type = ButtonPress;
      std::memcpy(press, produced, sizeof(XEvent));
      result.emplace_back(std::make_tuple(ButtonPressMask, press));

      e->type = ButtonRelease;
      result.emplace_back(std::make_tuple(ButtonReleaseMask, produced));
    }
  }

  return result;
}

std::vector<std::tuple<int, XEvent *>> press_release_events(
    const xi_pointer_interact_event *self, Window target, Window child,
    conky::vec2d target_pos) {
  std::vector<std::tuple<int, XEvent *>> result{};

  XEvent *produced = new XEvent;
  std::memset(produced, 0, sizeof(XEvent));

  XButtonEvent *e = &produced->xbutton;
  e->display = self->display;
  e->root = self->root;
  e->window = target;
  e->subwindow = child;
  e->time = CurrentTime;
  e->x = static_cast<int>(target_pos.x());
  e->y = static_cast<int>(target_pos.y());
  e->x_root = static_cast<int>(self->pos_absolute.x());
  e->y_root = static_cast<int>(self->pos_absolute.y());
  e->state = self->mods.effective;
  e->button = self->detail;
  e->same_screen = True;

  long event_mask = NoEventMask;
  switch (self->evtype) {
    case XI_ButtonPress:
      e->type = ButtonPress;
      event_mask = ButtonPressMask;
      break;
    case XI_ButtonRelease:
      e->type = ButtonRelease;
      event_mask = ButtonReleaseMask;
      switch (self->detail) {
        case 1:
          event_mask |= Button1MotionMask;
          break;
        case 2:
          event_mask |= Button2MotionMask;
          break;
        case 3:
          event_mask |= Button3MotionMask;
          break;
        case 4:
          event_mask |= Button4MotionMask;
          break;
        case 5:
          event_mask |= Button5MotionMask;
          break;
      }
      break;
  }

  result.emplace_back(std::make_tuple(event_mask, produced));

  return result;
}
std::vector<std::tuple<int, XEvent *>> xi_pointer_press::generate_events(
    Window target, Window child, conky::vec2d target_pos) const {
  return press_release_events(
      &static_cast<const xi_pointer_interact_event &>(*this), target, child,
      target_pos);
}
std::vector<std::tuple<int, XEvent *>> xi_pointer_release::generate_events(
    Window target, Window child, conky::vec2d target_pos) const {
  return press_release_events(
      &static_cast<const xi_pointer_interact_event &>(*this), target, child,
      target_pos);
}

std::vector<std::tuple<int, XEvent *>> crossing_events(
    const xi_pointer_crossing_event *self, int type, long event_mask,
    Window target, Window child, conky::vec2d target_pos) {
  std::vector<std::tuple<int, XEvent *>> result{};

  XEvent *produced = new XEvent;
  std::memset(produced, 0, sizeof(XEvent));

  XCrossingEvent *e = &produced->xcrossing;
  e->type = type;
  e->display = self->display;
  e->root = self->root;
  e->window = target;
  e->subwindow = child;
  e->time = CurrentTime;
  e->x = static_cast<int>(target_pos.x());
  e->y = static_cast<int>(target_pos.y());
  e->x_root = static_cast<int>(self->pos_absolute.x());
  e->y_root = static_cast<int>(self->pos_absolute.y());
  e->mode = self->mode;
  e->detail = self->detail;
  e->same_screen = self->same_screen;
  e->focus = self->focus;
  e->state = self->mods.effective;

  result.emplace_back(std::make_tuple(event_mask, produced));

  return result;
}
std::vector<std::tuple<int, XEvent *>> xi_pointer_enter::generate_events(
    Window target, Window child, conky::vec2d target_pos) const {
  return crossing_events(this, EnterNotify, EnterWindowMask, target, child,
                         target_pos);
}
std::vector<std::tuple<int, XEvent *>> xi_pointer_leave::generate_events(
    Window target, Window child, conky::vec2d target_pos) const {
  return crossing_events(this, LeaveNotify, LeaveWindowMask, target, child,
                         target_pos);
}

#endif /* BUILD_X11 */

#ifdef BUILD_MOUSE_EVENTS
simple_config_setting<std::string> lua_mouse_hook("lua_mouse_hook",
                                                  std::string(), true);
#endif

}  // namespace conky
