#!/usr/bin/env python3
"""
HP-Style RPN Calculator using Stack Language Backend
Implements classic HP calculator interface with stack display
"""

import tkinter as tk
from tkinter import ttk, font as tkfont
import sls_py

class HPCalculator:
    def __init__(self, root):
        self.root = root
        self.root.title("HP Stack Calculator")
        self.root.geometry("450x650")
        self.root.resizable(False, False)
        
        # Initialize interpreter
        self.interp = sls_py.InterpreterState()
        self.lexer = sls_py.LexerInfo()
        
        # Current entry buffer
        self.entry_buffer = ""
        self.is_new_entry = True
        
        # Set up UI
        self.setup_styles()
        self.create_widgets()
        
    def setup_styles(self):
        """Configure custom styles for the calculator"""
        style = ttk.Style()
        style.theme_use('clam')
        
        # Configure button styles
        style.configure('Calc.TButton',
                       font=('Arial', 12, 'bold'),
                       padding=10)
        style.configure('Display.TLabel',
                       font=('Courier', 14),
                       background='#f0f0f0',
                       relief='sunken',
                       padding=5)
        
    def create_widgets(self):
        """Create all calculator widgets"""
        # Main container
        main_frame = ttk.Frame(self.root, padding="10")
        main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
        
        # Stack display (shows 4 stack levels like classic HP)
        self.create_stack_display(main_frame)
        
        # Entry display
        self.create_entry_display(main_frame)
        
        # Button grid
        self.create_button_grid(main_frame)
        
    def create_stack_display(self, parent):
        """Create the 4-level stack display"""
        stack_frame = ttk.LabelFrame(parent, text="Stack", padding="5")
        stack_frame.grid(row=0, column=0, columnspan=4, sticky=(tk.W, tk.E), pady=(0, 10))
        
        self.stack_labels = []
        for i in range(4):
            label = ttk.Label(stack_frame,
                            text=f"T{3-i}: 0",
                            style='Display.TLabel',
                            anchor='e',
                            width=30)
            label.grid(row=i, column=0, sticky=(tk.W, tk.E), pady=2)
            self.stack_labels.append(label)
            
    def create_entry_display(self, parent):
        """Create the current entry display"""
        entry_frame = ttk.Frame(parent)
        entry_frame.grid(row=1, column=0, columnspan=4, sticky=(tk.W, tk.E), pady=(0, 10))
        
        self.entry_label = ttk.Label(entry_frame,
                                     text="0",
                                     style='Display.TLabel',
                                     anchor='e',
                                     font=('Courier', 16, 'bold'))
        self.entry_label.grid(row=0, column=0, sticky=(tk.W, tk.E))
        
    def create_button_grid(self, parent):
        """Create the calculator button grid"""
        buttons = [
            # Row 1: Stack operations
            [('√', self.sqrt), ('x²', self.square), ('1/x', self.reciprocal), ('÷', self.divide)],
            # Row 2: Numbers and operations
            [('7', lambda: self.digit('7')), ('8', lambda: self.digit('8')), 
             ('9', lambda: self.digit('9')), ('×', self.multiply)],
            # Row 3
            [('4', lambda: self.digit('4')), ('5', lambda: self.digit('5')), 
             ('6', lambda: self.digit('6')), ('−', self.subtract)],
            # Row 4
            [('1', lambda: self.digit('1')), ('2', lambda: self.digit('2')), 
             ('3', lambda: self.digit('3')), ('+', self.add)],
            # Row 5
            [('0', lambda: self.digit('0')), ('.', self.decimal), 
             ('±', self.negate), ('ENTER', self.enter)],
            # Row 6: Special functions
            [('SWAP', self.swap), ('DROP', self.drop), 
             ('DUP', self.dup), ('CLR', self.clear)]
        ]
        
        for row_idx, row in enumerate(buttons, start=2):
            for col_idx, (text, command) in enumerate(row):
                btn = ttk.Button(parent,
                               text=text,
                               command=command,
                               style='Calc.TButton',
                               width=8)
                btn.grid(row=row_idx, column=col_idx, padx=2, pady=2, sticky=(tk.W, tk.E, tk.N, tk.S))
        
        # Keyboard bindings
        self.root.bind('<Return>', lambda e: self.enter())
        self.root.bind('<KP_Enter>', lambda e: self.enter())
        for i in range(10):
            self.root.bind(str(i), lambda e, n=str(i): self.digit(n))
        self.root.bind('.', lambda e: self.decimal())
        self.root.bind('+', lambda e: self.add())
        self.root.bind('-', lambda e: self.subtract())
        self.root.bind('*', lambda e: self.multiply())
        self.root.bind('/', lambda e: self.divide())
        self.root.bind('<Escape>', lambda e: self.clear())
        
    def digit(self, d):
        """Handle digit button press"""
        if self.is_new_entry:
            self.entry_buffer = d
            self.is_new_entry = False
        else:
            self.entry_buffer += d
        self.update_entry_display()
        
    def decimal(self):
        """Handle decimal point"""
        if self.is_new_entry:
            self.entry_buffer = "0."
            self.is_new_entry = False
        elif '.' not in self.entry_buffer:
            self.entry_buffer += '.'
        self.update_entry_display()
        
    def negate(self):
        """Toggle sign of current entry"""
        if self.is_new_entry:
            # Negate top of stack
            self.execute_code("0 swap -")
        else:
            if self.entry_buffer.startswith('-'):
                self.entry_buffer = self.entry_buffer[1:]
            else:
                self.entry_buffer = '-' + self.entry_buffer
        self.update_entry_display()
        
    def enter(self):
        """Push current entry onto stack"""
        if self.entry_buffer:
            self.execute_code(self.entry_buffer)
            self.entry_buffer = ""
            self.is_new_entry = True
        self.update_displays()
        
    def add(self):
        """Addition operation"""
        self.enter()
        self.execute_code("+")
        self.update_displays()
        
    def subtract(self):
        """Subtraction operation"""
        self.enter()
        self.execute_code("-")
        self.update_displays()
        
    def multiply(self):
        """Multiplication operation"""
        self.enter()
        self.execute_code("*")
        self.update_displays()
        
    def divide(self):
        """Division operation"""
        self.enter()
        self.execute_code("/")
        self.update_displays()
        
    def sqrt(self):
        """Square root operation"""
        self.enter()
        self.execute_code("sqrt")
        self.update_displays()
        
    def square(self):
        """Square operation"""
        self.enter()
        self.execute_code("dup *")
        self.update_displays()
        
    def reciprocal(self):
        """Reciprocal (1/x) operation"""
        self.enter()
        self.execute_code("1 swap /")
        self.update_displays()
        
    def swap(self):
        """Swap top two stack items"""
        self.enter()
        self.execute_code("swap")
        self.update_displays()
        
    def drop(self):
        """Drop top stack item"""
        self.enter()
        self.execute_code("drop")
        self.update_displays()
        
    def dup(self):
        """Duplicate top stack item"""
        self.enter()
        self.execute_code("dup")
        self.update_displays()
        
    def clear(self):
        """Clear entry and stack"""
        self.entry_buffer = ""
        self.is_new_entry = True
        # Clear the stack
        while len(self.interp.stack) > 0:
            self.interp.pop()
        self.update_displays()
        
    def execute_code(self, code):
        """Execute stack language code"""
        try:
            self.lexer.source_code = code
            self.lexer.pos = 0
            self.lexer.column = 1
            self.lexer.line = 1
            
            tokens = sls_py.lexical_analysis(self.lexer)
            
            for token in tokens:
                if token.type == sls_py.TokenType.EOF:
                    break
                if not self.interp.execute(token):
                    print(f"Error executing: {code}")
                    break
        except Exception as e:
            print(f"Exception: {e}")
            
    def update_entry_display(self):
        """Update the entry display"""
        display_text = self.entry_buffer if self.entry_buffer else "0"
        self.entry_label.config(text=display_text)
        
    def update_displays(self):
        """Update both entry and stack displays"""
        self.update_entry_display()
        self.update_stack_display()
        
    def update_stack_display(self):
        """Update the 4-level stack display"""
        stack_size = len(self.interp.stack)
        
        for i in range(4):
            level = 3 - i  # T3, T2, T1, T0
            if stack_size > level:
                entry = self.interp.stack[-(level + 1)]
                value_str = self.format_stack_entry(entry)
                self.stack_labels[i].config(text=f"T{level}: {value_str}")
            else:
                self.stack_labels[i].config(text=f"T{level}: 0")
                
    def format_stack_entry(self, entry):
        """Format a stack entry for display"""
        if entry.type == sls_py.StackType.I64:
            return str(entry.value)
        elif entry.type == sls_py.StackType.DOUBLE:
            val = entry.value
            # Format with appropriate precision
            if abs(val) < 1e-10 and val != 0:
                return f"{val:.6e}"
            elif abs(val) > 1e10:
                return f"{val:.6e}"
            else:
                return f"{val:.10g}"
        elif entry.type == sls_py.StackType.BOOLEAN:
            return str(entry.value)
        else:
            return str(entry.value)


def main():
    root = tk.Tk()
    app = HPCalculator(root)
    root.mainloop()


if __name__ == "__main__":
    main()
