/*
 * mouse_events.h: 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/>.
 *
 */

#ifndef MOUSE_EVENTS_H
#define MOUSE_EVENTS_H

#include <X11/Xlib.h>
#include <bitset>
#include <cstdint>
#include <cstring>
#include <optional>
#include <string>

#include "config.h"

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

#ifdef BUILD_X11
#include <array>
#include <map>
#include <tuple>
#include <variant>
#include <vector>
#endif /* BUILD_X11 */

extern "C" {
#ifdef BUILD_X11
#include <X11/X.h>
#include <X11/extensions/XInput.h>
#include <X11/extensions/XInput2.h>
#undef COUNT  // define from X11/extensions/Xi.h
#endif        /* BUILD_X11 */

#include <lua.h>

#ifdef __linux
#include <linux/input-event-codes.h>
#elif __FreeBSD__
#include <dev/evdev/input-event-codes.h>
#elif __DragonFly__
#include <dev/misc/evdev/input-event-codes.h>
#else /* platform */
// Probably incorrect for some platforms, feel free to add your platform to the
// above list if it has other event codes or a standard file containing them.

// Left mouse button event code
#define BTN_LEFT 0x110
// Right mouse button event code
#define BTN_RIGHT 0x111
// Middle mouse button event code
#define BTN_MIDDLE 0x112

// Back mouse button event code
#define BTN_BACK 0x116
// Forward mouse button event code
#define BTN_FORWARD 0x115
#endif /* platform */
}

namespace conky {

#ifdef BUILD_MOUSE_EVENTS
enum class mouse_event_t : uint32_t {
  PRESS = 0,
  RELEASE = 1,
  SCROLL = 2,
  MOVE = 3,
  AREA_ENTER = 4,
  AREA_LEAVE = 5,
};
const size_t MOUSE_EVENT_COUNT =
    static_cast<size_t>(mouse_event_t::AREA_LEAVE) + 1;
constexpr uint32_t operator*(mouse_event_t index) {
  return static_cast<uint32_t>(index);
}

enum class mouse_button_t : uint32_t {
  LEFT = BTN_LEFT,
  RIGHT = BTN_RIGHT,
  MIDDLE = BTN_MIDDLE,
  BACK = BTN_BACK,
  FORWARD = BTN_FORWARD,
};
constexpr uint32_t operator*(mouse_button_t index) {
  return static_cast<uint32_t>(index);
}

#ifdef BUILD_X11
inline std::optional<mouse_button_t> x11_mouse_button_code(
    unsigned int x11_mouse_button) {
  switch (x11_mouse_button) {
    case Button1:
      return mouse_button_t::LEFT;
    case Button2:
      return mouse_button_t::MIDDLE;
    case Button3:
      return mouse_button_t::RIGHT;
    case 8:
      return mouse_button_t::BACK;
    case 9:
      return mouse_button_t::FORWARD;
    default:
      LOG_TRACE("X11 button {} is not mapped", x11_mouse_button);
      return std::nullopt;
  }
}
#endif /* BUILD_X11 */

struct mouse_event {
  mouse_event_t type;  // type of event
  std::size_t time;    // ms since epoch when the event happened

  explicit mouse_event(mouse_event_t type);

  void push_lua_table(lua_State *L) const;

  virtual void push_lua_data(lua_State *L) const = 0;
};

struct mouse_positioned_event : public mouse_event {
  /// @brief Position relative to event window
  vec2<size_t> pos;
  /// @brief Position relative to desktop/root window
  vec2<size_t> pos_absolute;

  mouse_positioned_event(mouse_event_t type, vec2<size_t> pos,
                         vec2<size_t> pos_absolute)
      : mouse_event(type), pos(pos), pos_absolute(pos_absolute) {};

  void push_lua_data(lua_State *L) const;
};

typedef std::bitset<6> modifier_state_t;
enum class modifier_key : uint32_t {
  SHIFT = 0,
  CONTROL = 1,
  ALT = 2,
  // Windows/MacOS key on most keyboards
  SUPER = 3,
  CAPS_LOCK = 4,
  NUM_LOCK = 5,
};
constexpr uint32_t operator*(modifier_key index) {
  return static_cast<uint32_t>(index);
}
std::string modifier_name(modifier_key key);

#ifdef BUILD_X11
inline modifier_state_t x11_modifier_state(unsigned int mods) {
  modifier_state_t result;
  result[*modifier_key::SHIFT] = mods & ShiftMask;
  result[*modifier_key::CONTROL] = mods & ControlMask;
  result[*modifier_key::ALT] = mods & Mod1Mask;
  result[*modifier_key::SUPER] = mods & Mod4Mask;
  result[*modifier_key::CAPS_LOCK] = mods & LockMask;
  result[*modifier_key::NUM_LOCK] = mods & Mod2Mask;
  return result;
}
#endif /* BUILD_X11 */

struct mouse_move_event : public mouse_positioned_event {
  modifier_state_t mods;  // held buttons and modifiers (ctrl, shift, ...)

