#!/usr/bin/env python
"""
Camera GUI Application

A PyQt6-based GUI application that demonstrates the camera module functionality
in FMUS-VID, allowing users to view and capture from webcams.
"""

import os
import sys
import time
from pathlib import Path
import logging
from typing import Optional, Dict, Any, List

# Add parent directory to path for imports
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))

# Required PyQt6 imports
try:
    from PyQt6.QtWidgets import (
        QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
        QPushButton, QLabel, QComboBox, QSpinBox, QCheckBox, QFileDialog,
        QSplitter, QFrame, QMessageBox, QSlider, QGroupBox, QStatusBar
    )
    from PyQt6.QtCore import Qt, QTimer, pyqtSlot, QSize, QDir, QThread, pyqtSignal
    from PyQt6.QtGui import QImage, QPixmap, QIcon, QAction, QKeySequence, QFont
except ImportError:
    print("PyQt6 is required for this example. Install it with: pip install PyQt6")
    sys.exit(1)

# Import OpenCV for image processing
try:
    import cv2
    import numpy as np
except ImportError:
    print("OpenCV is required for this example. Install it with: pip install opencv-python")
    sys.exit(1)

# Import FMUS-VID components
try:
    from fmusvid.capture import CameraCapture, list_cameras, get_camera_backends
except ImportError:
    print("FMUS-VID with camera module is required.")
    print("Make sure you've implemented the camera module in fmusvid/capture/camera.py")
    sys.exit(1)

# Set up logging
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)

# Constants
DEFAULT_WIDTH = 640
DEFAULT_HEIGHT = 480
DEFAULT_FPS = 30
DEFAULT_OUTPUT_DIR = os.path.join("examples", "output")


class FrameProcessor(QThread):
    """
    Thread for processing camera frames to avoid UI freezing.
    """
    frame_ready = pyqtSignal(np.ndarray)
    error = pyqtSignal(str)

    def __init__(
        self,
        camera_id: int = 0,
        width: int = DEFAULT_WIDTH,
        height: int = DEFAULT_HEIGHT,
        fps: float = DEFAULT_FPS,
        backend: Optional[int] = None,
        parent=None
    ):
        super().__init__(parent)
        self.camera_id = camera_id
        self.width = width
        self.height = height
        self.fps = fps
        self.backend = backend

        self.running = False
        self.camera = None
        self.timer_interval = 1000 // int(fps)  # ms between frames

    def run(self):
        """Thread main loop."""
        try:
            # Create camera capture
            self.camera = CameraCapture(
                camera_id=self.camera_id,
                width=self.width,
                height=self.height,
                fps=self.fps,
                backend=self.backend
            )

            if not self.camera.start():
                self.error.emit(f"Failed to start camera {self.camera_id}")
                return

            self.running = True

            # Main processing loop
            while self.running:
                frame = self.camera.get_frame()
                if frame is not None:
                    # Emit the frame for the UI to display
                    self.frame_ready.emit(frame)

                # Sleep to maintain frame rate
                time.sleep(1 / self.fps)

        except Exception as e:
            self.error.emit(f"Camera error: {str(e)}")
        finally:
            if self.camera:
                self.camera.stop()

    def stop(self):
        """Stop the frame processing thread."""
        self.running = False
        self.wait()
        if self.camera:
            self.camera.stop()


