#
# Copyright 2024 John Kendrick & Andrew Burnett
#
# This file is part of PDielec
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the MIT License
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#
# You should have received a copy of the MIT License along with this program, if not see https://opensource.org/licenses/MIT
#
"""MainTab module."""
import os.path
import platform

import numpy as np
from qtpy import compat
from qtpy.QtCore import QCoreApplication, QSize, Qt
from qtpy.QtWidgets import (
    QComboBox,
    QFormLayout,
    QHBoxLayout,
    QLabel,
    QLineEdit,
    QListWidget,
    QMessageBox,
    QPushButton,
    QVBoxLayout,
    QWidget,
)

from PDielec.Utilities import Debug, find_program_from_name, get_reader


class MainTab(QWidget):
    """MainTab is a comprehensive widget class designed to serve as a main interface tab within a Qt application. It is tailored for analyzing quantum chemistry or molecular dynamics simulation output files, offering functionalities such as file processing, data analysis, and settings management for quantum mechanics / molecular mechanics (QM/MM) programs.

    Parameters
    ----------
    parent : QWidget
        The parent widget in which MainTab is contained.
    program : str
        The name of the quantum mechanics or molecular mechanics program related to the data being analyzed or processed.
    filename : str
        The path to the output file generated by the specified `program`. This file contains the results to be analyzed.
    excelfile : str
        The name of an Excel file where results are stored. If specified, results will be written to this file upon program exit.
    debug : bool, optional
        Determines whether debugging messages should be printed to the console. Debugging is off by default (False).

    Attributes
    ----------
    debug : bool
        Indicates whether debugging is enabled.
    settings : dict
        A dictionary containing various program settings, such as program name, file names, and compatibility mode.
    directory : str
        The directory path of the `filename`.
    notebook : QWidget
        The parent widget, typically representing the notebook interface this tab is part of.
    reader : various, depending on the program
        The file reader object tasked with parsing the output file specified by `filename`.
    frequencies_cm1 : numpy.ndarray or None
        An array containing frequency values extracted from the output file, if applicable.
    refreshRequired : bool
        Indicates whether the widget requires refreshing due to changes in its data or settings.
    calculationRequired : bool
        Indicates whether a re-calculation or re-analysis is necessary due to changes in data or settings.

    Methods
    -------
    on_script_button_clicked()
        Handles actions to be taken when the 'Save settings' button is clicked.
    on_excel_button_clicked()
        Handles actions to be taken when the 'Save results' button is clicked, typically involving writing data to an Excel file.
    on_calculation_button_clicked()
        Initiates the processing of the output file and the refreshing of related UI components and data representations.
    writeSpreadsheet()
        Writes the current settings and analysis results to an Excel file specified by `excelfile`.
    read_output_file()
        Reads and processes the output file specified at initialization, updating UI and data structures based on its content.
    on_scriptsfile_le_changed(text)
        Updates program settings based on changes to the script file name input field.
    on_resultsfile_le_changed(text)
        Updates program settings based on changes to the Excel file name input field.
    on_file_button_clicked()
        Opens a file dialog for the user to select a new output or script file, and updates settings based on the selection.
    on_file_le_return()
        Triggers actions similar to `on_file_button_clicked` but is specifically for handling manual text entry in the file path input field.
    on_file_le_changed(text)
        Updates the output file setting based on changes to the file input field.
    on_program_cb_activated(index)
        Updates program settings based on the selection made in the QM/MM program combo box.
    requestRefresh()
        Requests a refresh of the interface and data representation.
    refresh(force=False)
        Refreshes the UI and data based on current settings and file contents, with an optional force parameter to enforce refreshing even if it's not marked as required.

    """

    def __init__(self, parent, program, filename, excelfile, debug=False):
        """Initialize the main GUI component, loading settings and preparing the interface.

        Parameters
        ----------
        parent : QWidget
            The parent widget to which this instance will belong.
        program : str
            The name of the dft/mm program 
        filename : str
            The path to the output file from the specified program to be analyzed.
        excelfile : str
            The path to an Excel file where results can be optionally stored.
        debug : bool, optional
            Enables or disables debug mode for additional output. Default is False.

        Notes
        -----
        Initializes GUI elements (like combo boxes, labels, and line edits), sets up layout,
        and connects signals to slots for events. Configuration is based on provided `program`,
        `filename`, `excelfile`, and `debug` parameters. Additionally, sets up compatibility modes based
        on the operating system and prepares for any necessary calculations or data retrieval
        based on the provided filename. Debug mode can be used to receive extra information about
        the processing steps.

        """        
        super(QWidget, self).__init__(parent)
        global debugger
        debugger = Debug(debug, "MainTab")
        debugger.print("Start:: initialising ",program, filename, excelfile)
        self.debug = debug
        self.settings = {}
        # Deal with the case that "qe" is used
        if program.lower() == "qe":
            program = "Quantum Espresso"
        if program != "":
            self.settings["Program"] = program.capitalize()
        else:
            self.settings["Program"] = "Castep"
        self.directory = os.path.dirname(filename)
        self.settings["Output file name"] = os.path.basename(filename)
        self.settings["Excel file name"] = excelfile
        self.settings["Script file name"] = ""
        if "Window" in platform.platform():
            self.settings["Compatibility mode"] = "Window"
        elif "Linux" in platform.platform():
            self.settings["Compatibility mode"] = "Linux"
        else:
            self.settings["Compatibility mode"] = "Linux"
        self.notebook = parent
        self.reader = None
        self.frequencies_cm1 = None
        self.refreshRequired = True
        self.calculationRequired = True
        # Create first tab - MAIN
        vbox = QVBoxLayout()
        form = QFormLayout()
        #
        # The program combobox
        #
        self.program_cb = QComboBox(self)
        self.program_cb.setToolTip("Choose QM/MM program")
        self.program_cb.addItem("Abinit")
        self.program_cb.addItem("Castep")
        self.program_cb.addItem("Crystal")
        self.program_cb.addItem("Experiment")
        self.program_cb.addItem("Gulp")
        self.program_cb.addItem("Phonopy")
        self.program_cb.addItem("Quantum Espresso")
        self.program_cb.addItem("Vasp")
        self.program_cb.addItem("PDGui")
        index = self.program_cb.findText(self.settings["Program"], Qt.MatchFixedString)
        if index >=0:
            self.program_cb.setCurrentIndex(index)
        self.program_cb.activated.connect(self.on_program_cb_activated)
        label = QLabel("QM/MM Program ")
        label.setToolTip("Choose QM/MM program")
        form.addRow(label, self.program_cb)
        #
        # The file selector
        #
        self.file_le = QLineEdit(self)
        self.file_le.setToolTip("Enter output file or script for processing (press return)")
        self.file_le.setText(self.settings["Output file name"])
        self.file_le.returnPressed.connect(self.on_file_le_return)
        self.file_le.textChanged.connect(self.on_file_le_changed)
        label = QLabel("Analyse this output file")
        label.setToolTip("Enter output file for analysis or choose a script to process (press return)")
        file_button = QPushButton(" File manager ")
        file_button.setToolTip("Open a file manager to choose a file for analysis or choose a script to process")
        file_button.clicked.connect(self.on_file_button_clicked)
        file_button.resize(file_button.sizeHint())
        file_button_size = file_button.size()
        hbox = QHBoxLayout()
        hbox.addWidget(self.file_le)
        hbox.addWidget(file_button)
        form.addRow(label, hbox)
        #
        # Store results
        #
        self.resultsfile_le = QLineEdit(self)
        self.resultsfile_le.setToolTip("Provide the name of an .xlsx file if results are to be stored in a spreadsheet.\nIf specified, the file will be written when the program exits.\nThe default directory is that of the QM/MM output file which has been read in.")
        self.resultsfile_le.setText(self.settings["Excel file name"])
        self.resultsfile_le.returnPressed.connect(self.on_excel_button_clicked)
        self.resultsfile_le.textChanged.connect(self.on_resultsfile_le_changed)
        label = QLabel("Excel spread sheet")
        label.setToolTip("Provide the name of an .xlsx file if results are to be stored in a spreadsheet.\nIf specified the file will be written when the program exits.\nThe default directory is that of the QM/MM output file which has been read in.")
        excel_button = QPushButton(" Save results ")
        excel_button.setToolTip("Save the results of calculation to the excel spreadsheet specified")
        excel_button.clicked.connect(self.on_excel_button_clicked)
        excel_button.resize(excel_button.sizeHint())
        excel_button_size = excel_button.size()
        hbox = QHBoxLayout()
        hbox.addWidget(self.resultsfile_le)
        hbox.addWidget(excel_button)
        form.addRow(label, hbox)
        #
        # Store a script
        #
        self.scriptsfile_le = QLineEdit(self)
        self.scriptsfile_le.setToolTip('Provide the name of a python script file to save the program settings to when the "Save settings" button is pressed.\nThe directory where the file is saved is the same as the directory containing the QM/MM output file read in.')
        self.scriptsfile_le.setText(self.settings["Script file name"])
        self.scriptsfile_le.returnPressed.connect(self.on_script_button_clicked)
        self.scriptsfile_le.textChanged.connect(self.on_scriptsfile_le_changed)
        label = QLabel("Script filename")
        label.setToolTip('Provide the name of a python script file to save the program settings to when the "Save settings" button is pressed.\nThe directory where the file is saved is the same as the directory containing the QM/MM output file read in.')
        script_button = QPushButton(" Save settings ")
        script_button.setToolTip("Save the setttings of calculation to a python file.\nThe directory where the file is saved is the same as the directory containing the QM/MM output file read in.")
        script_button.clicked.connect(self.on_script_button_clicked)
        script_button.resize(script_button.sizeHint())
        script_button_size = script_button.size()
        maximum_button_width = max(script_button_size.width(), excel_button_size.width(), file_button_size.width())
        maximum_button_height = max(script_button_size.height(), excel_button_size.height(), file_button_size.height())
        new_button_size = QSize(maximum_button_width, maximum_button_height)
        script_button.setFixedSize(new_button_size)
        excel_button.setFixedSize(new_button_size)
        file_button.setFixedSize(new_button_size)
        hbox = QHBoxLayout()
        hbox.addWidget(self.scriptsfile_le)
        hbox.addWidget(script_button)
        form.addRow(label, hbox)
        # add form layout
        vbox.addLayout(form)
        # output window for unit cell
        self.cell_window_l = QLabel("Unit-cell (Angstrom) from "+self.settings["Output file name"], self)
        vbox.addWidget(self.cell_window_l)
        self.cell_window_w = QListWidget(self)
        fm = self.cell_window_w.fontMetrics()
        h = fm.ascent() + fm.descent()
        self.cell_window_w.setMaximumHeight(6*h)
        vbox.addWidget(self.cell_window_w)
        # output window for frequencies
        self.frequencies_window_l = QLabel("Frequencies from "+self.settings["Output file name"], self)
        vbox.addWidget(self.frequencies_window_l)
        self.frequencies_window = QListWidget(self)
        vbox.addWidget(self.frequencies_window)
        # finalise the layout
        self.setLayout(vbox)
        QCoreApplication.processEvents()
        # If the filename was given then force it to be read and processed
        if filename != "":
            debugger.print("Reading output file in maintab initialisation")
            self.on_calculation_button_clicked()
        QCoreApplication.processEvents()
        debugger.print("Finished:: initialising ")
        return

    def on_script_button_clicked(self):
        """Handle the script button click event.

        This function is executed when the script button is clicked. It checks if the specified script file exists in the specified directory. If it does, it prompts the user to either overwrite the existing file or cancel the action. If the file does not exist, it creates a new script file. It uses a debugger to log actions taken during the process.

        Parameters
        ----------
        None

        Returns
        -------
        None

        """        
        debugger.print("Start:: on_script_button clicked")
        debugger.print("on_script_button clicked, directory=",self.directory)
        filename=os.path.join(self.directory,self.settings["Script file name"])
        if os.path.exists(filename):
            debugger.print("Script file already exists",self.directory)
            if self.notebook.overwriting:
                debugger.print("Overwriting existing script anyway",filename)
                self.notebook.print_settings(filename=filename)
            else:
                answer = QMessageBox.question(self,"","Script file already exists.  Continue?", QMessageBox.Yes | QMessageBox.No)
                if answer == QMessageBox.Yes:
                    debugger.print("Overwriting existing script",filename)
                    self.notebook.print_settings(filename=filename)
        else:
            debugger.print("Creating a new script",filename)
            self.notebook.print_settings(filename=filename)
        debugger.print("Finished:: on_script_button clicked")
        return

    def on_excel_button_clicked(self):
        """Handle the event triggered when Excel button is clicked.

        This function initiates the process of writing data to the Excel spreadsheet when the designated button is clicked in the UI. It prints log messages at the start and end of the operation.

        Parameters
        ----------
        None

        Returns
        -------
        None

        See Also
        --------
        notebook.writeSpreadsheet : The method called to write data to the spreadsheet.

        """        
        debugger.print("Start:: on_excel_button clicked")
        self.notebook.writeSpreadsheet()
        debugger.print("Finished:: on_excel_button clicked")
        return

    def on_calculation_button_clicked(self):
        """Handle the event triggered by the calculation button click.

        It proceeds to read an output file which is the result of a previous calculation. It checks if the settings tab exists within a notebook interface (e.g., a tabbed GUI component) and refreshes it if it does. Finally, it sets a flag indicating that a new calculation is not required anymore.

        Parameters
        ----------
        None

        Returns
        -------
        None

        """        
        debugger.print("Start:: on_calculation_button_clicked")
        #
        # Read the output file
        #
        self.read_output_file()
        if self.notebook.settingsTab is not None:
            self.notebook.settingsTab.refresh(force=True)
        self.calculationRequired = False
        debugger.print("Finished:: on_calculation_button_clicked")

    def writeSpreadsheet(self):
        """Write data to the configured spreadsheet.

        This method encompasses the process of selecting the 'Main' worksheet,
        deleting the existing content on it, and writing down new settings and frequency
        data onto it. It checks if
        the spreadsheet object is valid, selects the appropriate worksheet, clears it,
        and writes the settings and frequencies (if available) in a structured manner.

        Parameters
        ----------
        None

        Returns
        -------
        None

        """        
        debugger.print("Start:: writeSpreadsheet")
        sp = self.notebook.spreadsheet
        if sp is None:
            debugger.print("Finished:: writeSpreadsheet sp is None")
            return
        sp.selectWorkSheet("Main")
        sp.delete()
        debugger.print("writeSpreadsheet",self.settings)
        sp.writeNextRow( ["Main Tab Settings"], col=1 )
        sp.writeNextRow( ["Directory",self.directory], col=1 )
        for item in sorted(self.settings):
            sp.writeNextRow([item,self.settings[item]], col=1, check=1)
        sp.writeNextRow( [""], col=1 )
        if self.frequencies_cm1 is not None:
            sp.writeNextRow( ["Frequencies (cm1) as read from the output file"], col=1 )
            for ifreq in enumerate(self.frequencies_cm1):
                sp.writeNextRow(ifreq, col=1, check=1)
        debugger.print("Finished:: writeSpreadsheet")
        return

    def read_output_file(self):
        """Read the output file specified in the settings, process it, and updates the UI elements accordingly.

        Processes the output file indicated by the settings of the object. It performs several checks to ensure
        the file exists, is accessible, and contains valid data that can be read and processed. It extracts
        unit-cell parameters and vibrational frequencies from the output file, displaying them in the relevant
        interface elements. It also triggers refresh actions on various tabs present in the notebook attribute if available.

        Parameters
        ----------
        None

        Returns
        -------
        None

        """        
        debugger.print("Start:: read_output_file")
        debugger.print("Program =", self.settings["Program"])
        if self.settings["Output file name"] == "":
            debugger.print("Finished:: read_output_file output file is blank")
            return
        filename = os.path.join(self.directory,self.settings["Output file name"])
        if not os.path.isfile(filename):
            QMessageBox.about(self,"Processing output file","The filename for the output file to be processed is not correct: "+filename)
            debugger.print("Finished:: read_output_file output file does not exist")
            return
        debugger.print("Read output file - clear list widgets")
        debugger.print(self.settings["Program"],[ filename ])
        self.cell_window_w.clear()
        self.cell_window_l.setText("Unit-cell (Angstrom) from "+self.settings["Output file name"])
        self.frequencies_window.clear()
        self.frequencies_window_l.setText("Frequencies from "+self.settings["Output file name"])
        self.reader = get_reader(filename,
                                 self.settings["Program"],
                                 debug=self.debug)
        if self.reader is None:
            print("Error in reading files - program  is ",self.settings["Program"])
            print("Error in reading files - filename is ",filename)
            print("Need to choose the file and program properly")
            QMessageBox.about(self,"Processing output file","A reader has not been created for this filename: "+filename)
            debugger.print("Finished:: read_output_file reader is none")
            return
        #switch on debugging in the reader
        self.reader.debug = self.debug
        if self.debug:
            self.reader.read_output()
        else:
            try:
                self.reader.read_output()
            except Exception:
                print("Error in reading output files - program  is ",self.settings["Program"])
                print("Error in reading output files - filename is ",filename)
                print("Need to choose the file and program properly")
                QMessageBox.about(self,"Processing output file","Error on reading the output file using read_output(): "+filename)
                debugger.print("Finished:: read_output_file error on reading")
                return
            # end try
        # end if debug
        if len(self.reader.unit_cells) == 0:
            print("Error in reading output files - program  is ",self.settings["Program"])
            print("Error in reading output files - filename is ",filename)
            print("Need to choose the file and program properly")
            QMessageBox.about(self,"Processing output file","The output file has no unit cells in it: "+filename)
            debugger.print("Finished:: read_output_file output file has no unit cell")
            return
        # tell the notebook that we have read the info and we have a reader
        self.notebook.reader = self.reader
        if self.debug:
            self.reader.print()
        cell = self.reader.get_unit_cell()
        a = cell.lattice[0]
        b = cell.lattice[1]
        c = cell.lattice[2]
        self.cell_window_w.addItem("                                  ")
        self.cell_window_w.addItem(f"a = {a[0]: 4.5f}   {a[1]:4.5f}   {a[2]: 4.5f}")
        self.cell_window_w.addItem(f"b = {b[0]: 4.5f}   {b[1]:4.5f}   {b[2]: 4.5f}")
        self.cell_window_w.addItem(f"c = {c[0]: 4.5f}   {c[1]:4.5f}   {c[2]: 4.5f}")
        self.cell_window_w.addItem("                                  ")
        self.frequencies_cm1 = np.sort(self.reader.frequencies)
        for f in self.frequencies_cm1:
            self.frequencies_window.addItem(f"{f:.3f}")
        # tell the settings tab to update the widgets that depend on the contents of the reader
        debugger.print("processing a return in reading the output file")
        # Update any scenarios
        if self.notebook.scenarios is not None:
            debugger.print("about to refresh scenarios")
            debugger.print(f"notebook has {len(self.notebook.scenarios)} scenarios")
            for tab in self.notebook.scenarios:
                tab.requestRefresh()
        else:
            debugger.print("notebook has no scenarios yet")
        # Update the plotting tab
        if self.notebook.plottingTab is not None:
            debugger.print("about to refresh plottingtab")
            self.notebook.plottingTab.requestRefresh()
        else:
            debugger.print("notebook has no plotting tab yet")
        # Update the analysis tab
        if self.notebook.analysisTab is not None:
            debugger.print("about to refresh analysisTab")
            self.notebook.analysisTab.requestRefresh()
        else:
            debugger.print("notebook has no analysis tab yet")
        # Update the viewer tab
        if self.notebook.viewerTab is not None:
            debugger.print("about to refresh viewerTab")
            self.notebook.viewerTab.requestRefresh()
        else:
            debugger.print("notebook has no viewer tab yet")
        # Update the fitter tab
        if self.notebook.fitterTab is not None:
            # There is a subtle problem with the modes that are to be fitted, they need resetting
            self.notebook.fitterTab.modes_fitted = []
            debugger.print("about to refresh fitterTab")
            self.notebook.fitterTab.requestRefresh()
        else:
            debugger.print("notebook has no fitter tab yet")
        debugger.print("Finished:: read_output_file")

    def on_scriptsfile_le_changed(self, text):
        """Respond to changes in the scripts file input field.

        Parameters
        ----------
        text : str
            The text of line editor for the scripts file

        Returns
        -------
        None

            The new text of the scripts file input field. This parameter may not be used inside the function as the function retrieves the text from the QLineEdit widget directly.

        """        
        debugger.print("Start:: on_scriptsfile_changed", text)
        text = self.scriptsfile_le.text()
        self.settings["Script file name"] = text
        debugger.print("Finished:: on_scriptsfile_changed", text)

    def on_resultsfile_le_changed(self, text):
        """Handle changes to the results file text input field.

        Parameters
        ----------
        text : str
            The new text from the results file text input field.

        Returns
        -------
        None

        """        
        debugger.print("Start:: on_resultsfile_changed", text)
        text = self.resultsfile_le.text()
        self.settings["Excel file name"] = text
        debugger.print("Finished:: on_resultsfile_changed", text)

    def on_file_button_clicked(self):
        """Handle the "Open file" button click event in the GUI.

        This method is triggered upon clicking the "Open File" button in a graphical user interface. It facilitates the selection of an output file generated by various software tools (`Castep`, `Abinit`, `Gulp`, `Vasp`, `Quantum Espresso`, `Crystal`, `Phonopy`, `Experiment`, `Pdgui`, or any file type). Based on the selected file, it finds the corresponding program that generated the file, updates various settings in the application according to the detected program, sets the working directory to the location of the selected file, updates the GUI with the new output file name, and refreshes relevant parts of the application depending on whether the program is `pdgui` or another supported type.

        Parameters
        ----------
        None

        Returns
        -------
        None

        """        
        debugger.print("Start:: on_file_button_clicked ", self.file_le.text())
        # Open a file chooser
        #options = QFileDialog.Options()
        #options |= QFileDialog.DontUseNativeDialog
        if self.settings["Program"].lower() == "castep":
            selfilter = "Castep (*.castep)"
        elif self.settings["Program"].lower() == "abinit":
            selfilter = "Abinit (*.out *.abo)"
        elif self.settings["Program"].lower() == "gulp":
            selfilter = "Gulp (*.gout)"
        elif self.settings["Program"].lower() == "vasp":
            selfilter = "Vasp (OUTCAR*)"
        elif self.settings["Program"].lower() == "quantum espresso":
            selfilter = "Quantum Espresso (*.dynG)"
        elif self.settings["Program"].lower() == "crystal":
            selfilter = "Crystal (*.out  *.log)"
        elif self.settings["Program"].lower() == "phonopy":
            selfilter = "Phonopy (*.yaml)"
        elif self.settings["Program"].lower() == "experiment":
            selfilter = "Experiment (*.exp)"
        elif self.settings["Program"].lower() == "pdgui":
            selfilter = "PDGui (*.py)"
        else:
            selfilter = "All files (*)"
        filename,myfilter = compat.getopenfilename(self,"Open MM/QM Output file","","Abinit (*.out *.abo);;Castep (*.castep);;Crystal (*.out  *.log);;Experiment (*.exp);;Gulp (*.gout);;Phonopy (*.yaml);;Quantum Espresso (*.dynG);;Vasp (OUTCAR*);;PDGui (*.py);;All files(*)",selfilter)
        # Process the filename
        if filename != "":
            program = find_program_from_name(filename)
            if program == "":
                debugger.print("Program not found from filename",filename)
                debugger.print("Proceeding with defaults",self.settings["Program"])
            else:
                debugger.print("Program found from filename",program,filename)
                self.settings["Program"] = program.capitalize()
            self.directory = os.path.dirname(filename)
            self.notebook.app.setMyWindowTitle(self.directory)
            self.settings["Output file name"] = os.path.basename(filename)
            self.file_le.setText(self.settings["Output file name"])
            debugger.print("new file name", self.directory, self.settings["Output file name"])
            self.notebook.deleteAllScenarios()
            if self.settings["Program"].lower() == "pdgui":
                #
                # If we have changed the file then reset some of the settingsTab settings
                # before we read in the script.
                #
                if self.notebook.settingsTab is not None:
                    self.notebook.settingsTab.settings["Optical permittivity"] = None
                    self.notebook.settingsTab.settings["Mass definition"] = "average"
                    self.notebook.settingsTab.sigmas_cm1 = []
                    self.notebook.settingsTab.requestRefresh()
                self.notebook.app.readScript(filename)
            else:
                #
                # If we have changed the file then reset some of the settingsTab settings
                #
                if self.notebook.settingsTab is not None:
                    self.notebook.settingsTab.settings["Optical permittivity"] = None
                    self.notebook.settingsTab.settings["Mass definition"] = "average"
                    self.notebook.settingsTab.sigmas_cm1 = []
                    self.notebook.settingsTab.requestRefresh()
                self.refreshRequired = True
                self.calculationRequired = True
                self.refresh()
        debugger.print("Finished:: on_file_button_clicked ", self.file_le.text())
        return

    def getFullFileName(self):
        """Return the full file name based on the directory and output file name settings.

        Parameters
        ----------
        None

        Returns
        -------
        str
            The full path of the file, combining the directory and the output file name setting.

        """        
        return os.path.join(self.directory,self.settings["Output file name"])

    def getRelativeFileName(self):
        """Get the relative filename specified in settings.

        Parameters
        ----------
        None

        Returns
        -------
        str
            The file name specified in the settings as 'Output file name'.

        """        
        return self.settings["Output file name"]

    def on_file_le_return(self):
        """Handle the return action for a file entry line edit.

        It performs a series of steps to process the specified file, including finding a program associated with the file, updating various settings related to the chosen file and potentially the program determined, and initiating different actions or calculations based on the program type.

        Parameters
        ----------
        None

        Returns
        -------
        None

        Notes
        -----
        - The method performs an early return if the filename entered is an empty string.
        - It searches for a program associated with the entered filename and returns if none is found.
        - Updates the 'Program' and 'Output file name' settings based on the entered filename.
        - Changes the GUI window title to the directory of the entered filename.
        - Deletes all scenarios (if applicable) before proceeding with further settings updates or actions.
        - Depending on the program associated with the filename, it might read the script or mark the interface for refresh and calculation.
        - The actual actions, especially involving UI updates (e.g., setting window title, deleting scenarios, reading script) or marking for refresh and calculation, are dependent on the context and design of the surrounding application and GUI structure.
        - Specific behaviors, such as how 'pdgui' program type is specially handled or the resetting of certain settings, is contingent on the broader application logic and requirements.

        """        
        debugger.print("Start:: on_file_le_return ", self.file_le.text())
        filename = self.file_le.text()
        if filename != "":
            program = find_program_from_name(filename)
            if program == "":
                debugger.print("Finished:: on_file_le_return ")
                return
            self.settings["Program"] = program
            self.directory = os.path.dirname(os.path.abspath(filename))
            self.notebook.app.setMyWindowTitle(self.directory)
            self.settings["Output file name"] = os.path.basename(filename)
            debugger.print("new file name", self.settings["Output file name"])
            self.notebook.deleteAllScenarios()
            if self.settings["Program"].lower() == "pdgui":
                #
                # If we are reading a script the reset the defaults before reading the script
                #
                if self.notebook.settingsTab is not None:
                    self.notebook.settingsTab.settings["Optical permittivity"] = None
                    self.notebook.settingsTab.settings["Mass definition"] = "average"
                    self.notebook.settingsTab.sigmas_cm1 = []
                self.notebook.app.readScript(filename)
            else:
                #
                # If we have changed the file then reset some of the settingsTab settings
                # this is only done if it is not a script
                #
                if self.notebook.settingsTab is not None:
                    self.notebook.settingsTab.settings["Optical permittivity"] = None
                    self.notebook.settingsTab.settings["Mass definition"] = "average"
                    self.notebook.settingsTab.sigmas_cm1 = []
                self.refreshRequired = True
                self.calculationRequired = True
                self.refresh()
        debugger.print("Finished:: on_file_le_return ", self.file_le.text())
        return

    def on_file_le_changed(self, text):
        """Handle changes to the file location input field.

        The new output file name is stored and a flag set to require re-calculations

        Parameters
        ----------
        text : str
            The new text of the file location input field.

        Returns
        -------
        None

        """        
        debugger.print("Start:: on_file_changed", text)
        self.settings["Output file name"] = text
        self.calculationRequired = True
        debugger.print("Finished:: on_file_changed", text)

    def on_program_cb_activated(self,index):
        """Handle program selection change in a combobox and update settings accordingly.

        This method updates the program settings based on the user's selection from a combobox.
        It specifically handles cases where the selected program is Phonopy

        Parameters
        ----------
        index : int
            The index of the selected item in the combobox.

        Returns
        -------
        None

        Side Effects
        ------------
        - Updates the `settings` dictionary with the selected program and, if applicable,
          the corresponding QM program.
        - Sets `calculationRequired` attribute to `True`.

        """        
        debugger.print("Start:: on_program_combobox_activated", index)
        debugger.print("on_program_combobox_activated", self.program_cb.currentText())
        self.settings["Program"] = self.program_cb.currentText()
        debugger.print("Program is now  ", self.settings["Program"])
        self.calculationRequired = True
        debugger.print("Finished:: on_program_combobox_activated", index)
        
    def requestRefresh(self):
        """Request to refresh.

        Marks the current instance as requiring a refresh.

        Parameters
        ----------
        None

        Returns
        -------
        None

        """        
        debugger.print("Start:: requestRefresh")
        self.refreshRequired = True
        debugger.print("Finished:: requestRefresh")

    def refresh(self,force=False):
        """Refresh the current interface or force it to refresh, applying settings changes.

        Parameters
        ----------
        force : bool, optional
            If `True`, force the refresh even if it is not required based on internal conditions. 
            Defaults to `False`.

        Returns
        -------
        None

        Effects
        -------
        - Blocks signal for all child widgets during refresh.
        - Adjusts program names based on settings, handling special case names.
        - Sets the program combo box and filenames according to the updated settings.
        - Triggers a recalculation if required after settings update.
        - Unblocks signals for all child widgets after refresh.

        """        
        debugger.print("Start:: refresh", force)
        if not self.refreshRequired and not force:
            return
        #
        # Handle some changes to the api to make things backwards compatible
        #
        if self.settings["Program"] == "qe":
            self.settings["Program"] = "Quantum Espresso"
        self.settings["Program"] = self.settings["Program"].capitalize()
        #
        # Block signals during refresh
        #
        for w in self.findChildren(QWidget):
            w.blockSignals(True)
        index = self.program_cb.findText(self.settings["Program"], Qt.MatchFixedString)
        if index >=0:
            self.program_cb.setCurrentIndex(index)
        self.file_le.setText(self.settings["Output file name"])
        self.resultsfile_le.setText(self.settings["Excel file name"])
        if self.calculationRequired:
            debugger.print("on_calculation_button_clicked called from MainTab.refresh()")
            self.on_calculation_button_clicked()
        self.refreshRequired = False
        #
        # UnBlock signals
        #
        for w in self.findChildren(QWidget):
            w.blockSignals(False)
        debugger.print("Finished:: refresh", force)
        return
