import tkinter as tk
from tkinter import messagebox
import ast
import operator

# 安全表达式评估（只允许数字、二元和一元运算、括号）
operators = {
    ast.Add: operator.add,
    ast.Sub: operator.sub,
    ast.Mult: operator.mul,
    ast.Div: operator.truediv,
    ast.Mod: operator.mod,
    ast.Pow: operator.pow,
    ast.USub: operator.neg,
    ast.UAdd: operator.pos,
}


def eval_expr_node(node):
    if isinstance(node, ast.Expression):
        return eval_expr_node(node.body)
    if isinstance(node, ast.BinOp):
        left = eval_expr_node(node.left)
        right = eval_expr_node(node.right)
        op_type = type(node.op)
        if op_type in operators:
            return operators[op_type](left, right)
        raise ValueError(f"不支持的二元运算: {op_type}")
    if isinstance(node, ast.UnaryOp):
        operand = eval_expr_node(node.operand)
        op_type = type(node.op)
        if op_type in operators:
            return operators[op_type](operand)
        raise ValueError(f"不支持的一元运算: {op_type}")
    # 兼容不同Python版本的数字节点
    if isinstance(node, ast.Num):
        return node.n
    if isinstance(node, ast.Constant):
        if isinstance(node.value, (int, float)):
            return node.value
        raise ValueError("不支持的常量类型")
    raise ValueError(f"不支持的表达式节点: {type(node)}")


def safe_eval(expr):
    """
    使用 ast 安全计算表达式，允许：数字、+ - * / % ** 括号 和 一元 +-
    抛出异常表示表达式不合法或计算出错。
    """
    parsed = ast.parse(expr, mode='eval')
    return eval_expr_node(parsed)

# GUI 部分


class Calculator(tk.Tk):
    def __init__(self):
        super().__init__()
        self.title("计算器")
        self.resizable(False, False)
        self._create_widgets()
        self.bind_keys()

    def _create_widgets(self):
        # 显示框
        self.display_var = tk.StringVar()
        entry = tk.Entry(self, textvariable=self.display_var, font=(
            "Arial", 20), bd=5, relief=tk.RIDGE, justify='right', width=16)
        entry.grid(row=0, column=0, columnspan=4, padx=8, pady=8)
        entry.focus_set()

        # 按钮布局（文本, 行, 列, 宽度）
        buttons = [
            ('C', 1, 0), ('⌫', 1, 1), ('%', 1, 2), ('/', 1, 3),
            ('7', 2, 0), ('8', 2, 1), ('9', 2, 2), ('*', 2, 3),
            ('4', 3, 0), ('5', 3, 1), ('6', 3, 2), ('-', 3, 3),
            ('1', 4, 0), ('2', 4, 1), ('3', 4, 2), ('+', 4, 3),
            ('+/-', 5, 0), ('0', 5, 1), ('.', 5, 2), ('=', 5, 3),
        ]

        for (text, r, c) in buttons:
            btn = tk.Button(self, text=text, width=4,
                            height=2, font=("Arial", 14))
            btn.grid(row=r, column=c, padx=4, pady=4, sticky="nsew")
            if text == 'C':
                btn.config(command=self.clear)
            elif text == '⌫':
                btn.config(command=self.backspace)
            elif text == '=':
                btn.config(command=self.calculate)
            elif text == '+/-':
                btn.config(command=self.toggle_sign)
            else:
                btn.config(command=lambda t=text: self.insert_text(t))

        # 使列等宽
        for i in range(4):
            self.grid_columnconfigure(i, weight=1)

    def insert_text(self, txt):
        cur = self.display_var.get()
        # 控制连续操作符或小数点规则可以在此扩展
        self.display_var.set(cur + txt)

    def clear(self):
        self.display_var.set("")

    def backspace(self):
        cur = self.display_var.get()
        if cur:
            self.display_var.set(cur[:-1])

    def toggle_sign(self):
        cur = self.display_var.get().strip()
        if not cur:
            return
        # 尝试将最后一个数字的符号切换（简单实现：如果整个表达式是单个数字则切换）
        try:
            # 仅当整个表达式是单个数字时切换，其他情况不做处理以免复杂
            val = safe_eval(cur)
            self.display_var.set(str(-val))
        except Exception:
            # 无法解析则不处理
            pass

    def calculate(self):
        expr = self.display_var.get().strip()
        if not expr:
            return
        try:
            result = safe_eval(expr)
            # 去掉 .0 的显示
            if isinstance(result, float) and result.is_integer():
                result = int(result)
            self.display_var.set(str(result))
        except Exception as e:
            messagebox.showerror("错误", f"表达式错误或无法计算:\n{e}")

    # 键盘绑定
    def bind_keys(self):
        self.bind("<Return>", lambda e: self.calculate())
        self.bind("<BackSpace>", lambda e: self.backspace())
        self.bind("<Escape>", lambda e: self.clear())
        # 数字和运算符
        for char in "0123456789+-*/().%":
            self.bind(char, lambda e, ch=char: self.insert_text(ch))
        # 小数点和等号（回车已绑定）
        self.bind(".", lambda e: self.insert_text('.'))


if __name__ == "__main__":
    app = Calculator()
    app.mainloop()
