import math  # Import the math library for safe mathematical functions
import tkinter as tk  # Import tkinter and give it the short name "tk"
import tkinter.messagebox  # Import the messagebox module (used for pop-up dialogs)
from tkinter.constants import (
    SUNKEN,
)  # Import the SUNKEN style constant for the entry field border

# ─────────────────────────────────────────────────────────────
# SAFE MATH SETUP
# We allow only math functions like sqrt(), sin(), cos() etc.
# This prevents dangerous code from being typed into the calculator
# ─────────────────────────────────────────────────────────────

# Build a dictionary of safe math names from the math library
# We exclude anything that starts with "__" (those are internal Python things)
allowed_names = {k: v for k, v in math.__dict__.items() if not k.startswith("__")}


def safe_eval(expr):
    """
    Safely evaluate a math expression typed by the user.
    Instead of using Python's full eval() which can be dangerous,
    we restrict it to only math functions and constants.
    """
    try:
        # Evaluate the expression with no built-in functions, only math names
        return eval(expr, {"__builtins__": {}}, allowed_names)
    except ZeroDivisionError:
        # Handle division by zero specifically
        return "Division by 0!"
    except Exception:
        # Handle any other invalid expression
        return "Error"


# ─────────────────────────────────────────────────────────────
# WINDOW SETUP
# Everything you learned about creating a window,
# adding a frame, and placing widgets goes here
# ─────────────────────────────────────────────────────────────

# Create the main application window
win = tk.Tk()
win.title("Calculator")  # Set the window title

# Create a Frame — a container that holds and organizes our widgets
# bg sets the background color, padx adds horizontal spacing inside
frame = tk.Frame(win, bg="skyblue", padx=10)
frame.pack()  # Add the frame to the window

# ─────────────────────────────────────────────────────────────
# INPUT FIELD (Entry Widget)
# This is where the user sees what they are typing
# and where the result is displayed after pressing "="
# ─────────────────────────────────────────────────────────────

# Create an Entry widget — the text field at the top of the calculator
# relief=SUNKEN gives it a pressed-in visual style
# borderwidth=3 adds a border around it
# width=30 sets how wide it is (in characters)
# font sets the text style and size inside the field
entry = tk.Entry(frame, relief=SUNKEN, borderwidth=3, width=30, font=("Arial", 16))

# Place the entry field in the grid
# columnspan=4 makes it stretch across all 4 columns
# ipady adds internal vertical padding to make it taller
# pady adds space above and below it
entry.grid(row=0, column=0, columnspan=4, ipady=5, pady=5)

# ─────────────────────────────────────────────────────────────
# FUNCTIONS
# These are the actions that happen when buttons are clicked
# Everything you learned about functions is used here
# ─────────────────────────────────────────────────────────────


def click(num):
    """
    Called when a number or operator button is clicked.
    Inserts the button's text at the end of the entry field.
    tk.END means 'add it after whatever is already there'
    """
    entry.insert(tk.END, num)


def equal():
    """
    Called when the '=' button is clicked.
    Reads the expression from the entry field,
    calculates the result safely, then displays it.
    """
    res = safe_eval(entry.get())  # Get what the user typed and evaluate it
    entry.delete(0, tk.END)  # Clear the entry field completely
    entry.insert(0, str(res))  # Show the result starting from position 0


def clear():
    """
    Called when the 'Clear' button is clicked.
    Deletes everything in the entry field.
    0 means start from the beginning, tk.END means go to the end.
    """
    entry.delete(0, tk.END)


def backspace():
    """
    Called when the '<-' button is clicked.
    Removes only the last character typed in the entry field.
    This works like the backspace key on a keyboard.
    """
    current = entry.get()  # Get the current text in the entry field
    if current:  # Only do something if the field is not empty
        entry.delete(len(current) - 1, tk.END)  # Delete the last character


# ─────────────────────────────────────────────────────────────
# BUTTON LAYOUT
# Each button is defined as a tuple: (label, row, column)
# This is a clean way to manage many buttons without
# writing a separate line of code for each one
# ─────────────────────────────────────────────────────────────

# List of all buttons with their text, row position, and column position
# Row 0 is taken by the entry field above
# Buttons fill rows 1 to 6
buttons = [
    ("7", 1, 0),
    ("8", 1, 1),
    ("9", 1, 2),
    ("/", 1, 3),  # Row 1
    ("4", 2, 0),
    ("5", 2, 1),
    ("6", 2, 2),
    ("*", 2, 3),  # Row 2
    ("1", 3, 0),
    ("2", 3, 1),
    ("3", 3, 2),
    ("-", 3, 3),  # Row 3
    ("0", 4, 0),
    (".", 4, 1),
    ("%", 4, 2),
    ("+", 4, 3),  # Row 4
    ("(", 5, 0),
    (")", 5, 1),
    ("<-", 5, 2),
    ("=", 5, 3),  # Row 5
    ("Clear", 6, 0),  # Row 6
]

# Loop through the buttons list and create each button dynamically
# This saves us from writing Button() code 21 separate times
for txt, r, c in buttons:
    # Decide which function to connect to each button
    if txt == "Clear":
        action = clear  # Clear button calls clear()
    elif txt == "=":
        action = equal  # Equals button calls equal()
    elif txt == "<-":
        action = backspace  # Backspace button calls backspace()
    else:
        # For all number and operator buttons, use a lambda function
        # lambda t=txt: click(t) captures the button's text correctly
        # Without t=txt, all buttons would send the last value of txt
        action = lambda t=txt: click(t)

    # Create the button and place it in the grid at the correct row and column
    # padx and pady add spacing between buttons
    # width=4 makes all buttons the same width
    tk.Button(frame, text=txt, padx=20, pady=20, width=4, command=action).grid(
        row=r, column=c, pady=2, padx=2
    )

# ─────────────────────────────────────────────────────────────
# KEYBOARD SHORTCUTS
# Students can also type using their physical keyboard
# instead of only clicking the on-screen buttons
# ─────────────────────────────────────────────────────────────


def key_handler(event):
    """
    This function listens for keyboard key presses.
    event.keysym gives the name of the key pressed (like "Return", "Escape")
    event.char gives the actual character typed (like "5", "+", ".")
    """
    if event.keysym == "Return":  # Enter key works like clicking "="
        equal()
    elif event.keysym == "Escape":  # Escape key works like clicking "Clear"
        clear()
    elif event.keysym == "BackSpace":  # Backspace key works like clicking "<-"
        backspace()
    elif event.char in "0123456789+-*/().%":  # Any valid calculator character
        click(event.char)  # Insert it into the entry field


# Bind the key_handler function to the window
# This means every key press anywhere on the window triggers key_handler
win.bind("<Key>", key_handler)

# ─────────────────────────────────────────────────────────────
# MAIN LOOP
# Start the window and keep it running.
# The calculator waits here for the user to click buttons or press keys.
# Nothing below this line will ever run.
# ─────────────────────────────────────────────────────────────
win.mainloop()
