#import <ApplicationServices/ApplicationServices.h>
#include <stdbool.h>
#include <stdint.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

static CFMachPortRef eventTap = NULL;
static bool mouseCursorDisconnected = false;

typedef struct {
  bool down;
  CGKeyCode code;
} ModifierState;

typedef struct {
  const char *action;
  CGKeyCode code;
  const char *name;
} ModifierEvent;

typedef struct {
  int64_t dx;
  int64_t dy;
} PointerDelta;

typedef struct {
  const char *action;
  const char *button;
} PointerButton;

typedef struct {
  int64_t x;
  int64_t y;
} ScrollDelta;

static ModifierState modifierStates[4];

static void note_mouse_cursor_association(bool connected, CGError result) {
  if (result == kCGErrorSuccess) {
    mouseCursorDisconnected = !connected;
  }
}

static CGError set_mouse_cursor_association(bool connected) {
  CGError result = CGAssociateMouseAndMouseCursorPosition(connected);
  note_mouse_cursor_association(connected, result);
  return result;
}

static void restore_mouse_cursor_association(void) {
  if (mouseCursorDisconnected) {
    set_mouse_cursor_association(true);
  }
}

static void stop_signal_handler(int signalNumber) {
  restore_mouse_cursor_association();
  exit(128 + signalNumber);
}

static const char *key_name(CGKeyCode code) {
  switch (code) {
    case 0: return "a";
    case 1: return "s";
    case 2: return "d";
    case 3: return "f";
    case 4: return "h";
    case 5: return "g";
    case 6: return "z";
    case 7: return "x";
    case 8: return "c";
    case 9: return "v";
    case 11: return "b";
    case 12: return "q";
    case 13: return "w";
    case 14: return "e";
    case 15: return "r";
    case 16: return "y";
    case 17: return "t";
    case 18: return "1";
    case 19: return "2";
    case 20: return "3";
    case 21: return "4";
    case 22: return "6";
    case 23: return "5";
    case 24: return "=";
    case 25: return "9";
    case 26: return "7";
    case 27: return "-";
    case 28: return "8";
    case 29: return "0";
    case 30: return "]";
    case 31: return "o";
    case 32: return "u";
    case 33: return "[";
    case 34: return "i";
    case 35: return "p";
    case 36: return "enter";
    case 37: return "l";
    case 38: return "j";
    case 39: return "quote";
    case 40: return "k";
    case 41: return ";";
    case 42: return "backslash";
    case 43: return ",";
    case 44: return "/";
    case 45: return "n";
    case 46: return "m";
    case 47: return ".";
    case 48: return "tab";
    case 49: return "space";
    case 50: return "`";
    case 51: return "backspace";
    case 53: return "escape";
    case 65: return "decimal";
    case 67: return "*";
    case 69: return "+";
    case 71: return "clear";
    case 75: return "/";
    case 76: return "enter";
    case 78: return "-";
    case 81: return "=";
    case 82: return "0";
    case 83: return "1";
    case 84: return "2";
    case 85: return "3";
    case 86: return "4";
    case 87: return "5";
    case 88: return "6";
    case 89: return "7";
    case 91: return "8";
    case 92: return "9";
    case 96: return "f5";
    case 97: return "f6";
    case 98: return "f7";
    case 99: return "f3";
    case 100: return "f8";
    case 101: return "f9";
    case 103: return "f11";
    case 109: return "f10";
    case 111: return "f12";
    case 115: return "home";
    case 116: return "pageup";
    case 117: return "delete";
    case 118: return "f4";
    case 119: return "end";
    case 120: return "f2";
    case 121: return "pagedown";
    case 122: return "f1";
    case 123: return "left";
    case 124: return "right";
    case 125: return "down";
    case 126: return "up";
    default: return "unknown";
  }
}

static int dom_code(CGKeyCode code) {
  const char *name = key_name(code);
  if (strlen(name) == 1) {
    char c = name[0];
    if (c >= 'a' && c <= 'z') return (int)c - 32;
    if (c >= '0' && c <= '9') return (int)c;
  }

  switch (code) {
    case 36: return 13;
    case 48: return 9;
    case 49: return 32;
    case 51: return 8;
    case 53: return 27;
    case 55: return 91;
    case 54: return 93;
    case 56:
    case 60: return 16;
    case 58:
    case 61: return 18;
    case 59:
    case 62: return 17;
    case 117: return 46;
    case 115: return 36;
    case 119: return 35;
    case 116: return 33;
    case 121: return 34;
    case 123: return 37;
    case 124: return 39;
    case 125: return 40;
    case 126: return 38;
    default: return 0;
  }
}

