import os
import cv2
import subprocess
import time
import numpy as np


class Camera:
    def __init__(self, width=1280, height=720, fps=30, device=0):
        self.width = width
        self.height = height
        self.fps = fps
        self.device = device
        self.process = None
        self.cap = None

        self._setup_virtual_camera()
        self._start_pipeline()
        self._connect_opencv()

    # ------------------------------------------------------------------
    # Setup
    # ------------------------------------------------------------------

    def _setup_virtual_camera(self):
        os.system(
            f"sudo modprobe v4l2loopback devices=1 video_nr={self.device} "
            f"card_label='RPiCam' exclusive_caps=1"
        )

    def _start_pipeline(self):
        command = (
            f"libcamera-vid -t 0 --width {self.width} --height {self.height} "
            f"--framerate {self.fps} --codec yuv420 --output - | "
            f"ffmpeg -f rawvideo -pix_fmt yuv420p "
            f"-s {self.width}x{self.height} -r {self.fps} -i - "
            f"-f v4l2 /dev/video{self.device}"
        )
        self.process = subprocess.Popen(command, shell=True)
        time.sleep(2)

    def _connect_opencv(self):
        self.cap = cv2.VideoCapture(self.device)
        if not self.cap.isOpened():
            raise RuntimeError(
                f"Failed to open /dev/video{self.device}. "
                "Make sure v4l2loopback and libcamera are installed."
            )

    # ------------------------------------------------------------------
    # Core read interface
    # ------------------------------------------------------------------

    def read(self):
        """Read one BGR frame. Returns np.ndarray (H, W, 3)."""
        ret, frame = self.cap.read()
        if not ret:
            raise RuntimeError("Failed to read frame from camera.")
        return frame

    def read_gray(self):
        """Read one frame and convert to grayscale. Returns np.ndarray (H, W)."""
        return cv2.cvtColor(self.read(), cv2.COLOR_BGR2GRAY)

    def read_rgb(self):
        """Read one frame and convert to RGB. Returns np.ndarray (H, W, 3)."""
        return cv2.cvtColor(self.read(), cv2.COLOR_BGR2RGB)

    def read_resized(self, width, height):
        """Read one frame and resize it."""
        return cv2.resize(self.read(), (width, height))

    def read_flipped(self, flip_code=1):
        """
        Read one frame and flip it.
          flip_code=0  vertical
          flip_code=1  horizontal  (default, mirror)
          flip_code=-1 both
        """
        return cv2.flip(self.read(), flip_code)

    # ------------------------------------------------------------------
    # cv2.VideoCapture-compatible interface
    # Lets you drop Camera() anywhere you'd use cv2.VideoCapture()
    # ------------------------------------------------------------------

    def isOpened(self):
        return self.cap is not None and self.cap.isOpened()

    def grab(self):
        return self.cap.grab()

    def retrieve(self, flag=0):
        return self.cap.retrieve(flag)

    def get(self, prop_id):
        """Proxy to cv2.VideoCapture.get() — use any cv2.CAP_PROP_* constant."""
        return self.cap.get(prop_id)

    def set(self, prop_id, value):
        """Proxy to cv2.VideoCapture.set() — use any cv2.CAP_PROP_* constant."""
        return self.cap.set(prop_id, value)

    # ------------------------------------------------------------------
    # Display helpers
    # ------------------------------------------------------------------

    def show(self, window="RPiCam"):
        """Read and display one frame. Returns the frame."""
        frame = self.read()
        cv2.imshow(window, frame)
        return frame

    def snapshot(self, path="snapshot.jpg"):
        """Save a single frame to disk. Returns the frame."""
        frame = self.read()
        cv2.imwrite(path, frame)
        return frame

    # ------------------------------------------------------------------
    # Properties
    # ------------------------------------------------------------------

    @property
    def resolution(self):
        return (self.width, self.height)

    @property
    def shape(self):
        """Frame shape as (height, width, 3) — matches np.ndarray.shape."""
        return (self.height, self.width, 3)

    # ------------------------------------------------------------------
    # Lifecycle
    # ------------------------------------------------------------------

    def release(self):
        if self.cap:
            self.cap.release()
        if self.process:
            self.process.terminate()
        cv2.destroyAllWindows()

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.release()

    def __repr__(self):
        return (
            f"Camera(device={self.device}, "
            f"resolution={self.width}x{self.height}, "
            f"fps={self.fps})"
        )
