#!/usr/bin/env python3
"""
Native Virtual HID & CoreGraphics Input Driver for macOS (SDO HAL)
Bypasses high-level window manager filtering by posting low-level hardware events
directly to kCGHIDEventTap using CoreGraphics / ApplicationServices frameworks.
Supports relative mouse deltas for 3D/Metal/OpenGL/LWJGL applications and games (such as Minecraft on macOS).
"""

import sys
import os
import time
import json
import math
import string
import threading
import ctypes
from ctypes import c_void_p, c_int, c_uint32, c_int64, c_double, c_bool, Structure, POINTER, byref

# macOS CoreGraphics / ApplicationServices CTypes Setup
cg = None
if sys.platform == 'darwin':
    try:
        cg = ctypes.cdll.LoadLibrary('/System/Library/Frameworks/ApplicationServices.framework/ApplicationServices')
    except Exception:
        cg = None

# CGEventTapLocation
kCGHIDEventTap = 0
kCGSessionEventTap = 1
kCGAnnotatedSessionEventTap = 2

# CGEventType
kCGEventNull = 0
kCGEventLeftMouseDown = 1
kCGEventLeftMouseUp = 2
kCGEventRightMouseDown = 3
kCGEventRightMouseUp = 4
kCGEventMouseMoved = 5
kCGEventLeftMouseDragged = 6
kCGEventRightMouseDragged = 7
kCGEventKeyDown = 10
kCGEventKeyUp = 11
kCGEventFlagsChanged = 12
kCGEventScrollWheel = 22
kCGEventOtherMouseDown = 25
kCGEventOtherMouseUp = 26
kCGEventOtherMouseDragged = 27

# CGMouseButton
kCGMouseButtonLeft = 0
kCGMouseButtonRight = 1
kCGMouseButtonCenter = 2

# CGEventField
kCGMouseEventDeltaX = 4
kCGMouseEventDeltaY = 5
kCGKeyboardEventAutorepeat = 8

# CGEventFlags
kCGEventFlagMaskAlphaShift = 0x00010000
kCGEventFlagMaskShift = 0x00020000
kCGEventFlagMaskControl = 0x00040000
kCGEventFlagMaskAlternate = 0x00080000
kCGEventFlagMaskCommand = 0x00100000

class CGPoint(Structure):
    _fields_ = [("x", c_double), ("y", c_double)]

if cg:
    cg.CGEventCreateMouseEvent.argtypes = [c_void_p, c_uint32, CGPoint, c_uint32]
    cg.CGEventCreateMouseEvent.restype = c_void_p

    cg.CGEventCreateKeyboardEvent.argtypes = [c_void_p, c_uint32, c_bool]
    cg.CGEventCreateKeyboardEvent.restype = c_void_p

    cg.CGEventCreateScrollWheelEvent2.argtypes = [c_void_p, c_uint32, c_uint32, c_int, c_int, c_int]
    cg.CGEventCreateScrollWheelEvent2.restype = c_void_p

    cg.CGEventPost.argtypes = [c_uint32, c_void_p]
    cg.CGEventPost.restype = None

    cg.CGEventSetIntegerValueField.argtypes = [c_void_p, c_uint32, c_int64]
    cg.CGEventSetIntegerValueField.restype = None

    cg.CGEventSetFlags.argtypes = [c_void_p, c_uint32]
    cg.CGEventSetFlags.restype = None

    cg.CGEventKeyboardSetUnicodeString.argtypes = [c_void_p, c_uint32, POINTER(ctypes.c_wchar)]
    cg.CGEventKeyboardSetUnicodeString.restype = None

    cg.CFRelease.argtypes = [c_void_p]
    cg.CFRelease.restype = None

    cg.CGWarpMouseCursorPosition.argtypes = [CGPoint]
    cg.CGWarpMouseCursorPosition.restype = c_int

