"""IMI 카메라 제어 클래스 (Neptune API)"""

from ast import Global
import ctypes
from email.mime import image
import logging
import multiprocessing
import os
import sys
import time
from pathlib import Path
from typing import Optional

import cv2
import numpy as np
from PySide6.QtCore import QObject, Signal
from PySide6.QtGui import QImage, QPixmap

sys.path.insert(0, str(Path(__file__).resolve().parents[1]))  # Dynamic2 폴더

from camera.SmBlock import SmBlock
from utils.config_log import logger as log
from ConfigReader import GlobalConfig, banner
from camera.Neptune_API import *
from threading import Thread


class CameraStatus(Thread, QObject):
    image_received = Signal(np.ndarray)
    status_changed = Signal(str)
    def __init__(self):
        QObject.__init__(self)
        Thread.__init__(self)
        self.running = True
        self.smblock = SmBlock(name="SmBlock", shape=(2048,2448, 3), maxsize=100, create=True, lock=multiprocessing.Lock())
        self.rx_image = multiprocessing.Queue()
        self.rx_cmdq = multiprocessing.Queue()
        self.is_connected = False
        self.count_invalid = 0
        self.count_rx = 0
        
    def set_value(self, key, value):
        print(f"Set {key} = {value}")
        
    def put_cmd(self, cmd):
        self.rx_cmdq.put(cmd)
        
    def put_image(self, rx_frame, pImage):
        #print(f"put_image called self.count_rx={self.count_rx} count_invalid={self.count_invalid}")
        if rx_frame is None:
            self.rx_image.put(None)
            return
        image = (cv2.cvtColor(rx_frame.reshape(pImage.uiHeight, pImage.uiWidth), cv2.COLOR_GRAY2BGR)
                    if pImage.uiBitDepth != 8
                    else cv2.cvtColor(rx_frame.reshape(pImage.uiHeight, pImage.uiWidth, -1), 63))
        index = self.smblock.malloc2(image)
        self.rx_image.put((index, self.count_rx))
        
    def put_cmd(self, cmd):
        self.rx_cmdq.put(cmd)

    def run(self):
        self.running = True
        reason = "normal_exit"
        while self.running:
            if not self.rx_image.empty():
                image_id, frame_id = self.rx_image.get()
                if image_id is None:
                    reason = "image_none"
                    break
                #print(f"Received index:{image_id} rx={self.count_rx} invalid={self.count_invalid} is_connected = {self.is_connected}")
                image = self.smblock.get(image_id)
                self.image_received.emit(image.copy()) # 빨리 Frame들어오면 문제가 생김
                self.smblock.mfree(image_id)
            if not self.rx_cmdq.empty():
                cmd = self.rx_cmdq.get()
                banner(f"[CAM.SIGANL] <{cmd}> received")
                if cmd == "close":
                    self.running = False
                    self.is_connected = False
                    reason = "cmd_close"
                    break
                if cmd == "connected":
                    reason = "cmd_connected"
                    self.reopen()
                    self.is_connected = True
                    self.status_changed.emit("connected")
                if cmd == "disconnected":
                    reason = "cmd_disconnected"
                    self.is_connected = False
                    self.status_changed.emit("disconnected")
            time.sleep(0.01)
        banner(f"** CameraSignal thread stopped. reason: {reason} **")

    @staticmethod
    def numpy_to_pixmap( image: np.ndarray) -> QPixmap:
        if image.ndim == 2:
            height, width = image.shape
            q_image = QImage(image.data, width, height, width, QImage.Format_Grayscale8)
        else:
            height, width, channels = image.shape
            format_map = {3: QImage.Format_BGR888, 4: QImage.Format_RGBA8888}
            if channels not in format_map:
                raise ValueError(f"Unsupported channel number: {channels}")
            q_image = QImage(image.data, width, height, channels * width, format_map[channels])

        return QPixmap.fromImage(q_image)
        
