#!/usr/bin/env python3
"""
DirectInput & Raw Hardware Input Driver for Windows (SDO HAL)
Bypasses OS-level input filtering and high-level virtual keys to control DirectInput,
DirectX, OpenGL/GLFW, and LWJGL games (such as Minecraft) using raw hardware scan codes (PS/2)
and relative raw mouse deltas.
"""

import sys
import os
import time
import json
import math
import string
import threading
import ctypes
from ctypes import wintypes

# Ensure Windows compatibility
if sys.platform != 'win32':
    # Allow loading for testing/syntax checks on non-windows
    pass

user32 = ctypes.windll.user32 if hasattr(ctypes, 'windll') else None
kernel32 = ctypes.windll.kernel32 if hasattr(ctypes, 'windll') else None

# --- Windows Input Structures (SendInput) ---
INPUT_MOUSE = 0
INPUT_KEYBOARD = 1
INPUT_HARDWARE = 2

KEYEVENTF_EXTENDEDKEY = 0x0001
KEYEVENTF_KEYUP = 0x0002
KEYEVENTF_UNICODE = 0x0004
KEYEVENTF_SCANCODE = 0x0008

MOUSEEVENTF_MOVE = 0x0001
MOUSEEVENTF_LEFTDOWN = 0x0002
MOUSEEVENTF_LEFTUP = 0x0004
MOUSEEVENTF_RIGHTDOWN = 0x0008
MOUSEEVENTF_RIGHTUP = 0x0010
MOUSEEVENTF_MIDDLEDOWN = 0x0020
MOUSEEVENTF_MIDDLEUP = 0x0040
MOUSEEVENTF_WHEEL = 0x0800
MOUSEEVENTF_HWHEEL = 0x1000
MOUSEEVENTF_ABSOLUTE = 0x8000

ULONG_PTR = ctypes.c_ulonglong if ctypes.sizeof(ctypes.c_void_p) == 8 else ctypes.c_ulong

class MOUSEINPUT(ctypes.Structure):
    _fields_ = [
        ("dx", wintypes.LONG),
        ("dy", wintypes.LONG),
        ("mouseData", wintypes.DWORD),
        ("dwFlags", wintypes.DWORD),
        ("time", wintypes.DWORD),
        ("dwExtraInfo", ULONG_PTR),
    ]

class KEYBDINPUT(ctypes.Structure):
    _fields_ = [
        ("wVk", wintypes.WORD),
        ("wScan", wintypes.WORD),
        ("dwFlags", wintypes.DWORD),
        ("time", wintypes.DWORD),
        ("dwExtraInfo", ULONG_PTR),
    ]

class HARDWAREINPUT(ctypes.Structure):
    _fields_ = [
        ("uMsg", wintypes.DWORD),
        ("wParamL", wintypes.WORD),
        ("wParamH", wintypes.WORD),
    ]

class _INPUT_UNION(ctypes.Union):
    _fields_ = [
        ("mi", MOUSEINPUT),
        ("ki", KEYBDINPUT),
        ("hi", HARDWAREINPUT),
    ]

class INPUT(ctypes.Structure):
    _fields_ = [
        ("type", wintypes.DWORD),
        ("u", _INPUT_UNION),
    ]

