import tkinter as tk
from tkinter import ttk, messagebox
import time
import random

# 一些罗小黑主题的短文（可扩展）
PASSAGES = [
    "罗小黑是一个喜欢探险的小黑猫，它在城市和森林之间自由穿行，遇见了很多朋友。",
    "小黑的尾巴会发光，当它高兴时，光芒会更加亮丽。朋友们都喜欢和它一起玩耍。",
    "在一个风和日丽的早晨，罗小黑在河边遇到了一只受伤的小鸟，它小心翼翼地帮助了它。",
    "练习打字可以提高你的输入速度和准确度。坚持每天练习，进步会很明显。"
]

class TypingApp(tk.Tk):
    def __init__(self):
        super().__init__()
        self.title("罗小黑打字练习")
        self.geometry("900x500")
        self.resizable(False, False)

        self.passage = ""
        self.start_time = None
        self.elapsed = 0.0
        self.running = False
        self.typed = ""  # 用户输入
        self.errors = 0
        self.history = []  # 保存记录 (wpm, cpm, accuracy, time)

        self.create_widgets()
        self.new_passage()

    def create_widgets(self):
        top = ttk.Frame(self, padding=8)
        top.pack(fill="x")

        ttk.Label(top, text="选择短文：").pack(side="left")
        self.p_sel = tk.StringVar()
        combo = ttk.Combobox(top, textvariable=self.p_sel, state="readonly", width=60)
        combo['values'] = [p[:30] + ("..." if len(p) > 30 else "") for p in PASSAGES]
        combo.current(0)
        combo.pack(side="left", padx=6)
        combo.bind("<<ComboboxSelected>>", lambda e: self.new_passage(combo.current()))

        ttk.Button(top, text="随机短文", command=self.random_passage).pack(side="left", padx=6)
        ttk.Button(top, text="开始", command=self.start).pack(side="left", padx=6)
        ttk.Button(top, text="暂停", command=self.pause).pack(side="left", padx=6)
        ttk.Button(top, text="重置", command=self.reset).pack(side="left", padx=6)
        ttk.Button(top, text="查看历史", command=self.show_history).pack(side="left", padx=6)

        # 显示短文（逐字符标签）
        middle = ttk.Frame(self, padding=8)
        middle.pack(fill="both", expand=True)

        text_frame = ttk.Frame(middle)
        text_frame.pack(fill="both", expand=True)

        canvas = tk.Canvas(text_frame, height=200)
        canvas.pack(side="left", fill="both", expand=True)

        scrollbar = ttk.Scrollbar(text_frame, orient="vertical", command=canvas.yview)
        scrollbar.pack(side="right", fill="y")
        canvas.configure(yscrollcommand=scrollbar.set)

        self.passage_frame = ttk.Frame(canvas)
        self.passage_frame.bind("<Configure>", lambda e: canvas.configure(scrollregion=canvas.bbox("all")))
        canvas.create_window((0,0), window=self.passage_frame, anchor="nw")

        # 输入区
        entry_frame = ttk.Frame(middle, padding=(0,8))
        entry_frame.pack(fill="x")
        ttk.Label(entry_frame, text="请输入：").pack(anchor="w")
        self.input_var = tk.StringVar()
        self.entry = ttk.Entry(entry_frame, textvariable=self.input_var, font=("Arial", 14))
        self.entry.pack(fill="x", pady=4)
        self.entry.bind("<Key>", self.on_key)
        self.entry.bind("<KeyRelease>", self.on_key_release)
        self.entry.focus_set()

        # 底部统计区
        bottom = ttk.Frame(self, padding=8)
        bottom.pack(fill="x")

        self.time_var = tk.StringVar(value="用时: 0.0s")
        self.err_var = tk.StringVar(value="错误: 0")
        self.cpm_var = tk.StringVar(value="CPM: 0")
        self.wpm_var = tk.StringVar(value="WPM: 0")
        self.acc_var = tk.StringVar(value="准确率: 100%")
        self.progress = ttk.Progressbar(bottom, length=300, mode="determinate")

        ttk.Label(bottom, textvariable=self.time_var).pack(side="left", padx=6)
        ttk.Label(bottom, textvariable=self.err_var).pack(side="left", padx=6)
        ttk.Label(bottom, textvariable=self.cpm_var).pack(side="left", padx=6)
        ttk.Label(bottom, textvariable=self.wpm_var).pack(side="left", padx=6)
        ttk.Label(bottom, textvariable=self.acc_var).pack(side="left", padx=6)
        self.progress.pack(side="right", padx=8)

        # 定时刷新
        self.after_id = None

    def new_passage(self, idx=0):
        self.reset_stats()
        if isinstance(idx, int):
            self.passage = PASSAGES[idx % len(PASSAGES)]
        else:
            self.passage = PASSAGES[0]
        # 清理并创建字符标签
        for w in self.passage_frame.winfo_children():
            w.destroy()
        # 逐字符用 Label 放置，便于上色
        for i, ch in enumerate(self.passage):
            lbl = tk.Label(self.passage_frame, text=ch, font=("Arial", 16), padx=1, pady=2)
            lbl.grid(row=i//60, column=i%60, sticky="w")
        self.entry.delete(0, "end")
        self.input_var.set("")
        self.entry.focus_set()

    def random_passage(self):
        idx = random.randrange(len(PASSAGES))
        self.new_passage(idx)

    def reset_stats(self):
        self.start_time = None
        self.elapsed = 0.0
        self.running = False
        self.typed = ""
        self.errors = 0
        self.time_var.set("用时: 0.0s")
        self.err_var.set("错误: 0")
        self.cpm_var.set("CPM: 0")
        self.wpm_var.set("WPM: 0")
        self.acc_var.set("准确率: 100%")
        self.progress['value'] = 0
        if self.after_id:
            self.after_cancel(self.after_id)
            self.after_id = None
        # 恢复字符颜色
        for lbl in self.passage_frame.winfo_children():
            lbl.config(fg="black", bg=self.cget("bg"))

    def start(self):
        if not self.running:
            self.running = True
            if self.start_time is None:
                self.start_time = time.time()
            self._tick()

    def pause(self):
        if self.running:
            self.running = False
            if self.after_id:
                self.after_cancel(self.after_id)
                self.after_id = None

    def reset(self):
        self.reset_stats()
        self.entry.delete(0, "end")
        self.typed = ""

    def on_key(self, event):
        # 当按键时处理。KeyRelease 更稳定用于读取输入框内容
        if not self.running and event.keysym not in ("Shift_L","Shift_R","Caps_Lock"):
            # 自动开始计时（除去修饰键）
            self.start()
        # 不在这里读取输入内容，具体判断在 KeyRelease
        return

    def on_key_release(self, event):
        text = self.input_var.get()
        self.typed = text
        self.update_visuals()
        # 若完成全文则停止并记录成绩
        if len(text) >= len(self.passage):
            self.finish()
        # 更新统计
        self.update_stats()

    def update_visuals(self):
        # 将已输入字符和目标文本逐字符比较，设置颜色
        for i, lbl in enumerate(self.passage_frame.winfo_children()):
            if i < len(self.typed):
                if i < len(self.passage) and self.typed[i] == self.passage[i]:
                    lbl.config(fg="green")
                else:
                    lbl.config(fg="red")
            else:
                lbl.config(fg="black")

        # 高亮下一个待输入字符 (背景色)
        for i, lbl in enumerate(self.passage_frame.winfo_children()):
            lbl.config(bg=self.cget("bg"))
        if len(self.typed) < len(self.passage):
            next_lbl = self.passage_frame.winfo_children()[len(self.typed)]
            next_lbl.config(bg="#ffffcc")

    def update_stats(self):
        now = time.time()
        elapsed = (now - self.start_time) if self.start_time else 0.0
        # 计算错误数（在已输入文本中）
        errors = 0
        for i, ch in enumerate(self.typed):
            if i >= len(self.passage) or ch != self.passage[i]:
                errors += 1
        self.errors = errors
        self.elapsed = elapsed
        chars = len(self.typed)
        minutes = elapsed / 60.0 if elapsed > 0 else 1/60.0
        cpm = int(chars / minutes)
        wpm = int((chars/5) / minutes)  # 5 chars ~ 1 word
        accuracy = int(((chars - errors) / chars) * 100) if chars > 0 else 100
        self.time_var.set(f"用时: {elapsed:.1f}s")
        self.err_var.set(f"错误: {errors}")
        self.cpm_var.set(f"CPM: {cpm}")
        self.wpm_var.set(f"WPM: {wpm}")
        self.acc_var.set(f"准确率: {accuracy}%")
        # 进度
        prog = int((len(self.typed) / len(self.passage)) * 100) if self.passage else 0
        self.progress['value'] = prog

    def _tick(self):
        if not self.running:
            return
        self.update_stats()
        self.after_id = self.after(100, self._tick)

    def finish(self):
        self.running = False
        if self.after_id:
            self.after_cancel(self.after_id)
            self.after_id = None
        # 最终统计
        elapsed = max(self.elapsed, 0.001)
        chars = len(self.typed)
        minutes = elapsed / 60.0
        cpm = int(chars / minutes) if minutes > 0 else 0
        wpm = int((chars/5) / minutes) if minutes > 0 else 0
        errors = self.errors
        accuracy = int(((chars - errors) / chars) * 100) if chars > 0 else 100
        messagebox.showinfo("完成", f"练习完成！\n用时: {elapsed:.1f}s\nCPM: {cpm}\nWPM: {wpm}\n错误: {errors}\n准确率: {accuracy}%")
        # 保存历史
        self.history.append({"time": elapsed, "cpm": cpm, "wpm": wpm, "errors": errors, "accuracy": accuracy})
        # 将所有字符标为完成颜色
        for lbl in self.passage_frame.winfo_children():
            lbl.config(bg="#e8ffe8")

    def show_history(self):
        if not self.history:
            messagebox.showinfo("历史记录", "暂无记录。")
            return
        win = tk.Toplevel(self)
        win.title("历史记录")
        win.geometry("400x300")
        tree = ttk.Treeview(win, columns=("time","cpm","wpm","errors","acc"), show="headings")
        tree.heading("time", text="用时(s)")
        tree.heading("cpm", text="CPM")
        tree.heading("wpm", text="WPM")
        tree.heading("errors", text="错误")
        tree.heading("acc", text="准确率(%)")
        tree.pack(fill="both", expand=True)
        for rec in self.history:
            tree.insert("", "end", values=(f"{rec['time']:.1f}", rec['cpm'], rec['wpm'], rec['errors'], rec['accuracy']))

if __name__ == "__main__":
    app = TypingApp()
    app.mainloop()