class imi_camera(CameraStatus):
    """IMI 카메라 제어 클래스"""

    DriverInit = False
    HEARTBEAT_TIME = 500
    DEFAULT_MAC = "192.168.1.100"
   

    def __init__(self):
        super().__init__()
        self.handle = ctypes.c_void_p(0)
        self.mac = self.DEFAULT_MAC
   
        
        log.info("[ImiCamera] 인스턴스 생성")
      
        # self.flow_thread = CameraSignal(camera=self)

    @staticmethod
    def get_mac_list() -> list[str]:
        """연결된 카메라의 MAC 주소 목록 반환"""
        numberOfCamera = ctypes.c_uint32(0)
        err = ntcGetCameraCount(ctypes.pointer(numberOfCamera))
        # banner(f"get_mac_list: err= {err}, count= {self.numberOfCamera.value}")
        if err != ENeptuneError.NEPTUNE_ERR_Success.value or numberOfCamera.value == 0:
            log.error(f"[get_mac_list] 카메라를 찾을 수 없음 / 관리자권한, 케이블점검 err = {err} count= {numberOfCamera.value}")
            return []

        cam_count = numberOfCamera.value
        info = (NEPTUNE_CAM_INFO * cam_count)()
        err = ntcGetCameraInfo(info, cam_count)

        if err != ENeptuneError.NEPTUNE_ERR_Success.value:
            log.error(f"[get_mac_list] ntcGetCameraInfo 실패: err={err}")
            return []

        mac_list = [info[i].strMAC.decode('utf-8') for i in range(cam_count)]
        log.info(f"[get_mac_list] 카메라 {cam_count}대 발견: {mac_list}")
        return mac_list

    def close(self) -> None:
        banner(f"[CAM.CLOSE] {self.is_connected}")
        if not self.is_connected:
            return
        try:
            self.put_cmd("close")
            ntcClose(self.handle)
            count = 0
            while self.rx_image.qsize() > 0:
                image_id,frame_id = self.rx_image.get()
                self.smblock.mfree(image_id)
                count = count + 1
            banner(f"CAMEAR. CLOSE -------------------------------droped frames: {count}")
            self.put_cmd("disconnected")
        except Exception as e:
            log.exception(f"[ImiCamera] 종료 중 오류: {e}")
            self.put_cmd("disconnected")
            
    def open(self) -> bool:
        """카메라 연결"""
        banner("CAMEAR. OPEN ---------------------------------")
        if self.is_connected:
            log.warning("[ImiCamera] 이미 연결되어 있음")
            return True

        # 드라이버 초기화
        if not imi_camera.DriverInit:
            if ntcInit() != ENeptuneError.NEPTUNE_ERR_Success.value:
                log.error("[ImiCamera] 드라이버 초기화 실패")
                return False
            imi_camera.DriverInit = True
            log.info("[ImiCamera] 드라이버 초기화 완료")
            banner(f"connect: DriverInit= {imi_camera.DriverInit} success")

        # 기존 핸들 정리
        if self.handle:
            ntcClose(self.handle)
        self.handle = ctypes.c_void_p(0)

        # 카메라 검색 및 연결
        mac_list = imi_camera.get_mac_list()
        if not mac_list:
            log.error("[ImiCamera] 사용 가능한 카메라가 없음")
            return False
        self.mac = mac_list[0]
        self.reopen()

        # if ntcOpen(bytes(self.mac, encoding='utf-8'), ctypes.byref(self.handle)) != ENeptuneError.NEPTUNE_ERR_Success.value:
        #     log.error(f"[ImiCamera] ntcOpen 실패: {self.mac}")
        #     return False

        # if ntcSetHeartbeatTime(self.handle, self.HEARTBEAT_TIME) != ENeptuneError.NEPTUNE_ERR_Success.value:
        #     log.error("[ImiCamera] ntcSetHeartbeatTime 실패")
        #     return False

        # self.set_trigger_mode(False) # 처음 패킷막기

        # # 콜백 설정
        # self.callback_func = ctypes.CFUNCTYPE(None, NEPTUNE_IMAGE, ctypes.c_void_p)(self.RecvFrameCallBack)
        # ntcSetFrameCallback(self.handle, self.callback_func, self.handle)

        # self.callback_drop_func = ctypes.CFUNCTYPE(None, ctypes.c_void_p)(self.RecvFrameDropCallBack)
        # ntcSetFrameDropCallback(self.handle, self.callback_drop_func, self.handle)

        # self.callback_device_check_func = ctypes.CFUNCTYPE(None, ctypes.c_int, ctypes.c_void_p)(self.RecvDeviceCheckCallBack)
        # ntcSetDeviceCheckCallback(self.callback_device_check_func, self.handle)

        # # Acquisition 초기화
        # for i, state in enumerate([False, True, False, True]):
        #     ntcSetAcquisition(self.handle, ENeptuneBoolean.NEPTUNE_BOOL_TRUE.value if state else ENeptuneBoolean.NEPTUNE_BOOL_FALSE.value)

        self.thread = Thread(target=self.run, daemon=True)
        self.thread.start()
        self.put_cmd("connected")
        log.info(f"[ImiCamera] 카메라 연결 성공: {self.mac}")
        return True
    
    def reopen(self):
        banner("CAMEAR. RE-OPEN ---------------------------------")
       
        if ntcOpen(bytes(self.mac, encoding='utf-8'), ctypes.byref(self.handle)) != ENeptuneError.NEPTUNE_ERR_Success.value:
            log.error(f"[ImiCamera] ntcOpen 실패: {self.mac}")
            return False

        if ntcSetHeartbeatTime(self.handle, self.HEARTBEAT_TIME) != ENeptuneError.NEPTUNE_ERR_Success.value:
            log.error("[ImiCamera] ntcSetHeartbeatTime 실패")
            return False
        
        nExposeure = GlobalConfig.instance().getConfigValue("camera.exposure", 15000)
        exposure = ctypes.c_uint32(nExposeure)
        if ntcSetExposureTime(self.handle, exposure.value) != ENeptuneError.NEPTUNE_ERR_Success.value:
            log.error(f"[ImiCamera] ntcSetExposureTime 실패")
            return False
        
        self.set_trigger_mode(False) # 처음 패킷막기

        # 콜백 설정
        self.callback_func = ctypes.CFUNCTYPE(None, NEPTUNE_IMAGE, ctypes.c_void_p)(self.RecvFrameCallBack)
        ntcSetFrameCallback(self.handle, self.callback_func, self.handle)

        self.callback_drop_func = ctypes.CFUNCTYPE(None, ctypes.c_void_p)(self.RecvFrameDropCallBack)
        ntcSetFrameDropCallback(self.handle, self.callback_drop_func, self.handle)

        self.callback_device_check_func = ctypes.CFUNCTYPE(None, ctypes.c_int, ctypes.c_void_p)(self.RecvDeviceCheckCallBack)
        ntcSetDeviceCheckCallback(self.callback_device_check_func, self.handle)

        # Acquisition 초기화
        for i, state in enumerate([False, True, False, True]):
            ntcSetAcquisition(self.handle, ENeptuneBoolean.NEPTUNE_BOOL_TRUE.value if state else ENeptuneBoolean.NEPTUNE_BOOL_FALSE.value)

    def set_frame_rate(self, frame_rate: float) -> bool:
        emFPS = ctypes.c_uint32(0)
        dbFPS = ctypes.c_double(frame_rate)
        if ntcSetFrameRate(self.handle, emFPS.value, dbFPS.value) != ENeptuneError.NEPTUNE_ERR_Success.value:
            log.error(f"[ImiCamera] ntcSetFrameRate 실패")
            return False
        
    def set_trigger_mode(self, enabled: bool) -> bool:
        self.count_invalid = 0
        self.count_rx = 0
        st_trigger = NEPTUNE_TRIGGER()
        st_trigger.Source = ENeptuneTriggerSource.NEPTUNE_TRIGGER_SOURCE_LINE1.value
        st_trigger.Mode = ENeptuneTriggerMode.NEPTUNE_TRIGGER_MODE_0.value
        st_trigger.Polarity = ENeptunePolarity.NEPTUNE_POLARITY_FALLINGEDGE.value
        if enabled == True:
            st_trigger.OnOff = ENeptuneBoolean.NEPTUNE_BOOL_TRUE.value
            self.set_frame_rate(300.0)
        else :
            st_trigger.OnOff = ENeptuneBoolean.NEPTUNE_BOOL_FALSE.value
            self.set_frame_rate(5.0)
        err = ntcSetTrigger(self.handle, st_trigger)
        if err == ENeptuneError.NEPTUNE_ERR_Success.value:
            self.get_trigger_mode()
            return True
        else:
            banner(f"ntcSetTrigger err={err}")
            return False

    def get_trigger_mode(self):
        """ 카메라의 트리거 모드 정보 GET """
        neptune_trigger = NEPTUNE_TRIGGER()
        err = ntcGetTrigger(self.handle,ctypes.pointer(neptune_trigger))
        
        if err != 0:
            return (False, None)
        else :
            # 성공한 경우 값을 출력
            print(f"Trigger OnOff : {neptune_trigger.OnOff}"
                  f" Mode : {neptune_trigger.Mode}"
                  f" Polarity : {neptune_trigger.Polarity}"
            f"Trigger nParam : {neptune_trigger.nParam}")

            if neptune_trigger.OnOff == ENeptuneBoolean.NEPTUNE_BOOL_TRUE.value:
                return (True, True)
            else :
                return (True, False)

    def set_exposure(self, exposure: int) -> bool:
        banner(f"set_exposure: {exposure}")
        GlobalConfig.instance().setConfigValue("camera.exposure", exposure)
        pui_micro_sec = ctypes.c_uint32(exposure)
        err = ntcSetExposureTime(self.handle, pui_micro_sec)
        if err != 0:
            return False
        return True
    
    def get_exposure(self) -> int:
        """카메라 노출값 반환 (config에서)"""
        return GlobalConfig.instance().getConfigValue("camera.exposure", 15000)
    
    def clear_image(self):
        for i, state in enumerate([False, True, False, True]):
            ntcSetAcquisition(self.handle, ENeptuneBoolean.NEPTUNE_BOOL_TRUE.value if state else ENeptuneBoolean.NEPTUNE_BOOL_FALSE.value)
            
    def stop_stream(self):
        ntcSetAcquisition(self.handle, ENeptuneBoolean.NEPTUNE_BOOL_FALSE.value)
        
    def start_stream(self):
        for i, state in enumerate([False, True, False, True]):
            ntcSetAcquisition(self.handle, ENeptuneBoolean.NEPTUNE_BOOL_TRUE.value if state else ENeptuneBoolean.NEPTUNE_BOOL_FALSE.value)



    def RecvDeviceCheckCallBack(self, state: int, pContext: Optional[ctypes.c_void_p] = None) -> None:
        log.warning(f"[ImiCamera] 디바이스 {'추가' if state == 0 else '제거'}")
        if state == 0:
            self.put_cmd("connected")
            for s in [False, True, False, True]:
                ntcSetAcquisition(self.handle, ENeptuneBoolean.NEPTUNE_BOOL_TRUE.value if s else ENeptuneBoolean.NEPTUNE_BOOL_FALSE.value)
        else:
            self.put_cmd("disconnected")
        return None
    


    def RecvFrameCallBack(self, pImage: NEPTUNE_IMAGE, pContext: Optional[ctypes.c_void_p] = None) -> Optional[np.ndarray]:
        try:
            self.count_rx += 1
         
            rx_frame = np.frombuffer(pImage.pData[0:pImage.uiSize], dtype=np.uint8)
            self.put_image(rx_frame, pImage)
        except Exception as e:
            log.exception(f"[ImiCamera] 프레임 처리 오류: {e}")
            return None

    def RecvFrameDropCallBack(self, pContext: Optional[ctypes.c_void_p] = None) -> None:
        self.count_invalid += 1



def main() -> int:
    """테스트 메인 함수"""
    logging.basicConfig(
        level=logging.INFO,
        format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
    )

    camera = imi_camera()
    try:
        if not camera.open():
            log.error("카메라 연결 실패")
            return 1
        camera.set_trigger_mode(True)
        time.sleep(20)
        camera.close()
        log.info(f"최종 수신된 프레임 수: {camera.count_rx}")
        return 0

    except KeyboardInterrupt:
        log.info("사용자 중단 (Ctrl+C)")
        return 0
    except Exception as e:
        log.error(f"예외 발생: {e}")
        import traceback
        traceback.print_exc()
        return 1
    finally:
        camera.close()
        log.info("프로그램 종료")


if __name__ == "__main__":
    exit(main())
