import copy
import ctypes
import struct
import time
from ctypes import *
import sys

from PyQt5.QtCore import pyqtSignal, QMutex
from PyQt5.QtWidgets import QMessageBox

from components.component import Component

from .hikrobot_dll import *


class HikrobotSmartCamera(Component):
    def __init__(self, config=None, name=None, period=0.2, lazy=True, paused=False, threaded=True):
        super().__init__(config=config, name=name, period=period, lazy=lazy, paused=paused, threaded=threaded)
        self.simulate = "--sim-camera" in sys.argv
        self.num_cameras = 1
        self.connected = False
        self.ok_memory = True
        self.ok_frames = []
        self.ok_results = []

    def config_changed(self):
        self.connected = False
        global MV_VS_OK
        device_info_list = MV_VS_DEVICE_INFO_LIST()
        nRet = mv_lib.MV_VS_EnumDevices(ctypes.byref(device_info_list))
        enum_ok = False
        for n in range(3):
            if nRet != MV_VS_OK:
                print(f"Enum devices failed! nRet [{nRet:#x}]")
                return -2
            else:
                print("Enum devices OK")
                if device_info_list.nDeviceNum > 0:
                    print_device_info(device_info_list)
                    if device_info_list.nDeviceNum >= self.num_cameras:
                        enum_ok = True
                        break
                    else:
                        print("wrong number of devices")
                else:
                    print("Find No Devices!")
        if not enum_ok:
            return -2

        self.num_cameras = device_info_list.nDeviceNum
        self.cam_list = {}
        for cam_idx in range(self.num_cameras):
            self.log.info(f"INIT CAMERA # {cam_idx}")
            MV_VS_DEVICE_INFO = device_info_list.pDeviceInfo[cam_idx]
            pHandle = c_void_p()
            nRet = mv_lib.MV_VS_CreateHandle(byref(pHandle), MV_VS_DEVICE_INFO)
            if nRet != MV_VS_OK:
                self.log.error(f"CAM{cam_idx} Create Handle failed! [{nRet&0xFFFFFFFF:#x}]")
                return -2
            self.cam_list[cam_idx] = {"handle": pHandle}
            time.sleep(0.1)
            login_ok = False
            for n in range(3):
                nRet = mv_lib.MV_VS_Login(pHandle, b"Admin", b"Abc1234")
                if nRet != MV_VS_OK:
                    self.log.error(f"CAM{cam_idx} Login failed! [{nRet&0xFFFFFFFF:#x}]")
                    time.sleep(1)
                else:
                    login_ok = True
                    self.log.info(f"CAM{cam_idx} Login OK")
                    break
            if not login_ok:
                return -2
            time.sleep(0.2)

            if hasattr(self.config, "vision_config"):
                solution_name = self.config.vision_config.get("solution_name", None)
            else:
                solution_name = None
            self.switch_scheme(solution_name,cam_idx)

            nRet = mv_lib.MV_VS_SetBoolValue(pHandle, b"CommandImageMode", 0)
            if nRet != MV_VS_OK:
                self.log.error(f"CAM{cam_idx} CommandImageMode failed! [{nRet&0xFFFFFFFF:#x}]")
                return -2
            nRet = mv_lib.MV_VS_SetEnumValue(pHandle, b"AcquisitionMode", 0)
            if nRet != MV_VS_OK:
                self.log.error(f"CAM{cam_idx} AcquisitionMode failed! [{nRet&0xFFFFFFFF:#x}]")
                return -2

            # Start the camera test
            nRet = mv_lib.MV_VS_StartRun(pHandle)
            if nRet != MV_VS_OK:
                self.log.error(f"CAM{cam_idx} Start run failed! [{nRet & 0xFFFFFFFF:#x}]")
        self.connected = True

    def switch_scheme(self,solution_name,cam_idx):

        # --- Start of Added Code: Switch Scheme ---
        # As per SDK PDF Chapter 5.11, this section switches the camera's active scheme.
        # It reads a comma-separated list of scheme names from the config file.
        pHandle= self.cam_list[cam_idx]["handle"]
        try:


            if solution_name is not None:
                scheme_name = solution_name
                self.log.info(f"CAM{cam_idx} Attempting to switch to scheme: {scheme_name}")
                scheme_name_b = bytes(scheme_name,"utf-8")
                # Step 1: Set the scheme name to be switched using MV_VS_SetStringValue.
                # The node name is "SolutionName".
                nRet = mv_lib.MV_VS_SetStringValue(pHandle, b"SrcOperateSolutionName", c_char_p(scheme_name_b))
                if nRet != MV_VS_OK:
                    self.log.error(f"CAM{cam_idx} Set SolutionName failed! nRet [{nRet & 0xFFFFFFFF:#x}]")
                    # Clean up resources before returning
                    mv_lib.MV_VS_Logout(pHandle)
                    mv_lib.MV_VS_DestroyHandle(pHandle)
                    return -2

                # Step 2: Call the scheme switch command using MV_VS_SetCommandValue.
                # The command is "CommandSolutionSwitch".
                nRet = mv_lib.MV_VS_SetCommandValue(pHandle, b"CommandProjectLoad")
                if nRet != MV_VS_OK:
                    self.log.error(f"CAM{cam_idx} CommandSolutionSwitch failed! nRet [{nRet & 0xFFFFFFFF:#x}]")
                    # Clean up resources before returning
                    mv_lib.MV_VS_Logout(pHandle)
                    mv_lib.MV_VS_DestroyHandle(pHandle)
                    return -2

                self.log.info(f"CAM{cam_idx} Switched to scheme '{scheme_name}' successfully.")

                # Step 3: Wait for the scheme switch to complete.
                # As per SDK PDF Chapter 3.3.2, we poll "SolutionSwitchStatus".
                # A value of 0 indicates the switch is complete.
                self.log.info(f"CAM{cam_idx} Waiting for scheme switch to complete...")
                switch_timeout = 30  # 30-second timeout for the switch
                start_time = time.time()
                while True:
                    if time.time() - start_time > switch_timeout:
                        self.log.error(f"CAM{cam_idx} Timed out waiting for scheme switch.")
                        mv_lib.MV_VS_Logout(pHandle)
                        mv_lib.MV_VS_DestroyHandle(pHandle)
                        return -2

                    # Create a variable to store the status
                    status = ctypes.c_uint8()
                    nRet = mv_lib.MV_VS_GetEnumValue(pHandle, b"ScPushState", byref(status))
                    if nRet != MV_VS_OK:
                        self.log.error(f"CAM{cam_idx} Get SolutionSwitchStatus failed! nRet [{nRet & 0xFFFFFFFF:#x}]")
                        mv_lib.MV_VS_Logout(pHandle)
                        mv_lib.MV_VS_DestroyHandle(pHandle)
                        return -2

                    if status.value == 0:
                        self.log.info(f"CAM{cam_idx} Scheme switch completed successfully.")
                        break
                    else:
                        self.log.info(f"CAM{cam_idx} Scheme switch not yet complete. Waiting...")
                        time.sleep(1)

            else:
                self.log.warning(
                    f"CAM{cam_idx} No scheme name defined in config for this camera index. Using default scheme.")
        except KeyError:
            self.log.warning("'schemes' key not found in vision_config. Using camera's default/current scheme.")
        except Exception as e:
            self.log.error(f"CAM{cam_idx} An unexpected error occurred during scheme switching: {e}")
            # Clean up resources before returning
            mv_lib.MV_VS_Logout(pHandle)
            mv_lib.MV_VS_DestroyHandle(pHandle)
            return -2
        # --- End of Added Code ---


    @Component.reconfig_on_error
    def _get(self):
        if not self.connected:
            self.config_changed()
        if not self.connected:
            return
        concat_frame = None
        concat_results = []
        rot = self.config.vision_config["rotations"].split(",")
        for cam_idx in range(self.num_cameras):
            cam = self.cam_list[cam_idx]
            self.log.info(f"GET FRAME CAMERA # {cam_idx}")
            if self.ok_results[cam_idx]:
                # keep last good result
                frame = self.ok_frames[cam_idx]
                res = False
            else:
                # Trigger acquisition & test execution
                nRet = mv_lib.MV_VS_SetCommandValue(cam["handle"], b"AcquisitionStart")
                if nRet != MV_VS_OK:
                    self.log.error(f"CAM{cam_idx} AcquisitionStart failed! [{nRet&0xFFFFFFFF:#x}]")
                    self.connected = False
                    frame_res = None
                else:
                    # get frame
                    frame_res = MV_VS_GetFrame(cam["handle"])

                if frame_res is not None:
                    res = frame_res["res"]["ScDeviceSolutionRunningResult"]
                    frame = frame_res["frame"]
                    # rotate frame
                    if rot[cam_idx] == "1":
                        frame = cv2.rotate(frame, cv2.ROTATE_90_CLOCKWISE)
                    if rot[cam_idx] == "2":
                        frame = cv2.rotate(frame, cv2.ROTATE_90_COUNTERCLOCKWISE)
                    if rot[cam_idx] == "3":
                        frame = cv2.rotate(frame, cv2.ROTATE_180)
                else:
                    self.connected = False
            if concat_frame is None:
                concat_frame = copy.deepcopy(frame)
            else:
                concat_frame = cv2.hconcat([concat_frame, frame])
            concat_results.append(not res)
            if not res:
                self.ok_results[cam_idx] = True
                self.ok_frames[cam_idx] = copy.deepcopy(frame)

        super()._get([concat_frame, self.ok_results])

    def resume(self):
        self.log.info(f"RESUMING")
        if not self.connected:
            self.config_changed()
        if self.connected:
            # init OK result memory
            self.ok_frames = [None for n in range(self.num_cameras)]
            self.ok_results = [False for n in range(self.num_cameras)]

            if len(self.cam_list) == self.num_cameras:
                # Dummy acquisition for each camera to avoid reading past images
                for cam_idx in range(self.num_cameras):
                    solution_name = self.config.vision_config.get("solution_name", None)
                    self.log.info(f"Setting Solution {solution_name} to camera {cam_idx}")
                    self.switch_scheme(solution_name,cam_idx)
                    cam = self.cam_list[cam_idx]
                    nRet = mv_lib.MV_VS_SetCommandValue(cam["handle"], b"AcquisitionStart")
                    frame_res = MV_VS_GetFrame(cam["handle"])
        else:
            QMessageBox.critical(None, "Errore", f"ERRORE CONNESSIONE TELECAMERE\n")
        super().resume()