# --- macOS Virtual Keycodes (from HIToolbox/Events.h) ---
MACOS_KEY_MAP = {
    'a': 0x00, 's': 0x01, 'd': 0x02, 'f': 0x03, 'h': 0x04, 'g': 0x05, 'z': 0x06, 'x': 0x07,
    'c': 0x08, 'v': 0x09, 'b': 0x0B, 'q': 0x0C, 'w': 0x0D, 'e': 0x0E, 'r': 0x0F, 'y': 0x10,
    't': 0x11, '1': 0x12, '2': 0x13, '3': 0x14, '4': 0x15, '6': 0x16, '5': 0x17, '=': 0x18,
    '9': 0x19, '7': 0x1A, '-': 0x1B, '8': 0x1C, '0': 0x1D, ']': 0x1E, 'o': 0x1F, 'u': 0x20,
    '[': 0x21, 'i': 0x22, 'p': 0x23, 'l': 0x25, 'j': 0x26, "'": 0x27, 'k': 0x28, ';': 0x29,
    '\\': 0x2A, ',': 0x2B, '/': 0x2C, 'n': 0x2D, 'm': 0x2E, '.': 0x2F, '`': 0x32,
    ' ': 0x31, '\n': 0x24, '\t': 0x30, '\b': 0x33,
}

MACOS_NAMED_KEYS = {
    'KEY_A': 0x00, 'KEY_S': 0x01, 'KEY_D': 0x02, 'KEY_F': 0x03, 'KEY_H': 0x04, 'KEY_G': 0x05,
    'KEY_Z': 0x06, 'KEY_X': 0x07, 'KEY_C': 0x08, 'KEY_V': 0x09, 'KEY_B': 0x0B, 'KEY_Q': 0x0C,
    'KEY_W': 0x0D, 'KEY_E': 0x0E, 'KEY_R': 0x0F, 'KEY_Y': 0x10, 'KEY_T': 0x11, 'KEY_O': 0x1F,
    'KEY_U': 0x20, 'KEY_I': 0x22, 'KEY_P': 0x23, 'KEY_L': 0x25, 'KEY_J': 0x26, 'KEY_K': 0x28,
    'KEY_N': 0x2D, 'KEY_M': 0x2E,
    'KEY_1': 0x12, 'KEY_2': 0x13, 'KEY_3': 0x14, 'KEY_4': 0x15, 'KEY_5': 0x17, 'KEY_6': 0x16,
    'KEY_7': 0x1A, 'KEY_8': 0x1C, 'KEY_9': 0x19, 'KEY_0': 0x1D,
    'KEY_ENTER': 0x24, 'KEY_TAB': 0x30, 'KEY_SPACE': 0x31, 'KEY_BACKSPACE': 0x33, 'KEY_ESC': 0x35,
    'KEY_LEFTMETA': 0x37, 'KEY_RIGHTMETA': 0x37, # Command
    'KEY_LEFTSHIFT': 0x38, 'KEY_RIGHTSHIFT': 0x3C,
    'KEY_CAPSLOCK': 0x39, 'KEY_LEFTALT': 0x3A, 'KEY_RIGHTALT': 0x3D, # Option
    'KEY_LEFTCTRL': 0x3B, 'KEY_RIGHTCTRL': 0x3E,
    'KEY_UP': 0x7E, 'KEY_DOWN': 0x7D, 'KEY_LEFT': 0x7B, 'KEY_RIGHT': 0x7C,
    'KEY_HOME': 0x73, 'KEY_END': 0x77, 'KEY_PAGEUP': 0x74, 'KEY_PAGEDOWN': 0x79,
    'KEY_DELETE': 0x75,
    'KEY_F1': 0x7A, 'KEY_F2': 0x78, 'KEY_F3': 0x63, 'KEY_F4': 0x76, 'KEY_F5': 0x60,
    'KEY_F6': 0x61, 'KEY_F7': 0x62, 'KEY_F8': 0x64, 'KEY_F9': 0x65, 'KEY_F10': 0x6D,
    'KEY_F11': 0x67, 'KEY_F12': 0x6F
}

# Shift required characters
SHIFT_CHARS = {
    '!': '1', '@': '2', '#': '3', '$': '4', '%': '5', '^': '6', '&': '7', '*': '8', '(': '9', ')': '0',
    '_': '-', '+': '=', '{': '[', '}': ']', ':': ';', '"': "'", '~': '`', '|': '\\', '<': ',', '>': '.', '?': '/'
}

