#!/usr/bin/env python3
"""Fly the Delta Saber toward the tutorial waypoint by chasing the yellow
off-screen direction arrow.

Detection: no PIL/numpy in the box, so the frame comes from `convert ... txt:-`.
Two things make it fast AND correct:
  * `-sample` (point sampling, not `-resize`/`-scale` averaging) — a 1-px-thin
    glyph keeps its true colour, so the exact colour predicate still fires while
    the dump shrinks ~9x (3.5s -> 0.65s).
  * shape, not just colour — the instruction-frame bars and the throttle marker
    are the same dim yellow, so the arrow is the largest blob that is roughly as
    tall as it is wide, with the fixed throttle marker blacklisted by position.
A ~1.1 s control loop is fast enough to converge; the earlier ~6 s loop was not.
"""
import re, subprocess, sys, time

X0, Y0, W, H = 80, 60, 820, 640      # flight view (the arrow also rides the bottom edge)
K = 3.03                             # 1/0.33 sample factor
CX, CY = 640, 405                    # crosshair
THROTTLE = (382, 456)                # fixed yellow decoy on the speed gauge
PX = re.compile(r"^(\d+),(\d+):.*?srgb\((\d+),(\d+),(\d+)\)")


def pad(*a):
    subprocess.run(["/opt/sylph/vgamepad.py", *a], check=False)


def arrow(png="/tmp/ap.png"):
    subprocess.run(["/opt/sylph/screenshot.sh", png], check=False,
                   stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
    out = subprocess.run(["convert", png, "-crop", f"{W}x{H}+{X0}+{Y0}", "+repage",
                          "-sample", "33%", "txt:-"], capture_output=True, text=True).stdout
    pts = set()
    for l in out.splitlines()[1:]:
        m = PX.match(l)
        if not m:
            continue
        x, y, r, g, b = (int(v) for v in m.groups())
        if r >= 60 and g >= 48 and b <= 0.35 * g and (r - g) <= 0.45 * r:
            pts.add((x, y))
    seen, best = set(), None
    for p in pts:
        if p in seen:
            continue
        st, c = [p], []
        seen.add(p)
        while st:
            x, y = st.pop(); c.append((x, y))
            for dx in (-1, 0, 1):
                for dy in (-1, 0, 1):
                    q = (x + dx, y + dy)
                    if q in pts and q not in seen:
                        seen.add(q); st.append(q)
        xs = [q[0] for q in c]; ys = [q[1] for q in c]
        cx, cy = X0 + sum(xs) / len(c) * K, Y0 + sum(ys) / len(c) * K
        if abs(cx - THROTTLE[0]) < 30 and abs(cy - THROTTLE[1]) < 30:
            continue
        if len(c) < 8:
            continue
        if best is None or len(c) > best[2]:
            best = (cx, cy, len(c))
    return best


def main():
    steps = int(sys.argv[1]) if len(sys.argv) > 1 else 25
    centred = 0
    for i in range(steps):
        a = arrow()
        if a is None:
            print(f"{i:2d}: no arrow -> boost (target ahead)")
            pad("trig", "RT", "0.55"); time.sleep(1.2); pad("trig", "RT", "0")
            centred += 1
            if centred >= 6:
                print("   arrival likely — stopping"); return 0
            continue
        x, y, n = a
        dx, dy = x - CX, y - CY
        if abs(dx) < 70 and abs(dy) < 70:
            centred += 1
            print(f"{i:2d}: arrow ({x:.0f},{y:.0f}) centred -> boost")
            pad("trig", "RT", "0.55"); time.sleep(1.2); pad("trig", "RT", "0")
            continue
        centred = 0
        lx = max(-1.0, min(1.0, dx / 200))
        ly = max(-1.0, min(1.0, dy / 160))
        print(f"{i:2d}: arrow ({x:.0f},{y:.0f}) n={n} -> LX {lx:+.2f} LY {ly:+.2f}")
        pad("axis", "LX", f"{lx:.2f}"); pad("axis", "LY", f"{ly:.2f}")
        time.sleep(0.45)
        pad("axis", "LX", "0"); pad("axis", "LY", "0")
    print("steps exhausted")
    return 1


sys.exit(main())
