"""
Non-blocking camera frame reader using GStreamer subprocess.
Extracted from web_detection_stream.py for reuse across the system.
"""

import cv2
import time
import subprocess
import threading
import numpy as np


class CameraReader:
    """Non-blocking camera frame reader using a background thread."""

    def __init__(self, sensor_id, width=1280, height=720, fps=30):
        self.sensor_id = sensor_id
        self.width = width
        self.height = height
        self.frame = None
        self.lock = threading.Lock()
        self.running = True

        gst_pipeline = (
            f"gst-launch-1.0 --quiet -e "
            f"nvarguscamerasrc sensor-id={sensor_id} ! "
            f"'video/x-raw(memory:NVMM),width={width},height={height},framerate={fps}/1' ! "
            f"nvvidconv ! 'video/x-raw,format=BGRx,width={width},height={height}' ! "
            f"fdsink"
        )
        print(f"[CAM {sensor_id}] Starting GStreamer: {width}x{height}@{fps}")
        self.proc = subprocess.Popen(
            gst_pipeline, shell=True,
            stdout=subprocess.PIPE, stderr=subprocess.PIPE,
        )
        self.frame_bytes = width * height * 4

        time.sleep(2)
        if self.proc.poll() is not None:
            stderr = self.proc.stderr.read().decode()
            print(f"[CAM {sensor_id}] GStreamer failed: {stderr[:200]}")
            print(f"[CAM {sensor_id}] Falling back to V4L2")
            self.use_gst = False
            self.cap = cv2.VideoCapture(f"/dev/video{sensor_id}", cv2.CAP_V4L2)
        else:
            self.use_gst = True
            self.cap = None

        self.thread = threading.Thread(target=self._read_loop, daemon=True)
        self.thread.start()

    def _read_loop(self):
        while self.running:
            if self.use_gst:
                raw = self.proc.stdout.read(self.frame_bytes)
                if len(raw) != self.frame_bytes:
                    print(f"[CAM {self.sensor_id}] Pipe broken")
                    break
                f = np.frombuffer(raw, dtype=np.uint8).reshape(
                    self.height, self.width, 4)[:, :, :3].copy()
            else:
                ret, f = self.cap.read()
                if not ret:
                    time.sleep(0.01)
                    continue
                if f.shape[1] != self.width or f.shape[0] != self.height:
                    f = cv2.resize(f, (self.width, self.height))

            with self.lock:
                self.frame = f

    def grab(self):
        with self.lock:
            return self.frame.copy() if self.frame is not None else None

    def stop(self):
        self.running = False
        if self.use_gst and self.proc.poll() is None:
            self.proc.terminate()
        elif self.cap:
            self.cap.release()