def post_mouse_event(event_type, x, y, button=kCGMouseButtonLeft, dx=0, dy=0):
    if not cg: return
    pt = CGPoint(float(x), float(y))
    evt = cg.CGEventCreateMouseEvent(None, event_type, pt, button)
    if evt:
        if dx != 0 or dy != 0:
            cg.CGEventSetIntegerValueField(evt, kCGMouseEventDeltaX, int(dx))
            cg.CGEventSetIntegerValueField(evt, kCGMouseEventDeltaY, int(dy))
        cg.CGEventPost(kCGHIDEventTap, evt)
        cg.CFRelease(evt)

def post_key_event(vk, is_down):
    if not cg: return
    evt = cg.CGEventCreateKeyboardEvent(None, vk, is_down)
    if evt:
        cg.CGEventPost(kCGHIDEventTap, evt)
        cg.CFRelease(evt)

def tap_key(vk, delay=0.02):
    post_key_event(vk, True)
    if delay > 0:
        time.sleep(delay)
    post_key_event(vk, False)

def post_unicode_char(ch):
    if not cg: return
    evt = cg.CGEventCreateKeyboardEvent(None, 0, True)
    if evt:
        arr = (ctypes.c_wchar * 1)(ch)
        cg.CGEventKeyboardSetUnicodeString(evt, 1, arr)
        cg.CGEventPost(kCGHIDEventTap, evt)
        cg.CFRelease(evt)
    evt_up = cg.CGEventCreateKeyboardEvent(None, 0, False)
    if evt_up:
        cg.CGEventPost(kCGHIDEventTap, evt_up)
        cg.CFRelease(evt_up)

