import tkinter as tk
import math
import random

# ========================
# 全局配置
# ========================
WIDTH, HEIGHT = 750, 580

# ========================
# 主题
# ========================
THEMES = {
    "🌸 樱花粉": {"bg": "#FFF0F5", "fg": "#C2185B", "panel": "#FCE4EC", "btn": "#F48FB1", "accent": "#E91E63", "card": "#FFF"},
    "💙 天空蓝": {"bg": "#E3F2FD", "fg": "#0D47A1", "panel": "#BBDEFB", "btn": "#42A5F5", "accent": "#1565C0", "card": "#FFF"},
    "💚 抹茶绿": {"bg": "#E8F5E9", "fg": "#1B5E20", "panel": "#C8E6C9", "btn": "#66BB6A", "accent": "#2E7D32", "card": "#FFF"},
    "🧡 蜜桔橙": {"bg": "#FFF3E0", "fg": "#E65100", "panel": "#FFE0B2", "btn": "#FF9800", "accent": "#EF6C00", "card": "#FFF"},
    "💜 梦幻紫": {"bg": "#F3E5F5", "fg": "#4A148C", "panel": "#E1BEE7", "btn": "#AB47BC", "accent": "#7B1FA2", "card": "#FFF"},
    "🌈 糖果色": {"bg": "#FFF9C4", "fg": "#F57F17", "panel": "#FFF59D", "btn": "#FFD54F", "accent": "#FF8F00", "card": "#FFF"},
}
cur_theme = "🌸 樱花粉"

# ========================
# UI 引用
# ========================
root = None
canvas = None
entry_num = None
entry_den = None
lbl_result = None
lbl_steps = None
lbl_fraction = None
lbl_gcd = None
theme_btns = []
all_widgets = []
step_items = []

# ========================
# 数学工具
# ========================
def gcd(a, b):
    """最大公约数（欧几里得算法）"""
    a, b = abs(a), abs(b)
    while b:
        a, b = b, a % b
    return a

def lcm(a, b):
    """最小公倍数"""
    return abs(a * b) // gcd(a, b) if a and b else 0

def prime_factors(n):
    """质因数分解"""
    n = abs(n)
    factors = {}
    d = 2
    while d * d <= n:
        while n % d == 0:
            factors[d] = factors.get(d, 0) + 1
            n //= d
        d += 1 if d == 2 else 2  # 2之后只检查奇数
    if n > 1:
        factors[n] = factors.get(n, 0) + 1
    return factors

def format_factors(factors):
    """格式化质因数"""
    parts = []
    for p in sorted(factors.keys()):
        exp = factors[p]
        if exp == 1:
            parts.append(str(p))
        else:
            parts.append(f"{p}²" if exp == 2 else f"{p}^{exp}")
    return " × ".join(parts) if parts else "1"

# ========================
# 化简逻辑
# ========================
def simplify_fraction(num, den):
    """
    化简分数，返回详细步骤
    """
    steps = []
    original_num, original_den = num, den

    # 处理分母为0
    if den == 0:
        return None, ["❌ 分母不能为0！"]

    # 处理负数
    negative = (num < 0) ^ (den < 0)
    num, den = abs(num), abs(den)

    steps.append(f"📝 原始分数：{original_num}/{original_den}")

    # 求GCD
    g = gcd(num, den)
    steps.append(f"🔍 求最大公约数 GCD({num}, {den})")

    # 展示欧几里得过程
    a, b = num, den
    euclid_steps = []
    while b:
        euclid_steps.append(f"  {a} = {b} × {a//b} + {a%b}")
        a, b = b, a % b
    steps.append(f"  欧几里得算法：")
    steps.extend(euclid_steps)
    steps.append(f"  ✅ GCD = {g}")

    # 质因数分解
    nf = prime_factors(num)
    df = prime_factors(den)
    steps.append(f"")
    steps.append(f"🔢 分子质因数分解：{num} = {format_factors(nf)}")
    steps.append(f"🔢 分母质因数分解：{den} = {format_factors(df)}")

    # 约分
    new_num = num // g
    new_den = den // g

    if g > 1:
        steps.append(f"")
        steps.append(f"✂️ 约去公因数 {g}：")
        steps.append(f"  {num}/{den} = ({num}÷{g})/({den}÷{g}) = {new_num}/{new_den}")
    else:
        steps.append(f"")
        steps.append(f"✅ 已是最简分数（GCD=1），无需约分")

    # 最终结果
    final_num = -new_num if negative else new_num

    if new_den == 1:
        steps.append(f"")
        steps.append(f"🎯 最终结果：{final_num}")
    else:
        steps.append(f"")
        steps.append(f"🎯 最终结果：{final_num}/{new_den}")

    # 带分数
    if new_den != 1 and abs(final_num) > new_den:
        whole = abs(final_num) // new_den
        rem = abs(final_num) % new_den
        sign = "-" if final_num < 0 else ""
        if rem > 0:
            steps.append(f"📦 带分数形式：{sign}{whole} {rem}/{new_den}")
        else:
            steps.append(f"📦 整数形式：{sign}{whole}")

    return (final_num, new_den), steps