static const char *modifier_name(CGKeyCode code) {
  switch (code) {
    case 55:
    case 54: return "command";
    case 56:
    case 60: return "shift";
    case 58:
    case 61: return "alt";
    case 59:
    case 62: return "control";
    default: return NULL;
  }
}

static CGEventFlags modifier_mask(CGKeyCode code) {
  switch (code) {
    case 55:
    case 54: return kCGEventFlagMaskCommand;
    case 56:
    case 60: return kCGEventFlagMaskShift;
    case 58:
    case 61: return kCGEventFlagMaskAlternate;
    case 59:
    case 62: return kCGEventFlagMaskControl;
    default: return 0;
  }
}

static int modifier_index(CGKeyCode code) {
  switch (code) {
    case 55:
    case 54: return 0;
    case 56:
    case 60: return 1;
    case 58:
    case 61: return 2;
    case 59:
    case 62: return 3;
    default: return -1;
  }
}

static void reset_modifier_states(void) {
  memset(modifierStates, 0, sizeof(modifierStates));
}

static bool update_modifier_state(CGKeyCode code, CGEventFlags flags, ModifierEvent *output) {
  const char *name = modifier_name(code);
  CGEventFlags mask = modifier_mask(code);
  int index = modifier_index(code);
  if (!name || !mask || index < 0 || !output) {
    return false;
  }

  bool down = (flags & mask) != 0;
  ModifierState *state = &modifierStates[index];
  if (state->down == down) {
    return false;
  }

  output->action = down ? "down" : "up";
  output->name = name;
  if (down) {
    state->down = true;
    state->code = code;
    output->code = code;
  } else {
    output->code = state->code;
    state->down = false;
    state->code = 0;
  }
  return true;
}

static void emit_key(const char *action, CGKeyCode code, const char *name) {
  printf("{\"type\":\"key\",\"action\":\"%s\",\"code\":%d,\"macCode\":%d,\"key\":\"%s\"}\n",
         action, dom_code(code), code, name);
  fflush(stdout);
}

static bool is_pointer_motion_type(CGEventType type) {
  return type == kCGEventMouseMoved ||
    type == kCGEventLeftMouseDragged ||
    type == kCGEventRightMouseDragged ||
    type == kCGEventOtherMouseDragged;
}

static bool pointer_delta_for_event(CGEventType type, CGEventRef event, PointerDelta *output) {
  if (!event || !output || !is_pointer_motion_type(type)) {
    return false;
  }
  output->dx = CGEventGetIntegerValueField(event, kCGMouseEventDeltaX);
  output->dy = CGEventGetIntegerValueField(event, kCGMouseEventDeltaY);
  return true;
}

static void emit_pointer_delta(PointerDelta delta) {
  if (delta.dx == 0 && delta.dy == 0) {
    return;
  }
  printf("{\"type\":\"pointer\",\"dx\":%lld,\"dy\":%lld}\n",
         (long long)delta.dx,
         (long long)delta.dy);
  fflush(stdout);
}

static const char *other_mouse_button_name(CGEventRef event) {
  int64_t button = CGEventGetIntegerValueField(event, kCGMouseEventButtonNumber);
  switch (button) {
    case kCGMouseButtonCenter: return "middle";
    case 3: return "back";
    case 4: return "forward";
    default: return "middle";
  }
}

static bool pointer_button_for_event(CGEventType type, CGEventRef event, PointerButton *output) {
  if (!event || !output) {
    return false;
  }

  switch (type) {
    case kCGEventLeftMouseDown:
      output->action = "down";
      output->button = "left";
      return true;
    case kCGEventLeftMouseUp:
      output->action = "up";
      output->button = "left";
      return true;
    case kCGEventRightMouseDown:
      output->action = "down";
      output->button = "right";
      return true;
    case kCGEventRightMouseUp:
      output->action = "up";
      output->button = "right";
      return true;
    case kCGEventOtherMouseDown:
      output->action = "down";
      output->button = other_mouse_button_name(event);
      return true;
    case kCGEventOtherMouseUp:
      output->action = "up";
      output->button = other_mouse_button_name(event);
      return true;
    default:
      return false;
  }
}

static void emit_pointer_button(PointerButton button) {
  printf("{\"type\":\"mouse\",\"action\":\"%s\",\"button\":\"%s\"}\n",
         button.action,
         button.button);
  fflush(stdout);
}

