"""
GUIBRUSHR - GUI for Bayesian Retrieval Using Spectroscopy at High Resolution.

This module contains the main application class for the GUIBRUSHR graphical user interface.
The application provides a comprehensive interface for atmospheric retrieval analysis
using spectroscopic data at both high and low resolution.

Classes:
    WindowConfig: Configuration dataclass for window dimensions and scaling
    GUIBRUSHR: Main application class managing the GUI and its components

Functions:
    main: Entry point for the GUIBRUSHR application
"""

import os
os.environ['LANG'] = 'en_US.UTF-8'
os.environ['LC_ALL'] = 'en_US.UTF-8'
import platform
import signal
import socket
import time
import tkinter as tk
import tkinter.messagebox as messagebox
from dataclasses import dataclass
from pathlib import Path
from typing import Tuple

from screeninfo import get_monitors

# Thread configuration for numerical computations
# These settings help prevent thread contention in numerical libraries
# by limiting the number of threads used by various linear algebra libraries
THREAD_CONFIG = {
    "OMP_NUM_THREADS": "1",
    "OPENBLAS_NUM_THREADS": "1", 
    "MKL_NUM_THREADS": "1",
    "VECLIB_MAXIMUM_THREADS": "1",
    "NUMEXPR_NUM_THREADS": "1"
}

# Apply thread configuration to environment
for env_var, value in THREAD_CONFIG.items():
    os.environ[env_var] = value


def _show_config_setup_dialog(configuration_path: Path) -> bool:
    """
    Show a first-run setup dialog to create the configuration CSV file.

    Args:
        configuration_path: Path where configuration.csv should be created

    Returns:
        True if the file was created successfully, False if the user cancelled
    """
    root = tk.Tk()
    root.withdraw()

    dialog = tk.Toplevel(root)
    dialog.title("GUIBRUSHR - First Setup")
    dialog.resizable(False, False)

    created = [False]

    tk.Label(
        dialog,
        text="Configuration file not found.\nPlease enter the required paths to create it:",
        justify="left",
        pady=10
    ).grid(row=0, column=0, columnspan=2, padx=20, sticky="w")

    tk.Label(dialog, text="petitRADTRANS input_data path:").grid(row=1, column=0, sticky="w", padx=20, pady=5)
    prt_var = tk.StringVar()
    tk.Entry(dialog, textvariable=prt_var, width=55).grid(row=1, column=1, padx=10, pady=5)

    tk.Label(dialog, text="Target folders path:").grid(row=2, column=0, sticky="w", padx=20, pady=5)
    target_var = tk.StringVar()
    tk.Entry(dialog, textvariable=target_var, width=55).grid(row=2, column=1, padx=10, pady=5)

    def create_config():
        prt_path = prt_var.get().strip()
        target_path = target_var.get().strip()
        if not prt_path or not target_path:
            messagebox.showerror("Error", "Both paths are required.", parent=dialog)
            return
        configuration_path.parent.mkdir(parents=True, exist_ok=True)
        with open(configuration_path, 'w') as f:
            f.write("Element,Path\n")
            f.write(f"petitRadTrans_path,{prt_path}\n")
            f.write(f"path_target_folders,{target_path}\n")
        created[0] = True
        dialog.destroy()
        root.destroy()

    tk.Button(dialog, text="Create Configuration File", command=create_config).grid(
        row=3, column=0, columnspan=2, pady=15
    )

    dialog.protocol("WM_DELETE_WINDOW", root.destroy)
    dialog.grab_set()
    root.mainloop()

    return created[0]


