import pyautogui
import time
import math
import os
import ctypes
from PIL import Image, ImageDraw, ImageFont
import numpy as np
from colorsys import rgb_to_hsv

# ---------------- KEY DEFINITIONS ----------------
e = 0x12
enter = 0x1C
g = 0x22
shift = 0x2A
w = 0x11
d = 0x20
three = 0x04
PUL = ctypes.POINTER(ctypes.c_ulong)

class KeyBdInput(ctypes.Structure):
    _fields_ = [("wVk", ctypes.c_ushort),
                ("wScan", ctypes.c_ushort),
                ("dwFlags", ctypes.c_ulong),
                ("time", ctypes.c_ulong),
                ("dwExtraInfo", PUL)]


class HardwareInput(ctypes.Structure):
    _fields_ = [("uMsg", ctypes.c_ulong),
                ("wParamL", ctypes.c_short),
                ("wParamH", ctypes.c_ushort)]


class MouseInput(ctypes.Structure):
    _fields_ = [("dx", ctypes.c_long),
                ("dy", ctypes.c_long),
                ("mouseData", ctypes.c_ulong),
                ("dwFlags", ctypes.c_ulong),
                ("time", ctypes.c_ulong),
                ("dwExtraInfo", PUL)]


class Input_I(ctypes.Union):
    _fields_ = [("ki", KeyBdInput),
                ("mi", MouseInput),
                ("hi", HardwareInput)]


class Input(ctypes.Structure):
    _fields_ = [("type", ctypes.c_ulong),
                ("ii", Input_I)]
def PressKey(hexKeyCode):
    extra = ctypes.c_ulong(0)
    ii_ = Input_I()
    ii_.ki = KeyBdInput(0, hexKeyCode, 0x0008, 0, ctypes.pointer(extra))
    x = Input(ctypes.c_ulong(1), ii_)
    ctypes.windll.user32.SendInput(1, ctypes.pointer(x), ctypes.sizeof(x))


def ReleaseKey(hexKeyCode):
    extra = ctypes.c_ulong(0)
    ii_ = Input_I()
    ii_.ki = KeyBdInput(0, hexKeyCode, 0x0008 | 0x0002, 0, ctypes.pointer(extra))
    x = Input(ctypes.c_ulong(1), ii_)
    ctypes.windll.user32.SendInput(1, ctypes.pointer(x), ctypes.sizeof(x))

def MouseMoveTo(x: object, y: object) -> object:
    extra = ctypes.c_ulong(0)
    ii_ = Input_I()
    ii_.mi = MouseInput(x, y, 0, 0x0001, 0, ctypes.pointer(extra))

    command = Input(ctypes.c_ulong(0), ii_)
    ctypes.windll.user32.SendInput(1, ctypes.pointer(command), ctypes.sizeof(command))

def click_down(button):
    if button == "left":
        extra = ctypes.c_ulong(0)
        ii_ = Input_I()
        ii_.mi = MouseInput(0, 0, 0, 0x0002, 0, ctypes.pointer(extra))
        x = Input(ctypes.c_ulong(0), ii_)
        ctypes.windll.user32.SendInput(1, ctypes.pointer(x), ctypes.sizeof(x))
    else:
        extra = ctypes.c_ulong(0)
        ii_ = Input_I()
        ii_.mi = MouseInput(0, 0, 0, 0x0008, 0, ctypes.pointer(extra))
        x = Input(ctypes.c_ulong(0), ii_)
        ctypes.windll.user32.SendInput(1, ctypes.pointer(x), ctypes.sizeof(x))


def click_up(button):
    if button == "left":
        extra = ctypes.c_ulong(0)
        ii_ = Input_I()
        ii_.mi = MouseInput(0, 0, 0, 0x0004, 0, ctypes.pointer(extra))
        x = Input(ctypes.c_ulong(0), ii_)
        ctypes.windll.user32.SendInput(1, ctypes.pointer(x), ctypes.sizeof(x))
    else:
        extra = ctypes.c_ulong(0)
        ii_ = Input_I()
        ii_.mi = MouseInput(0, 0, 0, 0x0010, 0, ctypes.pointer(extra))
        x = Input(ctypes.c_ulong(0), ii_)
        ctypes.windll.user32.SendInput(1, ctypes.pointer(x), ctypes.sizeof(x))


def click(button, duration=0.05):
    if True:
        click_down(button)
        t.sleep(duration)
        click_up(button)


