#!/usr/bin/python3

import time
from datetime import datetime
from pathlib import Path

import numpy as np
from picamera2 import Picamera2
from picamera2.encoders import H264Encoder
from picamera2.outputs import CircularOutput2, PyavOutput


VIDEO_DIR = Path.home() / "videos"
VIDEO_DIR.mkdir(parents=True, exist_ok=True)

MAIN_SIZE = (1920, 1080)
LORES_SIZE = (320, 240)

BITRATE = 15_000_000
FRAMERATE = 30

# Seconds retained before motion is detected.
PREBUFFER_SECONDS = 5

# Stop recording this many seconds after the last detected movement.
STOP_DELAY_SECONDS = 15

# Process roughly five motion-detection frames per second.
DETECTION_INTERVAL = 0.20

# Increase this if IR grain causes false detections.
MOTION_THRESHOLD = 8.0


picam2 = Picamera2()

configuration = picam2.create_video_configuration(
    main={
        "size": MAIN_SIZE,
        "format": "YUV420",
    },
    lores={
        "size": LORES_SIZE,
        "format": "YUV420",
    },
    controls={
        "FrameRate": FRAMERATE,
    },
)

picam2.configure(configuration)

encoder = H264Encoder(
    bitrate=BITRATE,
    repeat=True,
)

output = CircularOutput2(
    buffer_duration_ms=PREBUFFER_SECONDS * 1000
)

picam2.start_recording(encoder, output)

width, height = LORES_SIZE
previous = None
recording = False
last_motion_time = 0.0

print("Motion detection active.")
print(f"Videos: {VIDEO_DIR}")
print("Press Ctrl+C to stop.")

try:
    while True:
        # YUV420 begins with the full-resolution grayscale Y plane.
        current = picam2.capture_array("lores")[:height, :width]

        if previous is not None:
            # Cast before subtraction to avoid unsigned-byte wraparound.
            difference = current.astype(np.int16) - previous.astype(np.int16)
            mse = float(np.square(difference).mean())

            if mse > MOTION_THRESHOLD:
                last_motion_time = time.monotonic()

                if not recording:
                    timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
                    filename = VIDEO_DIR / f"motion_{timestamp}.mp4"

                    output.open_output(PyavOutput(str(filename)))
                    recording = True

                    print(f"Motion detected ({mse:.1f}): {filename.name}")

            elif (
                recording
                and time.monotonic() - last_motion_time > STOP_DELAY_SECONDS
            ):
                output.close_output()
                recording = False
                print("Motion ended: recording closed.")

        previous = current
        time.sleep(DETECTION_INTERVAL)

except KeyboardInterrupt:
    print("\nStopping...")

finally:
    if recording:
        output.close_output()

    picam2.stop_recording()
    print("Stopped cleanly.")
