#!/usr/bin/env python3
"""
Hand Landmarks on a static image using MediaPipe Tasks.

Usage:
  python hand_landmarker_cli.py --image hand.png --model hand_landmarker.task --max_hands 2 --out annotated.png

What it does:
  • Loads the MediaPipe Hand Landmarker model (.task file)
  • Runs landmark detection on a single image
  • Prints handedness and 21 landmark coords for each detected hand
  • Saves an annotated image with landmarks and connections
"""

import argparse
import sys
from pathlib import Path

import cv2
import numpy as np
import mediapipe as mp

# MediaPipe Tasks API aliases
BaseOptions = mp.tasks.BaseOptions
HandLandmarker = mp.tasks.vision.HandLandmarker
HandLandmarkerOptions = mp.tasks.vision.HandLandmarkerOptions
VisionRunningMode = mp.tasks.vision.RunningMode

# Landmark connection topology (same as mp.solutions.hands.HAND_CONNECTIONS, copied to avoid extra dependency)
HAND_CONNECTIONS = [
    (0,1),(1,2),(2,3),(3,4),          # Thumb
    (0,5),(5,6),(6,7),(7,8),          # Index
    (5,9),(9,10),(10,11),(11,12),     # Middle
    (9,13),(13,14),(14,15),(15,16),   # Ring
    (13,17),(17,18),(18,19),(19,20),  # Pinky
    (0,17)                            # Palm base to pinky base
]

def draw_landmarks(image_bgr: np.ndarray, landmarks_norm: list):
    """
    Draws landmarks and connections on a BGR image.
    `landmarks_norm` is a list of normalized (x,y,z) MediaPipe landmarks (0..1).
    """
    h, w = image_bgr.shape[:2]

    # Convert normalized to pixel coords
    pts = []
    for lm in landmarks_norm:
        x = int(lm.x * w)
        y = int(lm.y * h)
        pts.append((x, y))

    # Draw connections
    for a, b in HAND_CONNECTIONS:
        if 0 <= a < len(pts) and 0 <= b < len(pts):
            cv2.line(image_bgr, pts[a], pts[b], (0, 255, 0), 2, cv2.LINE_AA)

    # Draw keypoints
    for i, (x, y) in enumerate(pts):
        cv2.circle(image_bgr, (x, y), 3, (255, 255, 255), -1, cv2.LINE_AA)
        cv2.circle(image_bgr, (x, y), 2, (0, 0, 255), -1, cv2.LINE_AA)

def main():
    ap = argparse.ArgumentParser(description="MediaPipe Hand Landmarker (static image)")
    ap.add_argument("--image", required=True, help="Path to an input image (e.g., hand.jpg)")
    ap.add_argument("--model", default="hand_landmarker.task", help="Path to MediaPipe .task model")
    ap.add_argument("--max_hands", type=int, default=2, help="Maximum hands to detect")
    ap.add_argument("--out", default="annotated.png", help="Output path for annotated image")
    args = ap.parse_args()

    img_path = Path(args.image)
    if not img_path.exists():
        print(f"[ERROR] Image not found: {img_path}", file=sys.stderr)
        sys.exit(1)

    model_path = Path(args.model)
    if not model_path.exists():
        print(f"[ERROR] Model not found: {model_path}", file=sys.stderr)
        print("Download the model bundle (.task) and point --model to it.", file=sys.stderr)
        sys.exit(2)

    # Load image for MP and for drawing
    mp_image = mp.Image.create_from_file(str(img_path))
    image_bgr = cv2.imread(str(img_path))
    if image_bgr is None:
        print(f"[ERROR] Could not read image with OpenCV: {img_path}", file=sys.stderr)
        sys.exit(3)

    # Configure and run the landmarker
    options = HandLandmarkerOptions(
        base_options=BaseOptions(model_asset_path=str(model_path)),
        running_mode=VisionRunningMode.IMAGE,
        num_hands=args.max_hands,
        min_hand_detection_confidence=0.5,
        min_hand_presence_confidence=0.5,
        min_tracking_confidence=0.5
    )

    with HandLandmarker.create_from_options(options) as landmarker:
        result = landmarker.detect(mp_image)

    # Print results
    if not result.hand_landmarks:
        print("No hands detected.")
    else:
        for i, (handedness, lms, world_lms) in enumerate(
            zip(result.handedness, result.hand_landmarks, result.hand_world_landmarks)
        ):
            label = handedness[0].category_name if handedness else "Unknown"
            score = handedness[0].score if handedness else 0.0
            print(f"\nHand #{i+1}: {label} (score {score:.3f})")
            for idx, lm in enumerate(lms):
                print(f"  L{idx:02d}: x={lm.x:.3f} y={lm.y:.3f} z={lm.z:.3f}")

            # Draw
            draw_landmarks(image_bgr, lms)
            # Put label
            cv2.putText(image_bgr, f"{label}", (10, 30 + i*30), cv2.FONT_HERSHEY_SIMPLEX, 1.0, (0,255,0), 2, cv2.LINE_AA)

    # Save annotated image
    cv2.imwrite(str(args.out), image_bgr)
    print(f"\nSaved annotated image to: {args.out}")

if __name__ == "__main__":
    main()