  mouse_move_event(vec2<size_t> pos, vec2<size_t> pos_absolute,
                   modifier_state_t mods = 0)
      : mouse_positioned_event{mouse_event_t::MOVE, pos, pos_absolute},
        mods(mods) {};

  void push_lua_data(lua_State *L) const;
};

enum class scroll_direction_t : uint8_t {
  UNKNOWN = 0,
  UP,
  DOWN,
  LEFT,
  RIGHT,
};
constexpr uint8_t operator*(scroll_direction_t index) {
  return static_cast<uint8_t>(index);
}

#ifdef BUILD_X11
inline scroll_direction_t x11_scroll_direction(unsigned int x11_mouse_button) {
  scroll_direction_t direction = scroll_direction_t::UNKNOWN;
  switch (x11_mouse_button) {
    case Button4:
      direction = scroll_direction_t::UP;
      break;
    case Button5:
      direction = scroll_direction_t::DOWN;
      break;
    case 6:
      direction = scroll_direction_t::LEFT;
      break;
    case 7:
      direction = scroll_direction_t::RIGHT;
      break;
  }
  return direction;
}
#endif /* BUILD_X11 */

struct mouse_scroll_event : public mouse_positioned_event {
  modifier_state_t mods;  // held buttons and modifiers (ctrl, shift, ...)
  scroll_direction_t direction;

  mouse_scroll_event(vec2<size_t> pos, vec2<size_t> pos_absolute,
                     scroll_direction_t direction, modifier_state_t mods = 0)
      : mouse_positioned_event{mouse_event_t::SCROLL, pos, pos_absolute},
        direction(direction),
        mods(mods) {};

  void push_lua_data(lua_State *L) const;
};

struct mouse_button_event : public mouse_positioned_event {
  modifier_state_t mods;  // held buttons and modifiers (ctrl, shift, ...)
  mouse_button_t button;

  mouse_button_event(mouse_event_t type, vec2<size_t> pos,
                     vec2<size_t> pos_absolute, mouse_button_t button,
                     modifier_state_t mods = 0)
      : mouse_positioned_event{type, pos, pos_absolute},
        button(button),
        mods(mods) {};

  void push_lua_data(lua_State *L) const;
};

struct mouse_crossing_event : public mouse_positioned_event {
  mouse_crossing_event(mouse_event_t type, vec2<size_t> pos,
                       vec2<size_t> pos_absolute)
      : mouse_positioned_event{type, pos, pos_absolute} {};
};
#endif /* BUILD_MOUSE_EVENTS */

#ifdef BUILD_X11
using xi_device_id = int;
/// XInput2 event type
///
/// e.g. `XI_KeyPress`
using xi_event_type = int;

enum class valuator_t : size_t { MOVE_X, MOVE_Y, SCROLL_X, SCROLL_Y, UNKNOWN };
const size_t VALUATOR_COUNT = static_cast<size_t>(valuator_t::UNKNOWN);
constexpr uint8_t operator*(valuator_t index) {
  return static_cast<uint8_t>(index);
}

class xi_event_bitflags {
  std::array<unsigned char, (XI_LASTEVENT + 7) / 8> data = {0};

 public:
  constexpr void set(xi_event_type event) {
    data[(event) >> 3] |= (1 << ((event) & 7));
  }
  constexpr void clear(xi_event_type event) {
    data[(event) >> 3] &= ~(1 << ((event) & 7));
  }
  void clear_all() { std::memset(data.data(), 0, data.size()); }
  constexpr bool test(xi_event_type event) const {
    return static_cast<bool>(data[(event) >> 3] & (1 << ((event) & 7)));
  }
  constexpr size_t size() const { return data.size(); }

