import cv2
import tkinter as tk
from tkinter import Canvas
from PIL import Image, ImageTk
import threading
import numpy as np
import warnings
import os
from config import config_manager

# 抑制OpenCV的警告信息（兼容不同版本）
import sys
import io
import time as _time

os.environ['OPENCV_LOG_LEVEL'] = 'SILENT'
os.environ['OPENCV_IO_ENABLE_OPENEXR'] = '0'

try:
    if hasattr(cv2, 'setLogLevel'):
        if hasattr(cv2, 'LOG_LEVEL_SILENT'):
            cv2.setLogLevel(cv2.LOG_LEVEL_SILENT)
        elif hasattr(cv2, 'LOG_LEVEL_ERROR'):
            cv2.setLogLevel(cv2.LOG_LEVEL_ERROR)
        elif hasattr(cv2, 'utils'):
            cv2.utils.setLogLevel(0)
except Exception:
    pass

# 简单日志限流
_last_log_times = {}
def log_throttle(key: str, interval_sec: float, text: str):
    now = _time.time()
    last = _last_log_times.get(key, 0)
    if now - last >= interval_sec:
        _last_log_times[key] = now
        try:
            print(text)
        except Exception:
            pass

class PreviewWindow:
    """采集卡预览窗口"""
    def __init__(self):
        self.config = config_manager.config
        self.caps = {}
        self.frames = {}
        self.large_windows = {}  # idx -> Toplevel
        self.running = True
        
    def init_cameras(self):
        """初始化所有相机"""
        print("🔧 开始初始化采集卡...")
        loaded_count = 0
        
        # 根据配置选择加载哪些组
        display = self.config.get('display', {})
        use_all = bool(display.get('preview_use_all_groups', True))
        if use_all:
            target_groups = self.config['groups']
            print(f"📷 预览全部配置组，共 {len(target_groups)} 组")
        else:
            target_groups = [g for g in self.config['groups'] if g.get('active', False)]
            print(f"📷 仅预览活动配置组，共 {len(target_groups)} 组")
            if not target_groups:
                print("⚠️ 没有活动的配置组，将不显示任何采集卡。可在配置中启用‘预览全部配置组’或设置活动组")
        
        # 重定向stderr来抑制OpenCV的错误输出
        old_stderr = sys.stderr
        suppressed_output = io.StringIO()
        
        try:
            sys.stderr = suppressed_output
            
            for i, group in enumerate(target_groups):
                try:
                    cam_idx = group['camera_index']
                    print(f"  尝试打开采集卡 {cam_idx} ({group['name']})...")
                    
                    cap = None
                    # 尝试多种后端打开
                    backends_to_try = [
                        (int(cam_idx), cv2.CAP_DSHOW),
                        (int(cam_idx), cv2.CAP_ANY),
                        (int(cam_idx), None),
                    ]
                    
                    for idx, backend in backends_to_try:
                        try:
                            with warnings.catch_warnings():
                                warnings.filterwarnings('ignore')
                                if backend is not None:
                                    cap = cv2.VideoCapture(idx, backend)
                                else:
                                    cap = cv2.VideoCapture(idx)
                            
                            if cap.isOpened():
                                # 测试读取一帧
                                ret, test_frame = cap.read()
                                if ret and test_frame is not None:
                                    break
                                else:
                                    cap.release()
                                    cap = None
                        except Exception:
                            if cap:
                                try:
                                    cap.release()
                                except:
                                    pass
                                cap = None
                            continue
                    
                    if cap and cap.isOpened():
                        try:
                            cap.set(cv2.CAP_PROP_FRAME_WIDTH, group['camera_width'])
                            cap.set(cv2.CAP_PROP_FRAME_HEIGHT, group['camera_height'])
                        except Exception as e:
                            print(f"    ⚠️ 设置分辨率失败: {e}")
                        
                        self.caps[i] = {
                            'cap': cap,
                            'group': group,
                            'name': group['name']
                        }
                        loaded_count += 1
                        print(f"  ✅ 采集卡 {cam_idx} 初始化成功")
                    else:
                        print(f"  ❌ 采集卡 {cam_idx} 无法打开")
                except Exception as e:
                    print(f"  ❌ 采集卡 {group.get('camera_index', '?')} 初始化失败: {e}")
                    import traceback
                    traceback.print_exc()
        finally:
            # 恢复stderr
            sys.stderr = old_stderr
        
        if loaded_count == 0:
            print("⚠️ 警告：没有成功加载任何采集卡！")
            print("请检查：")
            print("1. 采集卡是否正确连接")
            print("2. 采集卡索引是否正确")
            print("3. 是否有活动的配置组")
        else:
            print(f"✅ 成功加载 {loaded_count} 个采集卡")
    
    def grab_once(self):
        """抓取每個采集卡當前單幀（裁剪後，存入 self.frames），抓取结果增强debug输出"""
        for idx, data in self.caps.items():
            try:
                cap = data['cap']
                frm_name = data.get('name', str(idx))
                if cap is None:
                    print(f"[抓取] 采集卡{idx}({frm_name}) cap为None")
                    self.frames[idx] = None
                    continue
                ret, frame = cap.read()
                if not ret or frame is None:
                    print(f"[抓取] 采集卡{idx}({frm_name}) 没抓到帧 ret={ret} frame=None")
                    self.frames[idx] = None
                    continue
                height, width = frame.shape[:2]
                crop_top = 30
                crop_bottom = min(crop_top + 720, height)
                crop_width = min(1280, width)
                if crop_bottom <= crop_top or crop_width <= 0:
                    print(f"[抓取] 采集卡{idx}({frm_name}) 尺寸异常: H={height} W={width} 裁剪失败")
                    self.frames[idx] = None
                    continue
                frame = frame[crop_top:crop_bottom, 0:crop_width]
                # 额外检查：像素全黑？
                if np.mean(frame) < 3:
                    print(f"[抓取] 采集卡{idx}({frm_name}) 帧抓到但内容接近全黑 shape={frame.shape}")
                else:
                    print(f"[抓取] 采集卡{idx}({frm_name}) 成功 shape={frame.shape} 类型={frame.dtype}")
                self.frames[idx] = frame
            except Exception as e:
                print(f"[抓取] 采集卡{idx}({data.get('name', str(idx))}) 抓取异常: {e}")
                self.frames[idx] = None
    
    def capture_frames(self):
        """捕获帧"""
        while self.running:
            for idx, data in self.caps.items():
                try:
                    ret, frame = data['cap'].read()
                    if ret and frame is not None:
                        # 裁剪到配置的区域
                        height, width = frame.shape[:2]
                        crop_top = 30
                        crop_bottom = min(crop_top + 720, height)
                        crop_width = min(1280, width)
                        
                        # 确保裁剪范围有效
                        if crop_bottom > crop_top and crop_width > 0:
                            frame = frame[crop_top:crop_bottom, 0:crop_width]
                            
                            # 转换颜色空间
                            frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
                            self.frames[idx] = frame_rgb
                        else:
                            print(f"⚠️ 采集卡 {idx} 裁剪参数无效")
                    else:
                        # 读取失败，清除旧帧
                        if idx in self.frames:
                            self.frames[idx] = None
                except Exception as e:
                    print(f"捕获帧 {idx} 错误: {e}")
                    import traceback
                    traceback.print_exc()
            
            import time
            time.sleep(0.01)  # 避免CPU占用过高
    
    def create_grid_window(self, master=None):
        """创建网格窗口，可挂载在主Tk下作为Toplevel弹窗"""
        display = self.config.get('display', {})
        preview_width = display.get('preview_width', 1000)
        preview_height = display.get('preview_height', 700)
        columns = display.get('preview_columns', 2)
        rows = display.get('preview_rows', 2)

        if master is not None:
            root = tk.Toplevel(master)
        else:
            root = tk.Tk()
        root.title("采集卡预览 - 点击放大")
        root.geometry(f"{preview_width}x{preview_height}")
        root.update_idletasks()
        
        canvas = Canvas(root, bg='black', width=preview_width, height=preview_height)
        canvas.pack(fill=tk.BOTH, expand=True)
        
        # 存储图像对象
        self.photo_objects = {}
        
        # 用于控制调试输出（只打印前几次）
        self.debug_count = 0
        
        def update_frames_once():
            """在主线程中每5秒刷新画面（直接显示高频采集线程生成的帧，去除grab_once）"""
            if not self.running:
                return
            try:
                if not self.caps:
                    canvas.delete("all")
                    canvas.create_text(
                        root.winfo_width() // 2, 
                        root.winfo_height() // 2, 
                        text="未找到可用的采集卡\n\n请检查：\n1. 采集卡是否正确连接\n2. 配置组是否有活动的采集卡\n3. 采集卡索引是否正确", 
                        fill='yellow', 
                        font=('Arial', 14),
                        justify=tk.CENTER
                    )
                    root.after(5000, update_frames_once)
                    return

                # 计算每个预览窗口的位置和大小
                # 直接使用配置值作为画布尺寸（macOS上窗口尺寸可能在显示前返回默认值）
                canvas_width = preview_width
                canvas_height = preview_height
                
                # 尝试获取实际的窗口尺寸，如果有效则使用（大于配置值说明可能被手动调整了）
                try:
                    root.update_idletasks()
                    actual_width = root.winfo_width()
                    actual_height = root.winfo_height()
                    
                    # 只有在获取到合理的尺寸时才使用（大于100像素）
                    if actual_width > 100 and actual_height > 100:
                        canvas_width = actual_width
                        canvas_height = actual_height
                except:
                    pass  # 如果获取失败，使用配置值
                
                cell_width = max(10, canvas_width // columns)
                cell_height = max(10, canvas_height // rows)

                # 先收集所有需要显示的图像
                images_to_draw = []
                texts_to_draw = []
                frame_idx = 0
                
                for idx in self.caps.keys():
                    frame = self.frames.get(idx)
                    has_frame = frame is not None
                    print(f'[DEBUG] update UI idx={idx}, has_frame={has_frame}, frame_info={getattr(frame, "shape", None)}')
                    name = self.caps[idx]['name']
                    row = frame_idx // columns
                    col = frame_idx % columns
                    x = col * cell_width
                    y = row * cell_height
                    center_x = x + cell_width // 2
                    center_y = y + cell_height // 2
                    # 如果没拿到frame，临时主动再抓一次，并DEBUG
                    if not has_frame:
                        print(f'[DEBUG] UI未获得frame，尝试临时抓取采集卡{idx}...')
                        try:
                            cap_test = self.caps[idx]['cap']
                            ret, fr2 = cap_test.read()
                            print(f'[DEBUG] 临时抓取ret={ret}, shape={getattr(fr2, "shape", None)}')
                            if ret and fr2 is not None:
                                frame = fr2
                                has_frame = True
                            else:
                                frame = None
                                has_frame = False
                        except Exception as e:
                            print(f'[DEBUG] 临时抓取异常：{e}')
                            frame = None
                            has_frame = False
                    if has_frame:
                        try:
                            h, w = frame.shape[:2]
                            if w <= 0 or h <= 0:
                                detail = f"采集卡{idx}:{name}\n抓到帧但图像尺寸异常 ({w}x{h})"
                                texts_to_draw.append((center_x, center_y, detail, 'red'))
                                frame_idx += 1
                                continue
                            scale = min(cell_width / w, cell_height / h, 1.0) * 0.85
                            new_w = max(1, int(w * scale))
                            new_h = max(1, int(h * scale))
                            if new_w <= 0 or new_h <= 0:
                                detail = f"采集卡{idx}:{name}\n缩放计算异常 ({w}x{h})"
                                texts_to_draw.append((center_x, center_y, detail, 'red'))
                                frame_idx += 1
                                continue
                            display_frame = frame
                            if len(display_frame.shape) == 3 and display_frame.shape[2] == 3:
                                display_frame = cv2.cvtColor(display_frame, cv2.COLOR_BGR2RGB)
                            resized_frame = cv2.resize(display_frame, (new_w, new_h))
                            pil_image = Image.fromarray(resized_frame)
                            photo = ImageTk.PhotoImage(image=pil_image)
                            if not hasattr(self, 'photo_refs'):
                                self.photo_refs = {}
                            self.photo_refs[idx] = photo
                            canvas.create_image(center_x, center_y, image=photo, anchor='center')
                            canvas.update()  # 保证实时刷新
                            print(f'[DEBUG] UI idx={idx}, 绘制photo id={id(photo)}, photo_refs={len(self.photo_refs)}')
                            images_to_draw.append((photo, center_x, center_y))
                            texts_to_draw.append((center_x, y + 15, name, 'white'))
                            frame_idx += 1
                        except Exception as e:
                            errstr = str(e)
                            detail = f"采集卡{idx}:{name}\n帧处理异常：{errstr}"
                            print(f'[DEBUG] idx={idx}, 帧处理异常：{errstr}')
                            texts_to_draw.append((center_x, center_y, detail, 'red'))
                            frame_idx += 1
                    else:
                        detail = f"采集卡{idx}:{name}\n抓取失败/暂无画面"
                        print(f'[DEBUG] idx={idx} 依然无可用帧，最终显示错误提示')
                        texts_to_draw.append((center_x, center_y, detail, 'red'))
                        frame_idx += 1
                
                # 清空画布
                canvas.delete("all")
                
                # 先绘制所有图像（底层）
                if images_to_draw and self.debug_count <= 3:
                    log_throttle("draw_prepare", 2.0, f"✅ 准备绘制 {len(images_to_draw)} 个图像到画布 ({canvas_width}x{canvas_height})")
                    
                for photo, x, y in images_to_draw:
                    try:
                        # PhotoImage强引用，防止被GC
                        if not hasattr(self, 'photo_refs'):
                            self.photo_refs = {}
                        self.photo_refs[idx] = photo
                        canvas.create_image(x, y, image=photo, anchor='center')
                        canvas.update()  # 保证实时刷新
                        print(f'[DEBUG] idx={idx}, photo id={id(photo)}, photo refs={len(self.photo_refs)}')
                    except Exception as e:
                        if "pyimage" not in str(e).lower():
                            log_throttle("draw_image_error", 2.0, f"绘制图像错误: {e}")
                
                # 再绘制所有文本（上层）
                for x, y, text, color in texts_to_draw:
                    try:
                        if color == 'white':
                            canvas.create_text(x, y, text=text, fill=color, font=('Arial', 10, 'bold'))
                        else:
                            canvas.create_text(x, y, text=text, fill=color, font=('Arial', 10))
                    except Exception as e:
                        log_throttle("draw_text_error", 2.0, f"绘制文本错误: {e}")
                
                # 绘制分割线，区分不同的采集卡窗口
                try:
                    # 绘制垂直分割线（列之间的分割线）
                    for col in range(1, columns):
                        x = col * cell_width
                        canvas.create_line(
                            x, 0, 
                            x, canvas_height,
                            fill='white', 
                            width=2,
                            dash=(5, 5)  # 虚线效果，让分割线更明显
                        )
                    
                    # 绘制水平分割线（行之间的分割线）
                    for row in range(1, rows):
                        y = row * cell_height
                        canvas.create_line(
                            0, y,
                            canvas_width, y,
                            fill='white',
                            width=2,
                            dash=(5, 5)  # 虚线效果
                        )
                except Exception as e:
                    log_throttle("draw_grid_error", 2.0, f"绘制分割线错误: {e}")
                        
            except Exception as e:
                log_throttle("update_frame_error", 1.0, f"更新帧错误: {e}")
                import traceback
                traceback.print_exc()
            # 每5秒更新一次
            root.after(5000, update_frames_once)
        
        def on_canvas_click(event):
            """点击画布事件"""
            window_width = root.winfo_width()
            window_height = root.winfo_height()
            cell_width = window_width // columns
            cell_height = window_height // rows
            
            col = int(event.x // cell_width)
            row = int(event.y // cell_height)
            index = row * columns + col
            
            # 找到对应的配置
            if index < len(self.caps):
                idx = list(self.caps.keys())[index]
                self.show_large_window(idx)
        
        canvas.bind('<Button-1>', on_canvas_click)
        
        # 等待窗口完全初始化后再开始更新
        def start_updates():
            """延迟启动更新，确保窗口已完全显示"""
            # 强制更新窗口尺寸
            root.update_idletasks()
            root.update()
            # 等待窗口完全绘制
            import time
            time.sleep(0.2)  # 给窗口更多时间初始化
            root.update_idletasks()
            root.update()
            update_frames_once()
        
        # 使用after在主线程中循环刷新（延迟启动，给足够时间让窗口初始化）
        root.after(200, start_updates)
        
        def on_closing():
            """关闭窗口"""
            self.running = False
            for data in self.caps.values():
                data['cap'].release()
            root.destroy()
        
        root.protocol("WM_DELETE_WINDOW", on_closing)
        # 仅在独立窗口（无master）时进入事件循环
        if master is None:
            root.mainloop()
    
    def show_large_window(self, idx):
        """显示指定采集卡的大窗口（可多实例）"""
        # 如果已存在该索引窗口，先销毁再创建，确保刷新
        if idx in self.large_windows and self.large_windows[idx].winfo_exists():
            try:
                self.large_windows[idx].destroy()
            except Exception:
                pass
        
        win = tk.Toplevel()
        win.title(f"放大视图 - {self.caps[idx]['name']}")
        win.geometry("1280x720")
        self.large_windows[idx] = win
        
        canvas = Canvas(win, bg='black')
        canvas.pack(fill=tk.BOTH, expand=True)
        
        photo_obj = {}
        
        def update_large_once():
            if not self.running or not win.winfo_exists():
                return
            try:
                window_width = win.winfo_width() if win.winfo_width() > 1 else 1280
                window_height = win.winfo_height() if win.winfo_height() > 1 else 720
                
                if idx in self.frames and self.frames[idx] is not None:
                    frame = self.frames[idx]
                    h, w = frame.shape[:2]
                    scale = min(window_width / w, window_height / h)
                    new_w = int(w * scale)
                    new_h = int(h * scale)
                    resized_frame = cv2.resize(frame, (new_w, new_h))
                    if len(resized_frame.shape) == 3 and resized_frame.shape[2] == 3:
                        resized_frame = cv2.cvtColor(resized_frame, cv2.COLOR_BGR2RGB)
                    pil_image = Image.fromarray(resized_frame)
                    photo = ImageTk.PhotoImage(image=pil_image)
                    photo_obj['img'] = photo
                    canvas.delete("all")
                    canvas.create_image(window_width // 2, window_height // 2, image=photo, anchor='center')
                else:
                    canvas.delete("all")
                    # 显示更详细的调试信息
                    debug_text = f"等待画面...\n采集卡: {idx}\n帧状态: {idx in self.frames}\n"
                    if idx in self.frames:
                        debug_text += f"帧数据: {self.frames[idx] is not None}"
                    else:
                        debug_text += "帧数据: 未初始化"
                    canvas.create_text(
                        window_width // 2,
                        window_height // 2,
                        text=debug_text,
                        fill='gray',
                        font=('Arial', 12),
                        justify='center'
                    )
            except Exception as e:
                if "pyimage" not in str(e).lower():
                    log_throttle("large_update_error", 1.0, f"更新大窗口错误: {e}")
            # 放大視窗維持原本高頻刷新
            win.after(33, update_large_once)

        win.after(33, update_large_once)

        def on_close():
            try:
                if idx in self.large_windows:
                    del self.large_windows[idx]
            finally:
                win.destroy()
        win.protocol("WM_DELETE_WINDOW", on_close)
    
    def run(self, master=None):
        """运行预览，支持主Tk弹窗模式"""
        self.init_cameras()

        if not self.caps:
            print("⚠️ 没有可用的采集卡，将显示错误提示窗口")

        display = self.config.get('display', {})
        multi_window = bool(display.get('preview_multi_window', False))

        if self.caps:
            capture_thread = threading.Thread(target=self.capture_frames, daemon=True)
            capture_thread.start()
            import time
            time.sleep(0.5)

        if multi_window:
            root = tk.Toplevel(master) if master else tk.Tk()
            root.title("采集卡预览 - 多窗口模式")
            root.geometry("300x80")
            tk.Label(root, text="已打开多窗口预览，可单独拖动/调整大小").pack(pady=20)
            print("等待采集卡初始化...")
            max_wait = 50
            wait_count = 0
            while wait_count < max_wait and not any(frame is not None for frame in self.frames.values()):
                time.sleep(0.1)
                wait_count += 1
            if wait_count >= max_wait:
                print("⚠️ 采集卡初始化超时，可能无法显示画面")
            for idx in list(self.caps.keys()):
                print(f"打开采集卡 {idx} 的预览窗口...")
                self.show_large_window(idx)
            def on_root_close():
                self.running = False
                for data in self.caps.values():
                    data['cap'].release()
                try:
                    root.destroy()
                except Exception:
                    pass
            root.protocol("WM_DELETE_WINDOW", on_root_close)
            # 仅在独立窗口（无master）时进入事件循环
            if master is None:
                root.mainloop()
        else:
            self.create_grid_window(master)

if __name__ == "__main__":
    preview = PreviewWindow()
    preview.run()

