import cv2, numpy as np
import os

from Tbd.helper import CameraObject
from UI.UILogger import UILogger

from PySide6.QtCore import Qt, QTimer, QThread, Signal
from PySide6.QtGui import QImage, QPixmap
from PySide6.QtWidgets import QLineEdit
from PySide6.QtGui import QIntValidator
from PySide6.QtWidgets import QWidget, QLabel, QVBoxLayout, QPushButton, QHBoxLayout
from typing import List


class LoadCamerasWorker(QThread):
    """
    Opens/verifies/closes each camera off the UI thread - camera.open() does
    MSMF format negotiation with retries+sleeps, which used to block the Qt
    event loop (window shows as "Not Responding") for the whole duration.
    """
    cameraOpened = Signal(object)
    logMessage = Signal(str)

    def __init__(self, indexToAdd: List[int]):
        super().__init__()
        self.indexToAdd = indexToAdd

    def run(self):
        for index in self.indexToAdd:
            camera = CameraObject(capture=None, index=index)

            if not camera.open():
                self.logMessage.emit(f"cam{index}: failed to open, skipping.")
                continue

            ret, frame = camera.capture.read()
            if ret:
                self.logMessage.emit(f"cam{index}: verified, frame shape={frame.shape}")
            else:
                self.logMessage.emit(f"cam{index}: opened but failed to read a frame.")

            camera.close()
            self.cameraOpened.emit(camera)


class SetupPage(QWidget):
    """
    Page that handles camera preview & cycling through cameras.
    """

    def __init__(self, cameraList: List[CameraObject], on_cameraList_changed, logger : UILogger):
        super().__init__()

        self.on_cameraList_changed = on_cameraList_changed
        self.cameraList = cameraList
        self.logger = logger
        self.cameraObject = CameraObject(
            capture=None,
            index=None
        )

        # --- UI ---
        self.setUpLayout = QVBoxLayout(self)

        self.setUpLayout.addLayout(self._make_Setup_Controls())
        self.setUpLayout.addLayout(self._make_Setup_Options())

    def _make_Setup_Options(self):
        setupOptions = QHBoxLayout()
        setupOptions2 = QVBoxLayout()

        self.cameraIndexLable = QLabel("Camera Index:")
        self.cameraIndex = QLineEdit("0")
        self.cameraIndex.setValidator(QIntValidator(0, 9999, self))

        self.addCameraBTN = QPushButton("Add Camera")
        self.nextCameraBTN = QPushButton("Next Camera")
        self.previousCameraBTN = QPushButton("Previous Camera")

        setupOptions.addWidget(self.cameraIndexLable)
        setupOptions.addWidget(self.cameraIndex, 1)
        setupOptions.addWidget(self.addCameraBTN)
        setupOptions.addWidget(self.nextCameraBTN)
        setupOptions.addWidget(self.previousCameraBTN)

        self.nextCameraBTN.clicked.connect(self._nextIndex)
        self.previousCameraBTN.clicked.connect(self._previousIndex)
        #self.cameraIndex.editingFinished.connect(self._updateCamera)
        self.addCameraBTN.clicked.connect(self._addCamera)

        # ---
        self.camera_preview = QLabel("No camera")
        self.camera_preview.setAlignment(Qt.AlignCenter)
        self.camera_preview.setMinimumSize(640, 360)
        self.camera_preview.setStyleSheet("background: #222; color: #aaa;")

        self.timer = QTimer(self)
        self.timer.timeout.connect(self._updateCamera)

        setupOptions2.addLayout(setupOptions)
        setupOptions2.addWidget(self.camera_preview)


        return setupOptions2
    
    def _addCamera(self):

        if any(camera.index == int(self.cameraIndex.text()) for camera in self.cameraList):
            print("Camera already added.")
            return
        
        self.cameraList.append(self.cameraObject)
        self._cameraList_changed()


    def load_setup(self):
        if (os.path.exists(self.setupFilePath.text())):
            self.logger.log("Path found")
        else:
            self.logger.log("Path not found")

        indexToAdd = [1, 2, 3, 4, 5, 6]
        #indexToAdd = [0, 3]
        # Cameras are verified one at a time and left closed afterwards - this
        # hardware can't sustain many concurrent capture graphs (confirmed via
        # both CAP_DSHOW and a raw DirectShow graph), so callers open a camera
        # right before they need it (CameraSetupWidget.Start, calibration capture)
        # and close it right after, instead of keeping all 6 open simultaneously.
        self.loadBTN.setEnabled(False)
        self._loadWorker = LoadCamerasWorker(indexToAdd)
        self._loadWorker.logMessage.connect(self.logger.log)
        self._loadWorker.cameraOpened.connect(self._onCameraLoaded)
        self._loadWorker.finished.connect(lambda: self.loadBTN.setEnabled(True))
        self._loadWorker.start()

    def _onCameraLoaded(self, camera: CameraObject):
        self.cameraList.append(camera)
        self._cameraList_changed()

        
        
    def save_setup(self):

        print("ToDo")
        return True
    
    def _cameraList_changed(self):
        if self.on_cameraList_changed:
            self.on_cameraList_changed()
       
    def _updateCamera(self):
        if self.cameraObject.capture is None:
            return
        ret, frame = self.cameraObject.capture.read()
        if not ret:
            return

        frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
        h, w, ch = frame_rgb.shape
        bytes_per_line = ch * w
        qimg = QImage(frame_rgb.data, w, h, bytes_per_line, QImage.Format_RGB888)
        self.camera_preview.setPixmap(QPixmap.fromImage(qimg))
        
    def _nextIndex(self):
        cameraIndex = int(self.cameraIndex.text())
        self.cameraIndex.setText(str(cameraIndex + 1))

        if self.open_camera():
            if not self.timer.isActive():
                self.timer.start(30)

        self._updateCamera()
    
    def _previousIndex(self):
        cameraIndex = int(self.cameraIndex.text())

        if((cameraIndex - 1) < 0):
            self.cameraIndex.setText("0")

        self.cameraIndex.setText(str(cameraIndex - 1))

        if self.open_camera():
            if not self.timer.isActive():
                self.timer.start(30)
        
        self._updateCamera()
    
    def open_camera(self):
        index = int(self.cameraIndex.text())

        self.release_camera()

        # Reuse the CameraObject from cameraList (if this index is already known)
        # instead of opening a second handle to the same physical device.
        existing = next((cam for cam in self.cameraList if cam.index == index), None)
        self.cameraObject = existing if existing is not None else CameraObject(capture=None, index=index)

        if not self.cameraObject.open():
            self.camera_preview.setText(f"Failed to open camera {index}")
            return False

        self.camera_preview.setText("")

        return True

    def release_camera(self):
        if self.cameraObject.capture is not None:
            self.timer.stop()
            self.cameraObject.close()

    def closeEvent(self, event):
        self.release_camera()
        event.accept()

    def _make_Setup_Controls(self):
        # Create Setup Input
        setupFileLayout = QHBoxLayout()
        self.setupFilePathLable = QLabel("Setupfile path:")
        self.setupFilePath = QLineEdit()
        self.loadBTN = QPushButton("Load")
        self.saveBTN = QPushButton("Save")

        setupFileLayout.addWidget(self.setupFilePathLable)
        setupFileLayout.addWidget(self.setupFilePath, 1)
        setupFileLayout.addWidget(self.loadBTN)
        setupFileLayout.addWidget(self.saveBTN)

        self.loadBTN.clicked.connect(self.load_setup)
        self.saveBTN.clicked.connect(self.save_setup)

        return setupFileLayout
