import pygame
import sys
import json
import os
from pygame.locals import *
import textwrap

# 可选：复制到剪贴板（安装 pyperclip: pip install pyperclip）
try:
    import pyperclip
    CLIP_OK = True
except Exception:
    CLIP_OK = False

pygame.init()
WIDTH, HEIGHT = 1000, 640
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("数学公式查询 (Pygame)")
clock = pygame.time.Clock()

# 字体（中文环境请确保系统有合适字体）
FONT = pygame.font.SysFont("SimHei", 16)
FONT_B = pygame.font.SysFont("SimHei", 20, bold=True)
SMALL = pygame.font.SysFont("SimHei", 14)

BG = (24, 28, 34)
PANEL = (18, 22, 28)
ACCENT = (200, 160, 100)
TEXT = (230, 230, 230)
HINT = (160, 160, 160)
HIGHLIGHT = (60, 80, 110)

LEFT_W = 320

# 示例公式库（可扩展为从 JSON 加载）
FORMULAS = [
    {
        "id": "f1",
        "title": "二次公式（求根公式）",
        "category": "代数",
        "latex": r"x = \frac{-b \pm \sqrt{b^2 - 4ac}}{2a}",
        "summary": "一元二次方程 ax^2+bx+c=0 的解公式。",
        "notes": "推导：通过配方法得到。判别式 Δ=b^2-4ac，Δ>0 两实根，Δ=0 双根，Δ<0 两虚根。\n示例：x^2-3x+2=0 → 根为 1,2。",
    },
    {
        "id": "f2",
        "title": "牛顿-莱布尼茨公式",
        "category": "微积分",
        "latex": r"\int_a^b f'(x)\,dx = f(b)-f(a)",
        "summary": "定积分与原函数之间的基本联系。",
        "notes": "若 F'(x)=f(x)，则 ∫_a^b f(x) dx = F(b)-F(a)。用于计算定积分及验证原函数。",
    },
    {
        "id": "f3",
        "title": "二项式定理",
        "category": "组合",
        "latex": r"(x+y)^n = \sum_{k=0}^n \binom{n}{k} x^{n-k} y^k",
        "summary": "展开 (x+y)^n 的一般公式，包含二项系数。",
        "notes": "二项系数定义为 C(n,k)=n!/(k!(n-k)!)。可用于概率、组合计数等。",
    },
    {
        "id": "f4",
        "title": "欧拉公式",
        "category": "复数",
        "latex": r"e^{i\theta} = \cos\theta + i\sin\theta",
        "summary": "复指数与三角函数的关系，包含著名的等式 e^{i\pi}+1=0。",
        "notes": "在复分析与信号处理中极为重要，可用于旋转表示等。",
    },
    {
        "id": "f5",
        "title": "等比数列求和",
        "category": "数列",
        "latex": r"S_n = a\frac{1-r^n}{1-r}\quad(r\neq1)",
        "summary": "求等比数列前 n 项和。",
        "notes": "若 |r|<1, 当 n→∞，S_\infty = a/(1-r)。示例：a=2,r=1/2 → S_∞=4。",
    },
]

# UI 状态
selected_idx = 0
search_text = ""
search_active = False
show_notes = False
notes_rect = pygame.Rect(360, 120, 560, 380)

# helper draw text


def draw_text(surf, text, x, y, font=FONT, color=TEXT):
    surf.blit(font.render(text, True, color), (x, y))


def wrap_text(text, width_chars):
    # very simple wrap by characters
    lines = []
    for para in text.splitlines():
        if len(para) <= width_chars:
            lines.append(para)
        else:
            for i in range(0, len(para), width_chars):
                lines.append(para[i:i+width_chars])
    return lines


def filter_formulas(q):
    q = q.strip().lower()
    if not q:
        return FORMULAS
    res = []
    for f in FORMULAS:
        if q in f['title'].lower() or q in f.get('summary', '').lower() or q in f.get('latex', '').lower() or q in f.get('category', '').lower():
            res.append(f)
    return res


def draw_sidebar(filtered, start_index=0):
    s = pygame.Surface((LEFT_W, HEIGHT))
    s.fill(PANEL)
    draw_text(s, "公式库", 12, 12, FONT_B, ACCENT)
    # search box
    pygame.draw.rect(s, HIGHLIGHT if search_active else (
        30, 36, 42), (12, 48, LEFT_W-24, 34), border_radius=6)
    draw_text(s, search_text or "搜索公式（回车）", 18, 56,
              FONT, TEXT if search_text else HINT)
    # category header
    draw_text(s, "结果：", 12, 96, FONT_B, TEXT)
    # list items
    y = 126
    for i, f in enumerate(filtered):
        rect = pygame.Rect(12, y + i*70, LEFT_W-24, 64)
        # background on hover / selected
        if i == selected_idx:
            pygame.draw.rect(s, (40, 50, 70), rect, border_radius=6)
        pygame.draw.rect(s, (0, 0, 0, 0), rect, 1)
        draw_text(s, f['title'], 18, y + i*70 + 6, FONT_B, ACCENT)
        draw_text(s, f"{f['category']}  —  {f.get('summary','')[:48]}",
                  18, y + i*70 + 34, SMALL, TEXT)
    screen.blit(s, (0, 0))