# --- DirectInput / PS/2 Hardware Scan Code Map ---
# Format: char or key_name -> (scan_code, is_extended, requires_shift)
SCANCODE_MAP = {
    # Letters (lower / upper handled by shift flag)
    'a': (0x1E, False, False), 'b': (0x30, False, False), 'c': (0x2E, False, False),
    'd': (0x20, False, False), 'e': (0x12, False, False), 'f': (0x21, False, False),
    'g': (0x22, False, False), 'h': (0x23, False, False), 'i': (0x17, False, False),
    'j': (0x24, False, False), 'k': (0x25, False, False), 'l': (0x26, False, False),
    'm': (0x32, False, False), 'n': (0x31, False, False), 'o': (0x18, False, False),
    'p': (0x19, False, False), 'q': (0x10, False, False), 'r': (0x13, False, False),
    's': (0x1F, False, False), 't': (0x14, False, False), 'u': (0x16, False, False),
    'v': (0x2F, False, False), 'w': (0x11, False, False), 'x': (0x2D, False, False),
    'y': (0x15, False, False), 'z': (0x2C, False, False),

    # Numbers
    '1': (0x02, False, False), '2': (0x03, False, False), '3': (0x04, False, False),
    '4': (0x05, False, False), '5': (0x06, False, False), '6': (0x07, False, False),
    '7': (0x08, False, False), '8': (0x09, False, False), '9': (0x0A, False, False),
    '0': (0x0B, False, False),

    # Number row symbols (Shift + Number)
    '!': (0x02, False, True), '@': (0x03, False, True), '#': (0x04, False, True),
    '$': (0x05, False, True), '%': (0x06, False, True), '^': (0x07, False, True),
    '&': (0x08, False, True), '*': (0x09, False, True), '(': (0x0A, False, True),
    ')': (0x0B, False, True),

    # Common punctuation
    ' ':  (0x39, False, False), '\n': (0x1C, False, False), '\t': (0x0F, False, False),
    '\b': (0x0E, False, False),
    '-':  (0x0C, False, False), '_':  (0x0C, False, True),
    '=':  (0x0D, False, False), '+':  (0x0D, False, True),
    '[':  (0x1A, False, False), '{':  (0x1A, False, True),
    ']':  (0x1B, False, False), '}':  (0x1B, False, True),
    ';':  (0x27, False, False), ':':  (0x27, False, True),
    "'":  (0x28, False, False), '"':  (0x28, False, True),
    '`':  (0x29, False, False), '~':  (0x29, False, True),
    '\\': (0x2B, False, False), '|':  (0x2B, False, True),
    ',':  (0x33, False, False), '<':  (0x33, False, True),
    '.':  (0x34, False, False), '>':  (0x34, False, True),
    '/':  (0x35, False, False), '?':  (0x35, False, True),
}

# Uppercase letters
for ch in string.ascii_uppercase:
    lower_code, ext, _ = SCANCODE_MAP[ch.lower()]
    SCANCODE_MAP[ch] = (lower_code, ext, True)

# Named Keys (KEY_*)
NAMED_KEY_MAP = {
    # Movement & Gaming Controls (Minecraft / FPS)
    'KEY_W': (0x11, False), 'KEY_A': (0x1E, False), 'KEY_S': (0x1F, False), 'KEY_D': (0x20, False),
    'KEY_SPACE': (0x39, False), 'KEY_ENTER': (0x1C, False), 'KEY_TAB': (0x0F, False),
    'KEY_BACKSPACE': (0x0E, False), 'KEY_ESC': (0x01, False),
    'KEY_LEFTSHIFT': (0x2A, False), 'KEY_RIGHTSHIFT': (0x36, False),
    'KEY_LEFTCTRL': (0x1D, False), 'KEY_RIGHTCTRL': (0x1D, True),
    'KEY_LEFTALT': (0x38, False), 'KEY_RIGHTALT': (0x38, True),
    'KEY_LEFTMETA': (0x5B, True), 'KEY_RIGHTMETA': (0x5C, True),
    'KEY_E': (0x12, False), 'KEY_Q': (0x10, False), 'KEY_F': (0x21, False),
    'KEY_C': (0x2E, False), 'KEY_R': (0x13, False), 'KEY_T': (0x14, False),
    'KEY_G': (0x22, False), 'KEY_Z': (0x2C, False), 'KEY_X': (0x2D, False),
    'KEY_V': (0x2F, False), 'KEY_B': (0x30, False), 'KEY_M': (0x32, False),

    # Number keys
    'KEY_1': (0x02, False), 'KEY_2': (0x03, False), 'KEY_3': (0x04, False),
    'KEY_4': (0x05, False), 'KEY_5': (0x06, False), 'KEY_6': (0x07, False),
    'KEY_7': (0x08, False), 'KEY_8': (0x09, False), 'KEY_9': (0x0A, False),
    'KEY_0': (0x0B, False),

    # Navigation & Arrows
    'KEY_UP': (0x48, True), 'KEY_DOWN': (0x50, True), 'KEY_LEFT': (0x4B, True), 'KEY_RIGHT': (0x4D, True),
    'KEY_INSERT': (0x52, True), 'KEY_DELETE': (0x53, True),
    'KEY_HOME': (0x47, True), 'KEY_END': (0x4F, True),
    'KEY_PAGEUP': (0x49, True), 'KEY_PAGEDOWN': (0x51, True),
    'KEY_CAPSLOCK': (0x3A, False), 'KEY_NUMLOCK': (0x45, False), 'KEY_SCROLLLOCK': (0x46, False),
    'KEY_PAUSE': (0x45, True),

    # Function keys
    'KEY_F1': (0x3B, False), 'KEY_F2': (0x3C, False), 'KEY_F3': (0x3D, False),
    'KEY_F4': (0x3E, False), 'KEY_F5': (0x3F, False), 'KEY_F6': (0x40, False),
    'KEY_F7': (0x41, False), 'KEY_F8': (0x42, False), 'KEY_F9': (0x43, False),
    'KEY_F10': (0x44, False), 'KEY_F11': (0x57, False), 'KEY_F12': (0x58, False),

    # Punctuation & Symbols
    'KEY_DOT': (0x34, False), 'KEY_COMMA': (0x33, False), 'KEY_SEMICOLON': (0x27, False),
    'KEY_APOSTROPHE': (0x28, False), 'KEY_GRAVE': (0x29, False),
    'KEY_MINUS': (0x0C, False), 'KEY_EQUAL': (0x0D, False),
    'KEY_SLASH': (0x35, False), 'KEY_BACKSLASH': (0x2B, False),
    'KEY_LEFTBRACE': (0x1A, False), 'KEY_RIGHTBRACE': (0x1B, False),
}