@dataclass
class WindowConfig:
    """
    Configuration class for window management in GUIBRUSHR.

    This class manages window dimensions and scaling factors for the main
    application window. It provides a centralized way to handle window sizing
    and positioning across different screen resolutions and platforms.
    """
    # All values read from graphics.yaml via GraphicsConfig
    ASPECT_RATIO: float = 1.0
    MIN_WIDTH: int = 800
    MIN_HEIGHT: int = 600
    MAX_WIDTH: int = 1920
    MAX_HEIGHT: int = 1080
    DEFAULT_WIDTH: int = 1200
    DEFAULT_HEIGHT: int = 900
    SCREEN_SCALE_FACTOR: float = 0.8
    MACOS_SCALE_FACTOR: float = 1.0
    PANEL_MARGIN: int = 20
    PANEL_VERTICAL_SPACING: int = 40
    INPUT_PANEL_RATIO: float = 0.7

    @classmethod
    def get_default_dimensions(cls) -> Tuple[int, int]:
        """
        Get the default fallback window dimensions.

        Returns:
            Tuple of default width and height in pixels
        """
        return cls.DEFAULT_WIDTH, cls.DEFAULT_HEIGHT

    @classmethod
    def get_min_dimensions(cls) -> Tuple[int, int]:
        """
        Get the minimum allowed window dimensions.

        Returns:
            Tuple of minimum width and height in pixels
        """
        return cls.MIN_WIDTH, cls.MIN_HEIGHT

    @classmethod
    def get_max_dimensions(cls) -> Tuple[int, int]:
        """
        Get the maximum allowed window dimensions.

        Returns:
            Tuple of maximum width and height in pixels
        """
        return cls.MAX_WIDTH, cls.MAX_HEIGHT

    @classmethod
    def clamp_dimensions(cls, width: int, height: int) -> Tuple[int, int]:
        """
        Clamp window dimensions to min/max bounds while enforcing the fixed aspect ratio.

        The more constraining dimension (width or height) is used, then the
        other is derived from ASPECT_RATIO so the ratio is always maintained.

        Args:
            width: Proposed window width in pixels
            height: Proposed window height in pixels

        Returns:
            Tuple of clamped (width, height) with ASPECT_RATIO preserved
        """
        # Pick the most constraining dimension and derive the other
        width_from_height = int(height * cls.ASPECT_RATIO)
        if width_from_height <= width:
            width = width_from_height
        else:
            height = int(width / cls.ASPECT_RATIO)

        # Clamp to bounds (ratio is already baked into MIN/MAX/DEFAULT constants)
        if width < cls.MIN_WIDTH:
            width, height = cls.MIN_WIDTH, cls.MIN_HEIGHT
        elif width > cls.MAX_WIDTH:
            width, height = cls.MAX_WIDTH, cls.MAX_HEIGHT

        return width, height

    @classmethod
    def calculate_centered_position(cls, window_width: int, window_height: int,
                                     screen_width: int, screen_height: int) -> Tuple[int, int]:
        """
        Calculate window position to center it on the screen.

        Args:
            window_width: Width of the window in pixels
            window_height: Height of the window in pixels
            screen_width: Width of the screen in pixels
            screen_height: Height of the screen in pixels

        Returns:
            Tuple of x and y displacement in pixels to center the window
        """
        x_offset = max(0, (screen_width - window_width) // 2)
        y_offset = max(0, (screen_height - window_height) // 2)
        return x_offset, y_offset


class GUIBRUSHR:
    """
    Main GUI application class for GUIBRUSHR.
    
    This class manages the main application window and its components,
    including input/output panels and process management. It handles
    window initialization, panel creation, and proper cleanup on exit.
    
    Attributes:
        global_start: Flag indicating if the application is fully initialized
        width: Window width in pixels
        height: Window height in pixels
        displace_x: Horizontal window displacement from screen edge
        displace_y: Vertical window displacement from screen edge
        path_default: Default application path for resources
        dialog: Optional dialog window reference
        window: Main application window (tkinter.Tk)
        frame_input_master: Main input panel containing parameter controls
        frame_output_table: Main output panel for displaying results
    """

    def __init__(self, path_default: Path) -> None:
        """
        Initialize the GUIBRUSHR application.
        
        This method sets up the main application window, initializes
        dimensions based on screen size, creates input/output panels,
        and prepares the application for user interaction.
        
        Args:
            path_default: Base path for the application resources
        """


        # 2. IMPORT DINAMICO: Solo ora che il percorso è validato
        from GUIBRUSHR.GUI.LAYOUT.GraphicsConfig import GraphicsConfig as GC

        # 3. INIEZIONE DEI VALORI in WindowConfig
        WindowConfig.ASPECT_RATIO = GC.ASPECT_RATIO
        WindowConfig.MIN_WIDTH = GC.MIN_WIDTH
        WindowConfig.MIN_HEIGHT = GC.MIN_HEIGHT
        WindowConfig.MAX_WIDTH = GC.MAX_WIDTH
        WindowConfig.MAX_HEIGHT = GC.MAX_HEIGHT
        WindowConfig.DEFAULT_WIDTH = GC.DEFAULT_WIDTH
        WindowConfig.DEFAULT_HEIGHT = GC.DEFAULT_HEIGHT
        WindowConfig.SCREEN_SCALE_FACTOR = GC.SCREEN_SCALE_FACTOR
        WindowConfig.MACOS_SCALE_FACTOR = GC.MACOS_SCALE_FACTOR
        WindowConfig.PANEL_MARGIN = GC.PANEL_MARGIN
        WindowConfig.PANEL_VERTICAL_SPACING = GC.PANEL_VERTICAL_SPACING
        WindowConfig.INPUT_PANEL_RATIO = GC.INPUT_PANEL_RATIO

        # Initialize window properties
        self.width = None
        self.height = None
        self.displace_x = None
        self.displace_y = None
        self.global_start = False  # Application not fully initialized yet
        
        # Initialize window dimensions based on screen size
        self.initialize_window()
        
        # Store application paths
        self.path_default = path_default
        self.dialog = None
        
        # Setup main window and create panels
        self._setup_main_window()
        self._create_panels()

        self.initialize_DB()
        
        # Mark application as fully initialized
        self.global_start = True

    def _setup_main_window(self) -> None:
        """
        Set up the main application window with its properties and layout.
        
        This method creates the main tkinter window, configures its properties
        (title, icon, geometry), sets up grid layout, and applies window
        constraints.
        """
        # Create main window
        self.window = tk.Tk()

        # Initialize font scaling manager (must happen right after Tk())
        from GUIBRUSHR.GUI.LAYOUT.ScaleManager import ScaleManager
        ScaleManager.init(self.window)

        # Configure grid layout - make window contents expandable
        self.window.grid_rowconfigure(0, weight=1)
        self.window.grid_columnconfigure(0, weight=1)
        
        # Set window icon from application resources
        icon_path = self.path_default / "GUIBRUSHR" / "Files" / "logo.png"
        photo = tk.PhotoImage(file=str(icon_path))
        self.window.iconphoto(True, photo)
        
        # Set window properties
        self.window.client(socket.gethostname())
        geometry_string = f"{self.width}x{self.height}+{self.displace_x}+{self.displace_y}"
        self.window.geometry(geometry_string)
        self.window.title("GUIBRUSH®: GUI for Bayesian Retrieval Using Spectroscopy at High Resolution")
        self.window.resizable(True, True)

        # Set minimum window size to prevent UI from becoming unusable
        min_width, min_height = WindowConfig.get_min_dimensions()
        self.window.minsize(min_width, min_height)

    def _create_panels(self) -> None:
        """
        Create and configure the main input and output panels.

        This method calculates appropriate panel dimensions based on the
        window size and creates the InputPanel and OutputPanel objects
        that form the main interface components.
        """

        from GUIBRUSHR.GUI.Input_Output_Panels.Input_Panels.InputPanel import InputPanel
        from GUIBRUSHR.GUI.Input_Output_Panels.Output_Panels.OutputPanel import OutputPanel
        from GUIBRUSHR.General_Constants.FunctionsAndConstants.Constant_Variables import ConstantVariables

        # Calculate panel dimensions based on window size and configuration constants
        panel_width = self.width - WindowConfig.PANEL_MARGIN
        available_height = self.height - WindowConfig.PANEL_VERTICAL_SPACING
        input_panel_height = int(available_height * WindowConfig.INPUT_PANEL_RATIO)
        output_panel_height = int(available_height * (1.0 - WindowConfig.INPUT_PANEL_RATIO))
        
        # Create input panel (main parameter interface)
        self.frame_input_master = InputPanel(
            parent=self,
            row=0,
            column=0,
            color=ConstantVariables.LIST_TAB_MACRO[0][1],
            path_default=self.path_default,
            width_GUI=panel_width,
            height_frame=input_panel_height,
            window=self.window
        )
        
        # Create output panel (results display)
        self.frame_output_table = OutputPanel(
            parent=self,
            row=1,
            column=0,
            color=ConstantVariables.LIST_TAB_MACRO[0][1],
            path_default=self.path_default,
            width_GUI=panel_width,
            height_frame=output_panel_height,
            window=self.window
        )
        
        # Configure panel layout weights
        self.window.rowconfigure(0, weight=5)  # Input panel gets more space
        self.window.columnconfigure(0, weight=1)
        self.window.rowconfigure(1, weight=1)  # Output panel gets less space

    def initialize_window(self) -> None:
        """
        Initialize window dimensions based on screen size and configuration.

        This method detects the primary monitor dimensions and calculates
        appropriate window size using scaling factors. The window is automatically
        centered on the screen and dimensions are clamped to min/max bounds.
        """
        try:
            # Get primary monitor information
            monitors = get_monitors()
            primary_monitor = monitors[0]
            screen_width = primary_monitor.width
            screen_height = primary_monitor.height

            # Adjust scaling factor for macOS
            scale = WindowConfig.SCREEN_SCALE_FACTOR
            if platform.system() == 'Darwin':
                scale *= WindowConfig.MACOS_SCALE_FACTOR

            # Fit within scaled screen bounds while preserving aspect ratio
            max_w = int(screen_width * scale)
            max_h = int(screen_height * scale)
            # Choose the constraining dimension
            calculated_width = min(max_w, int(max_h * WindowConfig.ASPECT_RATIO))
            calculated_height = int(calculated_width / WindowConfig.ASPECT_RATIO)

            # Apply min/max constraints
            self.width, self.height = WindowConfig.clamp_dimensions(calculated_width, calculated_height)

            # Center window on screen
            self.displace_x, self.displace_y = WindowConfig.calculate_centered_position(
                self.width, self.height, screen_width, screen_height
            )

        except Exception as error:
            # Fallback to default dimensions if monitor detection fails
            print(f"Failed to get monitor info: {error}")
            self.width, self.height = WindowConfig.get_default_dimensions()
            # Position at top-left if we can't detect screen size
            self.displace_x = 0
            self.displace_y = 0

    def on_closing(self) -> None:
        """
        Handle window closing event with user confirmation.
        
        This method is called when the user attempts to close the window.
        It shows a confirmation dialog and ensures proper cleanup of child
        processes before terminating the application.
        """
        # Don't allow closing before full initialization
        if not self.global_start:
            return
            
        # Ask user for confirmation before closing
        if messagebox.askokcancel("Quit", "Do you want to quit?"):
            self._cleanup_processes()
            self.window.destroy()

    def _cleanup_processes(self) -> None:
        """
        Gracefully terminate all child processes before application exit.
        
        This method attempts to terminate child processes in two phases:
        1. First tries graceful termination using SIGTERM signal
        2. If process is still running after timeout, forces termination with SIGKILL
        
        This ensures that no background processes are left running when the
        application exits.
        """
        # Check if frame_input_master and its components exist
        if not hasattr(self, 'frame_input_master'):
            return
            
        if not hasattr(self.frame_input_master, 'frame_parameter'):
            return
            
        if not hasattr(self.frame_input_master.frame_parameter, 'panel_start'):
            return
            
        # Iterate through all tracked process IDs
        process_list = self.frame_input_master.frame_parameter.panel_start.list_pid
        
        for process_id in process_list.values():
            try:
                # Try graceful termination first
                os.kill(process_id.pid, signal.SIGTERM)
                time.sleep(0.5)  # Give process time to terminate gracefully
                
                # Check if process is still running and force kill if necessary
                try:
                    os.kill(process_id.pid, 0)  # Test if process exists
                    # Process still exists, force termination
                    os.kill(process_id.pid, signal.SIGKILL)
                except ProcessLookupError:
                    # Process already terminated, which is what we want
                    pass
                    
            except Exception as error:
                # Log termination errors but continue with other processes
                print(f"Failed to terminate process {process_id.pid}: {error}")


    def initialize_DB(self) -> None:
        """
        Initialize the SQLite database for the application.

        Creates the database file and tables if they do not already exist.
        """
        from GUIBRUSHR.GUI.DataInterface.DBSQLite3 import DBSQLite3
        # Initialize database
        database = DBSQLite3(self.path_default)
        database.create_db()
        database.close_DB()


def main() -> None:
    """
    Main entry point for the GUIBRUSHR application.

    This function performs the following initialization steps:
    1. Determines the default application path
    2. Prints paths of user-editable files
    3. Checks for configuration file, showing a setup dialog if missing
    4. Creates the main GUI application (which initializes the database internally)
    5. Sets up the window close event handler
    6. Starts the main event loop

    The function handles the complete lifecycle of the application from
    startup to shutdown.
    """
    # Determine application base path (parent of GUIBRUSHR directory)
    path_default = Path(__file__).parent.parent.resolve()

    # Print paths of user-editable files
    yaml_dir      = path_default / "GUIBRUSHR" / "Files" / "Configuration_Yaml"
    db_path       = path_default / "GUIBRUSHR" / "Files" / "DB" / "atmo.db"
    tp_profile    = path_default / "GUIBRUSHR" / "General_Constants" / "Classes" / "UserTemperatureProfile.py"
    config_path   = path_default / "GUIBRUSHR" / "Files" / "Configuration_Path" / "configuration.csv"

    print("\n" + "=" * 65)
    print("GUIBRUSHR - User-editable files:")
    print(f"  YAML configs  :  {yaml_dir}")
    print(f"  Database      :  {db_path}")
    print(f"  T-P profile   :  {tp_profile}")
    print(f"  Configuration :  {config_path}")
    print("=" * 65 + "\n")

    # Check configuration file; show setup dialog if missing
    if not config_path.exists():
        created = _show_config_setup_dialog(config_path)
        if not created:
            return

    # Create and configure main GUI application
    gui_application = GUIBRUSHR(path_default)
    gui_application.window.protocol("WM_DELETE_WINDOW", gui_application.on_closing)

    # Start main event loop (this blocks until window is closed)
    gui_application.window.mainloop()


if __name__ == '__main__':
    main()
