#define OPENMOUSE_KEYTAP_TEST 1
#include "../native/openmouse-keytap.m"

static int failures = 0;

static void expect_modifier_event(const char *label,
                                  CGKeyCode code,
                                  CGEventFlags flags,
                                  bool expected,
                                  const char *action,
                                  CGKeyCode outputCode,
                                  const char *name) {
  ModifierEvent event = {0};
  bool emitted = update_modifier_state(code, flags, &event);
  if (emitted != expected) {
    fprintf(stderr, "%s: expected emitted=%d got=%d\n", label, expected, emitted);
    failures += 1;
    return;
  }

  if (!expected) {
    return;
  }

  if (strcmp(event.action, action) != 0 ||
      event.code != outputCode ||
      strcmp(event.name, name) != 0) {
    fprintf(stderr,
            "%s: expected %s code=%d name=%s got %s code=%d name=%s\n",
            label,
            action,
            outputCode,
            name,
            event.action,
            event.code,
            event.name);
    failures += 1;
  }
}

static void expect_pointer_delta(const char *label,
                                 CGEventType type,
                                 int64_t dx,
                                 int64_t dy,
                                 bool expected) {
  CGEventRef event = expected
    ? CGEventCreateMouseEvent(NULL, type, CGPointMake(100, 100), kCGMouseButtonLeft)
    : CGEventCreate(NULL);
  CGEventSetIntegerValueField(event, kCGMouseEventDeltaX, dx);
  CGEventSetIntegerValueField(event, kCGMouseEventDeltaY, dy);

  PointerDelta output = {0};
  bool captured = pointer_delta_for_event(type, event, &output);
  if (captured != expected ||
      (expected && (output.dx != dx || output.dy != dy))) {
    fprintf(stderr,
            "%s: expected captured=%d delta=%lld,%lld got captured=%d delta=%lld,%lld\n",
            label,
            expected,
            dx,
            dy,
            captured,
            output.dx,
            output.dy);
    failures += 1;
  }
  CFRelease(event);
}

static void expect_pointer_button(const char *label,
                                  CGEventType type,
                                  CGMouseButton button,
                                  bool expected,
                                  const char *action,
                                  const char *name) {
  CGEventRef event = CGEventCreateMouseEvent(NULL, type, CGPointMake(100, 100), button);
  CGEventSetIntegerValueField(event, kCGMouseEventButtonNumber, button);

  PointerButton output = {0};
  bool captured = pointer_button_for_event(type, event, &output);
  if (captured != expected ||
      (expected && (strcmp(output.action, action) != 0 || strcmp(output.button, name) != 0))) {
    fprintf(stderr,
            "%s: expected captured=%d %s %s got captured=%d %s %s\n",
            label,
            expected,
            action ? action : "",
            name ? name : "",
            captured,
            output.action ? output.action : "",
            output.button ? output.button : "");
    failures += 1;
  }
  CFRelease(event);
}

static void expect_scroll_delta(const char *label,
                                CGEventType type,
                                int64_t x,
                                int64_t y,
                                CGEventFlags flags,
                                bool expected,
                                int64_t expectedX,
                                int64_t expectedY) {
  CGEventRef event = type == kCGEventScrollWheel
    ? CGEventCreateScrollWheelEvent(NULL, kCGScrollEventUnitPixel, 2, (int32_t)y, (int32_t)x)
    : CGEventCreate(NULL);
  CGEventSetFlags(event, flags);

  ScrollDelta output = {0};
  bool captured = scroll_delta_for_event(type, event, &output);
  if (captured != expected ||
      (expected && (output.x != expectedX || output.y != expectedY))) {
    fprintf(stderr,
            "%s: expected captured=%d delta=%lld,%lld got captured=%d delta=%lld,%lld\n",
            label,
            expected,
            expectedX,
            expectedY,
            captured,
            output.x,
            output.y);
    failures += 1;
  }
  CFRelease(event);
}

