import sys
import cv2
import time
import imutils
import mss
import pyautogui
from PIL import Image, ImageDraw, ImageFont
from PySide6.QtCore import Qt, QTimer, QThread, Signal, Slot, QMutex
from PySide6.QtGui import QImage, QPixmap
from PySide6.QtWidgets import (
    QApplication, QMainWindow, QLabel, QVBoxLayout, 
    QWidget, QComboBox, QPushButton, QHBoxLayout, QFileDialog, QTextBrowser, QSplitter
)
import numpy as np
from datetime import datetime
import httpx
from io import BytesIO
import asyncio
import nest_asyncio

# Apply nest_asyncio to allow nested event loops if needed
nest_asyncio.apply()

# Worker class to handle asynchronous requests in a separate thread
class AnalysisWorker(QThread):
    update_text = Signal(str)

    def __init__(self, server_url: str):
        super().__init__()
        self.server_url = server_url
        self.prompt_text = "Describe this image or current state"
        self.keep_running = True
        self.queue = []
        self.mutex = QMutex()

    def run(self):
        asyncio.run(self.process_queue())

    async def process_queue(self):
        async with httpx.AsyncClient() as client:
            while self.keep_running:
                frame = None
                self.mutex.lock()
                if self.queue:
                    frame = self.queue.pop(0)
                self.mutex.unlock()

                if frame is not None:
                    # Convert frame to PNG bytes
                    image_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
                    image_pil = Image.fromarray(image_rgb)
                    buffered = BytesIO()
                    image_pil.save(buffered, format="PNG")
                    image_bytes = buffered.getvalue()

                    # Prepare multipart form data
                    files = {
                        'prompt': (None, self.prompt_text),
                        'image': ('image.png', image_bytes, 'image/png')
                    }

                    try:
                        async with client.stream('POST', self.server_url, files=files, timeout=None) as response:
                            if response.status_code != 200:
                                self.update_text.emit(f"Server returned status code {response.status_code}\n")
                                continue
                            
                            async for chunk in response.aiter_text():
                                print(chunk, end="", flush=True)
                                self.update_text.emit(chunk)
                    except httpx.RequestError as e:
                        self.update_text.emit(f"Request error: {e}\n")
                    except Exception as e:
                        self.update_text.emit(f"Unexpected error: {e}\n")
                else:
                    await asyncio.sleep(0.1)  # Prevent tight loop

    def add_frame(self, frame):
        self.mutex.lock()
        self.queue.append(frame)
        self.mutex.unlock()

    def stop(self):
        self.keep_running = False
        self.wait()

