/*
 * LibInputSynth
 * Copyright 2018 Collabora Ltd.
 * Author: Christoph Haag <christoph.haag@collabora.com>
 * SPDX-License-Identifier: MIT
 */

#include "inputsynth-xi2.h"
#include <stdbool.h>
#include <stdint.h>

// xinput 2 for creating a second X pointer
#include <X11/extensions/XInput2.h>

// Xtest for sending fake events to the X server
#include <X11/Xlib.h>
#include <X11/extensions/XTest.h>

#define XK_MISCELLANY
#define XK_XKB_KEYS
#include <X11/keysymdef.h>

struct _InputSynthXi2
{
  InputSynth parent;
  Display *dpy;
  Window root_window;
  XDevice  pointer_slave_dev;
  XDevice  keyboard_slave_dev;
  struct KeySymTable *keysym_table;
  gboolean single_cursor;
};

G_DEFINE_TYPE (InputSynthXi2, input_synth_xi2, INPUT_TYPE_SYNTH)

typedef struct KeySymTable {
  KeySym *table;
  int min_keycode;
  //int max_keycode;
  int keysyms_per_keycode;
  int num_elements;

  XModifierKeymap *modmap;
} KeySymTable;

typedef struct ModifierKeyCodes {
  // there are only up to 8 modifier keys
  KeyCode key_codes[8];
  int length;
} ModifierKeyCodes;

static int
get_master_dev (Display *dpy, char *name, int use)
{
  int num_devices;
  XIDeviceInfo *info, *dev;
  info = XIQueryDevice (dpy, XIAllMasterDevices, &num_devices);
  for (int i = 0; i < num_devices; i++)
    {
      dev = &info[i];
      if (dev->use == use)
        if (strcmp (dev->name, name) == 0)
          return dev->deviceid;
    }
  return -1;
}

static int
get_slave_dev (Display *dpy, char *name, int use)
{
  int num_devices;
  XIDeviceInfo *info, *dev;
  info = XIQueryDevice (dpy, XIAllDevices, &num_devices);
  for (int i = 0; i < num_devices; i++)
    {
      dev = &info[i];
      if (dev->use == use)
        if (strcmp (dev->name, name) == 0)
          return dev->deviceid;
    }
  return -1;
}

static int
add_xi_master (Display *dpy, char *create_name, char *name)
{
  XIAnyHierarchyChangeInfo c = {
    .add.type = XIAddMaster,
    .add.name = create_name,
    .add.send_core = 1,
    .add.enable = 1
  };

  int ret = XIChangeHierarchy (dpy, &c, 1);
  int master_id = get_master_dev (dpy, name, XIMasterPointer);
  if (ret != 0)
    g_printerr ("Error while craeting Master pointer: %d\n", ret);

  return master_id;
}

static int
delete_xi_masters (Display *dpy, char *name)
{
  XIAnyHierarchyChangeInfo r = {
    .remove.type = XIRemoveMaster,
    .remove.deviceid = get_master_dev (dpy, name, XIMasterPointer),
    .remove.return_mode = XIFloating
  };

  int ret = XIChangeHierarchy (dpy, &r, 1);
  if (ret == 0)
    g_print ("Deleted master %s: %d\n", name, r.remove.deviceid);
  else
    g_printerr ("Error while deleting master pointer %s, %d\n",
                name, r.remove.deviceid);
  return 0;
}