def set_pos(x, y):
    x = 1 + int(x * 65536. / 1920.)
    y = 1 + int(y * 65536. / 1080.)
    extra = ctypes.c_ulong(0)
    ii_ = Input_I()
    ii_.mi = MouseInput(x, y, 0, (0x0001 | 0x8000), 0, ctypes.pointer(extra))
    command = Input(ctypes.c_ulong(0), ii_)
    ctypes.windll.user32.SendInput(1, ctypes.pointer(command), ctypes.sizeof(command))


def imgcheck(im1, im2):
    im1 = cv2.cvtColor(im1, cv2.COLOR_BGR2GRAY)
    im2 = cv2.cvtColor(im2, cv2.COLOR_BGR2GRAY)
    height, width = im1.shape
    diff = cv2.subtract(im1, im2)
    err = np.sum(diff ** 2)
    mse = err / (float(height * width))
    return mse



def press_d():
    PressKey(d)
    time.sleep(0.1)
    ReleaseKey(d)
    print(">>> PRESS D <<<")

def press_enter():
    PressKey(enter)
    time.sleep(0.1)
    ReleaseKey(enter)
    print(">>> PRESS ENTER <<<")

# ---------------- DETECTION CONFIG ----------------
# --- CONFIG ---
BASE_RES = (1920, 1080)

# --- dynamically get current screen resolution ---
SCREEN_W, SCREEN_H = pyautogui.size()
SCREEN_REGION = (0, 0, SCREEN_W, SCREEN_H)

# --- scaling helper ---
def scale(x, y):
    """Scale coordinates based on current vs base resolution."""
    return (int(x * SCREEN_W / BASE_RES[0]), int(y * SCREEN_H / BASE_RES[1]))

# --- scaled parameters ---
CROP_SIZE = int(6 * SCREEN_W / BASE_RES[0])
CENTER = scale(960, 552)
LOCK_OUTER_OFFSET = int(26 * SCREEN_W / BASE_RES[0])

# --- anchor color points for each ring (scaled automatically) ---
left1 = scale(892, 552)
left2 = scale(803, 552)
left3 = scale(716, 552)
left4 = scale(629, 552)
ring_radii = [math.dist(CENTER, p) for p in [left1, left2, left3, left4]]

# --- helpers ---
def crop_center(img, x, y, size):
    half = size // 2
    return img.crop((x - half, y - half, x + half, y + half))

def get_dominant_color(patch):
    np_img = np.array(patch).astype(float)
    avg = np.mean(np_img.reshape(-1, 3), axis=0)
    r, g, b = avg / 255.0
    h, s, v = rgb_to_hsv(r, g, b)
    h_deg = h * 360

    # Add brightness threshold (v) and saturation (s) filters
    if (h_deg < 30 or h_deg > 320) and s > 0.35 and v > 0.35:
        return "pink"
    elif 40 < h_deg < 75 and s > 0.35 and v > 0.35:
        return "yellow"
    elif 190 < h_deg < 250 and s > 0.4 and v > 0.4:
        return "blue"
    else:
        return "unknown"

def should_press_d(lock_color, circle_color):
    if lock_color == "unknown" and circle_color in ["pink", "yellow", "blue"]:
        return False
    if lock_color in ["pink", "yellow", "blue"] and circle_color == "unknown":
        return False
    if lock_color == "unknown" and circle_color == "unknown":
        return False
    if lock_color != circle_color:
        return True
    return False

def ring_points_from_color_radius(cx, cy, color_radius, ring_index, lock_offset):
    locks, colors = [], []
    for i in range(12):
        angle_deg = i * 30
        angle_rad = math.radians(angle_deg)
        col_x = cx + color_radius * math.cos(angle_rad)
        col_y = cy + color_radius * math.sin(angle_rad)
        colors.append((f"ring{ring_index}_color{i+1}", (int(round(col_x)), int(round(col_y)))))
        lr = color_radius + lock_offset
        lock_x = cx + lr * math.cos(angle_rad)
        lock_y = cy + lr * math.sin(angle_rad)
        locks.append((f"ring{ring_index}_lock{i+1}", (int(round(lock_x)), int(round(lock_y)))))
    return colors, locks

def press_d():
    pyautogui.press("d")

def press_enter():
    pyautogui.press("enter")

