# -*- coding: utf-8 -*-
"""
口算训练器（限时版）
纯 tkinter 实现，无需第三方依赖。
功能：四则运算选择、数字范围、题量、总时长/每题限时、倒计时进度条、
      即时判题反馈、结算成绩、错题回顾、错题重练、最高分记录。
"""

import json
import os
import platform
import random
import time
import tkinter as tk
from tkinter import font as tkfont
from tkinter import messagebox, ttk

SAVE_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "math_best.json")


def pick_cn_font(root):
    """挑选一个可用的中文字体，避免乱码/豆腐块。"""
    available = set(tkfont.families(root))
    for name in ("Microsoft YaHei", "Microsoft YaHei UI", "PingFang SC",
                 "Noto Sans CJK SC", "WenQuanYi Micro Hei", "Heiti SC", "SimHei"):
        if name in available:
            return name
    return "TkDefaultFont"


def gen_question(ops, rng):
    """按'结果/操作数不超过 rng'的口径生成一道题。"""
    op = random.choice(ops)
    if op == "+":
        a = random.randint(1, max(1, rng - 1))
        b = random.randint(1, max(1, rng - a))
        return f"{a} + {b}", a + b, op
    if op == "-":
        a = random.randint(1, rng)
        b = random.randint(1, a)
        return f"{a} - {b}", a - b, op
    if op == "×":
        hi_a = max(2, min(rng // 2, 12))
        a = random.randint(2, hi_a)
        hi_b = max(2, min(rng // max(a, 1), 12))
        b = random.randint(2, hi_b)
        return f"{a} × {b}", a * b, op
    # 除法：保证整除
    hi_b = max(2, min(rng // 2, 12))
    b = random.randint(2, hi_b)
    hi_c = max(2, min(rng // b, 12))
    c = random.randint(2, hi_c)
    return f"{b * c} ÷ {b}", c, op


class MathTrainer(tk.Tk):
    def __init__(self):
        super().__init__()
        self.cn = pick_cn_font(self)
        self.title("口算训练器 · 限时版")
        self.geometry("760x560")
        self.resizable(False, False)
        self.configure(bg="#F2F5FA")

        self._init_style()
        self.settings = {
            "ops": tk.StringVar(value="+-"),
            "rng": tk.IntVar(value=20),
            "count": tk.IntVar(value=20),
            "total": tk.IntVar(value=120),
            "per": tk.IntVar(value=0),
        }
        self.best = self._load_best()

        # 运行时状态
        self.questions = []
        self.q_index = 0
        self.answers = []          # (题目, 你的答案, 正确答案, 用时秒, 是否超时)
        self.total_limit = 0
        self.per_limit = 0
        self.start_at = 0.0
        self.q_start_at = 0.0
        self.locked = False        # 判题反馈中，禁止输入
        self.running = False       # 是否处于答题计时状态
        self._timer = None

        container = tk.Frame(self, bg="#F2F5FA")
        container.pack(fill="both", expand=True)
        container.grid_rowconfigure(0, weight=1)
        container.grid_columnconfigure(0, weight=1)
        self.frames = {}
        for name in ("setup", "quiz", "result"):
            f = tk.Frame(container, bg="#F2F5FA")
            f.grid(row=0, column=0, sticky="nsew")
            self.frames[name] = f

        self._build_setup()
        self._build_quiz()
        self._build_result()
        self.show("setup")

    # ---------------- 样式 ----------------
    def _init_style(self):
        st = ttk.Style(self)
        if platform.system() == "Windows":
            try:
                st.theme_use("vista")
            except tk.TclError:
                pass
        else:
            try:
                st.theme_use("clam")
            except tk.TclError:
                pass
        st.configure("TButton", font=(self.cn, 11), padding=6)
        st.configure("Go.TButton", font=(self.cn, 13, "bold"), padding=10)
        st.configure("TProgressbar", thickness=18, troughcolor="#E3E8F0",
                     background="#3B7DDD", borderwidth=0)
        st.configure("Red.Horizontal.TProgressbar", background="#E5534B")
        st.configure("Orange.Horizontal.TProgressbar", background="#F0A020")

    def show(self, name):
        self.frames[name].tkraise()

    # ---------------- 设置页 ----------------
    def _build_setup(self):
        f = self.frames["setup"]
        tk.Label(f, text="口算训练器", font=(self.cn, 26, "bold"),
                 bg="#F2F5FA", fg="#1F2A44").pack(pady=(34, 4))
        tk.Label(f, text="选好参数，点开始，键盘直接敲数字，回车提交",
                 font=(self.cn, 11), bg="#F2F5FA", fg="#6B778C").pack(pady=(0, 18))

        card = tk.Frame(f, bg="white", highlightthickness=0)
        card.pack(padx=60, fill="both", expand=True)
        card.columnconfigure(0, weight=1)

        # 运算类型
        row = 0
        tk.Label(card, text="运算类型", font=(self.cn, 12, "bold"),
                 bg="white", fg="#1F2A44").grid(row=row, column=0, sticky="w", padx=24, pady=(20, 6))
        row += 1
        ops_box = tk.Frame(card, bg="white")
        ops_box.grid(row=row, column=0, sticky="w", padx=20)
        self.op_vars = {}
        for label, key in (("加法 +", "+"), ("减法 −", "-"), ("乘法 ×", "×"), ("除法 ÷", "÷")):
            v = tk.BooleanVar(value=key in "+-")
            self.op_vars[key] = v
            tk.Checkbutton(ops_box, text=label, variable=v, font=(self.cn, 11),
                           bg="white", activebackground="white",
                           selectcolor="#DCE8FF").pack(side="left", padx=6)
        row += 1

        # 数字范围
        tk.Label(card, text="数字范围（结果不超过该值）", font=(self.cn, 12, "bold"),
                 bg="white", fg="#1F2A44").grid(row=row, column=0, sticky="w", padx=24, pady=(16, 6))
        row += 1
        rb = tk.Frame(card, bg="white")
        rb.grid(row=row, column=0, sticky="w", padx=20)
        for label, val in (("20 以内", 20), ("50 以内", 50), ("100 以内", 100), ("1000 以内", 1000)):
            tk.Radiobutton(rb, text=label, value=val, variable=self.settings["rng"],
                           font=(self.cn, 11), bg="white", activebackground="white",
                           selectcolor="#DCE8FF").pack(side="left", padx=6)
        row += 1

        # 题量 / 总时长 / 每题限时
        grid = tk.Frame(card, bg="white")
        grid.grid(row=row, column=0, sticky="ew", padx=24, pady=(16, 4))
        grid.columnconfigure((0, 1, 2), weight=1)
        self._spin(grid, "题目数量", "count", 0, (5, 10, 20, 30, 50, 100))
        self._spin(grid, "总时长（秒，0=不限）", "total", 1, (60, 120, 180, 300, 600))
        self._spin(grid, "每题限时（秒，0=不限）", "per", 2, (5, 8, 10, 15, 20))
        row += 1

        ttk.Button(card, text="开 始 训 练", style="Go.TButton",
                   command=self.start_quiz).grid(row=row, column=0, pady=(22, 8), ipadx=30)

        best_txt = ("最高分：%d（正确率 %.0f%%，用时 %.1fs）" % tuple(self.best["score"])
                    if self.best.get("score") else "还没有记录，来刷第一局吧")
        self.best_label = tk.Label(card, text=best_txt, font=(self.cn, 10),
                                   bg="white", fg="#8A94A6")
        self.best_label.grid(row=row + 1, column=0, pady=(2, 16))

    def _spin(self, parent, title, key, col, values):
        box = tk.Frame(parent, bg="white")
        box.grid(row=0, column=col, sticky="w", padx=6)
        tk.Label(box, text=title, font=(self.cn, 10), bg="white",
                 fg="#6B778C").pack(anchor="w")
        ttk.Spinbox(box, from_=0, to=10000, width=10, values=values,
                    textvariable=self.settings[key],
                    font=(self.cn, 11)).pack(anchor="w", pady=4)

    # ---------------- 答题页 ----------------
    def _build_quiz(self):
        f = self.frames["quiz"]

        top = tk.Frame(f, bg="#F2F5FA")
        top.pack(fill="x", padx=28, pady=(18, 6))
        self.lb_progress = tk.Label(top, text="第 1 / 20 题", font=(self.cn, 12, "bold"),
                                    bg="#F2F5FA", fg="#1F2A44")
        self.lb_progress.pack(side="left")
        self.lb_stat = tk.Label(top, text="✅ 0    ❌ 0", font=(self.cn, 12),
                                bg="#F2F5FA", fg="#6B778C")
        self.lb_stat.pack(side="right")
        ttk.Button(top, text="结束本局", command=lambda: self.end_quiz(manual=True)).pack(side="right", padx=12)

        self.pb_total = ttk.Progressbar(f, mode="determinate", maximum=100, value=100)
        self.pb_total.pack(fill="x", padx=28, pady=(4, 2))
        self.lb_time = tk.Label(f, text="剩余 120.0s", font=(self.cn, 11),
                                bg="#F2F5FA", fg="#6B778C")
        self.lb_time.pack(anchor="w", padx=28)

        card = tk.Frame(f, bg="white")
        card.pack(fill="both", expand=True, padx=28, pady=14)

        self.lb_question = tk.Label(card, text="7 + 8 = ?", font=(self.cn, 46, "bold"),
                                    bg="white", fg="#1F2A44")
        self.lb_question.pack(expand=True, pady=(10, 4))

        vcmd = (self.register(self._valid_int), "%P")
        self.var_answer = tk.StringVar()
        self.entry = ttk.Entry(card, textvariable=self.var_answer, width=10, justify="center",
                               font=(self.cn, 24), validate="key", validatecommand=vcmd)
        self.entry.pack(pady=6)
        self.entry.bind("<Return>", lambda e: self.submit())
        self.entry.bind("<Escape>", lambda e: self.end_quiz(manual=True))

        self.lb_feedback = tk.Label(card, text="", font=(self.cn, 16, "bold"),
                                    bg="white", height=2)
        self.lb_feedback.pack()

        btns = tk.Frame(card, bg="white")
        btns.pack(pady=10)
        ttk.Button(btns, text="提交 (Enter)", command=self.submit).pack(side="left", padx=6)
        ttk.Button(btns, text="跳过 (判错)", command=self.skip).pack(side="left", padx=6)

        self.pb_per = ttk.Progressbar(f, mode="determinate", maximum=100, value=100)
        self.pb_per.pack(fill="x", padx=28, pady=(0, 16))

    @staticmethod
    def _valid_int(text):
        if text in ("", "-"):
            return True
        try:
            int(text)
            return True
        except ValueError:
            return False

    # ---------------- 结算页 ----------------
    def _build_result(self):
        f = self.frames["result"]
        self.lb_title = tk.Label(f, text="训练结束", font=(self.cn, 24, "bold"),
                                 bg="#F2F5FA", fg="#1F2A44")
        self.lb_title.pack(pady=(28, 6))
        self.lb_score = tk.Label(f, text="", font=(self.cn, 15), bg="#F2F5FA", fg="#3B7DDD")
        self.lb_score.pack()

        box = tk.Frame(f, bg="white")
        box.pack(fill="both", expand=True, padx=32, pady=16)
        cols = ("题目", "你的答案", "正确答案")
        self.tree = ttk.Treeview(box, columns=cols, show="headings", height=9)
        for c in cols:
            self.tree.heading(c, text=c)
            self.tree.column(c, anchor="center", width=160)
        self.tree.pack(fill="both", expand=True, padx=10, pady=10)
        tk.Label(box, text="（仅列出答错与超时的题目）", font=(self.cn, 9),
                 bg="white", fg="#8A94A6").pack(pady=(0, 8))

        btns = tk.Frame(f, bg="#F2F5FA")
        btns.pack(pady=(0, 22))
        ttk.Button(btns, text="再来一局", command=self.start_quiz).pack(side="left", padx=6)
        ttk.Button(btns, text="只重做错题", command=self.retry_wrong).pack(side="left", padx=6)
        ttk.Button(btns, text="返回设置", command=lambda: self.show("setup")).pack(side="left", padx=6)

    # ---------------- 流程控制 ----------------
    def start_quiz(self):
        ops = "".join(k for k, v in self.op_vars.items() if v.get())
        if not ops:
            messagebox.showwarning("提示", "至少选择一种运算类型")
            return
        rng = max(2, self.settings["rng"].get())
        count = max(1, self.settings["count"].get())
        self.total_limit = max(0, self.settings["total"].get())
        self.per_limit = max(0, self.settings["per"].get())

        seen, self.questions = set(), []
        while len(self.questions) < count:
            text, ans, op = gen_question(ops, rng)
            if text in seen and len(seen) < count * 3:
                continue
            seen.add(text)
            self.questions.append((text, ans))

        self.q_index = 0
        self.answers = []
        self.start_at = time.time()
        self.running = True
        self.show("quiz")
        self._next_question()
        self._tick()

    def _next_question(self):
        self.locked = False
        self.var_answer.set("")
        self.lb_feedback.config(text="")
        if self.q_index >= len(self.questions):
            self.end_quiz()
            return
        text, _ = self.questions[self.q_index]
        self.lb_question.config(text=f"{text} = ?", fg="#1F2A44")
        self.lb_progress.config(text=f"第 {self.q_index + 1} / {len(self.questions)} 题")
        self.q_start_at = time.time()
        self.entry.focus_set()

    def submit(self, timeout=False):
        if self.locked or self.q_index >= len(self.questions):
            return
        text, correct = self.questions[self.q_index]
        raw = self.var_answer.get().strip()
        cost = time.time() - self.q_start_at
        self.locked = True

        if timeout or raw == "" or raw == "-":
            ok = False
            self.answers.append((text, "超时/跳过", correct, cost, True))
            self._feedback("⏰ 超时了！正确答案是 %s" % correct, "#E5534B")
            delay = 1200
        else:
            val = int(raw)
            ok = (val == correct)
            self.answers.append((text, val, correct, cost, False))
            if ok:
                self._feedback("✅ 正确！", "#2FA84F")
                delay = 320
            else:
                self._feedback(f"❌ 正确答案是 {correct}", "#E5534B")
                delay = 1100

        self._update_stat()
        self.q_index += 1
        self.after(delay, self._next_question)

    def skip(self):
        if not self.locked:
            self.var_answer.set("")
            self.submit()

    def _feedback(self, text, color):
        self.lb_feedback.config(text=text, fg=color)
        self.lb_question.config(fg=color if "正确答案" in text or "超时" in text else "#1F2A44")
        if color == "#E5534B":
            self.bell()

    def _update_stat(self):
        right = sum(1 for a in self.answers if a[1] == a[2])
        wrong = len(self.answers) - right
        self.lb_stat.config(text=f"✅ {right}    ❌ {wrong}")

    # ---------------- 计时 ----------------
    def _tick(self):
        if not self.running:          # 已结算或返回设置，停止计时循环
            return
        # 总时长
        if self.total_limit > 0:
            left = self.total_limit - (time.time() - self.start_at)
            left = max(0.0, left)
            self.pb_total["value"] = left / self.total_limit * 100
            self.pb_total.configure(style="Red.Horizontal.TProgressbar" if left <= 10
                                     else "TProgressbar")
            self.lb_time.config(text=f"剩余 {left:.1f}s")
            if left <= 0:
                self.end_quiz()
                return
        else:
            used = time.time() - self.start_at
            self.lb_time.config(text=f"已用 {used:.1f}s")
            self.pb_total["value"] = 100

        # 每题限时
        if self.per_limit > 0 and not self.locked:
            left_q = self.per_limit - (time.time() - self.q_start_at)
            left_q = max(0.0, left_q)
            self.pb_per["value"] = left_q / self.per_limit * 100
            self.pb_per.configure(style="Orange.Horizontal.TProgressbar" if left_q <= 3
                                   else "TProgressbar")
            if left_q <= 0:
                self.submit(timeout=True)
        else:
            self.pb_per["value"] = 100

        self._timer = self.after(100, self._tick)

    # ---------------- 结算 ----------------
    def end_quiz(self, manual=False):
        self.running = False
        if self._timer:
            self.after_cancel(self._timer)
            self._timer = None
        self.locked = True

        done = self.answers
        right = sum(1 for a in done if a[1] == a[2])
        total_done = len(done)
        total_q = len(self.questions)
        used = time.time() - self.start_at
        acc = right / total_done * 100 if total_done else 0.0
        score = int(right * 100 + max(0.0, (self.total_limit - used)) * 2) if self.total_limit else int(right * 100)

        if manual and total_done == 0:
            self.show("setup")
            return


        self.lb_title.config(text="时间到！" if (not manual and self.q_index < total_q) else "训练结束")
        self.lb_score.config(
            text=f"得分 {score}   答对 {right}/{total_done}   正确率 {acc:.0f}%   "
                 f"用时 {used:.1f}s   平均每题 {used / total_done if total_done else 0:.2f}s")

        for item in self.tree.get_children():
            self.tree.delete(item)
        for q, your, correct, cost, is_to in done:
            if your != correct:
                self.tree.insert("", "end", values=(f"{q} = ?", your, correct))

        # 最高分
        prev = self.best.get("score")
        if not prev or score > prev[0]:
            self.best["score"] = (score, acc, used)
            self._save_best()
            self.best_label.config(text="最高分：%d（正确率 %.0f%%，用时 %.1fs）" % (score, acc, used))
            self.lb_score.config(text=self.lb_score.cget("text") + "   🎉 新纪录！")

        self.wrong_bank = [(q, c) for q, your, c, _, _ in done if your != c]
        self.show("result")

    def retry_wrong(self):
        if not getattr(self, "wrong_bank", None):
            messagebox.showinfo("很棒", "没有错题，不用重做 😎")
            return
        self.questions = list(self.wrong_bank)
        self.q_index = 0
        self.answers = []
        self.start_at = time.time()
        self.running = True
        self.show("quiz")
        self._next_question()
        self._tick()

    # ---------------- 记录 ----------------
    def _load_best(self):
        try:
            with open(SAVE_FILE, "r", encoding="utf-8") as fp:
                return json.load(fp)
        except Exception:
            return {}

    def _save_best(self):
        try:
            with open(SAVE_FILE, "w", encoding="utf-8") as fp:
                json.dump(self.best, fp, ensure_ascii=False)
        except Exception:
            pass


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