static void
_init_input_synth_devs (InputSynthXi2 *self)
{
  int vrpointer_master_id =
      get_master_dev (self->dpy, INPUT_SYNTH_MASTER_NAME, XIMasterPointer);
  if (vrpointer_master_id == -1)
    {
      vrpointer_master_id = add_xi_master (
        self->dpy, INPUT_SYNTH_MASTER_CREATE_NAME, INPUT_SYNTH_MASTER_NAME);
      if (vrpointer_master_id != -1)
        g_print ("Created master %s: %d\n", INPUT_SYNTH_MASTER_NAME,
                 vrpointer_master_id);
      else
        g_printerr ("Could not create master pointer %s!\n",
                    INPUT_SYNTH_MASTER_NAME);
    }

  int vrpointer_slave_id =
      get_slave_dev (self->dpy, INPUT_SYNTH_SLAVE_NAME, XISlavePointer);

  if (vrpointer_slave_id != -1)
    g_print ("Using existing VR Pointer slave %d!\n", vrpointer_slave_id);
  else
    g_printerr ("Error: slave pointer %s not found!\n", INPUT_SYNTH_SLAVE_NAME);

  self->pointer_slave_dev.device_id = (uint64_t) vrpointer_slave_id;

  int vrkeyboard_slave_id =
    get_slave_dev (self->dpy, INPUT_SYNTH_KEYBOARD_SLAVE_NAME, XISlaveKeyboard);
  if (vrkeyboard_slave_id == -1)
    {
      g_print ("No xtest vr keyboard slave. This should not happen!\n");
    }
  self->keyboard_slave_dev.device_id = (uint64_t) vrkeyboard_slave_id;
}

static KeySymTable *_init_keysym_table (Display *dpy);

static void
input_synth_xi2_init (InputSynthXi2 *self)
{

  self->dpy = XOpenDisplay (NULL);
  self->root_window = DefaultRootWindow (self->dpy);

  g_print ("VR pointer mode: #%d Pointer\n", self->single_cursor ? 1 : 2);

  self->keysym_table = _init_keysym_table (self->dpy);

  self->single_cursor = FALSE; // TODO: config

  if (!self->single_cursor)
    _init_input_synth_devs (self);
}

static void
move_cursor_xi2 (InputSynth *self_parent, int x, int y)
{
  InputSynthXi2 *self = INPUT_SYNTH_XI2 (self_parent);

  if (self->single_cursor)
    XTestFakeMotionEvent(self->dpy, 0, x, y, CurrentTime);
  else
    {
      int axes[2] = {x, y};
      XTestFakeDeviceMotionEvent (self->dpy, &self->pointer_slave_dev, False, 0,
                                  axes, 2, CurrentTime);
    }
  XSync (self->dpy, false);
}

static void
click_xi2 (InputSynth *self_parent, int x, int y,
           int button, gboolean press)
{
  InputSynthXi2 *self = INPUT_SYNTH_XI2 (self_parent);

  if (self->single_cursor)
    XTestFakeButtonEvent (self->dpy, (uint32_t) button, press, CurrentTime);
  else
    {
      int axes[2] = {x, y};
      XTestFakeDeviceButtonEvent (self->dpy, &self->pointer_slave_dev,
                                  (uint32_t) button, press, axes, 2,
                                  CurrentTime);
    }
  XSync (self->dpy, false);
}

InputSynthXi2 *
input_synth_xi2_new (void)
{
  InputSynthXi2 *self = (InputSynthXi2*) g_object_new (INPUT_TYPE_SYNTH_XI2, 0);
  return self;
}

static void
input_synth_xi2_finalize (GObject *gobject)
{
  InputSynthXi2 *self = INPUT_SYNTH_XI2 (gobject);
  if (self->single_cursor)
    return;

  /* send a dummy event to the root window (not sure if this is useful) */
  Drawable rootW = XRootWindow (self->dpy, 0);
  XClientMessageEvent dummyEvent;
  memset(&dummyEvent, 0, sizeof(XClientMessageEvent));
  dummyEvent.type = ClientMessage;
  dummyEvent.window = rootW;
  dummyEvent.format = 32;
  XSendEvent(self->dpy, rootW, 0, 0, (XEvent*)&dummyEvent);

  /* XSync with discarding currently queued events */
  XSync(self->dpy, 1);

  /* we still have to wait in case clutter is currently processing an event
   * if it takes longer than 1 second, something is seriously broken anyway */
  g_usleep (100000);

  g_print ("Removing X11 VR pointer \"%s\"\n", INPUT_SYNTH_MASTER_NAME);
  delete_xi_masters (self->dpy, INPUT_SYNTH_MASTER_NAME);
  XSync (self->dpy, true);

  G_OBJECT_CLASS (input_synth_xi2_parent_class)->finalize (gobject);
}

