/*
 * 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 "input_common.h"
#include "evdev_input.h"
#include "uinput.h"
#include <fcntl.h>
#include <linux/input.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int create_input_backend(qad_backend_input_t *backend, char input_type[]) {
  if (strcmp(input_type, "uinput") == 0) {
    uinput_backend(backend);
  } else {
    evdev_input_backend(backend);
  }

  return 0;
}

int send_syn_event(int fd) {
  struct input_event syn;
  syn.input_event_sec = 0;
  syn.input_event_usec = 0;
  syn.type = EV_SYN;
  syn.code = SYN_REPORT;
  syn.value = 0;
  return write(fd, &syn, sizeof(syn));
}

int send_button_event(int value, int fd) {
  struct input_event button_event;
  button_event.input_event_sec = 0;
  button_event.input_event_usec = 0;
  button_event.type = EV_KEY;
  button_event.code = BTN_TOUCH;
  button_event.value = value;
  return write(fd, &button_event, sizeof(button_event));
}

int send_key_event(int key, int fd) {
  struct input_event key_event;
  key_event.input_event_sec = 0;
  key_event.input_event_usec = 0;
  key_event.type = EV_KEY;
  key_event.code = key;
  key_event.value = 1;

  int error = write(fd, &key_event, sizeof(key_event));

  if (error < 0)
    return error;

  key_event.type = EV_KEY;
  key_event.code = key;
  key_event.value = 0;

  return write(fd, &key_event, sizeof(key_event));
}

int send_text_event(int const *key_codes, int num_keycodes, int fd) {
  for (size_t i = 0; i < num_keycodes; i++) {
    int err = send_key_event(key_codes[i], fd);
    if (err < 0) {
      return err;
    }
    err = send_syn_event(fd);
    if (err < 0) {
      return err;
    }
    usleep(1000);
  }
  return 0;
}

int send_pressure_event(int value, int fd) {
  struct input_event pressure_event;
  pressure_event.input_event_sec = 0;
  pressure_event.input_event_usec = 0;
  pressure_event.type = EV_ABS;
  pressure_event.code = ABS_MT_PRESSURE;
  pressure_event.value = value;
  return write(fd, &pressure_event, sizeof(pressure_event));
}

int send_major_event(int value, int fd) {
  struct input_event major_event;
  major_event.input_event_sec = 0;
  major_event.input_event_usec = 0;
  major_event.type = EV_ABS;
  major_event.code = ABS_MT_TOUCH_MAJOR;
  major_event.value = value;

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

  major_event.code = ABS_MT_WIDTH_MAJOR;

  return write(fd, &major_event, sizeof(major_event));
}

int send_position_event_abs(int x, int y, int fd) {
  struct input_event position_event;
  position_event.input_event_sec = 0;
  position_event.input_event_usec = 0;
  position_event.type = EV_ABS;
  position_event.code = ABS_X;
  position_event.value = x;

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

  position_event.code = ABS_Y;
  position_event.value = y;

  return write(fd, &position_event, sizeof(position_event));
}

int send_position_event_mt(int x, int y, int fd) {
  struct input_event position_event;
  position_event.input_event_sec = 0;
  position_event.input_event_usec = 0;
  position_event.type = EV_ABS;
  position_event.code = ABS_MT_POSITION_X;
  position_event.value = x;

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

  position_event.code = ABS_MT_POSITION_Y;
  position_event.value = y;

  return write(fd, &position_event, sizeof(position_event));
}

int send_position_event_rel(int x, int y, int fd) {
  struct input_event position_event;
  position_event.input_event_sec = 0;
  position_event.input_event_usec = 0;
  position_event.type = EV_REL;
  position_event.code = REL_X;
  position_event.value = x;

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

  position_event.code = REL_Y;
  position_event.value = y;

  return write(fd, &position_event, sizeof(position_event));
}

int send_tracking_event(int value, int fd) {
  struct input_event tracking_event;
  tracking_event.input_event_sec = 0;
  tracking_event.input_event_usec = 0;
  tracking_event.type = EV_ABS;
  tracking_event.code = ABS_MT_TRACKING_ID;
  tracking_event.value = value;

  return write(fd, &tracking_event, sizeof(tracking_event));
}

int send_swipe_header(int major_value, int pressure, int fd) {
  if (send_major_event(major_value, fd) < 0) {
    return -1;
  }

  return send_pressure_event(pressure, fd);
}

int send_swipe_footer(int fd) {
  if (send_major_event(0, fd) < 0) {
    return -1;
  }
  if (send_pressure_event(0, fd) < 0) {
    return -1;
  }
  if (send_tracking_event(-1, fd) < 0) {
    return -1;
  }
  if (send_button_event(BTN_UP, fd) < 0) {
    return -1;
  }

  return send_syn_event(fd);
}

int send_touch(int x, int y, int duration, int fd) {
  if (send_tracking_event(100, fd) < 0) {
    return -1;
  }
  if (send_position_event_mt(x, y, fd) < 0) {
    return -1;
  }
  if (send_button_event(BTN_DOWN, fd) < 0) {
    return -1;
  }
  if (send_position_event_abs(x, y, fd) < 0) {
    return -1;
  }
  if (send_syn_event(fd) < 0) {
    return -1;
  }
  if (duration > 0) {
    sleep(duration);
  }
  if (send_tracking_event(-1, fd) < 0) {
    return -1;
  }
  if (send_button_event(BTN_UP, fd) < 0) {
    return -1;
  }

  return send_syn_event(fd);
}

int send_swipe(int x, int y, int x_2, int y_2, int v, int fd, int stop) {
  int steps_y = (y - y_2) / v * -1;
  int steps_x = (x - x_2) / v * -1;
  int i = y;
  int major_value = 50;
  int pressure = 50;
  int tracking_event = 100;

  if (send_swipe_header(major_value, pressure, fd) < 0) {
    return -1;
  }
  if (send_position_event_mt(x, y, fd) < 0) {
    return -1;
  }
  if (send_tracking_event(tracking_event, fd) < 0) {
    return -1;
  }
  if (send_button_event(BTN_DOWN, fd) < 0) {
    return -1;
  }
  if (send_syn_event(fd) < 0) {
    return -1;
  }

  for (i = 0; i < v; i++) {
    if (send_major_event(major_value++, fd) < 0) {
      return -1;
    }
    if (send_pressure_event(pressure++, fd) < 0) {
      return -1;
    }
    if (major_value > 100) {
      major_value = 100;
      pressure = 100;
    }
    if (send_tracking_event(tracking_event, fd) < 0) {
      return -1;
    }
    if (send_position_event_mt(x, y, fd) < 0) {
      return -1;
    }
    if (send_syn_event(fd) < 0) {
      return -1;
    }
    if (usleep(500) < 0) {
      return -1;
    }
    x += steps_x;
    y += steps_y;
  }

  if (send_major_event(major_value, fd) < 0) {
    return -1;
  }
  if (send_pressure_event(pressure, fd) < 0) {
    return -1;
  }
  if (send_position_event_mt(x_2, y_2, fd) < 0) {
    return -1;
  }
  if (send_syn_event(fd) < 0) {
    return -1;
  }

  // If stop has been specified, keep the pressure in the same place for that
  // many milliseconds
  if (stop > 0) {
    if (usleep(stop * 1000) < 0) {
      return -1;
    }
  }

  return send_swipe_footer(fd);
}
