#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
超长文章打字练习（中文 / English）
单文件运行，纯 Tkinter，无外部依赖
"""

import tkinter as tk
from tkinter import ttk, messagebox, filedialog
from tkinter.scrolledtext import ScrolledText
import random, time, csv, os

# -------------------- 生成超长文章（内置） --------------------
# 基础段落池（可扩展），程序会拼接并轻微变体化来生成许多超长文章
CH_BASE = [
    "春日融融，微风习习，行人于林间小径漫步，枝头鸟鸣清脆，花影婆娑；人们在暖阳下谈笑风生。",
    "历史的长河奔腾不息，文明在交错与融合中延续，古今智慧彼此照映，形成独特的人文景观。",
    "科技的发展推动社会变迁，从蒸汽机到电力，从计算机到人工智能，每一步都改变着我们的生活方式。",
    "读书可以滋养心灵，扩展视野。夜深人静时，一盏灯下，翻阅经典，思绪与作者对话，别有一番滋味。",
    "山川河流塑造了家园，四季更替绘出不同的景致。春的嫩绿、夏的繁茂、秋的金黄、冬的素裹，轮回而美。"
]

EN_BASE = [
    "Spring comes with a gentle breeze, and the world awakens. Trees bud and flowers bloom, creating a fresh, lively landscape.",
    "History flows like a great river, carrying stories of people and civilizations. Each era leaves traces that shape the present.",
    "Technological advances reshape society, from steam engines to electricity, from computers to artificial intelligence.",
    "Reading nourishes the mind. In quiet hours, a book becomes a companion, inviting reflection and new insights.",
    "Rivers carve the land and seasons paint it in changing hues: the green of spring, the lushness of summer, the gold of autumn, the hush of winter."
]

def make_long_text(paragraph_pool, approx_chars):
    """拼接并简单变体化段落直到达到指定字符数（approx_chars）"""
    pieces = []
    while sum(len(p) for p in pieces) < approx_chars:
        p = random.choice(paragraph_pool)
        # 轻微变体：替换一些连接词或重复短句，保持自然
        if random.random() < 0.3:
            p = p + (" " + p[:min(40, len(p))])
        if random.random() < 0.2:
            p = p.replace("，", "，").replace("。", "。")
        pieces.append(p)
    return "\n\n".join(pieces)

def build_article_library():
    """生成多篇不同长度的文章（每篇都较长）"""
    articles = {"中文": [], "English": []}
    # 长度级别（字符）
    lengths = [2000, 4000, 8000, 12000]  # 可生成到上万字符
    # 为每个长度生成若干篇不同主题文章
    for i, L in enumerate(lengths):
        for j in range(3):  # 每个长度级别生成 3 篇 => 共 12 篇每语言
            title_ch = f"长文（中文） {L}字 版本 {j+1}"
            text_ch = make_long_text(CH_BASE, L)
            articles["中文"].append({"title": title_ch, "text": text_ch})
            title_en = f"Long Text (English) {L}ch Ver {j+1}"
            text_en = make_long_text(EN_BASE, L)
            articles["English"].append({"title": title_en, "text": text_en})
    # 额外再添加几篇不同主题的较长段落（合并内置段落）
    for k in range(4):
        t = " ".join(random.sample(EN_BASE, len(EN_BASE))) * 80
        articles["English"].append({"title": f"Very Long English Extra {k+1}", "text": t})
        t2 = "\n".join(random.sample(CH_BASE, len(CH_BASE))) * 200
        articles["中文"].append({"title": f"超长中文附加 {k+1}", "text": t2})
    return articles

ARTICLES = build_article_library()

# -------------------- 主程序 --------------------
class TypingPracticeLongApp(tk.Tk):
    def __init__(self):
        super().__init__()
        self.title("超长文章打字练习")
        self.geometry("1100x700")
        self.minsize(900, 600)

        # 状态
        self.language = tk.StringVar(value="中文")
        self.duration = tk.IntVar(value=120)  # 默认 120 秒
        self.articles = ARTICLES
        self.cur_article = None
        self.target_text = ""
        self.running = False
        self.start_time = None
        self.time_left = 0

        # stats
        self.typed_text = ""
        self.correct_chars = 0
        self.total_typed = 0
        self.error_positions = []

        self.records = []  # 保存结果

        self._build_ui()
        self.refresh_article_list()

    def _build_ui(self):
        top = ttk.Frame(self, padding=6)
        top.pack(side=tk.TOP, fill=tk.X)

        ttk.Label(top, text="语言:").pack(side=tk.LEFT)
        lang_combo = ttk.Combobox(top, textvariable=self.language, values=["中文", "English"], state="readonly", width=8)
        lang_combo.pack(side=tk.LEFT, padx=4)
        lang_combo.bind("<<ComboboxSelected>>", lambda e: self.refresh_article_list())

        ttk.Label(top, text="时长(s):").pack(side=tk.LEFT, padx=(12,0))
        ttk.Entry(top, textvariable=self.duration, width=6).pack(side=tk.LEFT, padx=4)

        ttk.Button(top, text="随机文章", command=self.pick_random).pack(side=tk.LEFT, padx=6)
        ttk.Button(top, text="开始", command=self.start_test).pack(side=tk.LEFT, padx=6)
        ttk.Button(top, text="停止", command=self.stop_test).pack(side=tk.LEFT, padx=6)
        ttk.Button(top, text="保存成绩(CSV)", command=self.save_records).pack(side=tk.RIGHT, padx=6)

        main = ttk.Frame(self)
        main.pack(fill=tk.BOTH, expand=True, padx=6, pady=6)

        # 左侧文章列表
        left = ttk.Frame(main, width=320)
        left.pack(side=tk.LEFT, fill=tk.Y)
        ttk.Label(left, text="文章列表（含超长文本，选择后右侧加载）", font=("Segoe UI", 10, "bold")).pack(anchor=tk.W)
        self.search_var = tk.StringVar()
        s = ttk.Entry(left, textvariable=self.search_var)
        s.pack(fill=tk.X, pady=(4,6))
        s.bind("<KeyRelease>", lambda e: self.refresh_article_list())

        self.listbox = tk.Listbox(left, exportselection=False)
        self.listbox.pack(fill=tk.BOTH, expand=True)
        self.listbox.bind("<<ListboxSelect>>", lambda e: self.on_article_selected())

        # 右侧显示（目标文本 + 输入 + 统计）
        right = ttk.Frame(main)
        right.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)

        self.title_var = tk.StringVar(value="请选择左侧文章（或随机文章）")
        ttk.Label(right, textvariable=self.title_var, font=("Segoe UI", 12, "bold")).pack(anchor=tk.W)

        ttk.Label(right, text="目标文本（只读，内容很长，请滚动查看）:").pack(anchor=tk.W, pady=(6,0))
        self.target_display = ScrolledText(right, height=12, wrap='word')
        self.target_display.pack(fill=tk.BOTH, expand=False)
        self.target_display.config(state='disabled', background='#f0f0f0')

        ttk.Label(right, text="打字输入（开始后启用）：").pack(anchor=tk.W, pady=(8,0))
        self.input_text = ScrolledText(right, height=12, wrap='word')
        self.input_text.pack(fill=tk.BOTH, expand=True)
        self.input_text.config(state='disabled')
        self.input_text.bind("<<Modified>>", self.on_text_modified)
        # 阻止粘贴（简单处理）
        self.input_text.bind("<Control-v>", lambda e: "break")
        self.input_text.bind("<Control-V>", lambda e: "break")

        bottom = ttk.Frame(right)
        bottom.pack(fill=tk.X, pady=(6,0))
        self.timer_var = tk.StringVar(value="时间: 00:00")
        ttk.Label(bottom, textvariable=self.timer_var).pack(side=tk.LEFT, padx=(0,12))
        self.progress_var = tk.StringVar(value="已输入: 0，正确: 0，错误: 0，准确率: 0.00%")
        ttk.Label(bottom, textvariable=self.progress_var).pack(side=tk.LEFT)

        # 结果显示
        res_frame = ttk.Frame(self)
        res_frame.pack(side=tk.BOTTOM, fill=tk.X, padx=6, pady=6)
        ttk.Label(res_frame, text="本次成绩：", font=("Segoe UI", 10, "bold")).pack(anchor=tk.W)
        self.result_text = tk.Text(res_frame, height=6, wrap='word')
        self.result_text.pack(fill=tk.X, expand=True)
        self.result_text.config(state='disabled', background='#f8f8f8')

    # 文章管理
    def refresh_article_list(self):
        self.listbox.delete(0, tk.END)
        lang = self.language.get()
        q = self.search_var.get().strip().lower()
        pool = self.articles.get(lang, [])
        for a in pool:
            title = a.get("title", "")
            if q and q not in title.lower() and q not in a.get("text","").lower():
                continue
            self.listbox.insert(tk.END, title)
        self.listbox.selection_clear(0, tk.END)
        self.cur_article = None
        self.set_target_text("")

    def on_article_selected(self):
        sel = self.listbox.curselection()
        if not sel:
            return
        idx = sel[0]
        # rebuild filtered list to align index
        lang = self.language.get()
        q = self.search_var.get().strip().lower()
        filtered = [a for a in self.articles.get(lang, []) if not q or (q in a.get("title","").lower() or q in a.get("text","").lower())]
        if idx < len(filtered):
            self.cur_article = filtered[idx]
            self.title_var.set(self.cur_article.get("title",""))
            self.set_target_text(self.cur_article.get("text",""))

    def pick_random(self):
        lang = self.language.get()
        pool = self.articles.get(lang, [])
        if not pool:
            messagebox.showinfo("提示", "当前没有文章。")
            return
        self.cur_article = random.choice(pool)
        self.title_var.set(self.cur_article.get("title",""))
        self.set_target_text(self.cur_article.get("text",""))
        # 尝试在列表中选中对应项
        self.refresh_article_list()
        for i in range(self.listbox.size()):
            if self.listbox.get(i) == self.cur_article.get("title"):
                self.listbox.selection_set(i)
                break

    def set_target_text(self, s):
        self.target_text = s.rstrip("\n")
        self.target_display.config(state='normal')
        self.target_display.delete('1.0', tk.END)
        self.target_display.insert(tk.END, self.target_text)
        self.target_display.config(state='disabled')

    # 测试控制
    def start_test(self):
        if self.running:
            messagebox.showinfo("提示", "测试已经在进行中。")
            return
        if not self.target_text:
            messagebox.showwarning("提示", "请先选择文章或随机选择文章。")
            return
        try:
            d = int(self.duration.get())
            if d <= 0:
                raise ValueError()
        except:
            messagebox.showwarning("无效时长", "请输入有效的时长（秒）。")
            return
        self.time_left = d
        self.running = True
        self.start_time = time.time()
        # reset stats
        self.typed_text = ""
        self.correct_chars = 0
        self.total_typed = 0
        self.error_positions = []
        # enable input
        self.input_text.config(state='normal')
        self.input_text.delete('1.0', tk.END)
        self.input_text.focus_set()
        self.clear_tags()
        self.update_timer_display()
        self.update_progress_display()
        self._tick()

    def stop_test(self):
        if not self.running:
            return
        self.end_test()

    def _tick(self):
        if not self.running:
            return
        # 更新剩余时间（基于 start_time 保持精度）
        elapsed = int(time.time() - self.start_time)
        rem = max(0, int(self.duration.get()) - elapsed)
        self.time_left = rem
        self.update_timer_display()
        if self.time_left <= 0:
            self.end_test()
            return
        self.after(150, self._tick)

    def end_test(self):
        self.running = False
        self.time_left = 0
        self.update_timer_display()
        # 禁用输入
        self.input_text.config(state='disabled')
        # 统计最终
        content = self.input_text.get('1.0', tk.END).rstrip('\n')
        self.typed_text = content
        self._compute_stats(final=True)
        rec = self._make_record()
        self.records.append(rec)
        self.show_result(rec)

    # 打字处理与统计
    def on_text_modified(self, event=None):
        if not self.input_text.edit_modified():
            return
        self.input_text.edit_modified(False)
        if not self.running:
            return
        self.typed_text = self.input_text.get('1.0', tk.END).rstrip('\n')
        self._compute_stats()
        self.update_progress_display()
        # 高亮显示（可能会较耗时但能工作）
        self.highlight_input()

    def _compute_stats(self, final=False):
        tgt = self.target_text
        typed = self.typed_text
        correct = 0
        errors = []
        # 对齐逐字符比较（对于英文按字符算）
        for i, ch in enumerate(typed):
            if i < len(tgt) and ch == tgt[i]:
                correct += 1
            else:
                errors.append(i)
        self.correct_chars = correct
        self.total_typed = len(typed)
        self.error_positions = errors

    def update_progress_display(self):
        total = self.total_typed
        correct = self.correct_chars
        wrong = total - correct
        accuracy = (correct / total * 100) if total > 0 else 0.0
        elapsed_seconds = max(1, int(time.time() - self.start_time)) if self.running else 1
        minutes = elapsed_seconds / 60.0
        if self.language.get() == "中文":
            cpm = (correct / minutes) if minutes>0 else 0.0
            stats = f"已输入: {total}，正确: {correct}，错误: {wrong}，准确率: {accuracy:.2f}%，CPM: {cpm:.1f}"
        else:
            wpm = (correct / 5.0) / minutes if minutes>0 else 0.0
            stats = f"已输入: {total}，正确: {correct}，错误: {wrong}，准确率: {accuracy:.2f}%，WPM: {wpm:.1f}"
        self.progress_var.set(stats)

    def update_timer_display(self):
        s = self.time_left
        mm = s // 60
        ss = s % 60
        self.timer_var.set(f"时间: {mm:02d}:{ss:02d}")

    def highlight_input(self):
        txt = self.input_text
        # 为性能，先移除少量常用 tags
        txt.tag_remove("correct", "1.0", tk.END)
        txt.tag_remove("incorrect", "1.0", tk.END)
        txt.tag_remove("cursor", "1.0", tk.END)
        typed = self.typed_text
        tgt = self.target_text
        # 对每个字符打标签（长文可能较慢）
        for i, ch in enumerate(typed):
            start = f"1.0+{i}c"
            end = f"1.0+{i+1}c"
            if i < len(tgt) and ch == tgt[i]:
                txt.tag_add("correct", start, end)
            else:
                txt.tag_add("incorrect", start, end)
        # 当前光标位置显示下划线（或空位）
        cur = len(typed)
        cur_start = f"1.0+{cur}c"
        cur_end = f"1.0+{cur+1}c"
        txt.tag_add("cursor", cur_start, cur_end)
        txt.tag_configure("correct", foreground="green")
        txt.tag_configure("incorrect", foreground="red")
        txt.tag_configure("cursor", underline=1)

    def clear_tags(self):
        txt = self.input_text
        for t in ("correct","incorrect","cursor"):
            try:
                txt.tag_delete(t)
            except:
                pass

    # 结果与评价
    def _make_record(self):
        total = self.total_typed
        correct = self.correct_chars
        wrong = total - correct
        accuracy = (correct / total * 100) if total>0 else 0.0
        duration = int(time.time() - self.start_time) if self.start_time else 0
        minutes = max(1, duration) / 60.0
        if self.language.get() == "中文":
            speed = (correct / minutes) if minutes>0 else 0.0
            metric = "CPM"
        else:
            speed = (correct / 5.0) / minutes if minutes>0 else 0.0
            metric = "WPM"
        # 常见错误字符
        err_chars = {}
        for pos in self.error_positions:
            if pos < len(self.typed_text):
                ch = self.typed_text[pos]
            else:
                ch = "(空)"
            err_chars[ch] = err_chars.get(ch,0) + 1
        top_errors = sorted(err_chars.items(), key=lambda x: -x[1])[:8]
        return {
            "time": time.strftime("%Y-%m-%d %H:%M:%S"),
            "title": self.cur_article.get("title") if self.cur_article else "自定义",
            "language": self.language.get(),
            "duration_seconds": duration,
            "typed": total,
            "correct": correct,
            "wrong": wrong,
            "accuracy": round(accuracy,2),
            "speed": round(speed,2),
            "metric": metric,
            "top_errors": top_errors
        }

    def show_result(self, rec):
        self.result_text.config(state='normal')
        self.result_text.delete('1.0', tk.END)
        lines = []
        lines.append(f"时间：{rec['time']}")
        lines.append(f"文章：{rec['title']}（{rec['language']}）")
        lines.append(f"用时：{rec['duration_seconds']} 秒")
        lines.append(f"总输入：{rec['typed']}，正确：{rec['correct']}，错误：{rec['wrong']}")
        lines.append(f"准确率：{rec['accuracy']:.2f}%")
        lines.append(f"{rec['metric']}: {rec['speed']}")
        if rec['top_errors']:
            lines.append("常见错误（示例）： " + ", ".join(f"'{c}':{n}" for c,n in rec['top_errors']))
        lines.append("")
        lines.append("评价：")
        lines.append(self.evaluate(rec))
        self.result_text.insert(tk.END, "\n".join(lines))
        self.result_text.config(state='disabled')

    def evaluate(self, rec):
        acc = rec['accuracy']
        speed = rec['speed']
        lang = rec['language']
        suggestions = []
        if acc >= 98:
            grade = "优秀"
            suggestions.append("准确率高，建议保持稳定节奏并提高持续练习时间。")
        elif acc >= 92:
            grade = "良好"
            suggestions.append("注意减少回改与误触，多练习多次易错片段。")
        elif acc >= 80:
            grade = "中等"
            suggestions.append("先放慢速度以保证准确，然后逐步提高速度。")
        else:
            grade = "需要提高"
            suggestions.append("建议做短句慢打训练，纠正高频错误后再提速。")
        if lang == "English":
            if speed >= 60:
                suggestions.append("英文速度不错，可尝试更长文本或盲打练习。")
            elif speed >= 30:
                suggestions.append("英文速度一般，加强常见单词与双手配合练习。")
            else:
                suggestions.append("练习键位熟悉度与常用词库，提高指法准确性。")
        else:
            if speed >= 200:
                suggestions.append("中文打字速度优秀，可挑战更长文本。")
            elif speed >= 100:
                suggestions.append("中文速度良好，持续练习以减少波动。")
            else:
                suggestions.append("从常用词短句开始，建立稳定的打字节奏。")
        return f"{grade}。建议：{'；'.join(suggestions)}"

    # 保存记录
    def save_records(self):
        if not self.records:
            messagebox.showinfo("提示", "暂无成绩记录可保存。")
            return
        fn = filedialog.asksaveasfilename(title="保存成绩到 CSV", defaultextension=".csv",
                                          filetypes=[("CSV 文件","*.csv"),("所有文件","*.*")])
        if not fn:
            return
        try:
            with open(fn, "w", newline='', encoding='utf-8') as f:
                writer = csv.writer(f)
                writer.writerow(["time","title","language","duration_seconds","typed","correct","wrong","accuracy","metric","speed","top_errors"])
                for r in self.records:
                    te = ";".join([f"{c}:{n}" for c,n in r['top_errors']])
                    writer.writerow([r['time'], r['title'], r['language'], r['duration_seconds'],
                                     r['typed'], r['correct'], r['wrong'], r['accuracy'], r['metric'], r['speed'], te])
            messagebox.showinfo("已保存", f"成绩已保存到：{os.path.basename(fn)}")
        except Exception as e:
            messagebox.showerror("保存失败", str(e))

if __name__ == "__main__":
    app = TypingPracticeLongApp()
    app.mainloop()
