
import cv2
import threading
import time
from typing import Optional, Callable

class CameraSource:
    """
    A class to handle camera input using cv2.VideoCapture.
    It captures frames in a separate thread and can send them to a pipeline.
    """
    def __init__(self, 
                 camera_index: int = 0, 
                 resolution: Optional[tuple[int, int]] = None, 
                 fps: Optional[int] = None,
                 data_callback: Optional[Callable[[object], None]] = None,
                 frame_callback: Optional[Callable[[object], None]] = None):
        """
        Initializes the CameraSource.

        Args:
            camera_index (int): The index of the camera to use.
            resolution (Optional[tuple[int, int]]): The desired resolution (width, height).
            fps (Optional[int]): The desired frames per second.
            data_callback (Optional[Callable[[object], None]]): A callback function to send data to the pipeline.
            frame_callback (Optional[Callable[[object], None]]): A callback function for raw frame updates.
        """
        self.camera_index = camera_index
        self.resolution = resolution
        self.fps = fps
        self.data_callback = data_callback
        self.frame_callback = frame_callback

        self.cap = None
        self.running = False
        self.thread = None
        self._stop_event = threading.Event()

    def initialize(self) -> bool:
        """
        Initializes the camera capture.

        Returns:
            bool: True if initialization is successful, False otherwise.
        """
        print(f"Initializing camera at index {self.camera_index}...")
        self.cap = cv2.VideoCapture(self.camera_index)
        if not self.cap.isOpened():
            print(f"Error: Could not open camera at index {self.camera_index}.")
            return False

        if self.resolution:
            self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, self.resolution[0])
            self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, self.resolution[1])

        if self.fps:
            self.cap.set(cv2.CAP_PROP_FPS, self.fps)
        
        print("Camera initialized successfully.")
        return True

    def start(self):
        """
        Starts the frame capture thread.
        """
        if self.running:
            print("Camera source is already running.")
            return

        if not self.cap or not self.cap.isOpened():
            if not self.initialize():
                return

        self.running = True
        self._stop_event.clear()
        self.thread = threading.Thread(target=self._capture_loop, daemon=True)
        self.thread.start()
        print("Camera capture thread started.")

    def stop(self):
        """
        Stops the frame capture thread.
        """
        self.running = False
        if self.thread and self.thread.is_alive():
            self._stop_event.set()
            self.thread.join(timeout=2)
        
        if self.cap and self.cap.isOpened():
            self.cap.release()
            self.cap = None
        print("Camera source stopped.")

    def _capture_loop(self):
        """
        The main loop for capturing frames from the camera.
        """
        while self.running and not self._stop_event.is_set():
            ret, frame = self.cap.read()
            if not ret:
                print("Error: Could not read frame from camera. Reconnecting...")
                self.cap.release()
                time.sleep(1)
                self.initialize()
                continue

            if self.data_callback:
                try:
                    # Assuming the callback is thread-safe or handles its own locking
                    self.data_callback(frame)
                except Exception as e:
                    print(f"Error in data_callback: {e}")

            if self.frame_callback:
                try:
                    self.frame_callback(frame)
                except Exception as e:
                    print(f"Error in frame_callback: {e}")
            
            # Control frame rate if FPS is set
            if self.fps:
                time.sleep(1.0 / self.fps)

    def set_data_callback(self, callback: Callable[[object], None]):
        """
        Sets the data callback function.
        """
        self.data_callback = callback

    def get_frame(self) -> Optional[object]:
        """
        Gets a single frame from the camera. Not recommended for continuous capture.
        """
        if not self.cap or not self.cap.isOpened():
            if not self.initialize():
                return None
        
        ret, frame = self.cap.read()
        if not ret:
            return None
        return frame