# --- Windows SendInput Wrappers ---
def send_raw_input(inputs):
    if not user32 or not inputs:
        return
    n = len(inputs)
    arr = (INPUT * n)(*inputs)
    user32.SendInput(n, arr, ctypes.sizeof(INPUT))

def send_scancode_key(scan_code, is_extended=False, is_down=True):
    flags = KEYEVENTF_SCANCODE
    if is_extended:
        flags |= KEYEVENTF_EXTENDEDKEY
    if not is_down:
        flags |= KEYEVENTF_KEYUP
    
    inp = INPUT()
    inp.type = INPUT_KEYBOARD
    inp.u.ki.wVk = 0
    inp.u.ki.wScan = scan_code
    inp.u.ki.dwFlags = flags
    inp.u.ki.time = 0
    inp.u.ki.dwExtraInfo = 0
    send_raw_input([inp])

def tap_scancode_key(scan_code, is_extended=False, delay=0.02):
    send_scancode_key(scan_code, is_extended=is_extended, is_down=True)
    if delay > 0:
        time.sleep(delay)
    send_scancode_key(scan_code, is_extended=is_extended, is_down=False)

def send_unicode_char(ch):
    inp_down = INPUT()
    inp_down.type = INPUT_KEYBOARD
    inp_down.u.ki.wVk = 0
    inp_down.u.ki.wScan = ord(ch)
    inp_down.u.ki.dwFlags = KEYEVENTF_UNICODE
    
    inp_up = INPUT()
    inp_up.type = INPUT_KEYBOARD
    inp_up.u.ki.wVk = 0
    inp_up.u.ki.wScan = ord(ch)
    inp_up.u.ki.dwFlags = KEYEVENTF_UNICODE | KEYEVENTF_KEYUP
    
    send_raw_input([inp_down, inp_up])

def send_mouse_move_relative(dx, dy):
    inp = INPUT()
    inp.type = INPUT_MOUSE
    inp.u.mi.dx = int(dx)
    inp.u.mi.dy = int(dy)
    inp.u.mi.mouseData = 0
    inp.u.mi.dwFlags = MOUSEEVENTF_MOVE
    inp.u.mi.time = 0
    inp.u.mi.dwExtraInfo = 0
    send_raw_input([inp])

def send_mouse_click(button='left', is_down=True, is_up=True):
    flags = 0
    if button == 'right':
        if is_down: flags |= MOUSEEVENTF_RIGHTDOWN
        if is_up: flags |= MOUSEEVENTF_RIGHTUP
    elif button == 'middle':
        if is_down: flags |= MOUSEEVENTF_MIDDLEDOWN
        if is_up: flags |= MOUSEEVENTF_MIDDLEUP
    else:
        if is_down: flags |= MOUSEEVENTF_LEFTDOWN
        if is_up: flags |= MOUSEEVENTF_LEFTUP
        
    inp = INPUT()
    inp.type = INPUT_MOUSE
    inp.u.mi.dx = 0
    inp.u.mi.dy = 0
    inp.u.mi.mouseData = 0
    inp.u.mi.dwFlags = flags
    inp.u.mi.time = 0
    inp.u.mi.dwExtraInfo = 0
    send_raw_input([inp])