def draw_debug_overlay(img, ring_idx, colors, locks, color_map):
    overlay = img.copy()
    draw = ImageDraw.Draw(overlay)

    for (lock_name, (lx, ly)) in locks:
        lock_col = color_map.get(lock_name, {}).get("lock", "unknown")
        circle_col = color_map.get(lock_name, {}).get("circle", "unknown")

        # color for lock point
        clr = {
            "pink": (255, 100, 255),
            "yellow": (255, 255, 80),
            "blue": (80, 120, 255),
            "unknown": (180, 180, 180),
        }.get(lock_col, (255, 255, 255))
        draw.ellipse((lx - 5, ly - 5, lx + 5, ly + 5), outline=clr, width=2)
        draw.text((lx + 6, ly - 4), lock_col, fill=clr)

    for (_, (cx, cy)) in colors:
        draw.rectangle((cx - 4, cy - 4, cx + 4, cy + 4), outline=(0, 255, 0), width=1)

    overlay.save(f"ring{ring_idx+1}_debug.png")
    print(f"🖼️ Saved overlay: ring{ring_idx+1}_debug.png")




def save_initial_overlay():
    """Draw all color/lock points for all rings once at start."""
    base = Image.new("RGB", (1920, 1080), (0, 0, 0))
    draw = ImageDraw.Draw(base)

    for ring_idx, (colors, locks) in enumerate(zip(all_colors, all_locks), start=1):
        for (_, (cx, cy)) in colors:
            draw.rectangle((cx - 3, cy - 3, cx + 3, cy + 3), outline=(0, 255, 0), width=1)

        for (lname, (lx, ly)) in locks:
            draw.ellipse((lx - 4, ly - 4, lx + 4, ly + 4), outline=(255, 0, 0), width=2)

        # optional ring circle outline
        r = ring_radii[ring_idx - 1]
        bbox = (CENTER[0] - r, CENTER[1] - r, CENTER[0] + r, CENTER[1] + r)
        draw.ellipse(bbox, outline=(100, 100, 100), width=1)

    draw.text((20, 20), "All ring overlays (Green=color, Red=lock)", fill=(255, 255, 255))
    base.save("overlay_initial.png")
    print("🖼️ Saved initial overlay: overlay_initial.png")
# --- MAIN LOOP (fixed) ---
def lockpick_house():
    print("""
    =====================================================================
    THIS IS A FUNCTION CREATED FOR GTA 5 RP LEGACY ROLEPLAY SERVERS
    WHERE YOU HAVE TO LOCKPICK HOUSES AT NIGHT TO ROB THEM.

    ⚠️ Originally built for 1920x1080 (landscape) resolution. 
    It may or may not work correctly for other resolutions.

    You have to write your own game-switching function that switches
    to the game and uses your lockpick. The lockpicking will start
    automatically after 2 seconds.

    ⏳ If you need more time, use a separate sleep() before calling this.

    Made by Piyush the Great AKA Demol 💀
    =====================================================================
    """)
    # --- precompute all ring coordinates ---
    all_colors, all_locks = [], []
    for idx, r in enumerate(ring_radii, start=1):
        colors, locks = ring_points_from_color_radius(CENTER[0], CENTER[1], r, idx, LOCK_OUTER_OFFSET)
        all_colors.append(colors)
        all_locks.append(locks)

    # --- MAIN LOOP ---
    print("Starting multi-ring loop (4 rings)... Press Ctrl+C to stop.")
    pyautogui.keyDown("alt")
    pyautogui.press("tab")
    pyautogui.keyUp("alt")
    time.sleep(2)
    for ring_idx in range(4):

        print(f"\n===== Checking Ring {ring_idx + 1} =====")
        colors = all_colors[ring_idx]
        locks = all_locks[ring_idx]

        while True:
            img_live = pyautogui.screenshot(region=SCREEN_REGION).convert("RGB")
            # Save overlay once at start
            mismatch_found = False
            color_map = {}

            for (lock_name, (lx, ly)) in locks:
                closest_color = min(colors, key=lambda c: abs(c[1][0] - lx) + abs(c[1][1] - ly))
                _, (cx, cy) = closest_color

                lock_patch = crop_center(img_live, lx, ly, CROP_SIZE)
                color_patch = crop_center(img_live, cx, cy, CROP_SIZE)

                lock_col = get_dominant_color(lock_patch)
                circle_col = get_dominant_color(color_patch)

                color_map[lock_name] = {"lock": lock_col, "circle": circle_col}
                print(f"{lock_name}: lock={lock_col}, circle={circle_col}")

                if should_press_d(lock_col, circle_col):
                    print(f"Mismatch → Pressing D and restarting Ring {ring_idx + 1}")
                    press_d()
                    mismatch_found = True
                    break

            # ✅ Always safe: will not KeyError even if partial color_map

            if not mismatch_found:
                print(f"✅ Ring {ring_idx + 1} OK — moving to next ring...")
                press_enter()
                break

            time.sleep(0.4)

def main_cli():
    # optionally parse args (argparse) for start_delay, etc.
    lockpick_house()