class CameraViewWidget(QWidget):
    """
    Widget for displaying camera preview.
    """
    def __init__(self, parent=None):
        super().__init__(parent)
        self.init_ui()

        # Initialize variables
        self.frame = None
        self.frame_processor = None
        self.recording = False
        self.recording_path = None

        # Camera settings
        self.camera_id = 0
        self.width = DEFAULT_WIDTH
        self.height = DEFAULT_HEIGHT
        self.fps = DEFAULT_FPS
        self.backend = None

        # Available cameras
        self.cameras = list_cameras()
        self.backends = get_camera_backends()
        self.populate_camera_list()
        self.populate_backend_list()

    def init_ui(self):
        """Initialize the UI components."""
        # Main layout
        main_layout = QVBoxLayout(self)

        # Camera display
        self.display_label = QLabel()
        self.display_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
        self.display_label.setMinimumSize(640, 480)
        self.display_label.setStyleSheet("background-color: #222; color: white;")
        self.display_label.setText("Camera Preview")

        # Camera controls
        controls_layout = QHBoxLayout()

        # Camera selection group
        camera_group = QGroupBox("Camera Settings")
        camera_layout = QVBoxLayout(camera_group)

        # Camera selection
        camera_select_layout = QHBoxLayout()
        camera_select_layout.addWidget(QLabel("Camera:"))
        self.camera_combo = QComboBox()
        camera_select_layout.addWidget(self.camera_combo)
        camera_layout.addLayout(camera_select_layout)

        # Backend selection
        backend_layout = QHBoxLayout()
        backend_layout.addWidget(QLabel("Backend:"))
        self.backend_combo = QComboBox()
        backend_layout.addWidget(self.backend_combo)
        camera_layout.addLayout(backend_layout)

        # Resolution controls
        resolution_layout = QHBoxLayout()
        resolution_layout.addWidget(QLabel("Width:"))
        self.width_spin = QSpinBox()
        self.width_spin.setRange(160, 4096)
        self.width_spin.setValue(DEFAULT_WIDTH)
        resolution_layout.addWidget(self.width_spin)

        resolution_layout.addWidget(QLabel("Height:"))
        self.height_spin = QSpinBox()
        self.height_spin.setRange(120, 2160)
        self.height_spin.setValue(DEFAULT_HEIGHT)
        resolution_layout.addWidget(self.height_spin)
        camera_layout.addLayout(resolution_layout)

        # FPS control
        fps_layout = QHBoxLayout()
        fps_layout.addWidget(QLabel("FPS:"))
        self.fps_spin = QSpinBox()
        self.fps_spin.setRange(1, 120)
        self.fps_spin.setValue(DEFAULT_FPS)
        fps_layout.addWidget(self.fps_spin)
        camera_layout.addLayout(fps_layout)

        # Start/Stop camera button
        self.camera_button = QPushButton("Start Camera")
        camera_layout.addWidget(self.camera_button)

        # Capture controls group
        capture_group = QGroupBox("Capture Controls")
        capture_layout = QVBoxLayout(capture_group)

        # Capture image button
        self.capture_button = QPushButton("Capture Image")
        self.capture_button.setEnabled(False)
        capture_layout.addWidget(self.capture_button)

        # Record video controls
        record_layout = QHBoxLayout()
        self.record_button = QPushButton("Start Recording")
        self.record_button.setEnabled(False)
        record_layout.addWidget(self.record_button)

        self.record_duration_spin = QSpinBox()
        self.record_duration_spin.setRange(0, 3600)
        self.record_duration_spin.setValue(10)
        self.record_duration_spin.setSuffix(" sec")
        self.record_duration_spin.setSpecialValueText("Unlimited")
        record_layout.addWidget(self.record_duration_spin)

        capture_layout.addLayout(record_layout)

        # Output directory
        output_layout = QHBoxLayout()
        output_layout.addWidget(QLabel("Output:"))

        self.output_label = QLabel(DEFAULT_OUTPUT_DIR)
        output_layout.addWidget(self.output_label)

        self.browse_button = QPushButton("Browse...")
        output_layout.addWidget(self.browse_button)

        capture_layout.addLayout(output_layout)

        # Add controls to main layout
        controls_layout.addWidget(camera_group)
        controls_layout.addWidget(capture_group)

        # Add components to main layout
        main_layout.addWidget(self.display_label, 1)
        main_layout.addLayout(controls_layout)

        # Status bar
        self.status_label = QLabel("Ready")
        main_layout.addWidget(self.status_label)

        # Connect signals
        self.camera_button.clicked.connect(self.toggle_camera)
        self.capture_button.clicked.connect(self.capture_image)
        self.record_button.clicked.connect(self.toggle_recording)
        self.browse_button.clicked.connect(self.browse_output_dir)

        self.camera_combo.currentIndexChanged.connect(self.update_camera_settings)
        self.backend_combo.currentIndexChanged.connect(self.update_camera_settings)
        self.width_spin.valueChanged.connect(self.update_camera_settings)
        self.height_spin.valueChanged.connect(self.update_camera_settings)
        self.fps_spin.valueChanged.connect(self.update_camera_settings)

    def populate_camera_list(self):
        """Populate the camera selection dropdown."""
        self.camera_combo.clear()

        if not self.cameras:
            self.camera_combo.addItem("No cameras found")
            self.camera_combo.setEnabled(False)
            return

        for camera in self.cameras:
            name = f"{camera['name']} ({camera['width']}x{camera['height']})"
            self.camera_combo.addItem(name, camera['id'])

        self.camera_combo.setEnabled(True)

    def populate_backend_list(self):
        """Populate the backend selection dropdown."""
        self.backend_combo.clear()

        # Add "Default" option
        self.backend_combo.addItem("Default", None)

        for backend_id, name in self.backends.items():
            self.backend_combo.addItem(name, backend_id)

    def update_camera_settings(self):
        """Update camera settings from UI controls."""
        if self.camera_combo.isEnabled():
            self.camera_id = self.camera_combo.currentData()

        self.backend = self.backend_combo.currentData()
        self.width = self.width_spin.value()
        self.height = self.height_spin.value()
        self.fps = self.fps_spin.value()

        # If camera is running, restart it with new settings
        if self.frame_processor and self.frame_processor.running:
            self.stop_camera()
            self.start_camera()

    def toggle_camera(self):
        """Toggle camera on/off."""
        if self.frame_processor and self.frame_processor.running:
            self.stop_camera()
        else:
            self.start_camera()

    def start_camera(self):
        """Start the camera with current settings."""
        try:
            # Create and start frame processor thread
            self.frame_processor = FrameProcessor(
                camera_id=self.camera_id,
                width=self.width,
                height=self.height,
                fps=self.fps,
                backend=self.backend
            )

            # Connect signals
            self.frame_processor.frame_ready.connect(self.update_frame)
            self.frame_processor.error.connect(self.show_error)

            # Start processing
            self.frame_processor.start()

            # Update UI
            self.camera_button.setText("Stop Camera")
            self.capture_button.setEnabled(True)
            self.record_button.setEnabled(True)

            self.status_label.setText(f"Camera started: {self.width}x{self.height} at {self.fps} FPS")

        except Exception as e:
            self.show_error(f"Failed to start camera: {str(e)}")

    def stop_camera(self):
        """Stop the camera."""
        if self.frame_processor:
            self.frame_processor.stop()
            self.frame_processor = None

        # Stop recording if active
        if self.recording:
            self.toggle_recording()

        # Update UI
        self.camera_button.setText("Start Camera")
        self.capture_button.setEnabled(False)
        self.record_button.setEnabled(False)

        # Clear display
        self.display_label.setText("Camera Preview")
        self.frame = None

        self.status_label.setText("Camera stopped")

    @pyqtSlot(np.ndarray)
    def update_frame(self, frame):
        """Update the display with a new frame."""
        self.frame = frame

        # Convert frame to QImage
        h, w, channels = frame.shape
        bytes_per_line = channels * w

        # Convert BGR to RGB for display
        rgb_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)

        q_img = QImage(
            rgb_frame.data, w, h, bytes_per_line, QImage.Format.Format_RGB888
        )

        # Scale the image to fit the label while maintaining aspect ratio
        pixmap = QPixmap.fromImage(q_img)
        self.display_label.setPixmap(
            pixmap.scaled(
                self.display_label.size(),
                Qt.AspectRatioMode.KeepAspectRatio,
                Qt.TransformationMode.SmoothTransformation
            )
        )

    def capture_image(self):
        """Capture and save the current frame as an image."""
        if not self.frame is not None:
            self.status_label.setText("No frame available to capture")
            return

        try:
            # Create output directory if it doesn't exist
            os.makedirs(self.output_label.text(), exist_ok=True)

            # Generate filename with timestamp
            timestamp = time.strftime("%Y%m%d_%H%M%S")
            filename = f"capture_{timestamp}.jpg"
            filepath = os.path.join(self.output_label.text(), filename)

            # Save image
            cv2.imwrite(filepath, self.frame)

            self.status_label.setText(f"Image saved: {filepath}")

        except Exception as e:
            self.show_error(f"Failed to capture image: {str(e)}")

    def toggle_recording(self):
        """Toggle video recording."""
        if self.recording:
            # Stop recording
            self.recording = False
            self.record_button.setText("Start Recording")
            self.status_label.setText(f"Recording saved: {self.recording_path}")

        else:
            # Start recording
            try:
                # Create output directory if it doesn't exist
                os.makedirs(self.output_label.text(), exist_ok=True)

                # Generate filename with timestamp
                timestamp = time.strftime("%Y%m%d_%H%M%S")
                filename = f"recording_{timestamp}.mp4"
                self.recording_path = os.path.join(self.output_label.text(), filename)

                # Get duration (0 means unlimited)
                duration = self.record_duration_spin.value()
                if duration == 0:
                    duration = None

                # TODO: Implement actual recording
                # For now, just update UI
                self.recording = True
                self.record_button.setText("Stop Recording")

                if duration:
                    self.status_label.setText(f"Recording for {duration}s to: {self.recording_path}")
                else:
                    self.status_label.setText(f"Recording to: {self.recording_path}")

            except Exception as e:
                self.show_error(f"Failed to start recording: {str(e)}")

    def browse_output_dir(self):
        """Open a dialog to select output directory."""
        dir_path = QFileDialog.getExistingDirectory(
            self,
            "Select Output Directory",
            self.output_label.text(),
            QFileDialog.Option.ShowDirsOnly
        )

        if dir_path:
            self.output_label.setText(dir_path)

    @pyqtSlot(str)
    def show_error(self, message):
        """Display an error message."""
        QMessageBox.critical(self, "Error", message)
        self.status_label.setText(f"Error: {message}")

        # If camera was running, stop it
        if self.frame_processor and self.frame_processor.running:
            self.stop_camera()