static KeySymTable *
_init_keysym_table (Display *dpy)
{
  int min_keycode;
  int max_keycode;
  int keysyms_per_keycode;
  XDisplayKeycodes (dpy, &min_keycode, &max_keycode);


  int keycode_count = max_keycode - min_keycode + 1;
  KeySym *keysyms = XGetKeyboardMapping (dpy, (KeyCode) min_keycode, keycode_count,
                                         &keysyms_per_keycode);

  int num_elements = keycode_count * keysyms_per_keycode;

  XModifierKeymap *modmap = XGetModifierMapping(dpy);

  KeySymTable *keysym_table = malloc (sizeof (KeySymTable));
  keysym_table->table = keysyms;
  keysym_table->min_keycode = min_keycode;
  //keysym_table->max_keycode = max_keycode;
  keysym_table->keysyms_per_keycode = keysyms_per_keycode;
  keysym_table->num_elements = num_elements;
  keysym_table->modmap = modmap;

  return keysym_table;
}

static int
_find_modifier_num (KeySymTable *keysym_table, KeySym sym)
{
  int min_keycode = keysym_table->min_keycode;
  int num_elements = keysym_table->num_elements;
  int keysyms_per_keycode = keysym_table->keysyms_per_keycode;
  KeySym *keysyms = keysym_table->table;
  for (int keycode = min_keycode; keycode < num_elements / keysyms_per_keycode;
     keycode++)
    {
      for (int keysym_num = 0; keysym_num < keysyms_per_keycode; keysym_num++)
        {

          // TODO: 2 (ctr+alt) or 3 (ctrl+alt+shift) are weird
          // XK_apostrophe (') on the german keyboard is Shift+#, but also the
          // weird Ctrl+Alt+ä combinatoin, which comes first, but only works in
          // some (?) apps, so skip the weird ones.
          if (keysym_num == 2 || keysym_num == 3) continue;

          int index = (keycode - min_keycode) * keysyms_per_keycode +
            keysym_num;
          if (keysyms[index] == sym)
            {
              int modifier_num = keysym_num;
              return modifier_num;
            }
        }
    }
  return -1;
}

static ModifierKeyCodes
_modifier_keycode_for (InputSynthXi2 *self,
                       KeySym sym)
{
  /* each row of the key sym table is one key code
   * in each row there are up to "keysyms_per_keycode" key syms that are located
   * on the same physical key in the current layout
   *
   * for example in the german layout the row for the q key looks like this
   * key code 24; key syms: q, Q, q, Q, at, Greek_OMEGA, at
   * (note that XKeysymToString() omits the XK_ prefix that can be found in the
   * #define in keysymdef.h, e.g. the "at" key sym is defined as XK_at)
   *
   * _find_modifier_num() finds the index of the first applicable sym:
   * XK_q => 0
   * XK_Q => 1
   * XK_at => 4
   */
  int modifier_num = _find_modifier_num (self->keysym_table, sym);

  if (modifier_num == -1) {
    g_print ("ERROR: Did not find key sym!\n");
    return (ModifierKeyCodes) { .length = -1 };
  } else {
    //g_print ("Found key sym with mod number %d!\n", modifier_num);
  }

  /* There are up to 8 modifiers that can be enumerated (shift, ctrl, etc.)
   *
   * each of these 8 keys has up to "max_keypermod" "equivalent" modifiers
   * (e.g. left shift, right shift, etc.)
   *
   * TODO:
   * from XK_at appearing in column 4 of the table above we know that XK_at
   * can be achieved by q + modifier "4" and
   * I know from my keyboard that modifier 4 is the alt gr key (which has keysym
   * XK_ISO_Level3_Shift) but I do not know how to programatically find wich
   * entry of the XModifierKeymap entries below corresponds to e.g. column 4
   *
   * If modmap->modifiermap[modmap_index(4)] was XK_ISO_Level3_Shift we would
   * have solved the problem

  XModifierKeymap *modmap = keysym_table->modmap;
  // + 0: There are modmap->max_keypermod "equivalent" modifier keys.
  int modmap_index = modifier_num * modmap->max_keypermod + 0;
  KeyCode key_code = modmap->modifiermap[modmap_index];
  return key_code;
  */

  // for now hardcode the modifier key
  switch (modifier_num) {
  case 0:
    return (ModifierKeyCodes) {
      .key_codes[0] = XKeysymToKeycode (self->dpy, NoSymbol),
      .length = 1
    };
  case 1:
    return (ModifierKeyCodes) {
      .key_codes[0] = XKeysymToKeycode (self->dpy, XK_Shift_L),
      .length = 1
    };
  /* TODO: 2 and 3 are super weird, we don't use them */
  case 4:
    return (ModifierKeyCodes) {
      .key_codes[0] = XKeysymToKeycode (self->dpy, XK_ISO_Level3_Shift),
      .length = 1
    };
  case 5:
    return (ModifierKeyCodes) {
      .key_codes[0] = XKeysymToKeycode (self->dpy, XK_Shift_L),
      .key_codes[1] = XKeysymToKeycode (self->dpy, XK_ISO_Level3_Shift),
      .length = 2
    };
  default:
    return (ModifierKeyCodes) { .length = 0 };
  }
}