static bool scroll_delta_for_event(CGEventType type, CGEventRef event, ScrollDelta *output) {
  if (type != kCGEventScrollWheel || !event || !output) {
    return false;
  }

  int64_t x = CGEventGetIntegerValueField(event, kCGScrollWheelEventPointDeltaAxis2);
  int64_t y = CGEventGetIntegerValueField(event, kCGScrollWheelEventPointDeltaAxis1);
  if (x == 0 && y == 0) {
    x = CGEventGetIntegerValueField(event, kCGScrollWheelEventDeltaAxis2);
    y = CGEventGetIntegerValueField(event, kCGScrollWheelEventDeltaAxis1);
  }

  CGEventFlags flags = CGEventGetFlags(event);
  if ((flags & kCGEventFlagMaskShift) != 0 && x == 0 && y != 0) {
    x = y;
    y = 0;
  }
  output->x = x;
  output->y = y;
  return true;
}

static void emit_scroll_delta(ScrollDelta delta) {
  if (delta.x == 0 && delta.y == 0) {
    return;
  }
  printf("{\"type\":\"scroll\",\"x\":%lld,\"y\":%lld}\n",
         (long long)delta.x,
         (long long)delta.y);
  fflush(stdout);
}

static CGEventMask captured_event_mask(void) {
  return CGEventMaskBit(kCGEventKeyDown) |
    CGEventMaskBit(kCGEventKeyUp) |
    CGEventMaskBit(kCGEventFlagsChanged) |
    CGEventMaskBit(kCGEventMouseMoved) |
    CGEventMaskBit(kCGEventLeftMouseDragged) |
    CGEventMaskBit(kCGEventRightMouseDragged) |
    CGEventMaskBit(kCGEventOtherMouseDragged) |
    CGEventMaskBit(kCGEventLeftMouseDown) |
    CGEventMaskBit(kCGEventLeftMouseUp) |
    CGEventMaskBit(kCGEventRightMouseDown) |
    CGEventMaskBit(kCGEventRightMouseUp) |
    CGEventMaskBit(kCGEventOtherMouseDown) |
    CGEventMaskBit(kCGEventOtherMouseUp) |
    CGEventMaskBit(kCGEventScrollWheel);
}

static CGEventRef callback(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon) {
  if (type == kCGEventTapDisabledByTimeout || type == kCGEventTapDisabledByUserInput) {
    if (eventTap) {
      CGEventTapEnable(eventTap, true);
    }
    return event;
  }

  PointerDelta pointer = {0};
  if (pointer_delta_for_event(type, event, &pointer)) {
    pid_t sourcePid = (pid_t)CGEventGetIntegerValueField(event, kCGEventSourceUnixProcessID);
    if (sourcePid == getppid()) {
      return event;
    }
    emit_pointer_delta(pointer);
    return NULL;
  }

  PointerButton button = {0};
  if (pointer_button_for_event(type, event, &button)) {
    emit_pointer_button(button);
    return NULL;
  }

  ScrollDelta scroll = {0};
  if (scroll_delta_for_event(type, event, &scroll)) {
    emit_scroll_delta(scroll);
    return NULL;
  }

  if (type != kCGEventKeyDown && type != kCGEventKeyUp && type != kCGEventFlagsChanged) {
    return event;
  }

  CGKeyCode code = (CGKeyCode)CGEventGetIntegerValueField(event, kCGKeyboardEventKeycode);
  CGEventFlags flags = CGEventGetFlags(event);

  if (type == kCGEventFlagsChanged) {
    ModifierEvent output = {0};
    if (update_modifier_state(code, flags, &output)) {
      emit_key(output.action, output.code, output.name);
    }
    return NULL;
  }

  if (type == kCGEventKeyDown) {
    emit_key("down", code, key_name(code));
    return NULL;
  }

  emit_key("up", code, key_name(code));
  return NULL;
}

#ifndef OPENMOUSE_KEYTAP_TEST
int main(void) {
  setvbuf(stdout, NULL, _IOLBF, 0);
  atexit(restore_mouse_cursor_association);
  signal(SIGINT, stop_signal_handler);
  signal(SIGTERM, stop_signal_handler);

  CGEventMask mask = captured_event_mask();

  eventTap = CGEventTapCreate(kCGHIDEventTap,
                              kCGHeadInsertEventTap,
                              kCGEventTapOptionDefault,
                              mask,
                              callback,
                              NULL);
  if (!eventTap) {
    fprintf(stderr, "failed to create keyboard event tap; Accessibility permission is required\n");
    return 2;
  }

  CFMachPortSetInvalidationCallBack(eventTap, NULL);
  CFRunLoopSourceRef source = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, eventTap, 0);
  CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes);
  CGEventTapEnable(eventTap, true);
  CGError cursorError = set_mouse_cursor_association(false);
  printf("{\"type\":\"ready\",\"pointer\":true,\"cursorLocked\":%s,\"cursorError\":%d}\n",
         mouseCursorDisconnected ? "true" : "false",
         cursorError);
  fflush(stdout);
  CFRunLoopRun();
  return 0;
}
#endif
