import cv2
from threading import Thread, Lock
import time

class Camera:
    """
    Module 1: The Asynchronous Core Engine (Enterprise Edition)
    Features: Thread-safe memory locks, dynamic HD resolution, built-in FPS tracking.
    """
    def __init__(self, source=0, title="Aelion Vision", mirror=True, width=1280, height=720):
        print(f"[Aelion Vision] Booting Asynchronous Camera Engine (Source: {source})...")
        self.stream = cv2.VideoCapture(source)
        
        # Force the hardware to broadcast in High Definition (HD)
        self.stream.set(cv2.CAP_PROP_FRAME_WIDTH, width)
        self.stream.set(cv2.CAP_PROP_FRAME_HEIGHT, height)
        
        self.title = title
        self.mirror = mirror
        
        # Enterprise Thread Safety
        self.read_lock = Lock()
        
        # Real-time FPS Tracking
        self.prev_time = time.time()
        self.fps = 0
        
        # Grab the very first frame to initialize
        self.grabbed, self.frame = self.stream.read()
        self._running = True

        if not self.stream.isOpened():
            print(f"[!] Aelion Vision Error: Could not open camera source {source}")
            self._running = False
        else:
            # Start the background daemon thread
            self.thread = Thread(target=self._update, args=())
            self.thread.daemon = True
            self.thread.start()
            print("[Aelion Vision] Asynchronous Thread Online.")

    def _update(self):
        """BACKGROUND THREAD: Constantly grabs frames safely."""
        while self._running:
            grabbed, frame = self.stream.read()
            
            # Lock the memory just for the 1 microsecond it takes to swap frames
            with self.read_lock:
                self.grabbed = grabbed
                self.frame = frame
                
            # Micro-rest to prevent CPU thermal throttling
            time.sleep(0.001)

    def running(self):
        """Keeps the main loop alive until the user quits."""
        return self._running

    def read(self):
        """MAIN THREAD: Safely extracts the frame and calculates FPS."""
        if not self.grabbed or self.frame is None:
            return None
            
        # Safely copy the frame while the background thread is locked
        with self.read_lock:
            frame = self.frame.copy() 
            
        if self.mirror:
            frame = cv2.flip(frame, 1)
            
        # Calculate Frame Rate Speed
        current_time = time.time()
        if current_time - self.prev_time > 0:
            self.fps = 1 / (current_time - self.prev_time)
        self.prev_time = current_time
            
        return frame
        
    def get_fps(self):
        """Returns the current framerate as an integer."""
        return int(self.fps)

    def show(self, frame, show_fps=False):
        """Displays the frame and handles UI. Optionally overlays the FPS counter."""
        if frame is not None:
            if show_fps:
                cv2.putText(frame, f"FPS: {int(self.fps)}", (10, 30), 
                            cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)
            cv2.imshow(self.title, frame)
        
        key = cv2.waitKey(1) & 0xFF
        try:
            if key == ord('q') or cv2.getWindowProperty(self.title, cv2.WND_PROP_VISIBLE) < 1:
                self.close()
        except cv2.error:
            self.close()

    def close(self):
        """Safely collapses the threads and C++ memory allocations."""
        if self._running:
            print("[Aelion Vision] Collapsing async threads...")
        self._running = False
        
        if hasattr(self, 'thread') and self.thread.is_alive():
            self.thread.join(timeout=1.0)
            
        if self.stream.isOpened():
            self.stream.release()
        cv2.destroyAllWindows()