static void
character_xi2 (InputSynth *self_parent, char c)
{
  InputSynthXi2 *self = INPUT_SYNTH_XI2 (self_parent);

  /* many openvr key codes match Latin 1 keysyms from X11/keysymdef.h
   * lucky coincidence or subject to change?
   */
  int key_sym;
  /* OpenVR Backspace = 8 and XK_Backspace = 0xff08 etc. */
  if (c >= 8 && c <= 17)
    key_sym = 0xff00 + c;
  else
    key_sym = c;

  /* character 10 on the open vr keyboard (Line Feed)
  * should be return for us. There is no return on the openvr keyboard?!
   */
  if (c == 10)
      key_sym = XK_Return;

  ModifierKeyCodes mod_key_codes =
    _modifier_keycode_for (self, (KeySym) key_sym);

  if (mod_key_codes.length == -1)
    {
      g_print ("There was an error, not synthing %c keysym %d %s\n",
               c, key_sym, XKeysymToString ((KeySym) key_sym));
      return;
    }

  for (int i = 0; i < mod_key_codes.length; i++)
    {
      KeySym key_code = mod_key_codes.key_codes[i];
      //g_print ("%c: modkey %lu\n", c, key_code);
      XTestFakeKeyEvent (self->dpy, (uint32_t) key_code, true, 0);
    }

  {
    unsigned int key_code = XKeysymToKeycode (self->dpy, (KeySym) key_sym);
    XTestFakeKeyEvent (self->dpy, key_code, true, 0);
    XTestFakeKeyEvent (self->dpy, key_code, false, 0);
  }

  for (int i = 0; i < mod_key_codes.length; i++)
    {
      KeySym key_code = mod_key_codes.key_codes[i];
      //g_print ("%c: modkey %lu\n", c, key_code);
      XTestFakeKeyEvent (self->dpy, (uint32_t) key_code, false, 0);
    }

  XSync (self->dpy, false);
}

static GString *
get_backend_name_xi2 (InputSynth *self_parent)
{
  InputSynthXi2 *self = INPUT_SYNTH_XI2 (self_parent);
  return g_string_new (self->single_cursor ?
                       "xinput2 with one cursor" :
                       "xinput2 with two cursors");
}

static void
input_synth_xi2_class_init (InputSynthXi2Class *klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);

  object_class->finalize = input_synth_xi2_finalize;

  InputSynthClass *input_synth_class = INPUT_SYNTH_CLASS (klass);
  input_synth_class->click = click_xi2;
  input_synth_class->move_cursor = move_cursor_xi2;
  input_synth_class->character = character_xi2;

  input_synth_class->get_backend_name = get_backend_name_xi2;
}