  XIEventMask mask(xi_device_id device) const {
    return XIEventMask{
        device,
        static_cast<int>(data.size()),
        const_cast<unsigned char *>(data.data()),
    };
  }
};

struct conky_valuator_info {
  size_t index = SIZE_MAX;
  double min = 0.0;
  double max = 0.0;
  double value = 0.0;
  bool relative = false;
  /// Scroll increment from XIScrollClassInfo; sign defines direction
  /// convention. Positive means increasing valuator value = down/right.
  double increment = 1.0;
};

struct device_info {
  /// @brief Device name.
  xi_device_id id;
  xi_device_id master;
  std::string name;
  std::array<conky_valuator_info, VALUATOR_COUNT> valuators{};

  static device_info *from_xi_id(xi_device_id id, Display *display = nullptr);

  conky_valuator_info &valuator(valuator_t valuator);

 private:
  void init_xi_device(Display *display,
                      std::variant<xi_device_id, XIDeviceInfo *> device);
};

void handle_xi_device_change(const XIHierarchyEvent *event);

/// `XIEvent` header, removed constant data.
struct xi_event_base {
  /// XI2 event type
  ///
  /// e.g. `XI_KeyPress`
  xi_event_type evtype;
  /// Serial number of last request processed by server
  unsigned long serial;
  /// True if the event came from a `SendEvent` request.
  Bool send_event;
  /// Display the event was read from
  Display *display;
  /// XI extension offset
  int extension;
  /// X server timestamp of the event
  Time time;

  virtual bool read_from_cookie(Display *display, const void *data);

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

#define GENERATES_EVENTS                                  \
  std::vector<std::tuple<int, XEvent *>> generate_events( \
      Window target, Window child, conky::vec2d target_pos) const override;

struct xi_pointer_event : public xi_event_base {
  device_info *device;
  int sourceid;
  int detail;
  /// Root window owning the `event` and `child` windows.
  Window root;
  /// Window that registered the event listener.
  Window event;
  /// Direct descendant of `event` whoose child (or itself) got the event.
  ///
  /// This is not the "final" child that actually got the event, just the
  /// direct descendant of `event`.
  ///
  /// You need `XQueryPointer` to get the actual leaf window that recieved
  /// the event. `child` is sufficient in most cases though.
  ///
  /// When `child == event`, the value is `None`.
  Window child;
  /// `display` relative event coordinates
  conky::vec2d pos_absolute;
  /// `event` window relative event coordinates
  conky::vec2d pos;

  virtual bool read_from_cookie(Display *display, const void *data) override;
};

/// Almost an exact copy of `XIDeviceEvent`, except it owns all data.
struct xi_pointer_interact_event : public xi_pointer_event {
  int flags;
  /// Pressed button mask
  std::bitset<32> buttons;
  std::map<size_t, double> valuators;
  XIModifierState mods;
  XIGroupState group;

  // Extra data

  /// Precomputed relative values
  std::array<double, VALUATOR_COUNT> valuators_relative{};

  bool read_from_cookie(Display *display, const void *data) override;

  bool test_valuator(valuator_t id) const;
  conky_valuator_info *valuator_info(valuator_t id) const;
  std::optional<double> valuator_value(valuator_t id) const;
  std::optional<double> valuator_relative_value(valuator_t valuator) const;
};

struct xi_pointer_move final : public xi_pointer_interact_event {
  GENERATES_EVENTS
};
struct xi_pointer_press final : public xi_pointer_interact_event {
  GENERATES_EVENTS
};
struct xi_pointer_release final : public xi_pointer_interact_event {
  GENERATES_EVENTS
};

/// Almost an exact copy of `XIEnterEvent`, except it owns all data.
struct xi_pointer_crossing_event : public xi_pointer_event {
  int mode;
  Bool focus;
  Bool same_screen;
  /// Pressed button mask
  std::bitset<32> buttons;
  XIModifierState mods;
  XIGroupState group;

  bool read_from_cookie(Display *display, const void *data) override;
};

struct xi_pointer_enter final : public xi_pointer_crossing_event {
  GENERATES_EVENTS
};
struct xi_pointer_leave final : public xi_pointer_crossing_event {
  GENERATES_EVENTS
};
// Focus events are sink-only: they're never propagated, so they inherit
// `xi_event_base::generate_events` (which yields nothing) instead of
// overriding it.
struct xi_pointer_focus_in final : public xi_pointer_crossing_event {};
struct xi_pointer_focus_out final : public xi_pointer_crossing_event {};

#undef GENERATES_EVENTS

#endif /* BUILD_X11 */

#ifdef BUILD_MOUSE_EVENTS
extern simple_config_setting<std::string> lua_mouse_hook;
#endif

}  // namespace conky

#endif /* MOUSE_EVENTS_H */