# ========================
# 运算功能
# ========================
def add_fractions(n1, d1, n2, d2):
    """分数加法"""
    l = lcm(d1, d2)
    return n1 * (l // d1) + n2 * (l // d2), l

def sub_fractions(n1, d1, n2, d2):
    """分数减法"""
    l = lcm(d1, d2)
    return n1 * (l // d1) - n2 * (l // d2), l

def mul_fractions(n1, d1, n2, d2):
    """分数乘法"""
    return n1 * n2, d1 * d2

def div_fractions(n1, d1, n2, d2):
    """分数除法"""
    if n2 == 0:
        return None
    return n1 * d2, d1 * n2

# ========================
# 绘制分数显示
# ========================
def draw_fraction_display(num, den, label="原始分数"):
    """在画布上绘制大号分数"""
    canvas.delete("fraction")
    t = THEMES[cur_theme]

    cx = WIDTH // 2
    cy = 70

    # 标签
    canvas.create_text(cx, cy - 45, text=label, font=("Comic Sans MS", 12, "bold"),
                       fill=t["accent"], tags="fraction")

    # 分子
    canvas.create_text(cx, cy - 15, text=str(num),
                       font=("Comic Sans MS", 28, "bold"), fill=t["fg"], tags="fraction")

    # 分数线
    canvas.create_line(cx - 40, cy + 5, cx + 40, cy + 5,
                       width=4, fill=t["fg"], tags="fraction")

    # 分母
    canvas.create_text(cx, cy + 30, text=str(den),
                       font=("Comic Sans MS", 28, "bold"), fill=t["fg"], tags="fraction")

def draw_result(final_pair, original_num, original_den):
    """绘制化简结果"""
    canvas.delete("result")
    t = THEMES[cur_theme]

    if final_pair is None:
        canvas.create_text(WIDTH//2, 160, text="❌ 无法计算",
                           font=("Comic Sans MS", 16, "bold"), fill="#F44336", tags="result")
        return

    num, den = final_pair
    cx = WIDTH // 2
    cy = 170

    # 箭头
    canvas.create_text(cx - 80, cy, text="➡️", font=("Comic Sans MS", 20), tags="result")

    if den == 1:
        canvas.create_text(cx + 40, cy, text=f"= {num}",
                           font=("Comic Sans MS", 30, "bold"), fill=t["accent"], tags="result")
    else:
        # 分子
        canvas.create_text(cx + 20, cy - 18, text=str(num),
                           font=("Comic Sans MS", 22, "bold"), fill=t["accent"], tags="result")
        # 分数线
        canvas.create_line(cx - 5, cy, cx + 45, cy, width=3, fill=t["accent"], tags="result")
        # 分母
        canvas.create_text(cx + 20, cy + 18, text=str(den),
                           font=("Comic Sans MS", 22, "bold"), fill=t["accent"], tags="result")

def draw_gcd_visual(num, den, g):
    """可视化GCD过程"""
    canvas.delete("gcd_vis")
    t = THEMES[cur_theme]

    y = 260
    x_start = 60

    # 分子圆点
    canvas.create_text(x_start, y - 25, text=f"分子 {num}", font=("Comic Sans MS", 10, "bold"),
                       fill=t["fg"], tags="gcd_vis")
    group_size = g if g > 0 else 1
    for i in range(min(num, 40)):
        x = x_start + (i % 10) * 18
        yy = y + (i // 10) * 16
        color = "#E91E63" if (i + 1) % group_size == 0 else "#42A5F5"
        canvas.create_oval(x-5, yy-5, x+5, yy+5, fill=color, outline="", tags="gcd_vis")

    # 分母圆点
    y2 = y + 70
    x2_start = WIDTH - 250
    canvas.create_text(x2_start, y2 - 25, text=f"分母 {den}", font=("Comic Sans MS", 10, "bold"),
                       fill=t["fg"], tags="gcd_vis")
    for i in range(min(den, 40)):
        x = x2_start + (i % 10) * 18
        yy = y2 + (i // 10) * 16
        color = "#FF9800" if (i + 1) % group_size == 0 else "#66BB6A"
        canvas.create_oval(x-5, yy-5, x+5, yy+5, fill=color, outline="", tags="gcd_vis")

    # GCD标注
    canvas.create_text(WIDTH//2, y2 + 55, text=f"🔗 每组 {g} 个 → GCD = {g}",
                       font=("Comic Sans MS", 11, "bold"), fill=t["accent"], tags="gcd_vis")

# ========================
# 步骤显示
# ========================
def show_steps(steps):
    """显示步骤"""
    lbl_steps.config(state="normal")
    lbl_steps.delete("1.0", "end")
    for s in steps:
        lbl_steps.insert("end", s + "\n")
    lbl_steps.config(state="disabled")

# ========================
# 主操作
# ========================
def do_simplify():
    """化简"""
    try:
        num = int(entry_num.get())
        den = int(entry_den.get())
    except:
        lbl_result.config(text="❌ 请输入整数！", fg="#F44336")
        return

    result, steps = simplify_fraction(num, den)
    draw_fraction_display(num, den, "📝 原始分数")
    draw_gcd_visual(num, den, gcd(abs(num), abs(den)))

    if result is None:
        lbl_result.config(text="❌ 分母不能为0！", fg="#F44336")
        draw_result(None, num, den)
    else:
        rnum, rden = result
        if rden == 1:
            lbl_result.config(text=f"✅ 结果：{rnum}", fg="#4CAF50")
        else:
            lbl_result.config(text=f"✅ 结果：{rnum}/{rden}", fg="#4CAF50")
        draw_result(result, num, den)

    show_steps(steps)

def do_operation(op):
    """四则运算"""
    try:
        n1 = int(entry_num.get())
        d1 = int(entry_den.get())
        n2 = int(entry_n2.get())
        d2 = int(entry_d2.get())
    except:
        lbl_result.config(text="❌ 请输入整数！", fg="#F44336")
        return

    if op == "+":
        r = add_fractions(n1, d1, n2, d2)
        op_text = f"{n1}/{d1} + {n2}/{d2}"
    elif op == "-":
        r = sub_fractions(n1, d1, n2, d2)
        op_text = f"{n1}/{d1} - {n2}/{d2}"
    elif op == "×":
        r = mul_fractions(n1, d1, n2, d2)
        op_text = f"{n1}/{d1} × {n2}/{d2}"
    else:
        if n2 == 0:
            lbl_result.config(text="❌ 除数不能为0！", fg="#F44336")
            return
        r = div_fractions(n1, d1, n2, d2)
        op_text = f"{n1}/{d1} ÷ {n2}/{d2}"

    if r is None:
        lbl_result.config(text="❌ 计算错误", fg="#F44336")
        return

    # 化简结果
    result, steps = simplify_fraction(r[0], r[1])
    steps.insert(0, f"📝 算式：{op_text}")
    steps.append(f"")
    steps.append(f"📐 通分计算后化简：")

    draw_fraction_display(r[0], r[1], f"📝 {op_text}")
    if result:
        draw_result(result, r[0], r[1])
        rnum, rden = result
        if rden == 1:
            lbl_result.config(text=f"✅ {op_text} = {rnum}", fg="#4CAF50")
        else:
            lbl_result.config(text=f"✅ {op_text} = {rnum}/{rden}", fg="#4CAF50")
    show_steps(steps)

def random_fraction():
    """随机生成分数"""
    num = random.randint(-20, 20)
    den = random.randint(2, 20)
    while gcd(abs(num), den) == min(abs(num), den):  # 确保不是最简
        num = random.randint(-20, 20)
        den = random.randint(2, 20)
    entry_num.delete(0, "end")
    entry_num.insert(0, str(num))
    entry_den.delete(0, "end")
    entry_den.insert(0, str(den))

def clear_all():
    """清空"""
    entry_num.delete(0, "end")
    entry_den.delete(0, "end")
    entry_n2.delete(0, "end")
    entry_d2.delete(0, "end")
    lbl_result.config(text="", fg="#333")
    lbl_steps.config(state="normal")
    lbl_steps.delete("1.0", "end")
    lbl_steps.config(state="disabled")
    canvas.delete("all")

# ========================
# 换肤
# ========================
def apply_theme(name):
    global cur_theme
    cur_theme = name
    t = THEMES[name]

    root.config(bg=t["bg"])
    for w in all_widgets:
        try:
            w.config(bg=t["bg"], fg=t["fg"])
        except:
            pass

    for btn in theme_btns:
        btn.config(bg=t["btn"], fg="white")

    # 重绘
    try:
        num = int(entry_num.get())
        den = int(entry_den.get())
        draw_fraction_display(num, den, "📝 分数")
        draw_gcd_visual(num, den, gcd(abs(num), abs(den)))
    except:
        pass

# ========================
# 构建界面
# ========================
def build_ui():
    global root, canvas, entry_num, entry_den, lbl_result
    global lbl_steps, lbl_fraction, lbl_gcd
    global entry_n2, entry_d2, theme_btns, all_widgets

    root = tk.Tk()
    root.title("📐 分数化简 · 约分工具")
    root.geometry(f"{WIDTH}x{HEIGHT}")
    root.resizable(False, False)

    # ====== 顶部 ======
    top = tk.Frame(root)
    top.pack(fill="x", pady=5)

    tk.Label(top, text="📐 分数化简 · 约分工具 ✨", font=("Comic Sans MS", 18, "bold")).pack()

    # 主题栏
    tf = tk.Frame(top)
    tf.pack(pady=2)
    tk.Label(tf, text="🎨 ", font=("Comic Sans MS", 9)).pack(side="left")
    for name in THEMES:
        btn = tk.Button(tf, text=name, font=("Comic Sans MS", 8, "bold"),
                         relief="raised", bd=1, padx=4,
                         command=lambda n=name: apply_theme(n))
        btn.pack(side="left", padx=2)
        theme_btns.append(btn)

    # ====== 输入区 ======
    input_frame = tk.Frame(root)
    input_frame.pack(pady=8)

    # 第一个分数
    tk.Label(input_frame, text="分数 ①", font=("Comic Sans MS", 11, "bold")).grid(row=0, column=0, padx=5)

    entry_num = tk.Entry(input_frame, font=("Comic Sans MS", 14, "bold"),
                          width=6, justify="center", relief="solid", bd=2)
    entry_num.grid(row=0, column=1, padx=2)
    entry_num.insert(0, "12")

    tk.Label(input_frame, text="—", font=("Comic Sans MS", 16, "bold")).grid(row=0, column=2)

    entry_den = tk.Entry(input_frame, font=("Comic Sans MS", 14, "bold"),
                          width=6, justify="center", relief="solid", bd=2)
    entry_den.grid(row=0, column=3, padx=2)
    entry_den.insert(0, "18")

    # 按钮
    btn_simplify = tk.Button(input_frame, text="✂️ 化简/约分", font=("Comic Sans MS", 11, "bold"),
                              bg="#E91E63", fg="white", padx=8, command=do_simplify)
    btn_simplify.grid(row=0, column=4, padx=8)

    btn_random = tk.Button(input_frame, text="🎲 随机", font=("Comic Sans MS", 10),
                            bg="#FF9800", fg="white", command=random_fraction)
    btn_random.grid(row=0, column=5, padx=3)

    btn_clear = tk.Button(input_frame, text="🗑️ 清空", font=("Comic Sans MS", 10),
                           bg="#9E9E9E", fg="white", command=clear_all)
    btn_clear.grid(row=0, column=6, padx=3)

    # 第二个分数（运算用）
    tk.Label(input_frame, text="分数 ②", font=("Comic Sans MS", 11, "bold")).grid(row=1, column=0, padx=5, pady=5)

    entry_n2 = tk.Entry(input_frame, font=("Comic Sans MS", 12),
                         width=5, justify="center", relief="solid", bd=1)
    entry_n2.grid(row=1, column=1, padx=2)
    entry_n2.insert(0, "3")

    tk.Label(input_frame, text="—", font=("Comic Sans MS", 14)).grid(row=1, column=2)

    entry_d2 = tk.Entry(input_frame, font=("Comic Sans MS", 12),
                         width=5, justify="center", relief="solid", bd=1)
    entry_d2.grid(row=1, column=3, padx=2)
    entry_d2.insert(0, "4")

    # 运算按钮
    ops = [("➕", "+"), ("➖", "-"), ("✖️", "×"), ("➗", "÷")]
    for i, (emoji, op) in enumerate(ops):
        btn = tk.Button(input_frame, text=f"{emoji} {op}",
                         font=("Comic Sans MS", 9, "bold"),
                         bg="#42A5F5", fg="white", width=5,
                         command=lambda o=op: do_operation(o))
        btn.grid(row=1, column=4+i, padx=2)

    # ====== 画布（可视化区域）======
    canvas_frame = tk.Frame(root)
    canvas_frame.pack(fill="both", expand=True, padx=5, pady=2)

    canvas = tk.Canvas(canvas_frame, width=WIDTH-20, height=240, highlightthickness=0)
    canvas.pack(fill="both", expand=True)

    # ====== 结果 ======
    result_frame = tk.Frame(root)
    result_frame.pack(fill="x", pady=3)

    lbl_result = tk.Label(result_frame, text="", font=("Comic Sans MS", 16, "bold"))
    lbl_result.pack()

    # ====== 步骤区域 ======
    steps_frame = tk.Frame(root)
    steps_frame.pack(fill="both", expand=True, padx=10, pady=3)

    tk.Label(steps_frame, text="📋 详细步骤：", font=("Comic Sans MS", 10, "bold")).pack(anchor="w")

    steps_scroll = tk.Scrollbar(steps_frame)
    steps_scroll.pack(side="right", fill="y")

    lbl_steps = tk.Text(steps_frame, font=("Consolas", 10), height=8,
                         wrap="word", yscrollcommand=steps_scroll.set)
    lbl_steps.pack(fill="both", expand=True)
    steps_scroll.config(command=lbl_steps.yview)
    lbl_steps.config(state="disabled")

    # 底部
    bottom = tk.Frame(root)
    bottom.pack(fill="x", side="bottom", pady=2)
    tk.Label(bottom, text="💡 输入分数后点「化简」| 两个分数可四则运算 | 支持负数",
             font=("Comic Sans MS", 9)).pack()

    # 收集
    all_widgets.extend([top, tf, input_frame, result_frame, steps_frame,
                        bottom, btn_simplify, btn_random, btn_clear, lbl_result])

# ========================
# 启动
# ========================
def init():
    build_ui()
    apply_theme("🌸 樱花粉")
    # 默认演示
    entry_num.delete(0, "end")
    entry_num.insert(0, "12")
    entry_den.delete(0, "end")
    entry_den.insert(0, "18")
    do_simplify()

if __name__ == "__main__":
    init()
    root.mainloop()