def main():
    cur_x, cur_y = 960.0, 540.0
    
    state_lock = threading.Lock()
    target_x = None
    target_y = None
    move_duration = 0.0
    
    print(json.dumps({"status": "ready"}), flush=True)
    print(json.dumps({"type": "position", "x": int(cur_x), "y": int(cur_y), "origin": "SYSTEM"}), flush=True)

    def reflex_arc_loop():
        nonlocal cur_x, cur_y, target_x, target_y, move_duration
        while True:
            with state_lock:
                tx, ty = target_x, target_y
                md = move_duration
                
            if tx is not None and ty is not None:
                dx = tx - cur_x
                dy = ty - cur_y
                dist = math.hypot(dx, dy)
                if dist > 300:
                    cur_x, cur_y = tx, ty
                    if cg:
                        cg.CGWarpMouseCursorPosition(CGPoint(cur_x, cur_y))
                    post_mouse_event(kCGEventMouseMoved, cur_x, cur_y, dx=dx, dy=dy)
                    print(json.dumps({"type": "position", "x": int(cur_x), "y": int(cur_y), "origin": "SDO"}), flush=True)
                    with state_lock:
                        target_x = None
                        target_y = None
                elif dist > 1.0:
                    speed = min(1.0, 0.12 / max(0.01, md))
                    step_dx = dx * speed
                    step_dy = dy * speed
                    cur_x += step_dx
                    cur_y += step_dy
                    if cg:
                        cg.CGWarpMouseCursorPosition(CGPoint(cur_x, cur_y))
                    post_mouse_event(kCGEventMouseMoved, cur_x, cur_y, dx=step_dx, dy=step_dy)
                    print(json.dumps({"type": "position", "x": int(cur_x), "y": int(cur_y), "origin": "SDO"}), flush=True)
                else:
                    cur_x, cur_y = tx, ty
                    if cg:
                        cg.CGWarpMouseCursorPosition(CGPoint(cur_x, cur_y))
                    post_mouse_event(kCGEventMouseMoved, cur_x, cur_y)
                    print(json.dumps({"type": "position", "x": int(cur_x), "y": int(cur_y), "origin": "SDO"}), flush=True)
                    with state_lock:
                        target_x = None
                        target_y = None
            time.sleep(0.016)

    t_reflex = threading.Thread(target=reflex_arc_loop, daemon=True)
    t_reflex.start()

    # Stdin Command Listener
    for line in sys.stdin:
        if not line.strip():
            continue
        try:
            cmd = json.loads(line)
            action = cmd.get('type')
            
            if action == 'expect':
                tx = cmd.get('target_x', cur_x)
                ty = cmd.get('target_y', cur_y)
                adr = cmd.get('adrenaline', 0.0)
                ser = cmd.get('serotonin', 0.0)
                dist = math.hypot(tx - cur_x, ty - cur_y)
                base_duration = 0.1 + (dist * 0.0015)
                duration = base_duration * (1.0 - (adr * 0.3)) * (1.0 + (ser * 0.3))
                with state_lock:
                    target_x, target_y = float(tx), float(ty)
                    move_duration = max(0.05, duration)
                    
            elif action == 'move':
                with state_lock:
                    target_x = None
                    target_y = None
                dx = float(cmd.get('dx', 0))
                dy = float(cmd.get('dy', 0))
                cur_x += dx
                cur_y += dy
                if cg:
                    cg.CGWarpMouseCursorPosition(CGPoint(cur_x, cur_y))
                post_mouse_event(kCGEventMouseMoved, cur_x, cur_y, dx=dx, dy=dy)
                print(json.dumps({"type": "position", "x": int(cur_x), "y": int(cur_y), "origin": "SDO"}), flush=True)
                
            elif action == 'click':
                btn_str = cmd.get('button', 'left').lower()
                if btn_str == 'right':
                    post_mouse_event(kCGEventRightMouseDown, cur_x, cur_y, button=kCGMouseButtonRight)
                    time.sleep(0.02)
                    post_mouse_event(kCGEventRightMouseUp, cur_x, cur_y, button=kCGMouseButtonRight)
                else:
                    post_mouse_event(kCGEventLeftMouseDown, cur_x, cur_y, button=kCGMouseButtonLeft)
                    time.sleep(0.02)
                    post_mouse_event(kCGEventLeftMouseUp, cur_x, cur_y, button=kCGMouseButtonLeft)
                    
            elif action == 'scroll':
                dx = int(cmd.get('dx', 0))
                dy = int(cmd.get('dy', 0))
                if cg:
                    evt = cg.CGEventCreateScrollWheelEvent2(None, 0, 2, dy, dx, 0)
                    if evt:
                        cg.CGEventPost(kCGHIDEventTap, evt)
                        cg.CFRelease(evt)
                        
            elif action == 'combo':
                mods = cmd.get('mods', [])
                key_str = cmd.get('key')
                
                active_mods = []
                for mod in mods:
                    if mod in MACOS_NAMED_KEYS:
                        vk = MACOS_NAMED_KEYS[mod]
                        post_key_event(vk, True)
                        active_mods.append(vk)
                        
                time.sleep(0.02)
                
                if key_str:
                    if key_str in MACOS_NAMED_KEYS:
                        tap_key(MACOS_NAMED_KEYS[key_str], delay=0.03)
                    elif len(key_str) == 1 and key_str.lower() in MACOS_KEY_MAP:
                        tap_key(MACOS_KEY_MAP[key_str.lower()], delay=0.03)
                    elif len(key_str) == 1:
                        post_unicode_char(key_str)
                        
                for vk in reversed(active_mods):
                    post_key_event(vk, False)
                    
            elif action == 'type':
                text = cmd.get('text', '')
                for ch in text:
                    if ch in SHIFT_CHARS:
                        base_ch = SHIFT_CHARS[ch]
                        vk = MACOS_KEY_MAP[base_ch]
                        post_key_event(0x38, True) # Shift down
                        tap_key(vk, delay=0.015)
                        post_key_event(0x38, False) # Shift up
                    elif ch.isupper() and ch.lower() in MACOS_KEY_MAP:
                        vk = MACOS_KEY_MAP[ch.lower()]
                        post_key_event(0x38, True) # Shift down
                        tap_key(vk, delay=0.015)
                        post_key_event(0x38, False) # Shift up
                    elif ch in MACOS_KEY_MAP:
                        vk = MACOS_KEY_MAP[ch]
                        tap_key(vk, delay=0.015)
                    else:
                        post_unicode_char(ch)
                    time.sleep(0.01)
                    
        except Exception as e:
            print(json.dumps({"status": "error", "message": str(e)}), flush=True)

if __name__ == '__main__':
    main()
