import sys
import ast
import tty
import termios
import readline

import pyperclip

# TODO: 
#   - % should calculate percents



ARITHMETIC_OPERATORS = {'+', '-', '*', '/', '^', '%', '\\'}
UNARY_OPERATORS = {'r', 'n'}
ALL_OPERATORS = ARITHMETIC_OPERATORS | UNARY_OPERATORS


    
def getch():
    fd = sys.stdin.fileno()
    old_settings = termios.tcgetattr(fd)
    try:
        tty.setraw(fd)
        ch = sys.stdin.read(1)
    finally:
        termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
    return ch



class RPNCalculator:
    def __init__(self):
        self.stack = []
        self.intro_message = "RPN Calculator. press h or ? for help. Enter numbers, then operators (eg 2 <enter> 3 +  to add 2 and 3)"
        self.current_input = ""
        readline.set_history_length(1000)
        self.history_offset = 0

    def clear_screen(self):
        sys.stdout.write("\033[2J\033[H")
        sys.stdout.flush()

    def move_cursor(self, x, y):
        sys.stdout.write(f"\033[{y};{x}H")
        sys.stdout.flush()

    def clear_line(self):
        sys.stdout.write("\033[K")
        sys.stdout.flush()

    def swap(self):
        if len(self.stack) < 2:
            return
        self.stack[-1], self.stack[-2] = self.stack[-2], self.stack[-1]

    def display(self):
        self.move_cursor(1, 1)
        lines_printed = 0
        if self.intro_message:
            print(self.intro_message)
            self.intro_message = ""
            lines_printed += 1

        for i, value in enumerate(self.stack, 1):
            print(f"{len(self.stack) - i + 1}: {value:,}                    ")
            lines_printed += 1

        for _ in range(lines_printed, 20):  # Assume max 20 lines for safety
            self.clear_line()
            self.move_cursor(1, _ + 1)

        # Move cursor to the input line
        self.move_cursor(1, lines_printed + 1)
        self.clear_line()
        sys.stdout.write(f"> {self.current_input}")
        sys.stdout.flush()


    def push(self, value):
        input_value = value
        try:
            if not isinstance(value, (int, float, complex)):
                value = ast.literal_eval(value)
            if not isinstance(value, (int, float, complex)):
                raise ValueError("Should be a number...")
            self.stack.append(value)
            readline.add_history(str(input_value))
            self.history_offset = 0
        except (ValueError, SyntaxError) as e:
            raise ValueError(f"Invalid input: {value}. Error: {str(e)}")

    def pop(self):
        return self.stack.pop() if self.stack else 0

    def dup(self):
        self.stack.append(self.stack[-1])
        self.display()

    def perform_operation(self, op):
        if op == 'r':
            if len(self.stack) < 1:
                return
            a = self.pop()
            if self.current_input:
                try:
                    precision = int(self.current_input)
                    self.push(round(a, precision))
                    self.current_input = ""
                except ValueError:
                    self.push(round(a))
            else:
                self.push(round(a))
        elif op == 'n':
            if len(self.stack) < 1:
                return
            a = self.pop()
            self.push(-a)
        

        if self.current_input and self.current_input not in ALL_OPERATORS:
            self.push(self.current_input)
            self.current_input = ""

        if op in ARITHMETIC_OPERATORS:
            if len(self.stack) < 2:
                return
            b, a = self.pop(), self.pop()
            if op == '+':
                self.push(a + b)
            elif op == '-':
                self.push(a - b)
            elif op == '*':
                self.push(a * b)
            elif op == '/':
                self.push(a / b if b != 0 else float('inf'))
            elif op == '^':
                self.push(a ** b)
            elif op == '%':
                # self.push(a % b)
                self.push(a*b/100.0)
            elif op == '\\':
                self.push(a % b)
        
        self.display()  # Update the display after each operation


    def handle_up_arrow(self):
        n_history = readline.get_current_history_length()
        if n_history > 0:
            self.current_input = readline.get_history_item(n_history-self.history_offset) # n_history - self.history_index)
            self.history_offset = min(self.history_offset+1, n_history-1)

    def handle_down_arrow(self):
        n_history = readline.get_current_history_length()
        if readline.get_current_history_length() > 0:
            self.current_input = readline.get_history_item(n_history-self.history_offset)
            self.history_offset = max(self.history_offset-1, 0)


    def run(self):
        self.clear_screen()
        while True:
            self.display()
            char = getch()
            
            if char == '\x1b':  # ESC character
                next1, next2 = getch(), getch()
                if next1 == '[':
                    if next2 == 'A':  # Up arrow
                        self.handle_up_arrow()
                    elif next2 == 'B':  # Down arrow
                        self.handle_down_arrow()

            elif char == '-' and self.current_input == "":
                # '-' is a valid operator, but we need to check if it's a negation or a subtraction operator
                self.current_input += char
            elif char in ALL_OPERATORS:
                self.perform_operation(char)
                self.current_input = ""
            elif char.isdigit() or char in {'.', 'e', 'E'}:
                self.current_input += char
            elif char in {'\r', '\n'}:
                if self.current_input:
                    if self.current_input in ARITHMETIC_OPERATORS:
                        self.perform_operation(self.current_input)
                    else:
                        try:
                            self.push(self.current_input)
                        except ValueError as e:
                            input(f"\nInvalid input: {self.current_input}.\npress any key to continue...")
                    self.current_input = ""
                elif self.stack:
                    self.dup()
            elif char == 'd':
                if self.stack:
                    self.pop()
            elif char == 'c':
                self.clear_screen()
                self.stack = []
                self.current_input = ""
            elif char in ('\x08', '\x7f'):  # Backspace character (ASCII and DEL)
                if self.current_input:
                    self.current_input = self.current_input[:-1]
            elif char in ('h', '?'):
                self.clear_screen()
                print("RPN Calculator")
                print("==============")
                print("Operators:")
                print("  +: Add")
                print("  -: Subtract")
                print("  *: Multiply")
                print("  /: Divide")
                print("  \: (Modulus) Remainder")
                print("  ^: Exponentiation")
                print("  %: Percent")
                print("  r: Round (optionally, enter a number to specify precision, e.g. \"3r\" rounds to 3 decimal places)")
                print("  n: Negate: Toggle sign of the top element of the stack)")
                print("  s: Swap last two elements")
                print("  d: Drop last element")
                print("  c: Clear")
                print("  <Enter>: Duplicate last line")
                print("  h/?: Help")
                print("  q: Quit")
                input("Press Enter to continue...")
            elif char == 's':
                self.swap()
            elif char == 'q':
                if len(self.stack) > 0:
                    pyperclip.copy(self.stack[0])
                self.clear_screen()  
                sys.exit(0)


if __name__ == "__main__":
    calculator = RPNCalculator()
    calculator.run()