def draw_main_view(f):
    # title and latex text (as plain text)
    x = LEFT_W + 20
    y = 18
    draw_text(screen, f['title'], x, y, FONT_B, ACCENT)
    draw_text(screen, f"分类：{f.get('category','')}", x, y+36, FONT, HINT)
    draw_text(screen, "LaTeX:", x, y+72, FONT, TEXT)
    # draw latex block background
    rect = pygame.Rect(x, y+100, WIDTH - LEFT_W - 40, 120)
    pygame.draw.rect(screen, (28, 32, 40), rect, border_radius=6)
    # render latex as monospaced-ish text
    wrap = wrap_text(f.get('latex', ''), 80)
    for i, line in enumerate(wrap):
        draw_text(screen, line, rect.x + 12, rect.y +
                  8 + i*22, SMALL, (200, 220, 200))
    # summary
    draw_text(screen, "简介：", x, rect.y + rect.height + 16, FONT, TEXT)
    sum_lines = wrap_text(f.get('summary', ''), 80)
    for i, line in enumerate(sum_lines):
        draw_text(screen, line, x, rect.y +
                  rect.height + 46 + i*22, SMALL, HINT)
    # buttons
    btn_x = WIDTH - 240
    btn_y = rect.y + rect.height + 20
    btn_copy = pygame.Rect(btn_x, btn_y, 100, 36)
    btn_notes = pygame.Rect(btn_x+120, btn_y, 140, 36)
    pygame.draw.rect(screen, ACCENT, btn_copy, border_radius=6)
    pygame.draw.rect(screen, (60, 80, 110), btn_notes, border_radius=6)
    draw_text(screen, "复制公式", btn_copy.x+14, btn_copy.y+8, FONT, (10, 10, 10))
    draw_text(screen, "显示注释 / 推导", btn_notes.x+12, btn_notes.y+8, FONT, TEXT)
    return btn_copy, btn_notes


def draw_notes_window(f):
    s = pygame.Surface((notes_rect.width, notes_rect.height))
    s.fill((18, 20, 26))
    draw_text(s, "注释 / 推导", 12, 8, FONT_B, ACCENT)
    # render notes with wrapping
    lines = wrap_text(f.get('notes', '未提供注释'), 60)
    for i, line in enumerate(lines):
        draw_text(s, line, 12, 44 + i*20, SMALL, HINT)
    pygame.draw.rect(s, (120, 120, 120), (notes_rect.width -
                     36, 6, 28, 28), 2)  # close btn visual
    screen.blit(s, (notes_rect.x, notes_rect.y))


def export_formula(f):
    # save latex and title to txt
    fname = f"{f['id']}.txt"
    with open(fname, "w", encoding="utf-8") as fp:
        fp.write(
            f"{f['title']}\n\nLaTeX:\n{f.get('latex','')}\n\n简介:\n{f.get('summary','')}\n\n注释:\n{f.get('notes','')}\n")
    return fname


def main():
    global selected_idx, search_text, search_active, show_notes
    running = True
    filtered = filter_formulas("")
    while running:
        mx, my = pygame.mouse.get_pos()
        for ev in pygame.event.get():
            if ev.type == QUIT:
                running = False
            elif ev.type == KEYDOWN:
                if ev.key == K_ESCAPE:
                    if show_notes:
                        show_notes = False
                    else:
                        running = False
                elif ev.key == K_RETURN:
                    # execute search
                    filtered = filter_formulas(search_text)
                    selected_idx = 0
                    search_active = False
                elif ev.key == K_BACKSPACE:
                    if search_active:
                        search_text = search_text[:-1]
                elif ev.key == K_TAB:
                    # focus toggle
                    search_active = not search_active
                elif ev.key == K_UP:
                    selected_idx = max(0, selected_idx - 1)
                elif ev.key == K_DOWN:
                    selected_idx = min(len(filtered) - 1, selected_idx + 1)
                else:
                    # input characters to search if active
                    if search_active and ev.unicode:
                        search_text += ev.unicode
            elif ev.type == MOUSEBUTTONDOWN:
                if ev.button == 1:
                    # click sidebar list?
                    if mx < LEFT_W:
                        # compute which item
                        rel_y = my - 126
                        idx = rel_y // 70
                        if 0 <= idx < len(filtered):
                            selected_idx = idx
                    else:
                        # click buttons on main view
                        f = filtered[selected_idx] if filtered else None
                        if f:
                            # note this draws immediately but ok
                            btn_copy, btn_notes = draw_main_view(f)
                            if btn_copy.collidepoint(mx, my):
                                # copy latex to clipboard if available
                                if CLIP_OK:
                                    pyperclip.copy(f.get('latex', ''))
                                else:
                                    # fallback: export to txt
                                    export_formula(f)
                            if btn_notes.collidepoint(mx, my):
                                show_notes = not show_notes
                        # close notes if clicked on close icon
                        if show_notes and notes_rect.collidepoint(mx, my):
                            # check close btn area
                            local_x = mx - notes_rect.x
                            local_y = my - notes_rect.y
                            if local_x >= notes_rect.width-40 and 6 <= local_y <= 36:
                                show_notes = False

        # draw UI
        screen.fill(BG)
        filtered = filter_formulas(search_text) if search_text else FORMULAS
        if selected_idx >= len(filtered):
            selected_idx = max(0, len(filtered)-1)
        draw_sidebar(filtered)
        if filtered:
            f = filtered[selected_idx]
            btn_copy, btn_notes = draw_main_view(f)
            if show_notes:
                draw_notes_window(f)
        else:
            draw_text(screen, "未找到匹配公式。", LEFT_W+24, 120, FONT, HINT)

        # hints
        draw_text(screen, "提示: 点击左侧公式选择，Tab 切换搜索框，回车执行搜索。Esc 关闭注释/退出。",
                  LEFT_W+20, HEIGHT-36, SMALL, HINT)

        pygame.display.flip()
        clock.tick(60)

    pygame.quit()
    sys.exit()


if __name__ == "__main__":
    main()