class VideoPlayer(QMainWindow):
    def __init__(self):
        super().__init__()
        self.WIDTH = 800
        self.cap = None
        self.fps = 0
        self.st = time.time()
        self.frames_to_count = 20
        self.cnt = 0
        self.current_source = None
        self.previous_source = None
        self.previous_camera_index = 0
        
        # For screen recording
        self.sct = mss.mss()
        self.monitor = self.sct.monitors[1]
        try:
            self.font = ImageFont.truetype("arial.ttf", 36)
        except:
            self.font = ImageFont.load_default()
        try:
            mouse_icon = Image.open('mouse_icon.png').convert('RGBA')
            icon_width, icon_height = 26, 26
            self.mouse_icon = mouse_icon.resize((icon_width, icon_height))
        except:
            print("Mouse icon not found, using default cursor")
            self.mouse_icon = None
        
        # For video recording
        self.is_recording = False
        self.out = None
        self.record_width = 1920
        self.record_height = 1080
        
        # Set the modern dark blue theme stylesheet
        self.setStyleSheet("""
            QMainWindow {
                background: #1E213E;
            }
            QLabel {
                color: #ffffff;
                font-family: 'Segoe UI', sans-serif;
                padding: 5px;
            }
            QPushButton {
                background: #4A5BF9;
                border: none;
                border-radius: 8px;
                color: white;
                padding: 10px 20px;
                font-size: 14px;
                font-weight: 500;
                min-width: 150px;
                margin: 5px;
            }
            QPushButton:hover {
                background: #5A6AFF;
            }
            QComboBox {
                background: rgba(74, 91, 249, 0.1);
                border: 2px solid #4A5BF9;
                border-radius: 6px;
                color: white;
                padding: 8px 15px;
                min-width: 150px;
                margin: 5px;
            }
            QComboBox QAbstractItemView {
                background-color: #262B4D;
                color: white;
                selection-background-color: #4A5BF9;
            }
            QComboBox::drop-down {
                border: none;
                background: #4A5BF9;
                width: 30px;
                border-top-right-radius: 4px;
                border-bottom-right-radius: 4px;
            }
            QComboBox::down-arrow {
                image: none;
            }
            QWidget {
                background: transparent;
            }
            QStatusBar {
                background: #262B4D;
                color: #ffffff;
            }
            QTextBrowser {
                background: #262B4D;
                color: #ffffff;
                border: 2px solid rgba(74, 91, 249, 0.3);
                border-radius: 15px;
                padding: 20px;
                font-family: 'Segoe UI', sans-serif;
            }
        """)
        
        self.setWindowTitle("Dahyun Vision")
        self.setup_ui()
        
        self.timer = QTimer()
        self.timer.timeout.connect(self.update_frame)
        
        self.camera_count = self.count_cameras()
        self.populate_camera_list()

        # Setup Analysis Worker
        base =  "https://c0b2-34-90-240-101.ngrok-free.app" # Replace with your actual server URL
        server_endpoint = f"{base}/process"
        self.analysis_worker = AnalysisWorker(server_url=server_endpoint)
        self.analysis_worker.update_text.connect(self.append_text_browser)
        self.analysis_worker.start()

    def setup_ui(self):
        central_widget = QWidget()
        self.setCentralWidget(central_widget)
        main_layout = QHBoxLayout(central_widget)
        main_layout.setSpacing(10)
        main_layout.setContentsMargins(10, 10, 10, 10)

        splitter = QSplitter(Qt.Horizontal)

        # Left Side: QTextBrowser
        self.text_browser = QTextBrowser()
        self.text_browser.setFixedWidth(300)
        splitter.addWidget(self.text_browser)

        # Center and Right: Video Display and Control Panels
        right_widget = QWidget()
        right_layout = QVBoxLayout(right_widget)
        right_layout.setSpacing(20)
        right_layout.setContentsMargins(10, 10, 10, 10)

        title_label = QLabel("Dahyun Vision")
        title_label.setStyleSheet("""
            font-size: 24px;
            color: #ffffff;
            font-weight: bold;
            padding: 15px;
            background: #262B4D;
            border-radius: 12px;
        """)
        title_label.setAlignment(Qt.AlignCenter)
        right_layout.addWidget(title_label)
        
        # Add FPS Label
        self.fps_label = QLabel("FPS: 0")
        self.fps_label.setStyleSheet("""
            font-size: 18px;
            color: #4A5BF9;
            font-weight: bold;
            padding: 10px;
            background: rgba(74, 91, 249, 0.1);
            border-radius: 8px;
        """)
        self.fps_label.setAlignment(Qt.AlignCenter)
        right_layout.addWidget(self.fps_label)
        
        controls_container = QWidget()
        controls_container.setStyleSheet("""
            QWidget {
                background: #262B4D;
                border-radius: 15px;
                padding: 20px;
            }
        """)
        controls_layout = QHBoxLayout(controls_container)
        
        camera_layout = QVBoxLayout()
        self.camera_combo = QComboBox()
        self.camera_combo.currentIndexChanged.connect(self.source_changed)
        self.camera_button = QPushButton("▶ Start Camera/Screen")
        self.camera_button.clicked.connect(self.toggle_camera_or_screen)
        camera_layout.addWidget(QLabel("Select Camera or Screen:"))
        camera_layout.addWidget(self.camera_combo)
        camera_layout.addWidget(self.camera_button)
        
        video_layout = QVBoxLayout()
        self.video_button = QPushButton("📁 Select Video")
        self.video_button.clicked.connect(self.select_video_file)
        video_layout.addWidget(QLabel("Video File:"))
        video_layout.addWidget(self.video_button)
        
        record_layout = QVBoxLayout()
        self.record_button = QPushButton("🔴 Start Recording")
        self.record_button.clicked.connect(self.toggle_recording)
        record_layout.addWidget(QLabel("Recording:"))
        record_layout.addWidget(self.record_button)
        
        controls_layout.addLayout(camera_layout)
        controls_layout.addLayout(video_layout)
        controls_layout.addLayout(record_layout)
        right_layout.addWidget(controls_container)
        
        self.video_label = QLabel()
        self.video_label.setAlignment(Qt.AlignCenter)
        self.video_label.setStyleSheet("""
            QLabel {
                background: #262B4D;
                border-radius: 15px;
                padding: 20px;
                border: 2px solid rgba(74, 91, 249, 0.3);
            }
        """)
        self.video_label.setText("No video source selected")
        right_layout.addWidget(self.video_label)
        
        splitter.addWidget(right_widget)
        splitter.setStretchFactor(1, 1)
        main_layout.addWidget(splitter)
        
        self.resize(self.WIDTH + 300, 600)  # Adjust width to accommodate text browser

    def calculate_fps(self):
        self.cnt += 1
        if self.cnt == self.frames_to_count:
            try:
                self.fps = round(self.frames_to_count / (time.time() - self.st))
                self.st = time.time()
                self.cnt = 0
                self.fps_label.setText(f"FPS: {self.fps}")
            except:
                pass

    def count_cameras(self):
        camera_count = 0
        max_tested = 10  # Limit to prevent long loops
        for i in range(max_tested):
            cap = cv2.VideoCapture(i)
            if cap.isOpened():
                camera_count += 1
                cap.release()
            else:
                break
        if camera_count == 0:
            print("No cameras found. Please connect a camera and restart the application.")
        return camera_count

    def populate_camera_list(self):
        self.camera_combo.clear()
        for i in range(self.camera_count):
            self.camera_combo.addItem(f"Camera {i}")
        self.camera_combo.addItem("Screen")

    def source_changed(self, index):
        if self.current_source == 'file':
            # If a video is playing, changing the camera source should stop the video
            self.stop_video()
        
        if index == self.camera_count:
            self.switch_to_screen_recording()
        else:
            self.switch_to_camera(index)

    def toggle_camera_or_screen(self):
        if self.current_source in ['camera', 'screen']:
            self.stop_camera_or_screen()
        else:
            if self.current_source == 'file':
                self.stop_video()
            self.start_camera_or_screen()

    def start_camera_or_screen(self):
        current_index = self.camera_combo.currentIndex()
        if current_index == self.camera_count:
            self.switch_to_screen_recording()
        else:
            self.switch_to_camera(current_index)

    def switch_to_camera(self, camera_index):
        if self.cap is not None:
            self.cap.release()
        
        self.cap = cv2.VideoCapture(camera_index)
        if self.cap.isOpened():
            self.timer.stop()  # Stop existing timer
            self.timer.start(30)  # Restart timer
            self.current_source = 'camera'
            self.previous_source = 'camera'
            self.previous_camera_index = camera_index
            self.camera_button.setText("⏹ Stop Camera/Screen")
            # Reset FPS counter
            self.cnt = 0
            self.st = time.time()
            self.fps = 0
            # Clear any pending analysis
            self.analysis_worker.queue.clear()
        else:
            self.video_label.setText("Failed to access camera.")
            self.current_source = None

    def switch_to_screen_recording(self):
        if self.cap is not None and self.cap.isOpened():
            self.cap.release()
            
        self.cap = None
        self.timer.stop()  # Stop existing timer
        self.timer.start(30)  # Restart timer
        self.current_source = 'screen'
        self.previous_source = 'screen'
        self.previous_camera_index = None
        self.camera_button.setText("⏹ Stop Camera/Screen")
        # Reset FPS counter
        self.cnt = 0
        self.st = time.time()
        self.fps = 0
        # Clear any pending analysis
        self.analysis_worker.queue.clear()

    def select_video_file(self):
        file_name, _ = QFileDialog.getOpenFileName(
            self, "Select Video File", "",
            "Video Files (*.mp4 *.avi *.mkv *.mov);;All Files (*.*)"
        )
        if file_name:
            if self.current_source in ['camera', 'screen']:
                self.stop_camera_or_screen()
            else:
                self.stop_video()

            self.cap = cv2.VideoCapture(file_name)
            if self.cap.isOpened():
                self.timer.stop()  # Stop existing timer
                self.timer.start(30)  # Restart timer
                self.current_source = 'file'
                # Reset FPS counter
                self.cnt = 0
                self.st = time.time()
                self.fps = 0
                self.video_label.setText("Playing video...")
                self.camera_button.setText("▶ Start Camera/Screen")
                # Clear any pending analysis
                self.analysis_worker.queue.clear()
            else:
                self.video_label.setText("Failed to open video file.")
                self.current_source = None
    def stop_video(self):
        self.timer.stop()
        if self.cap is not None:
            self.cap.release()
            self.cap = None

        if self.current_source == 'file' and self.previous_source:
            if self.previous_source == 'screen':
                self.camera_combo.setCurrentIndex(self.camera_count)
                self.switch_to_screen_recording()
            elif self.previous_source == 'camera' and self.previous_camera_index is not None:
                self.camera_combo.setCurrentIndex(self.previous_camera_index)
                self.switch_to_camera(self.previous_camera_index)
        else:
            self.video_label.setText("No video source selected")
            self.current_source = None
            self.camera_button.setText("▶ Start Camera/Screen")
            self.fps_label.setText("FPS: 0")

    def update_frame(self):
        frame = None
        
        try:
            if self.current_source == 'file' or self.current_source == 'camera':
                ret, frame = self.cap.read()
                if not ret:
                    self.stop_video()
                    return
                    
            elif self.current_source == 'screen':
                screen_image = self.sct.grab(self.monitor)
                img = Image.frombytes("RGB", (screen_image.width, screen_image.height), screen_image.rgb)
                draw = ImageDraw.Draw(img)
                mouse_x, mouse_y = pyautogui.position()
                if self.mouse_icon:
                    img.paste(self.mouse_icon, (mouse_x, mouse_y), self.mouse_icon)
                frame = cv2.cvtColor(np.array(img), cv2.COLOR_RGB2BGR)
            
            if frame is not None:
                # Calculate FPS
                self.calculate_fps()
                
                # Resize and display the frame
                display_frame = imutils.resize(frame, width=self.WIDTH)
                img = QImage(display_frame.data, display_frame.shape[1], display_frame.shape[0], QImage.Format_BGR888)
                pix = QPixmap.fromImage(img)
                self.video_label.setPixmap(pix)
                
                # Send frame for analysis
                self.analysis_worker.queue.clear()  # Clear any pending frames
                self.analysis_worker.add_frame(frame.copy())  # Send a copy of the current frame
                
                # Handle recording if active
                if self.is_recording:
                    frame_resized = cv2.resize(frame, (self.record_width, self.record_height))
                    self.out.write(frame_resized)
                    
        except Exception as e:
            print(f"Error in update_frame: {str(e)}")
            self.stop_video()

    def display_frame(self, frame):
        frame = imutils.resize(frame, width=self.WIDTH)
        img = QImage(frame.data, frame.shape[1], frame.shape[0], QImage.Format_BGR888)
        pix = QPixmap.fromImage(img)
        self.video_label.setPixmap(pix)

    def toggle_recording(self):
        if not self.is_recording:
            self.start_recording()
        else:
            self.stop_recording()

    def start_recording(self):
        if self.current_source is None:
            return

        # Generate the filename with the current date and time
        current_datetime = datetime.now().strftime("%Y%m%d_%H%M%S")
        default_filename = f"recording_{current_datetime}.mp4"
        
        file_name, _ = QFileDialog.getSaveFileName(self, "Save Video", default_filename, "MP4 files (*.mp4)")
        if file_name:
            fourcc = cv2.VideoWriter_fourcc(*'mp4v')
            self.out = cv2.VideoWriter(file_name, fourcc, 20.0, (self.record_width, self.record_height))
            self.is_recording = True
            self.record_button.setText("⏹ Stop Recording")
            self.record_button.setStyleSheet("background: #FF4136;")

    def stop_recording(self):
        if self.is_recording:
            self.is_recording = False
            self.out.release()
            self.record_button.setText("🔴 Start Recording")
            self.record_button.setStyleSheet("background: #4A5BF9;")

    @Slot(str)
    def append_text_browser(self, text):
        self.text_browser.append(text)
        # Auto-scroll to the bottom
        self.text_browser.verticalScrollBar().setValue(self.text_browser.verticalScrollBar().maximum())

    def closeEvent(self, event):
        # Clean up resources
        self.timer.stop()
        if self.cap is not None:
            self.cap.release()
        if self.is_recording and self.out is not None:
            self.out.release()
        self.analysis_worker.stop()
        event.accept()

if __name__ == "__main__":
    app = QApplication(sys.argv)
    player = VideoPlayer()
    player.show()
    sys.exit(app.exec())









[Running] python -u "c:\Users\ALL USER\Desktop\Python\networks\udp\New folder\alll\hd_record.py"
[ERROR:0@2.222] global obsensor_uvc_stream_channel.cpp:158 cv::obsensor::getStreamChannelGroup Camera index out of range
Traceback (most recent call last):
  File "c:\Users\ALL USER\Desktop\Python\networks\udp\New folder\alll\hd_record.py", line 341, in source_changed
    self.switch_to_camera(index)
  File "c:\Users\ALL USER\Desktop\Python\networks\udp\New folder\alll\hd_record.py", line 375, in switch_to_camera
    self.analysis_worker.queue.clear()
AttributeError: 'VideoPlayer' object has no attribute 'analysis_worker'
The image is a solid, uniform block of bright orange.  There are no other elements, shapes, or textures present.  It's a single, flat color.
The image is a solid, uniform block of bright orange.  There are no other elements, shapes, or textures present.  It's a single, saturated color filling the entire frame.