/*
 * Copyright © 2022 Codethink Ltd.
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice (including the
 * next paragraph) shall be included in all copies or substantial
 * portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT.  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

#include "uinput.h"
#include "input_common.h"
#include <fcntl.h>
#include <linux/uinput.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

struct devices input_devices;

void uinput_backend(qad_backend_input_t *backend) {
  create_uinput_devices();
  backend->move = uinput_move;
  backend->button = uinput_button;
  backend->touch = uinput_touch;
  backend->swipe = uinput_swipe;
  backend->key = uinput_key;
  backend->text = uinput_text;
}

int get_uinput_fd(int event) {
  if (event == 0) {
    return input_devices.mouse;
  } else if (event == 1) {
    return input_devices.keyboard;
  } else if (event == 2) {
    return input_devices.touch;
  }
  return -1;
}

int create_mouse() {
  struct uinput_setup usetup;

  int fd = open("/dev/uinput", O_WRONLY | O_NONBLOCK);

  /* enable mouse button left and relative events */
  ioctl(fd, UI_SET_EVBIT, EV_KEY);
  ioctl(fd, UI_SET_KEYBIT, BTN_LEFT);
  ioctl(fd, UI_SET_KEYBIT, BTN_RIGHT);

  ioctl(fd, UI_SET_EVBIT, EV_REL);
  ioctl(fd, UI_SET_RELBIT, REL_X);
  ioctl(fd, UI_SET_RELBIT, REL_Y);

  memset(&usetup, 0, sizeof(usetup));
  usetup.id.bustype = BUS_USB;
  usetup.id.vendor = 0x1234;  /* sample vendor */
  usetup.id.product = 0x5678; /* sample product */
  strcpy(usetup.name, "QAD mouse device");

  ioctl(fd, UI_DEV_SETUP, &usetup);
  ioctl(fd, UI_DEV_CREATE);

  return fd;
}

int create_keyboard() {
  struct uinput_setup usetup;

  int fd = open("/dev/uinput", O_WRONLY | O_NONBLOCK);

  QAD_CHECK_ERR(fd);

  /* enable key input events */
  QAD_CHECK_ERR(ioctl(fd, UI_SET_EVBIT, EV_KEY));

  for (int i = KEY_ESC; i <= KEY_RIGHT; i++) {
    ioctl(fd, UI_SET_KEYBIT, i);
  }

  memset(&usetup, 0, sizeof(usetup));
  usetup.id.bustype = BUS_USB;
  usetup.id.vendor = 0x1234;  /* sample vendor */
  usetup.id.product = 0x5678; /* sample product */
  strcpy(usetup.name, "QAD keyboard device");

  QAD_CHECK_ERR(ioctl(fd, UI_DEV_SETUP, &usetup));
  QAD_CHECK_ERR(ioctl(fd, UI_DEV_CREATE));

  return fd;
}

int create_touch() {
  // struct uinput_setup usetup;
  struct uinput_user_dev usetup;

  int fd = open("/dev/uinput", O_WRONLY | O_NONBLOCK);

  /* enable abs touch events and mouse buttons */
  ioctl(fd, UI_SET_EVBIT, EV_ABS);
  ioctl(fd, UI_SET_ABSBIT, ABS_MT_PRESSURE);

  ioctl(fd, UI_SET_ABSBIT, ABS_MT_TRACKING_ID);
  ioctl(fd, UI_SET_ABSBIT, ABS_MT_POSITION_X);
  ioctl(fd, UI_SET_ABSBIT, ABS_MT_POSITION_Y);
  ioctl(fd, UI_SET_ABSBIT, ABS_X);
  ioctl(fd, UI_SET_ABSBIT, ABS_Y);

  ioctl(fd, UI_SET_EVBIT, EV_KEY);

  ioctl(fd, UI_SET_KEYBIT, BTN_TOUCH);

  ioctl(fd, UI_SET_ABSBIT, ABS_MT_SLOT);

  memset(&usetup, 0, sizeof(usetup));
  usetup.id.bustype = BUS_USB;
  usetup.id.vendor = 0x1234;
  usetup.id.product = 0x5678;
  strcpy(usetup.name, "QAD touchinput device");

  usetup.absmin[ABS_X] = 0;
  usetup.absmax[ABS_X] = 32767;

  usetup.absmin[ABS_Y] = 0;
  usetup.absmax[ABS_Y] = 32767;

  usetup.absmin[ABS_MT_POSITION_X] = 0;
  usetup.absmax[ABS_MT_POSITION_X] = 32767;

  usetup.absmin[ABS_MT_POSITION_Y] = 0;
  usetup.absmax[ABS_MT_POSITION_Y] = 32767;

  usetup.absmin[ABS_MT_PRESSURE] = 0;
  usetup.absmax[ABS_MT_PRESSURE] = 100;

  if (write(fd, &usetup, sizeof(usetup)) < 0) {
    return -1;
  }

  if (ioctl(fd, UI_DEV_CREATE) < 0) {
    return -1;
  }

  ioctl(fd, UI_DEV_SETUP, &usetup);
  ioctl(fd, UI_DEV_CREATE);

  return fd;
}

void create_uinput_devices() {
  input_devices.mouse = create_mouse();
  input_devices.keyboard = create_keyboard();
  input_devices.touch = create_touch();
}

int uinput_move(int x, int y, int event) {
  int fd = get_uinput_fd(event);
  if (fd < 0) {
    return -1;
  }
  if (send_position_event_mt(x, y, fd) < 0) {
    close(fd);
    return -1;
  }

  return send_syn_event(fd);
}

int uinput_button(int value, int event) {
  int fd = get_uinput_fd(event);
  if (fd < 0) {
    return -1;
  }

  int tracking_event = 100;
  if (value == 0)
    tracking_event = -1;

  if (send_tracking_event(tracking_event, event) < 0) {
    close(fd);
    return -1;
  }

  if (send_button_event(value, event) < 0) {
    close(fd);
    return -1;
  }

  return send_syn_event(event);
}

int uinput_touch(int x, int y, int duration, int event) {
  int fd = get_uinput_fd(event);
  if (fd < 0) {
    return -1;
  }

  int res = send_touch(x, y, duration, fd);
  if (res < 0) {
    close(fd);
    return -1;
  }

  return res;
}

int uinput_swipe(int x, int y, int x_2, int y_2, int velocity, int event,
                 int stop) {
  int fd = get_uinput_fd(event);
  if (fd < 0) {
    return -1;
  }

  return send_swipe(x, y, x_2, y_2, velocity, fd, stop);
}

int uinput_key(int key, int event) {
  int fd = get_uinput_fd(event);
  if (fd < 0) {
    return -1;
  }
  int error = send_key_event(key, fd);

  if (error < 0) {
    close(fd);
    return -1;
  }

  return send_syn_event(fd);
}

int uinput_text(int const *key_codes, int num_keys, int event) {
  int fd = get_uinput_fd(event);
  if (fd < 0) {
    return -1;
  }

  return send_text_event(key_codes, num_keys, fd);
}
