import tkinter as tk
# Add PhotoImage to the import
from tkinter import ttk, filedialog, messagebox, PhotoImage
import random
# import time
import os
import csv
from datetime import date
from pypinyin import pinyin, Style
import sys # Import sys for path joining

class RollCallApp:
    def __init__(self, master):
        self.master = master
        master.title("课堂点名系统")

        # --- Set Window Icon ---
        try:
            # Determine the base path (works for scripts and frozen executables)
            if getattr(sys, 'frozen', False):
                base_path = sys._MEIPASS # PyInstaller temp folder
            else:
                base_path = os.path.dirname(os.path.abspath(__file__))

            icon_path = os.path.join(base_path, 'rollcall_icon.png') # Make sure 'rollcall_icon.png' exists here!

            if os.path.exists(icon_path):
                 # Use PhotoImage for broader compatibility (PNG, GIF)
                app_icon = PhotoImage(file=icon_path)
                master.iconphoto(True, app_icon) # The 'True' makes it the default icon
            else:
                print(f"Warning: Icon file not found at {icon_path}. Using default icon.")
        except tk.TclError as e:
            # Handle cases where the image format might be unsupported or file is corrupt
            print(f"Warning: Could not load icon '{icon_path}': {e}. Using default icon.")
        except Exception as e:
            # Catch any other potential errors during icon loading
            print(f"Warning: An unexpected error occurred while loading the icon: {e}. Using default icon.")
        # --- End Set Window Icon ---


        master.minsize(width=1100, height=700)

        # ... (the rest of your __init__ code remains exactly the same) ...
        # --- Define Font Families (User specified) ---
        self.font_family_ui = "微软雅黑"
        self.font_family_display = "华文中宋"

        # --- Define Font Sizes (Slightly adjusted for modern feel, easy to revert) ---
        self.font_size_standard = 14
        self.font_size_counter = 18
        self.font_size_display = 42
        self.font_size_title = 28

        self.font_title_tuple = (self.font_family_ui, self.font_size_title, "bold")
        self.font_counter_tuple = (self.font_family_ui, self.font_size_counter)
        self.font_display_tuple = (self.font_family_display, self.font_size_display, "bold")
        self.font_standard_tuple = (self.font_family_ui, self.font_size_standard)
        self.font_button_tuple = (self.font_family_ui, self.font_size_standard, "bold")
        self.font_footer_tuple = (self.font_family_ui, 11)


        # --- Style and Colors ---
        self.style = ttk.Style(master)
        try:
            self.style.theme_use('clam')
        except tk.TclError:
            print("Clam theme not available, using default theme. Styling will still be applied.")

        self.COLOR_BACKGROUND = "#ECEFF1"
        self.COLOR_TEXT = "#263238"
        self.COLOR_PRIMARY = "#007BFF"
        self.COLOR_PRIMARY_DARK = "#0056b3"
        self.COLOR_PRIMARY_HOVER = "#0069D9"
        self.COLOR_PRIMARY_TEXT = "#FFFFFF"
        self.COLOR_SECONDARY_BG = "#FFFFFF"
        self.COLOR_BORDER = "#B0BEC5"
        self.COLOR_DISABLED_FG = "#78909C"
        self.COLOR_DISABLED_BG = "#CFD8DC"

        self.COLOR_NAME_DISPLAY_DEFAULT_BG = self.COLOR_SECONDARY_BG
        self.COLOR_NAME_DISPLAY_DEFAULT_FG = self.COLOR_TEXT
        self.COLOR_NAME_DISPLAY_FLASH_BG = "#FFF9C4"      # Pale Yellow (Material Design Yellow 100)
        self.COLOR_NAME_DISPLAY_FLASH_FG = self.COLOR_TEXT
        self.COLOR_NAME_DISPLAY_SELECTED_BG = "#C8E6C9"   # Pale Green (Material Design Green 100)
        self.COLOR_NAME_DISPLAY_SELECTED_FG = "#2E7D32"   # Dark Green (Material Design Green 800)
        self.COLOR_NAME_DISPLAY_ABSENT_BG = "#FFCDD2"     # Pale Red (Material Design Red 100)
        self.COLOR_NAME_DISPLAY_ABSENT_FG = "#C62828"     # Dark Red (Material Design Red 800)

        master.configure(bg=self.COLOR_BACKGROUND)

        self.style.configure(".", background=self.COLOR_BACKGROUND, foreground=self.COLOR_TEXT,
                             font=self.font_standard_tuple)
        self.style.configure("TFrame", background=self.COLOR_BACKGROUND)

        self.style.configure("TLabel", background=self.COLOR_BACKGROUND, foreground=self.COLOR_TEXT)
        self.style.configure("Title.TLabel", font=self.font_title_tuple, foreground=self.COLOR_PRIMARY)
        self.style.configure("Counter.TLabel", font=self.font_counter_tuple)
        self.style.configure("Value.TLabel", font=self.font_standard_tuple)
        self.style.configure("Footer.TLabel", font=self.font_footer_tuple, foreground="#546E7A")

        self.style.configure("TButton", font=self.font_button_tuple, padding=(12, 7), relief="raised")
        self.style.map("TButton",
            foreground=[('disabled', self.COLOR_DISABLED_FG),
                        ('pressed', self.COLOR_PRIMARY_TEXT),
                        ('active', self.COLOR_PRIMARY_TEXT),
                        ('!disabled', self.COLOR_PRIMARY_TEXT)],
            background=[('disabled', self.COLOR_DISABLED_BG),
                        ('pressed', self.COLOR_PRIMARY_DARK),
                        ('active', self.COLOR_PRIMARY_HOVER),
                        ('!disabled', self.COLOR_PRIMARY)],
            relief=[('pressed', 'sunken'), ('!pressed', 'raised')])

        self.style.configure("TEntry", font=self.font_standard_tuple, fieldbackground=self.COLOR_SECONDARY_BG,
                             relief="flat")
        self.style.map("TEntry",
            bordercolor=[('focus', self.COLOR_PRIMARY), ('!focus', self.COLOR_BORDER)],
            borderwidth=[('focus', 1), ('!focus', 1)],
            lightcolor=[('focus', self.COLOR_PRIMARY)])

        self.style.configure("Horizontal.TScale", background=self.COLOR_BACKGROUND, troughcolor=self.COLOR_SECONDARY_BG)

        # --- Data storage ---
        self.all_students = []
        self.remaining_students = []
        self.called_students = []
        self.absent_students = []
        self.current_student_info = None
        self.flash_timer = None
        self.stop_timer = None
        self.is_flashing = False
        self.file_path = tk.StringVar(value="尚未选择文件")
        self.flash_duration_var = tk.IntVar(value=1)
        self.flash_interval_var = tk.IntVar(value=25)

        # --- UI Elements ---
        self.title_label = ttk.Label(master, text="课堂随机点名系统", style="Title.TLabel")
        self.title_label.pack(pady=(25, 15))

        self.counter_label = ttk.Label(master, text="已点名：0/0人 （总人数：0）", style="Counter.TLabel")
        self.counter_label.pack(pady=(0, 20))

        self.name_display_border_frame = tk.Frame(master, background=self.COLOR_BORDER, relief="flat", bd=1)
        self.name_display_border_frame.pack(pady=10, padx=30, fill=tk.X)
        self.name_display = tk.Label(self.name_display_border_frame, text="请先加载名单", font=self.font_display_tuple,
                                     bg=self.COLOR_NAME_DISPLAY_DEFAULT_BG,
                                     fg=self.COLOR_NAME_DISPLAY_DEFAULT_FG,
                                     justify="center")
        self.name_display.pack(fill=tk.X, ipady=15, padx=1, pady=1)

        flash_control_frame = ttk.Frame(master, style="TFrame")
        flash_control_frame.pack(pady=15, fill=tk.X, padx=30)

        duration_frame = ttk.Frame(flash_control_frame) # Will inherit style from parent or "."
        duration_frame.pack(pady=8, anchor='center')
        ttk.Label(duration_frame, text="闪烁时间 (秒):").pack(side=tk.LEFT, padx=(0,10))
        self.duration_slider = ttk.Scale(duration_frame, from_=1, to=5, orient=tk.HORIZONTAL,
                                         variable=self.flash_duration_var, length=250,
                                         command=lambda v: self.flash_duration_var.set(int(float(v))),
                                         style="Horizontal.TScale")
        self.duration_slider.pack(side=tk.LEFT, padx=5)
        self.duration_label = ttk.Label(duration_frame, textvariable=self.flash_duration_var, width=3, style="Value.TLabel")
        self.duration_label.pack(side=tk.LEFT)
        self.duration_slider.config(state=tk.DISABLED)

        interval_frame = ttk.Frame(flash_control_frame)
        interval_frame.pack(pady=8, anchor='center')
        ttk.Label(interval_frame, text="闪烁间隔 (毫秒):").pack(side=tk.LEFT, padx=(0,10))
        self.speed_interval_slider = ttk.Scale(interval_frame, from_=25, to=300, orient=tk.HORIZONTAL,
                                               variable=self.flash_interval_var, length=250,
                                               command=lambda v: self.flash_interval_var.set(int(float(v))),
                                               style="Horizontal.TScale")
        self.speed_interval_slider.pack(side=tk.LEFT, padx=5)
        self.interval_label = ttk.Label(interval_frame, textvariable=self.flash_interval_var, width=4, style="Value.TLabel")
        self.interval_label.pack(side=tk.LEFT)
        self.speed_interval_slider.config(state=tk.DISABLED)

        # --- File Selection Area ---
        file_frame = ttk.Frame(master)
        file_frame.pack(pady=20, padx=30)
        ttk.Label(file_frame, text="学生名单 (学号,姓名.txt):").pack(side=tk.LEFT, padx=(0,10))
        self.file_entry = ttk.Entry(file_frame, textvariable=self.file_path, width=25, state='readonly')
        self.file_entry.pack(side=tk.LEFT, padx=5, ipady=2) # Small ipady for entry height consistency
        self.load_button = ttk.Button(file_frame, text="选择文件", command=self.load_file)
        self.load_button.pack(side=tk.LEFT, padx=(5,0))

        # --- Control Button Area ---
        button_frame = ttk.Frame(master)
        button_frame.pack(pady=25)
        # Grid padx/pady for spacing between buttons
        self.start_button = ttk.Button(button_frame, text="开始点名", command=self.start_roll_call, state=tk.DISABLED)
        self.start_button.grid(row=0, column=0, padx=10, pady=8)
        self.mark_absent_button = ttk.Button(button_frame, text="标记未到场", command=self.mark_absent, state=tk.DISABLED)
        self.mark_absent_button.grid(row=0, column=1, padx=10, pady=8)
        self.export_button = ttk.Button(button_frame, text="导出未到场(CSV)", command=self.export_absent_list, state=tk.DISABLED)
        self.export_button.grid(row=1, column=0, padx=10, pady=8)
        self.clear_button = ttk.Button(button_frame, text="清空考勤数据", command=self.clear_data, state=tk.DISABLED)
        self.clear_button.grid(row=1, column=1, padx=10, pady=8)

        # --- 底部版权信息 ---
        self.footer_label = ttk.Label(
            master,
            text="Ver. 2.2 @杨昱幸. All Rights Reserved.",
            style="Footer.TLabel",
            anchor='center'
        )
        self.footer_label.pack(side=tk.BOTTOM, fill=tk.X, pady=(15,10))

        self.update_counter()


    # ... (load_file, _reset_data_core, etc. methods remain unchanged) ...
    def load_file(self):
        filepath = filedialog.askopenfilename(
            title="请选择学生名单文件 (学号,姓名)",
            filetypes=[("Text files", "*.txt")]
        )
        if not filepath: return

        try:
            self._reset_data_full()
            self.file_path.set(os.path.basename(filepath))
            loaded_students = []
            invalid_lines = []
            line_num = 0
            with open(filepath, 'r', encoding='utf-8') as f:
                 for line in f:
                    line_num += 1
                    original_line = line
                    line = line.strip()
                    if not line: continue
                    parts = line.split(',', 1)
                    if len(parts) == 2:
                        student_id_str = parts[0].strip()
                        name = parts[1].strip()
                        if student_id_str and name:
                            pinyin_list = pinyin(name, style=Style.NORMAL)
                            pinyin_str = "".join([item[0] for item in pinyin_list])
                            student_info = (student_id_str, name, pinyin_str)
                            loaded_students.append(student_info)
                        else: invalid_lines.append((line_num, "学号或姓名为空", original_line))
                    else: invalid_lines.append((line_num, "格式错误", original_line))

            if not loaded_students:
                messagebox.showwarning("警告", "文件未包含有效格式的学生信息 (学号,姓名)。")
                self._reset_ui_after_load_fail()
                return

            self.all_students = loaded_students
            self.remaining_students = self.all_students[:]
            random.shuffle(self.remaining_students)

            self.name_display.config(text="名单已加载，准备点名", fg=self.COLOR_NAME_DISPLAY_DEFAULT_FG, bg=self.COLOR_NAME_DISPLAY_DEFAULT_BG)
            self.master.update_idletasks()

            self.update_counter()
            self.start_button.config(state=tk.NORMAL)
            self.clear_button.config(state=tk.NORMAL)
            self.export_button.config(state=tk.DISABLED)
            self.mark_absent_button.config(state=tk.DISABLED)
            self.duration_slider.config(state=tk.NORMAL)
            self.speed_interval_slider.config(state=tk.NORMAL)

            info_msg = f"成功加载 {len(self.all_students)} 位学生！"
            if invalid_lines:
                info_msg += f"\n\n但有 {len(invalid_lines)} 行格式无效已被跳过:"
                for i, (ln, reason, orig) in enumerate(invalid_lines[:5]):
                    info_msg += f"\n第{ln}行: {reason} -> '{orig.strip()}'"
                if len(invalid_lines) > 5: info_msg += "\n..."
                messagebox.showwarning("加载部分成功", info_msg)
            else:
                 messagebox.showinfo("成功", info_msg)

        except FileNotFoundError:
            messagebox.showerror("错误", f"文件未找到: {filepath}")
            self._reset_ui_after_load_fail()
        except Exception as e:
            messagebox.showerror("错误", f"加载文件时出错: {e}")
            self._reset_ui_after_load_fail()

    def _reset_data_core(self):
        self.remaining_students = []
        self.called_students = []
        self.absent_students = []
        self.current_student_info = None
        if self.flash_timer: self.master.after_cancel(self.flash_timer); self.flash_timer = None
        if self.stop_timer: self.master.after_cancel(self.stop_timer); self.stop_timer = None
        self.is_flashing = False

    def _reset_data_full(self):
        self._reset_data_core()
        self.all_students = []
        self.file_path.set("尚未选择文件")

    def _reset_ui_after_load_fail(self):
         self.file_path.set("尚未选择文件")
         self.name_display.config(text="请先加载名单", fg=self.COLOR_NAME_DISPLAY_DEFAULT_FG, bg=self.COLOR_NAME_DISPLAY_DEFAULT_BG)
         self.start_button.config(state=tk.DISABLED)
         self.mark_absent_button.config(state=tk.DISABLED)
         self.export_button.config(state=tk.DISABLED)
         self.clear_button.config(state=tk.DISABLED)
         self.duration_slider.config(state=tk.DISABLED)
         self.speed_interval_slider.config(state=tk.DISABLED)
         self.update_counter()

    def update_counter(self):
        total = len(self.all_students)
        called_count = len(self.called_students) + len(self.absent_students)
        self.counter_label.config(text=f"已点名：{called_count}/{total}人 （总人数：{total}）")

    def start_roll_call(self):
        if self.is_flashing: return
        if not self.remaining_students:
            self.name_display.config(text="所有学生已点完！", fg=self.COLOR_NAME_DISPLAY_SELECTED_FG, bg=self.COLOR_NAME_DISPLAY_SELECTED_BG)
            messagebox.showinfo("提示", "所有学生均已点名。")
            self.start_button.config(state=tk.DISABLED); self.mark_absent_button.config(state=tk.DISABLED)
            return

        self.start_button.config(state=tk.DISABLED)
        self.load_button.config(state=tk.DISABLED)
        self.mark_absent_button.config(state=tk.DISABLED)
        self.clear_button.config(state=tk.DISABLED)
        self.export_button.config(state=tk.DISABLED)
        self.duration_slider.config(state=tk.DISABLED)
        self.speed_interval_slider.config(state=tk.DISABLED)

        self.is_flashing = True
        self.name_display.config(bg=self.COLOR_NAME_DISPLAY_FLASH_BG, fg=self.COLOR_NAME_DISPLAY_FLASH_FG)
        self._flash_name()

        duration_ms = self.flash_duration_var.get() * 1000
        self.stop_timer = self.master.after(duration_ms, self.stop_flashing)

    def _flash_name(self):
        if not self.is_flashing or not self.remaining_students:
            if self.is_flashing:
                 self.stop_flashing() # Cleanly stop if conditions change
            return

        display_student = random.choice(self.remaining_students)
        display_text = f"{display_student[0]}，{display_student[1]}，{display_student[2]}"
        # Text color for flashing already set in start_roll_call
        self.name_display.config(text=display_text)

        flash_interval_ms = self.flash_interval_var.get()
        self.flash_timer = self.master.after(flash_interval_ms, self._flash_name)

    def stop_flashing(self):
        if not self.is_flashing: return # Already stopped or never started
        
        if self.flash_timer:
            self.master.after_cancel(self.flash_timer)
            self.flash_timer = None
        # self.stop_timer is the timer for the duration of flashing.
        # If this method is called by that timer, self.stop_timer will be None automatically.
        # If called externally (e.g. by _flash_name), cancel it.
        if self.stop_timer:
            self.master.after_cancel(self.stop_timer)
            self.stop_timer = None

        self.is_flashing = False
        self._select_final_student()

    def _select_final_student(self):
        if self.is_flashing: # Should not happen if stop_flashing was called correctly
             self.is_flashing = False # Force stop
             if self.flash_timer: self.master.after_cancel(self.flash_timer); self.flash_timer = None
             if self.stop_timer: self.master.after_cancel(self.stop_timer); self.stop_timer = None

        # Restore button states
        self.load_button.config(state=tk.NORMAL)
        self.clear_button.config(state=tk.NORMAL)
        can_enable_sliders = bool(self.all_students)
        self.duration_slider.config(state=tk.NORMAL if can_enable_sliders else tk.DISABLED)
        self.speed_interval_slider.config(state=tk.NORMAL if can_enable_sliders else tk.DISABLED)

        if not self.remaining_students:
            self.name_display.config(text="所有学生已点完！", fg=self.COLOR_NAME_DISPLAY_SELECTED_FG, bg=self.COLOR_NAME_DISPLAY_SELECTED_BG)
            self.start_button.config(state=tk.DISABLED)
            self.mark_absent_button.config(state=tk.DISABLED)
            self.export_button.config(state=tk.NORMAL if self.absent_students else tk.DISABLED)
            return

        self.current_student_info = self.remaining_students.pop(0)
        self.called_students.append(self.current_student_info)

        display_text = f"{self.current_student_info[0]}，{self.current_student_info[1]}，{self.current_student_info[2]}"
        self.name_display.config(text=display_text, fg=self.COLOR_NAME_DISPLAY_SELECTED_FG, bg=self.COLOR_NAME_DISPLAY_SELECTED_BG)
        self.update_counter()

        if self.remaining_students:
            self.start_button.config(state=tk.NORMAL)
        else: # No more students left to call
            self.start_button.config(state=tk.DISABLED)
            self.name_display.config(text=display_text + " (最后一位)", fg=self.COLOR_NAME_DISPLAY_SELECTED_FG, bg=self.COLOR_NAME_DISPLAY_SELECTED_BG)

        self.mark_absent_button.config(state=tk.NORMAL)
        self.export_button.config(state=tk.NORMAL if self.absent_students else tk.DISABLED)

    def mark_absent(self):
        if not self.current_student_info or self.is_flashing: return
        
        if self.current_student_info in self.called_students: # Check if they were marked present first
            self.called_students.remove(self.current_student_info)
            if self.current_student_info not in self.absent_students: # Avoid duplicates
                 self.absent_students.append(self.current_student_info)
            
            self.update_counter() # Counter includes both called and absent
            display_text = f"{self.current_student_info[0]}，{self.current_student_info[1]}，{self.current_student_info[2]} [未到]"
            self.name_display.config(text=display_text, fg=self.COLOR_NAME_DISPLAY_ABSENT_FG, bg=self.COLOR_NAME_DISPLAY_ABSENT_BG)
            self.mark_absent_button.config(state=tk.DISABLED) # Prevent re-marking for current student
            self.export_button.config(state=tk.NORMAL) # Enable export since there's an absent student
        # else: Student might already be in absent_students or not selected, do nothing.

    def export_absent_list(self):
        if not self.absent_students:
            messagebox.showinfo("提示", "当前没有未到场的学生记录。")
            return

        today_str = date.today().strftime('%Y-%m-%d')
        default_filename = f"未到场名单_{today_str}.csv"

        filepath = filedialog.asksaveasfilename(
            title="保存未到场名单 (CSV)",
            defaultextension=".csv",
            filetypes=[("CSV files", "*.csv"), ("All files", "*.*")],
            initialfile=default_filename
        )
        if not filepath: return

        try:
            sorted_absent = sorted(self.absent_students, key=lambda x: x[0]) # Sort by student ID
            with open(filepath, 'w', encoding='utf-8-sig', newline='') as f:
                writer = csv.writer(f)
                writer.writerow(["学号", "姓名"]) # Header row
                for student in sorted_absent:
                    writer.writerow([student[0], student[1]]) # Student ID and Name
            messagebox.showinfo("导出成功", f"未到场名单已成功导出为 CSV 文件:\n{filepath}")
        except Exception as e:
            messagebox.showerror("导出失败", f"导出文件时出错: {e}")

    def clear_data(self):
        if not self.all_students: # No student list loaded
            messagebox.showinfo("提示", "尚未加载学生名单，无数据可清空。")
            return

        if messagebox.askyesno("确认", "确定要清空当前考勤数据吗？\n这将重置点名状态，但学生名单会保留。"):
            self._reset_data_core() # Clears called, absent, current_student, timers
            
            # Re-initialize remaining_students from all_students
            self.remaining_students = self.all_students[:]
            random.shuffle(self.remaining_students)

            self.name_display.config(text="考勤数据已清空，可重新点名", fg=self.COLOR_NAME_DISPLAY_DEFAULT_FG, bg=self.COLOR_NAME_DISPLAY_DEFAULT_BG)

            can_enable_controls = bool(self.all_students)
            self.start_button.config(state=tk.NORMAL if can_enable_controls else tk.DISABLED)
            self.mark_absent_button.config(state=tk.DISABLED) # No student selected yet
            self.export_button.config(state=tk.DISABLED) # Absent list is now empty
            self.clear_button.config(state=tk.NORMAL if can_enable_controls else tk.DISABLED)

            self.duration_slider.config(state=tk.NORMAL if can_enable_controls else tk.DISABLED)
            self.speed_interval_slider.config(state=tk.NORMAL if can_enable_controls else tk.DISABLED)

            self.update_counter()
            self.load_button.config(state=tk.NORMAL) # Load button should generally be active


# --- Main program entry point ---
if __name__ == "__main__":
    missing_libs = []
    try:
        import pypinyin
    except ImportError:
        missing_libs.append("pypinyin (`pip install pypinyin`)")
    try:
        import csv
    except ImportError: # csv is standard, but good practice if it were external
         missing_libs.append("csv (standard library module)")

    if missing_libs:
        temp_root = tk.Tk()
        temp_root.withdraw()
        messagebox.showerror("依赖缺失", f"请先安装或确保以下库可用:\n" + "\n".join(missing_libs))
        temp_root.destroy()
        exit()

    root = tk.Tk()
    app = RollCallApp(root)
    root.mainloop()