"""
Person-triggered motion clips (OpenMV + YOLO LC)

When the built-in YOLO LC person model detects someone in frame, record a
short JPEG burst and upload it to Contact via send_clip_burst (kind="clip").
Contact transcodes the burst into an MP4 with browser playback controls.
Clips have no recipient — they appear in the Contact clip gallery and on the
camera entity's file activity. After each clip finishes, wait at least 3 seconds
before recording again — even if someone is still in frame.

Person detection is adapted from OpenMV's TensorFlow Lite YOLO LC Person
Detector example (camera + model setup):

  https://github.com/openmv/openmv/blob/master/scripts/examples/03-Machine-Learning/00-TensorFlow/yolo_lc_person_detector.py

Model: ``/rom/yolo_lc_192.tflite`` with ``YoloLC(threshold=0.4)``.

Requirements:
- OpenMV cam with ``csi`` and ``ml`` modules
- ``requests`` installed on the device
- File transfer enabled on your Contact account
- Entity token with entity:SendFiles (upload permission)
"""

import time

try:
    import csi
    import ml
    from ml.postprocessing.darknet import YoloLC
except ImportError:
    raise SystemExit("This example requires OpenMV csi + ml (YOLO LC person model)")

from tendrl import Client

# --- Configure for your deployment ------------------------------------------

RECORD_SECONDS = 3            # clip length per trigger
COOLDOWN_SECONDS = 3          # minimum gap between clips
CLIP_FPS = 5                  # frames per second (lower = smaller zip)
CLIP_SCALE = 1.0              # 1.0 = full csi.window size (400x400 here)
CLIP_QUALITY = 85             # JPEG quality 1–100
LOCK_EXPOSURE = True          # freeze auto gain/exposure during each clip

DETECT_EVERY_MS = 200        # how often to run inference between clips

MODEL_PATH = "/rom/yolo_lc_192.tflite"
DETECT_THRESHOLD = 0.4       # match your model test script

# -----------------------------------------------------------------------------


def setup_camera():
    """Sensor setup from OpenMV yolo_lc_person_detector.py (see module docstring URL)."""
    csi0 = csi.CSI()
    csi0.reset()
    csi0.pixformat(csi.RGB565)   # RGB565 for YOLO
    csi0.framesize(csi.VGA)
    csi0.window((400, 400))
    csi0.snapshot(time=2000)     # let auto gain / exposure settle
    return csi0


def set_auto_controls(csi0, enabled):
    """Disable auto gain/exposure/white balance during clip capture if supported."""
    for name in ("auto_gain", "auto_exposure", "auto_whitebal", "auto_awb"):
        fn = getattr(csi0, name, None)
        if not callable(fn):
            continue
        try:
            fn(enabled)
        except Exception:
            pass


def load_person_model():
    model = ml.Model(MODEL_PATH, postprocess=YoloLC(threshold=DETECT_THRESHOLD))
    print(model)
    return model


def detect_person(img, model):
    """Return True if YOLO LC reports at least one detection."""
    boxes = model.predict([img])
    for class_detections in boxes:
        if class_detections:
            return True
    return False


def cooldown_remaining_ms(last_clip_end_ms):
    if last_clip_end_ms == 0:
        return 0
    elapsed = time.ticks_diff(time.ticks_ms(), last_clip_end_ms)
    need = COOLDOWN_SECONDS * 1000
    if elapsed >= need:
        return 0
    return need - elapsed


def main():
    print("=" * 60)
    print("YOLO LC person-triggered clip upload")
    print("=" * 60)

    csi0 = setup_camera()
    model = load_person_model()

    client = Client(mode="sync", debug=True)
    client.start()
    print("Waiting for network...")
    time.sleep(5)

    last_clip_end_ms = 0
    clock = time.clock()

    print(
        "Watching for people (record %ds, %d fps, scale=%s, quality=%d, cooldown %ds)..."
        % (RECORD_SECONDS, CLIP_FPS, CLIP_SCALE, CLIP_QUALITY, COOLDOWN_SECONDS)
    )

    while True:
        remaining = cooldown_remaining_ms(last_clip_end_ms)
        if remaining > 0:
            time.sleep_ms(min(remaining, DETECT_EVERY_MS))
            continue

        clock.tick()
        img = csi0.snapshot()

        if not detect_person(img, model):
            time.sleep_ms(DETECT_EVERY_MS)
            continue

        print("Person detected (%.1f fps) — recording %ds clip..." % (clock.fps(), RECORD_SECONDS))

        if LOCK_EXPOSURE:
            set_auto_controls(csi0, False)

        scale = CLIP_SCALE if CLIP_SCALE != 1.0 else None
        resp = client.send_clip_burst(
            csi0.snapshot,
            duration_s=RECORD_SECONDS,
            fps=CLIP_FPS,
            scale=scale,
            quality=CLIP_QUALITY,
            filename="person_clip.zip",
            meta={
                "trigger": "yolo_lc_person",
                "record_s": RECORD_SECONDS,
            },
        )

        if LOCK_EXPOSURE:
            set_auto_controls(csi0, True)

        last_clip_end_ms = time.ticks_ms()

        if resp:
            print("Clip uploaded:", resp.get("transfer_id"), resp.get("status"))
        else:
            print("Clip upload failed (see debug output above)")

        print("Cooldown %ds before next capture..." % COOLDOWN_SECONDS)


if __name__ == "__main__":
    try:
        main()
    except KeyboardInterrupt:
        print("Stopped.")
