"""
MediaPipe 손 랜드마크 예제 (수업용)
──────────────────────────────────
필요한 것 : 파이썬 3.10~3.12, 웹캠, 인터넷(첫 실행 때 모델 자동 다운로드)
설치      : pip install mediapipe opencv-python
실행      : IDLE에서 열고 F5  /  종료는 ESC 키
"""

import os
import time
import urllib.request

import cv2
import mediapipe as mp

# ──────────────────────────────────────────────
# 설정값 모음 — 이 블록만 바꿔도 동작이 달라집니다
# ──────────────────────────────────────────────
NUM_HANDS = 1               # [수정 지점] 인식할 손 개수 (1 또는 2)
DOT_COLOR = (0, 255, 0)     # [수정 지점] 점 색깔 (파랑, 초록, 빨강) 각 0~255
DOT_SIZE = 5                # [수정 지점] 점 크기 (픽셀)
LINE_COLOR = (255, 160, 0)  # [수정 지점] 뼈대 선 색깔
CAM_INDEX = 0               # [수정 지점] 카메라가 안 켜지면 1, 2로

MODEL_URL = ("https://storage.googleapis.com/mediapipe-models/"
             "hand_landmarker/hand_landmarker/float16/1/hand_landmarker.task")
MODEL_FILE = "hand_landmarker.task"

# 손 뼈대: 이어 그릴 랜드마크 번호 쌍 (0=손목, 4=엄지 끝, 8=검지 끝, 12=중지 끝...)
HAND_CONNECTIONS = [
    (0, 1), (1, 2), (2, 3), (3, 4),          # 엄지
    (0, 5), (5, 6), (6, 7), (7, 8),          # 검지
    (5, 9), (9, 10), (10, 11), (11, 12),     # 중지
    (9, 13), (13, 14), (14, 15), (15, 16),   # 약지
    (13, 17), (17, 18), (18, 19), (19, 20),  # 새끼
    (0, 17),                                 # 손바닥 아래쪽
]


def download_model():
    """모델 파일이 없으면 인터넷에서 한 번만 받아 옵니다."""
    if os.path.exists(MODEL_FILE):
        return
    print("모델 내려받는 중... (처음 한 번만)")
    urllib.request.urlretrieve(MODEL_URL, MODEL_FILE)
    print("완료:", MODEL_FILE)


def draw_hand(frame, landmarks, w, h):
    """랜드마크 21개를 선과 점으로 그립니다. 좌표는 0~1 비율값이라 화면 크기를 곱합니다."""
    for a, b in HAND_CONNECTIONS:
        pa = (int(landmarks[a].x * w), int(landmarks[a].y * h))
        pb = (int(landmarks[b].x * w), int(landmarks[b].y * h))
        cv2.line(frame, pa, pb, LINE_COLOR, 2)
    for p in landmarks:
        cv2.circle(frame, (int(p.x * w), int(p.y * h)), DOT_SIZE, DOT_COLOR, -1)


def count_fingers(landmarks):
    """편 손가락 개수를 셉니다. (간단한 규칙이라 완벽하지는 않아요)

    [수정 지점] 규칙을 고치거나, 특정 손가락(예: 검지만)을
      검사하는 나만의 함수로 바꿔 보세요.
    """
    up = 0
    # 검지~새끼: 손가락 끝(tip)이 가운데 마디(pip)보다 위에 있으면(=y가 작으면) 편 것
    for tip, pip in [(8, 6), (12, 10), (16, 14), (20, 18)]:
        if landmarks[tip].y < landmarks[pip].y:
            up += 1
    # 엄지: 끝(4)이 마디(3)보다 새끼손가락(17)에서 멀면 편 것
    if abs(landmarks[4].x - landmarks[17].x) > abs(landmarks[3].x - landmarks[17].x):
        up += 1
    return up


def main():
    download_model()

    BaseOptions = mp.tasks.BaseOptions
    HandLandmarker = mp.tasks.vision.HandLandmarker
    HandLandmarkerOptions = mp.tasks.vision.HandLandmarkerOptions
    RunningMode = mp.tasks.vision.RunningMode

    options = HandLandmarkerOptions(
        base_options=BaseOptions(model_asset_path=MODEL_FILE),
        running_mode=RunningMode.VIDEO,  # 웹캠 프레임을 순서대로 처리하는 모드
        num_hands=NUM_HANDS,
    )

    cap = cv2.VideoCapture(CAM_INDEX)
    if not cap.isOpened():
        print("카메라를 열 수 없어요. CAM_INDEX 값을 바꿔 보세요.")
        return

    fps = 0.0
    prev_t = time.time()

    with HandLandmarker.create_from_options(options) as landmarker:
        while True:
            ok, frame = cap.read()
            if not ok:
                break
            frame = cv2.flip(frame, 1)  # 거울 모드 (지우면 좌우가 반대가 돼요)
            h, w = frame.shape[:2]

            # OpenCV는 BGR, MediaPipe는 RGB 순서를 사용 → 색 순서 변환
            rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
            mp_image = mp.Image(image_format=mp.ImageFormat.SRGB, data=rgb)
            result = landmarker.detect_for_video(mp_image, int(time.monotonic() * 1000))

            # 인식된 손마다 그림 + 손가락 개수 표시
            for i, landmarks in enumerate(result.hand_landmarks):
                draw_hand(frame, landmarks, w, h)
                fingers = count_fingers(landmarks)
                cv2.putText(frame, f"Fingers: {fingers}", (10, 70 + i * 40),
                            cv2.FONT_HERSHEY_SIMPLEX, 1.0, (255, 255, 255), 2)
                # [수정 지점] 검지 끝 좌표를 출력하려면 아래 줄의 # 을 지우세요
                # print("검지 끝:", round(landmarks[8].x, 2), round(landmarks[8].y, 2))

            # FPS(초당 처리 장수) 표시 — 값이 튀지 않게 90%는 이전 값을 유지
            now = time.time()
            fps = fps * 0.9 + (1.0 / max(now - prev_t, 1e-6)) * 0.1
            prev_t = now
            cv2.putText(frame, f"FPS: {fps:.0f}", (10, 30),
                        cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 255), 2)

            cv2.imshow("Hand Landmarks (ESC: quit)", frame)
            if cv2.waitKey(1) & 0xFF == 27:  # ESC 키
                break

    cap.release()
    cv2.destroyAllWindows()


if __name__ == "__main__":
    main()
