import tkinter as tk
from tkinter import ttk, messagebox, simpledialog
import cv2
from config import config_manager
import threading
import serial.tools.list_ports

class ConfigGUI:
    """配置GUI界面"""
    def __init__(self):
        self.root = tk.Tk()
        self.root.title("配置管理 - 火炬之光自动化")
        self.root.geometry("1000x720")
        self.root.minsize(900, 600)  # 设置最小尺寸
        
        self.selected_index = 0
        self.preview_thread = None  # 添加预览线程引用
        self.setup_ui()
        self.load_current_config()
    
    def setup_ui(self):
        """设置UI界面"""
        # 左侧：配置组列表
        left_frame = ttk.Frame(self.root, padding="10")
        left_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=False)
        
        ttk.Label(left_frame, text="配置组列表", font=('Arial', 12, 'bold')).pack()
        
        # 配置组列表
        self.group_listbox = tk.Listbox(left_frame, width=20, height=25)
        self.group_listbox.pack(fill=tk.BOTH, expand=True, pady=5)
        self.group_listbox.bind('<<ListboxSelect>>', self.on_group_select)
        
        # 按钮
        btn_frame = ttk.Frame(left_frame)
        btn_frame.pack(fill=tk.X)
        
        ttk.Button(btn_frame, text="添加组", command=self.add_group).pack(fill=tk.X, pady=2)
        ttk.Button(btn_frame, text="删除组", command=self.delete_group).pack(fill=tk.X, pady=2)
        ttk.Button(btn_frame, text="设为活动", command=self.set_active).pack(fill=tk.X, pady=2)
        
        # 右侧：配置详情
        right_frame = ttk.Frame(self.root, padding="10")
        right_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True)
        
        ttk.Label(right_frame, text="配置详情", font=('Arial', 12, 'bold')).pack()
        
        # 配置项
        self.config_vars = {}
        
        # 基本配置
        basic_frame = ttk.LabelFrame(right_frame, text="基本配置", padding="10")
        basic_frame.pack(fill=tk.X, pady=5)
        
        self.create_entry(basic_frame, "name", "配置组名称:")
        # 采集卡索引：使用下拉+扫描
        cam_row = ttk.Frame(basic_frame)
        cam_row.pack(fill=tk.X, pady=3)
        ttk.Label(cam_row, text="采集卡索引:", width=15).pack(side=tk.LEFT)
        self.camera_index_var = tk.StringVar()
        self.camera_index_cb = ttk.Combobox(cam_row, textvariable=self.camera_index_var, width=23, state="readonly")
        self.camera_index_cb.pack(side=tk.LEFT, padx=5)
        ttk.Button(cam_row, text="扫描采集卡", command=self.scan_cameras).pack(side=tk.LEFT, padx=5)
        # 将变量放进config_vars统一管理
        self.config_vars["camera_index"] = self.camera_index_var
        self.create_entry(basic_frame, "camera_width", "采集宽度:")
        self.create_entry(basic_frame, "camera_height", "采集高度:")
        
        # 串口配置
        serial_frame = ttk.LabelFrame(right_frame, text="串口配置", padding="10")
        serial_frame.pack(fill=tk.X, pady=5)
        
        # 串口：使用下拉+扫描
        port_row = ttk.Frame(serial_frame)
        port_row.pack(fill=tk.X, pady=3)
        ttk.Label(port_row, text="串口:", width=15).pack(side=tk.LEFT)
        self.serial_port_var = tk.StringVar()
        self.serial_port_cb = ttk.Combobox(port_row, textvariable=self.serial_port_var, width=23, state="readonly")
        self.serial_port_cb.pack(side=tk.LEFT, padx=5)
        ttk.Button(port_row, text="扫描串口", command=self.scan_ports).pack(side=tk.LEFT, padx=5)
        # 将变量放进config_vars统一管理
        self.config_vars["serial_port"] = self.serial_port_var
        
        self.create_entry(serial_frame, "serial_baudrate", "波特率:")
        
        # 游戏配置
        game_frame = ttk.LabelFrame(right_frame, text="游戏配置", padding="10")
        game_frame.pack(fill=tk.X, pady=5)
        
        self.create_entry(game_frame, "move_velocity", "移动速度(v值):")
        
        # 预览配置
        preview_frame = ttk.LabelFrame(right_frame, text="预览配置", padding="10")
        preview_frame.pack(fill=tk.X, pady=5)
        
        self.create_entry(preview_frame, "preview_width", "预览宽度:", prefix="display")
        self.create_entry(preview_frame, "preview_height", "预览高度:", prefix="display")
        self.create_entry(preview_frame, "preview_columns", "预览列数:", prefix="display")
        self.create_entry(preview_frame, "preview_rows", "预览行数:", prefix="display")

        # 多窗口预览开关
        multi_row = ttk.Frame(preview_frame)
        multi_row.pack(fill=tk.X, pady=3)
        ttk.Label(multi_row, text="多窗口预览:", width=15).pack(side=tk.LEFT)
        self.preview_multi_window_var = tk.BooleanVar(value=False)
        multi_cb = ttk.Checkbutton(multi_row, variable=self.preview_multi_window_var)
        multi_cb.pack(side=tk.LEFT, padx=5)
        self.config_vars["display_preview_multi_window"] = self.preview_multi_window_var

        # 预览全部配置组开关
        all_row = ttk.Frame(preview_frame)
        all_row.pack(fill=tk.X, pady=3)
        ttk.Label(all_row, text="预览全部配置组:", width=15).pack(side=tk.LEFT)
        self.preview_use_all_groups_var = tk.BooleanVar(value=True)
        all_cb = ttk.Checkbutton(all_row, variable=self.preview_use_all_groups_var)
        all_cb.pack(side=tk.LEFT, padx=5)
        self.config_vars["display_preview_use_all_groups"] = self.preview_use_all_groups_var
        
        # 保存按钮
        save_frame = ttk.Frame(right_frame)
        save_frame.pack(fill=tk.X, pady=10)
        
        ttk.Button(save_frame, text="保存配置", command=self.save_config).pack(side=tk.LEFT, padx=5)
        ttk.Button(save_frame, text="启动预览", command=self.start_preview).pack(side=tk.LEFT, padx=5)
        
        # 启动程序按钮组
        start_frame = ttk.Frame(right_frame)
        start_frame.pack(fill=tk.X, pady=5)
        
        ttk.Button(start_frame, text="启动单个配置组", command=self.start_program).pack(side=tk.LEFT, padx=5)
        ttk.Button(start_frame, text="启动多个配置组", command=self.start_multi_program).pack(side=tk.LEFT, padx=5)
    
    def create_entry(self, parent, key, label, prefix=None):
        """创建输入框"""
        frame = ttk.Frame(parent)
        frame.pack(fill=tk.X, pady=3)
        
        ttk.Label(frame, text=label, width=15).pack(side=tk.LEFT)
        
        var = tk.StringVar()
        entry = ttk.Entry(frame, textvariable=var, width=25)
        entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=5)
        
        # 保存变量引用
        if prefix:
            full_key = f"{prefix}_{key}"
        else:
            full_key = key
        self.config_vars[full_key] = var
    
    def load_current_config(self):
        """加载当前配置到界面"""
        # 加载配置组列表
        self.update_group_list()
        
        # 如果有配置组，默认选择第一个
        if len(config_manager.config['groups']) > 0:
            self.selected_index = 0
            self.group_listbox.selection_set(0)
            self.load_group_config(0)
        
        # 加载预览配置
        display = config_manager.config.get('display', {})
        for key in ['preview_width', 'preview_height', 'preview_columns', 'preview_rows']:
            if key in display:
                self.config_vars[f"display_{key}"].set(str(display[key]))
        # 多窗口预览
        if 'preview_multi_window' in display:
            self.preview_multi_window_var.set(bool(display['preview_multi_window']))
        # 预览全部配置组
        if 'preview_use_all_groups' in display:
            self.preview_use_all_groups_var.set(bool(display['preview_use_all_groups']))
        # 初次自动扫描一次采集卡和串口
        try:
            self.scan_cameras()
        except Exception:
            pass
        try:
            self.scan_ports()
        except Exception:
            pass
    
    def update_group_list(self):
        """更新配置组列表"""
        self.group_listbox.delete(0, tk.END)
        for i, group in enumerate(config_manager.config['groups']):
            status = "✓" if group.get('active', False) else "  "
            self.group_listbox.insert(tk.END, f"{status} {group['name']}")
    
    def on_group_select(self, event):
        """选择配置组"""
        selection = self.group_listbox.curselection()
        if selection:
            self.selected_index = selection[0]
            self.load_group_config(self.selected_index)
    
    def load_group_config(self, index):
        """加载配置组详情"""
        group = config_manager.get_group_by_index(index)
        if group:
            for key, var in self.config_vars.items():
                if not key.startswith('display_'):
                    if key in group:
                        var.set(str(group[key]))
        # 确保下拉框列表与当前值一致
        if group and 'camera_index' in group:
            # 如果当前索引不在选项里，追加
            values = list(self.camera_index_cb.cget('values')) if self.camera_index_cb.cget('values') else []
            display_value = f"{group['camera_index']}"
            if display_value not in values:
                values.append(display_value)
                self.camera_index_cb['values'] = values
            self.camera_index_cb.set(display_value)
        
        # 确保串口下拉框列表与当前值一致
        if group and 'serial_port' in group:
            # 如果当前串口不在选项里，追加
            values = list(self.serial_port_cb.cget('values')) if self.serial_port_cb.cget('values') else []
            port_value = group['serial_port']
            if port_value not in values:
                values.append(port_value)
                self.serial_port_cb['values'] = values
            self.serial_port_cb.set(port_value)

    def scan_cameras(self, max_index: int = 10):
        """扫描系统可用的采集卡索引，并填充下拉框"""
        import warnings
        import sys
        import io
        found = []
        
        # 临时设置OpenCV日志级别（兼容不同版本）
        import os
        old_level = os.environ.get('OPENCV_LOG_LEVEL', '')
        os.environ['OPENCV_LOG_LEVEL'] = 'SILENT'  # 尝试更严格的级别
        os.environ['OPENCV_IO_ENABLE_OPENEXR'] = '0'
        
        # 尝试设置日志级别（不同版本的OpenCV API不同）
        try:
            if hasattr(cv2, 'setLogLevel'):
                # OpenCV 4.x
                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)  # 0=SILENT/ERROR级别
        except Exception:
            pass
        
        # 重定向stderr来捕获OpenCV的错误输出
        old_stderr = sys.stderr
        suppressed_output = io.StringIO()
        
        try:
            # 暂时重定向stderr以抑制OpenCV的错误消息
            sys.stderr = suppressed_output
            
            for idx in range(max_index + 1):
                cap = None
                try:
                    with warnings.catch_warnings():
                        warnings.filterwarnings('ignore')
                        # 尝试DSHOW后端
                        cap = cv2.VideoCapture(idx, cv2.CAP_DSHOW)
                        if not cap.isOpened():
                            # 回退默认后端再试
                            cap = cv2.VideoCapture(idx)
                        
                        if cap.isOpened():
                            # 测试读取一帧，确保真正可用
                            ret, test_frame = cap.read()
                            if ret and test_frame is not None:
                                found.append(str(idx))
                            cap.release()
                except Exception:
                    if cap:
                        try:
                            cap.release()
                        except:
                            pass
                    continue
        finally:
            # 恢复stderr
            sys.stderr = old_stderr
            # 恢复原来的日志级别
            if old_level:
                os.environ['OPENCV_LOG_LEVEL'] = old_level
            else:
                os.environ.pop('OPENCV_LOG_LEVEL', None)
            os.environ.pop('OPENCV_IO_ENABLE_OPENEXR', None)
        
        if not found:
            found = ["0"]  # 至少给一个默认项，避免为空
            messagebox.showwarning("扫描完成", "未发现可用采集卡，已添加默认选项 0")
        else:
            messagebox.showinfo("扫描完成", f"发现可用采集卡索引: {', '.join(found)}")
        
        self.camera_index_cb['values'] = found
        # 若当前无选择，则选择第一项
        if not self.camera_index_var.get() and found:
            self.camera_index_cb.set(found[0])
    
    def scan_ports(self):
        """扫描系统可用的串口，并填充下拉框"""
        found_real = []
        try:
            ports = serial.tools.list_ports.comports()
            found_real = [port.device for port in ports]
            # 按端口名排序
            found_real.sort(key=lambda x: int(x.replace('COM', '')) if x.replace('COM', '').isdigit() else 999)
        except Exception as e:
            print(f"扫描串口错误: {e}")
        
        # 如果没有发现实际端口，使用默认端口列表
        if not found_real:
            found = ["COM1", "COM2", "COM3", "COM4", "COM5", "COM6"]  # 默认给一些常见端口
            messagebox.showwarning("警告", "未发现可用串口设备，已添加常用默认选项")
        else:
            found = found_real
            messagebox.showinfo("扫描完成", f"发现可用串口: {', '.join(found)}")
        
        self.serial_port_cb['values'] = found
        # 若当前无选择，则选择第一项
        if not self.serial_port_var.get() and found:
            self.serial_port_cb.set(found[0])
    
    def add_group(self):
        """添加配置组"""
        name = simpledialog.askstring("添加配置组", "请输入配置组名称:", initialvalue=f"配置组{len(config_manager.config['groups'])+1}")
        if name:
            config_manager.add_group(name)
            self.update_group_list()
            messagebox.showinfo("成功", f"已添加配置组: {name}")
    
    def delete_group(self):
        """删除配置组"""
        selection = self.group_listbox.curselection()
        if not selection:
            messagebox.showwarning("警告", "请先选择要删除的配置组")
            return
        
        if messagebox.askyesno("确认", "确定要删除选中的配置组吗？"):
            config_manager.delete_group(selection[0])
            self.update_group_list()
            messagebox.showinfo("成功", "配置组已删除")
    
    def set_active(self):
        """设置为活动配置组"""
        selection = self.group_listbox.curselection()
        if not selection:
            messagebox.showwarning("警告", "请先选择要设为活动的配置组")
            return
        
        config_manager.set_active_group(selection[0])
        self.update_group_list()
        messagebox.showinfo("成功", "已设置为活动配置组")
    
    def save_config(self):
        """保存当前编辑的配置"""
        # 检查索引有效性
        if self.selected_index < 0 or self.selected_index >= len(config_manager.config['groups']):
            messagebox.showerror("错误", f"请先选择一个有效的配置组")
            return False
            
        # 保存当前组的配置
        group = config_manager.get_group_by_index(self.selected_index)
        if not group:
            messagebox.showerror("错误", f"配置组不存在")
            return False
            
        for key, var in self.config_vars.items():
            if not key.startswith('display_'):
                try:
                    value_str = var.get().strip() if var.get() else ""
                    
                    # 特殊处理：某些字段需要转换为整数
                    if key in ['camera_index', 'camera_width', 'camera_height', 'serial_baudrate', 'move_velocity']:
                        try:
                            # 如果为空，使用当前值或默认值
                            if value_str:
                                value = int(value_str)
                            else:
                                # 如果下拉框为空，尝试从当前配置获取
                                value = group.get(key, 0)
                        except ValueError:
                            # 转换失败，使用当前值
                            value = group.get(key, 0)
                            print(f"⚠️ 字段 {key} 的值 '{value_str}' 无效，使用当前值 {value}")
                    else:
                        # 字符串字段
                        value = value_str if value_str else group.get(key, '')
                    
                    group[key] = value
                except Exception as e:
                    print(f"保存字段 {key} 时出错: {e}")
                    import traceback
                    traceback.print_exc()
                    # 保存失败时使用当前值
                    if key in group:
                        pass  # 保持原值不变
        
        # 保存预览配置
        display = config_manager.config.get('display', {})
        for key in ['preview_width', 'preview_height', 'preview_columns', 'preview_rows']:
            var = self.config_vars.get(f"display_{key}")
            if var:
                try:
                    display[key] = int(var.get())
                except:
                    pass
        # 保存多窗口预览布尔值
        display['preview_multi_window'] = bool(self.preview_multi_window_var.get())
        # 保存预览全部配置组布尔值
        display['preview_use_all_groups'] = bool(self.preview_use_all_groups_var.get())
        config_manager.config['display'] = display
        
        # 保存到文件
        if config_manager.save_config():
            # 更新左侧列表显示
            self.update_group_list()
            messagebox.showinfo("成功", "配置已保存")
            return True
        else:
            messagebox.showerror("错误", "配置保存失败")
            return False
    
    def start_preview(self):
        """启动预览窗口（主线程，弹窗模式）"""
        if not self.save_config_silent():
            messagebox.showerror("错误", "配置保存失败，无法启动预览")
            return
        config_manager.load_config()
        from preview import PreviewWindow
        PreviewWindow().run(self.root)  # 以主窗口为父Toplevel弹出独立预览窗
    
    def save_config_silent(self):
        """静默保存配置（不显示消息框）"""
        # 保存当前组的配置
        if self.selected_index < 0 or self.selected_index >= len(config_manager.config['groups']):
            print(f"⚠️ 选中索引 {self.selected_index} 无效")
            return False
            
        group = config_manager.get_group_by_index(self.selected_index)
        if not group:
            print(f"⚠️ 配置组不存在")
            return False
            
        for key, var in self.config_vars.items():
            if not key.startswith('display_'):
                try:
                    value_str = var.get().strip() if var.get() else ""
                    
                    # 特殊处理：某些字段需要转换为整数
                    if key in ['camera_index', 'camera_width', 'camera_height', 'serial_baudrate', 'move_velocity']:
                        try:
                            # 如果为空，使用当前值或默认值
                            if value_str:
                                value = int(value_str)
                            else:
                                # 如果下拉框为空，尝试从当前配置获取
                                value = group.get(key, 0)
                        except ValueError:
                            # 转换失败，使用当前值
                            value = group.get(key, 0)
                            print(f"⚠️ 字段 {key} 的值 '{value_str}' 无效，使用当前值 {value}")
                    else:
                        # 字符串字段
                        value = value_str if value_str else group.get(key, '')
                    
                    group[key] = value
                except Exception as e:
                    print(f"保存字段 {key} 时出错: {e}")
                    import traceback
                    traceback.print_exc()
                    # 保存失败时保持原值不变
        
        # 保存预览配置
        display = config_manager.config.get('display', {})
        for key in ['preview_width', 'preview_height', 'preview_columns', 'preview_rows']:
            var = self.config_vars.get(f"display_{key}")
            if var:
                try:
                    display[key] = int(var.get())
                except:
                    pass
        # 保存多窗口预览布尔值（静默）
        display['preview_multi_window'] = bool(self.preview_multi_window_var.get())
        # 保存预览全部配置组布尔值（静默）
        display['preview_use_all_groups'] = bool(self.preview_use_all_groups_var.get())
        config_manager.config['display'] = display
        
        # 保存到文件
        result = config_manager.save_config()
        
        # 更新左侧列表显示
        if result:
            self.update_group_list()
        
        return result
    
    def start_program(self):
        """启动单个配置组的主程序"""
        # 保存配置（静默）
        if not self.save_config_silent():
            messagebox.showerror("错误", "配置保存失败")
            return
        
        # 检查是否有活动配置组
        active_groups = [g for g in config_manager.config['groups'] if g.get('active', False)]
        if not active_groups:
            messagebox.showwarning("警告", "没有活动的配置组\n\n请先选择一个配置组并设置为活动")
            return
        
        # 启动单个配置组
        import subprocess
        import sys
        try:
            # 找到活动配置组的索引
            active_group = active_groups[0]
            group_index = config_manager.config['groups'].index(active_group)
            
            subprocess.Popen([
                sys.executable, 
                "main_single.py",
                str(group_index)
            ], creationflags=subprocess.CREATE_NEW_CONSOLE if sys.platform == 'win32' else 0)
            
            messagebox.showinfo("成功", f"已启动配置组: {active_group['name']}\n\n请在控制台查看运行状态")
        except Exception as e:
            messagebox.showerror("错误", f"启动失败: {e}")
    
    def start_multi_program(self):
        """启动多个配置组的主程序"""
        # 保存配置（静默）
        if not self.save_config_silent():
            messagebox.showerror("错误", "配置保存失败")
            return
        
        # 启动多配置组管理器
        import subprocess
        import sys
        try:
            subprocess.Popen([
                sys.executable, 
                "main_multi.py"
            ], creationflags=subprocess.CREATE_NEW_CONSOLE if sys.platform == 'win32' else 0)
            
            messagebox.showinfo("提示", "多配置组启动器已打开\n\n请在控制台中选择要启动的配置组")
        except Exception as e:
            messagebox.showerror("错误", f"启动失败: {e}")
    
    def run(self):
        """运行GUI"""
        self.root.mainloop()

if __name__ == "__main__":
    app = ConfigGUI()
    app.run()