class CameraApp(QMainWindow):
    """
    Main application window for the camera GUI.
    """
    def __init__(self):
        super().__init__()
        self.init_ui()

    def init_ui(self):
        """Initialize the UI."""
        self.setWindowTitle("FMUS-VID Camera Application")
        self.setMinimumSize(800, 600)

        # Create the camera view widget
        self.camera_view = CameraViewWidget()
        self.setCentralWidget(self.camera_view)

        # Create menu bar
        self.create_menu_bar()

        # Create status bar
        self.statusBar().showMessage("Ready")

    def create_menu_bar(self):
        """Create the application menu bar."""
        menu_bar = self.menuBar()

        # File menu
        file_menu = menu_bar.addMenu("&File")

        # Exit action
        exit_action = QAction("E&xit", self)
        exit_action.setShortcut(QKeySequence.StandardKey.Quit)
        exit_action.setStatusTip("Exit the application")
        exit_action.triggered.connect(self.close)
        file_menu.addAction(exit_action)

        # Camera menu
        camera_menu = menu_bar.addMenu("&Camera")

        # Start camera action
        start_action = QAction("&Start Camera", self)
        start_action.setStatusTip("Start the camera")
        start_action.triggered.connect(self.camera_view.start_camera)
        camera_menu.addAction(start_action)

        # Stop camera action
        stop_action = QAction("S&top Camera", self)
        stop_action.setStatusTip("Stop the camera")
        stop_action.triggered.connect(self.camera_view.stop_camera)
        camera_menu.addAction(stop_action)

        camera_menu.addSeparator()

        # Capture image action
        capture_action = QAction("&Capture Image", self)
        capture_action.setShortcut(QKeySequence("Ctrl+C"))
        capture_action.setStatusTip("Capture an image")
        capture_action.triggered.connect(self.camera_view.capture_image)
        camera_menu.addAction(capture_action)

        # Record video action
        record_action = QAction("&Record Video", self)
        record_action.setShortcut(QKeySequence("Ctrl+R"))
        record_action.setStatusTip("Record video")
        record_action.triggered.connect(self.camera_view.toggle_recording)
        camera_menu.addAction(record_action)

        # Help menu
        help_menu = menu_bar.addMenu("&Help")

        # About action
        about_action = QAction("&About", self)
        about_action.setStatusTip("About the application")
        about_action.triggered.connect(self.show_about)
        help_menu.addAction(about_action)

    def show_about(self):
        """Show the about dialog."""
        QMessageBox.about(
            self,
            "About FMUS-VID Camera App",
            """
            <h1>FMUS-VID Camera Application</h1>
            <p>A PyQt6-based GUI application for the FMUS-VID camera module.</p>
            <p>This application demonstrates camera capture functionality using OpenCV.</p>
            <p><b>Version:</b> 1.0.0</p>
            """
        )

    def closeEvent(self, event):
        """Handle window close event."""
        # Stop camera if running
        if self.camera_view.frame_processor and self.camera_view.frame_processor.running:
            self.camera_view.stop_camera()

        event.accept()


def main():
    """Main application entry point."""
    # Create output directory if it doesn't exist
    os.makedirs(DEFAULT_OUTPUT_DIR, exist_ok=True)

    # Create application
    app = QApplication(sys.argv)
    app.setStyle('Fusion')  # Use Fusion style for consistent look across platforms

    # Create and show main window
    main_window = CameraApp()
    main_window.show()

    # Run application
    sys.exit(app.exec())


if __name__ == "__main__":
    main()
