"""
@file application.py
@brief Calculator application GUI and event handling.
@details
Provides the main application window and user interface event handlers
for the calculator application using PyQt5. Connects UI buttons to
calculator operations and manages display updates.
@author Jiří Hronský
@date 2026-04-15
"""

import sys
import os
import subprocess
import platform
from PyQt5.QtWidgets import QApplication, QMainWindow
from PyQt5.QtCore import Qt

from ui import Ui_MainWindow
from calculator import Calculator, Operation


class CalculatorApp(QMainWindow):
    """@brief Main calculator application window and controller."""

    def __init__(self):
        """@brief Initialize the calculator application.

        Sets up the UI, calculator instance, and connects all button signals
        to their respective slot functions.
        """
        super().__init__()
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)

        self.calculator = Calculator(max_digits=17)
        self.ui.label.setText(self.calculator.get_accumulator_string()) # Initialize the display with "0"
        self.ui.summary_label.setText(self.calculator.get_result_string()) # Initialize the display with "0"
        
        # Extra functions
        self.ui.extra_functions_panel.setVisible(False)
        self.ui.toggle_menu_button.clicked.connect(self.toggle_extra_functions)
        self.ui.manual_button.clicked.connect(self.open_manual)
        
        # Connect buttons to their respective functions
        for btn, digit in [
            (self.ui.zero_button, 0), (self.ui.one_button, 1), (self.ui.two_button, 2),
            (self.ui.three_button, 3), (self.ui.four_button, 4), (self.ui.five_button, 5),
            (self.ui.six_button, 6), (self.ui.seven_button, 7), (self.ui.eight_button, 8),
            (self.ui.nine_button, 9)
        ]:
            btn.clicked.connect(lambda _, d=digit: self.add_digit(d))
            
        # Decimal point and sign toggle
        self.ui.decimal_button.clicked.connect(lambda _ :self.add_decimal())    # .
        self.ui.op_button.clicked.connect(lambda _ :self.toggle_sign())         # +/-

        # Operators
        self.ui.plus_button.clicked.connect(lambda _ : self.perform_operation(Operation.ADD))           # +
        self.ui.minus_button.clicked.connect(lambda _ : self.perform_operation(Operation.SUBTRACT))     # -
        self.ui.multiply_button.clicked.connect(lambda _ : self.perform_operation(Operation.MULTIPLY))  # *
        self.ui.divide_button.clicked.connect(lambda _ : self.perform_operation(Operation.DIVIDE))      # /
        self.ui.power_button.clicked.connect(lambda _ : self.perform_operation(Operation.POWER))        # ^
        self.ui.sqrt_button.clicked.connect(lambda _ : self.perform_operation(Operation.ROOT))          # √
        self.ui.modulo_button.clicked.connect(lambda _ : self.perform_operation(Operation.MODULO))      # %

        # factorial
        self.ui.factorial_button.clicked.connect(lambda _ : self.print_factorial())                     # !
        # pi
        self.ui.pi_button.clicked.connect(lambda _ : self.print_pi())                                   # π

        # Action buttons
        self.ui.equal_button.clicked.connect(lambda _ : self.perform_operation(Operation.EQUAL))        # =
        self.ui.delete_button.clicked.connect(lambda _ : self.delete_last_character())                  # DEL
        self.ui.deleteHistory_button.clicked.connect(lambda _ : self.clear_all())                       # C
        self.ui.deleteEntry_button.clicked.connect(lambda _ : self.clear_entry())                       # CE

    def keyPressEvent(self, event):
        """@brief Handle keyboard input for calculator operations.

        @param event The keyboard event.
        """
        key = event.text()

        if key.isdigit(): self.add_digit(int(key)) # Number keys (0-9)
        elif key in ['.', ',']: self.add_decimal() # Decimal point
        elif key == '+': self.perform_operation(Operation.ADD) # Addition
        elif key == '-': self.perform_operation(Operation.SUBTRACT) # Subtraction
        elif key in ['*', 'x', 'X']: self.perform_operation(Operation.MULTIPLY) # Multiplication
        elif key == '/': self.perform_operation(Operation.DIVIDE) # Division
        elif key == '^': self.perform_operation(Operation.POWER) # Power
        elif key in ['√', 'v', 'V']: self.perform_operation(Operation.ROOT) # Root
        elif key == '%': self.perform_operation(Operation.MODULO) # Modulo
        elif event.key() in [Qt.Key_Return, Qt.Key_Enter] or key == '=': self.perform_operation(Operation.EQUAL) # Enter or Equals
        elif event.key() == Qt.Key_Backspace: self.delete_last_character() # Backspace for delete
        elif event.key() == Qt.Key_Delete: self.clear_entry() # Delete key for clear entry
        elif key.upper() == 'C': self.clear_all() # C for clear all
        elif key.upper() == 'S': self.toggle_sign() # S for sign toggle
        elif key == '!' or key.upper() == 'F': self.print_factorial() # ! or F for factorial
        elif key.upper() == 'P': self.print_pi() # P for pi
        elif event.key() == Qt.Key_F1: self.open_manual() # F1 for opening user manual
        else: super().keyPressEvent(event)

    def add_digit(self, digit: int) -> None:
        """@brief Handle digit button press.

        @param digit The digit to add (0-9).
        """
        self.ui.label.setText(self.calculator.add_digit(digit))

    def add_decimal(self) -> None:
        """@brief Handle decimal point button press."""
        self.ui.label.setText(self.calculator.add_decimal_point())

    def toggle_sign(self) -> None:
        """@brief Handle sign toggle button press (+/-)."""
        self.ui.label.setText(self.calculator.change_sign())

    def perform_operation(self, operation: Operation) -> None:
        """@brief Handle arithmetic operation button press.

        @param operation The operation to perform.
        """
        self.calculator.perform_operation(operation)
        self.ui.label.setText(self.calculator.get_accumulator_string())

        if operation == Operation.EQUAL:
            self.ui.summary_label.setText(f"<span style='background-color: orange; color: black;'>{self.calculator.get_result_string()}</span>")
        else:
            self.ui.summary_label.setText(self.calculator.get_result_string())

    def print_factorial(self) -> None:
        """@brief Handle factorial button press and update display."""
        self.ui.label.setText(self.calculator.set_factorial())

    def print_pi(self) -> None:
        """@brief Handle pi button press and update display."""
        self.ui.label.setText(self.calculator.set_pi())

    def delete_last_character(self) -> None:
        """@brief Handle delete button press (DEL) to remove last digit."""
        self.ui.label.setText(self.calculator.delete_digit())

    def clear_all(self) -> None:
        """@brief Handle clear all button press (C) to reset calculator state."""
        self.ui.label.setText(self.calculator.reset())
        self.ui.summary_label.setText(self.calculator.get_result_string())

    def clear_entry(self) -> None:
        """@brief Handle clear entry button press (CE) to clear input."""
        self.ui.label.setText(self.calculator.clear())

    def toggle_extra_functions(self) -> None:
        """@brief Handle visibility of extra functions panel."""
        is_visible = self.ui.extra_functions_panel.isVisible()
        self.ui.extra_functions_panel.setVisible(not is_visible)
        
    def open_manual(self) -> None:
        """@brief Opens manual (manual.pdf) from root dir."""
        pdf_path = os.path.abspath(
            os.path.join(os.path.dirname(__file__), "..", "manual.pdf")
        )
        if not os.path.exists(pdf_path):
            print(f"Error: File {pdf_path} does not exist.")
            return

        try:
            # Windows
            if sys.platform == "win32":
                os.startfile(pdf_path)
            # MacOS
            elif sys.platform == "darwin":
                subprocess.call(["open", pdf_path])
            # Linux and WSL
            else:
                if "microsoft" in platform.uname().release.lower():
                    win_path = subprocess.check_output(["wslpath", "-w", pdf_path]).decode("utf-8").strip()
                    subprocess.call(["cmd.exe", "/c", "start", "", win_path])
                else:
                    # Linux
                    subprocess.call(["xdg-open", pdf_path])
                    
        except Exception as e:
            print(f"Cannot access manual: {e}")

# Entry point of the application
if __name__ == "__main__":
    """@brief Create and run the calculator application."""
    app = QApplication(sys.argv)
    window = CalculatorApp()
    window.show()
    sys.exit(app.exec_())