def send_mouse_scroll(dx=0, dy=0):
    inputs = []
    if dy != 0:
        inp_y = INPUT()
        inp_y.type = INPUT_MOUSE
        inp_y.u.mi.dx = 0
        inp_y.u.mi.dy = 0
        inp_y.u.mi.mouseData = int(dy * 120)
        inp_y.u.mi.dwFlags = MOUSEEVENTF_WHEEL
        inputs.append(inp_y)
    if dx != 0:
        inp_x = INPUT()
        inp_x.type = INPUT_MOUSE
        inp_x.u.mi.dx = 0
        inp_x.u.mi.dy = 0
        inp_x.u.mi.mouseData = int(dx * 120)
        inp_x.u.mi.dwFlags = MOUSEEVENTF_HWHEEL
        inputs.append(inp_x)
    if inputs:
        send_raw_input(inputs)

def get_current_cursor_pos():
    if user32:
        pt = wintypes.POINT()
        if user32.GetCursorPos(ctypes.byref(pt)):
            return pt.x, pt.y
    return 0, 0

def set_current_cursor_pos(x, y):
    if user32:
        user32.SetCursorPos(int(x), int(y))

# --- Main Driver Loop ---
def main():
    screen_w = user32.GetSystemMetrics(0) if user32 else 1920
    screen_h = user32.GetSystemMetrics(1) if user32 else 1080
    
    cur_x, cur_y = get_current_cursor_pos()
    
    # Reflex arc state
    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": cur_x, "y": 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
                
            real_x, real_y = get_current_cursor_pos()
            cur_x, cur_y = real_x, real_y
            
            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:
                    set_current_cursor_pos(tx, ty)
                    cur_x, cur_y = tx, ty
                    print(json.dumps({"type": "position", "x": cur_x, "y": 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
                    send_mouse_move_relative(round(step_dx), round(step_dy))
                    new_x, new_y = get_current_cursor_pos()
                    cur_x, cur_y = new_x, new_y
                    print(json.dumps({"type": "position", "x": cur_x, "y": cur_y, "origin": "SDO"}), flush=True)
                else:
                    set_current_cursor_pos(tx, ty)
                    cur_x, cur_y = tx, ty
                    print(json.dumps({"type": "position", "x": cur_x, "y": 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 = tx, ty
                    move_duration = max(0.05, duration)
                    
            elif action == 'move':
                with state_lock:
                    target_x = None
                    target_y = None
                dx = cmd.get('dx', 0)
                dy = cmd.get('dy', 0)
                # Send raw relative DirectInput movement
                send_mouse_move_relative(dx, dy)
                cur_x, cur_y = get_current_cursor_pos()
                print(json.dumps({"type": "position", "x": cur_x, "y": cur_y, "origin": "SDO"}), flush=True)
                
            elif action == 'click':
                btn = cmd.get('button', 'left').lower()
                send_mouse_click(btn, is_down=True, is_up=True)
                
            elif action == 'scroll':
                dx = cmd.get('dx', 0)
                dy = cmd.get('dy', 0)
                send_mouse_scroll(dx, dy)
                
            elif action == 'combo':
                mods = cmd.get('mods', [])
                key_str = cmd.get('key')
                
                # Press modifier keys using raw scan codes
                active_mods = []
                for mod in mods:
                    if mod in NAMED_KEY_MAP:
                        sc, ext = NAMED_KEY_MAP[mod]
                        send_scancode_key(sc, is_extended=ext, is_down=True)
                        active_mods.append((sc, ext))
                        
                time.sleep(0.02)
                
                # Press primary key using raw scan code
                if key_str:
                    if key_str in NAMED_KEY_MAP:
                        sc, ext = NAMED_KEY_MAP[key_str]
                        tap_scancode_key(sc, is_extended=ext, delay=0.03)
                    elif len(key_str) == 1 and key_str.lower() in SCANCODE_MAP:
                        sc, ext, _ = SCANCODE_MAP[key_str.lower()]
                        tap_scancode_key(sc, is_extended=ext, delay=0.03)
                    elif len(key_str) == 1:
                        send_unicode_char(key_str)
                        
                # Release modifier keys
                for sc, ext in reversed(active_mods):
                    send_scancode_key(sc, is_extended=ext, is_down=False)
                    
            elif action == 'type':
                text = cmd.get('text', '')
                for ch in text:
                    if ch in SCANCODE_MAP:
                        sc, ext, req_shift = SCANCODE_MAP[ch]
                        if req_shift:
                            send_scancode_key(0x2A, is_extended=False, is_down=True) # LSHIFT down
                            time.sleep(0.01)
                        tap_scancode_key(sc, is_extended=ext, delay=0.015)
                        if req_shift:
                            send_scancode_key(0x2A, is_extended=False, is_down=False) # LSHIFT up
                    else:
                        send_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()