int main(void) {
  mouseCursorDisconnected = false;
  note_mouse_cursor_association(false, kCGErrorSuccess);
  if (!mouseCursorDisconnected) {
    fprintf(stderr, "successful disconnect should mark cursor disconnected\n");
    failures += 1;
  }
  note_mouse_cursor_association(true, kCGErrorSuccess);
  if (mouseCursorDisconnected) {
    fprintf(stderr, "successful reconnect should mark cursor connected\n");
    failures += 1;
  }
  note_mouse_cursor_association(false, kCGErrorCannotComplete);
  if (mouseCursorDisconnected) {
    fprintf(stderr, "failed disconnect should preserve connected state\n");
    failures += 1;
  }

  reset_modifier_states();
  expect_modifier_event("left command down",
                        55,
                        kCGEventFlagMaskCommand,
                        true,
                        "down",
                        55,
                        "command");
  expect_modifier_event("right command down while left is held",
                        54,
                        kCGEventFlagMaskCommand,
                        false,
                        NULL,
                        0,
                        NULL);
  expect_modifier_event("left command up while right is held",
                        55,
                        kCGEventFlagMaskCommand,
                        false,
                        NULL,
                        0,
                        NULL);
  expect_modifier_event("right command up releases original remote command",
                        54,
                        0,
                        true,
                        "up",
                        55,
                        "command");

  reset_modifier_states();
  expect_modifier_event("right command down",
                        54,
                        kCGEventFlagMaskCommand,
                        true,
                        "down",
                        54,
                        "command");
  expect_modifier_event("right command up",
                        54,
                        0,
                        true,
                        "up",
                        54,
                        "command");

  reset_modifier_states();
  expect_modifier_event("unseen command release",
                        55,
                        0,
                        false,
                        NULL,
                        0,
                        NULL);

  expect_pointer_delta("mouse move delta", kCGEventMouseMoved, 7, -4, true);
  expect_pointer_delta("left drag delta", kCGEventLeftMouseDragged, -3, 9, true);
  expect_pointer_delta("right drag delta", kCGEventRightMouseDragged, 2, 5, true);
  expect_pointer_delta("other drag delta", kCGEventOtherMouseDragged, -8, -6, true);
  expect_pointer_delta("keyboard is not pointer motion", kCGEventKeyDown, 4, 4, false);

  expect_pointer_button("left down", kCGEventLeftMouseDown, kCGMouseButtonLeft, true, "down", "left");
  expect_pointer_button("left up", kCGEventLeftMouseUp, kCGMouseButtonLeft, true, "up", "left");
  expect_pointer_button("right down", kCGEventRightMouseDown, kCGMouseButtonRight, true, "down", "right");
  expect_pointer_button("right up", kCGEventRightMouseUp, kCGMouseButtonRight, true, "up", "right");
  expect_pointer_button("middle down", kCGEventOtherMouseDown, kCGMouseButtonCenter, true, "down", "middle");
  expect_pointer_button("back up", kCGEventOtherMouseUp, 3, true, "up", "back");
  expect_pointer_button("forward down", kCGEventOtherMouseDown, 4, true, "down", "forward");
  expect_pointer_button("movement is not a button", kCGEventMouseMoved, kCGMouseButtonLeft, false, NULL, NULL);

  expect_scroll_delta("vertical scroll", kCGEventScrollWheel, 0, 7, 0, true, 0, 7);
  expect_scroll_delta("horizontal scroll", kCGEventScrollWheel, -3, 0, 0, true, -3, 0);
  expect_scroll_delta(
    "shift converts vertical scroll to horizontal",
    kCGEventScrollWheel,
    0,
    5,
    kCGEventFlagMaskShift,
    true,
    5,
    0
  );
  expect_scroll_delta("key is not scroll", kCGEventKeyDown, 0, 4, 0, false, 0, 0);
  if ((captured_event_mask() & CGEventMaskBit(kCGEventScrollWheel)) == 0) {
    fprintf(stderr, "capture mask must include scroll wheel events\n");
    failures += 1;
  }

  if (failures) {
    return 1;
  }

  printf("modifier state tests passed\n");
  return 0;
}
