# recognize.py — Live LBPH tester (no Flask), HUD + threshold tuning
import json, pickle, time
from pathlib import Path
import cv2, numpy as np

APP_ROOT = Path(__file__).parent.resolve()
MODELS_DIR = APP_ROOT / "models"
CONFIG_PATH = MODELS_DIR / "config.json"
HAAR_PATH = cv2.data.haarcascades + "haarcascade_frontalface_default.xml"

face_cascade = cv2.CascadeClassifier(HAAR_PATH)

def normalize_face(gray_200):
    clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
    norm = clahe.apply(gray_200)
    return cv2.GaussianBlur(norm, (3,3), 0)

def detect(gray, min_box=80):
    faces = face_cascade.detectMultiScale(gray, 1.1, 4, minSize=(min_box, min_box))
    if len(faces) == 0: return None
    x,y,w,h = max(faces, key=lambda f: f[2]*f[3])
    f = cv2.resize(gray[y:y+h, x:x+w], (200,200))
    return (x,y,w,h), normalize_face(f)

def load_model():
    m = MODELS_DIR / "lbph_model.xml"
    l = MODELS_DIR / "labels.pickle"
    if not (m.exists() and l.exists()):
        print("[!] Model missing. Run training first.")
        return None, None
    rec = cv2.face.LBPHFaceRecognizer_create()
    rec.read(str(m))
    with open(l, "rb") as f:
        label_to_code = pickle.load(f)
    return rec, label_to_code

def load_cfg():
    cfg = {"lbph_threshold": 75}
    if CONFIG_PATH.exists():
        try:
            with open(CONFIG_PATH,"r") as f: cfg.update(json.load(f))
        except: pass
    return cfg

def main():
    cfg = load_cfg()
    thr = float(cfg.get("lbph_threshold", 75.0))
    min_box = 80

    print("[i] ]/[ to change threshold, s to save, m/M change min face size, q to quit")
    print("[i] Start threshold:", thr)

    rec, label_to_code = load_model()
    if rec is None: return

    cap = cv2.VideoCapture(0, cv2.CAP_DSHOW)
    if not cap.isOpened():
        print("[!] Camera not found.")
        return

    while True:
        ok, frame = cap.read()
        if not ok: break
        gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
        det = detect(gray, min_box)
        tag = "No face"; color = (0,0,255)

        if det:
            (x,y,w,h), face = det
            pred, conf = rec.predict(face)
            code = label_to_code.get(pred)
            okm = (code is not None) and (conf <= thr)
            color = (0,200,0) if okm else (0,0,255)
            cv2.rectangle(frame, (x,y), (x+w, y+h), color, 2)
            tag = (f"{code}  c:{conf:.1f}≤{thr:.0f}" if okm else f"Unknown  c:{conf:.1f}≤{thr:.0f}")
            tw,_ = cv2.getTextSize(tag, cv2.FONT_HERSHEY_SIMPLEX, 0.7, 2)[0]
            cv2.rectangle(frame, (x, max(0,y-26)), (x+tw+12, y-4), color, -1)
            cv2.putText(frame, tag, (x+6, y-8), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255,255,255), 2, cv2.LINE_AA)

        cv2.putText(frame, f"thr:[{thr:.0f}] min_box:[{min_box}] (s save, [/] thr, m/M box, q quit)",
                    (12, frame.shape[0]-12), cv2.FONT_HERSHEY_SIMPLEX, 0.55, (255,255,255), 2, cv2.LINE_AA)
        cv2.imshow("Recognize (LBPH)", frame)
        k = cv2.waitKey(1) & 0xFF
        if k == ord('q'): break
        elif k == ord(']'): thr = min(95.0, thr + 2.0)
        elif k == ord('['): thr = max(40.0, thr - 2.0)
        elif k == ord('m'): min_box = max(40, min_box - 10)
        elif k == ord('M'): min_box = min(200, min_box + 10)
        elif k == ord('s'):
            cfg["lbph_threshold"] = float(thr)
            with open(CONFIG_PATH,"w") as f: json.dump(cfg,f,indent=2)
            print("[i] Saved to", CONFIG_PATH)

    cap.release(); cv2.destroyAllWindows()

if __name__ == "__main__":
    main()
