import os
from PyQt5 import QtWidgets
from PyQt5.QtCore import QThread
from PyQt5.QtWidgets import QFileDialog, QWidget
from utils.files.input import ScannedObject
from utils.gui.pyqt.settings.Settings import Settings
from utils.settings.SettingManager import SettingManager
from utils.graph2D.visplot_render import cross_section, render2D
from utils.graph3D.visplot_render import render3D
from utils.gui.pyqt.main_window.UI_MainWindow import Ui_MainWindow
from utils.gui.pyqt.main_window.Workers.DiscreteDataWorker import DiscreteDataProcessWorker
from utils.gui.pyqt.main_window.Workers.PreProcessWorker import PreProcessWorker
from utils.gui.pyqt.main_window.Workers.RawDataWorker import RawDataProcessWorker
from utils.gui.pyqt.error_popup.ErrorPopup import ErrorPopup
class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
    """
    Main window of the application
    """

    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)
        # Retrieve the UI
        self.setupUi(self)
        # Setup buttons listeners
        self.start_analyse_button.clicked.connect(self.start_preprocess)
        self.input_file_choose_btn.clicked.connect(self.select_file)
        self.output_folder_choose_btn.clicked.connect(self.select_folder)
        self.show_graph_checkbox.stateChanged.connect(self.toggle_graphs)
        self.actionSauvegarder_le_model_redress.triggered.connect(self.save_model)
        self.actionPr_f_rennces.triggered.connect(self.show_settings)
        

        self.graphType = [
            "Aucun",
            "Mesh3D",
            "Coupe XZ",
            "Coupe YZ",
            "Evolution du rayon moyen",
        ]
        
        self.obj = None
        self.raw_data= None
        self.discrete_data = None

        self.completed = 0
        self.total = 2

        self.comboBoxes = [
            self.slot0ComboBox,
            self.slot1ComboBox,
            self.slot2ComboBox,
            self.slot3ComboBox,
            self.slot4ComboBox,
            self.slot5ComboBox,
            self.slot6ComboBox,
            self.slot7ComboBox,
            self.slot8ComboBox,
            self.slot9ComboBox,
            self.slot10ComboBox
        ]

        for cb in self.comboBoxes:
            cb.addItems(self.graphType)

        self.slots = [
                    [self.slot0,"Aucun"],
                    [self.slot1,"Aucun"],
                    [self.slot2,"Aucun"],
                    [self.slot3,"Aucun"],
                    [self.slot4,"Aucun"],
                    [self.slot5,"Aucun"],
                    [self.slot6,"Aucun"],
                    [self.slot7,"Aucun"],
                    [self.slot8,"Aucun"],
                    [self.slot9,"Aucun"],
                    [self.slot10,"Aucun"]
                    ]
        
        for slot_nb,slot in  enumerate(self.slots):
            slot[1] = SettingManager.get_instance().get_last_graph(slot_nb)
            self.comboBoxes[slot_nb].setCurrentText(slot[1])

        self.settings_window = Settings()

        self.threads = []

###############################################################################
#                                                                             #
#                                                                             #
#                           Input/Setting Management                          #
#                                                                             #
#                                                                             #
###############################################################################
    def select_file(self):
        """
        Open a file dialog to select the input file
        """
        file = QFileDialog.getOpenFileName()[0]
        self.input_file_path.setPlainText(file)
        self.output_file_prefix.setText(os.path.splitext(os.path.basename(file))[0])

    def select_folder(self):
        """
        Open a file dialog to select the output folder
        """
        self.output_folder_path.setPlainText(QFileDialog.getExistingDirectory())

    def check_input_file(self):
        """
        Check if the input file is valid
        """
        if not os.path.isfile(self.input_file_path.toPlainText()):
            ErrorPopup("Fichier d'entrée invalide",button_label="Choisir un fichier d'entrée",button_callback=self.select_file).show_popup()
            return False
        return True

    def check_output_folder(self):
        """
        Check if the output folder is valid
        """
        if not os.path.isdir(self.output_folder_path.toPlainText()):
            ErrorPopup("Dossier de sortie invalide",button_label="Choisir un dossier de sortie",button_callback=self.select_folder).show_popup()
            return False
        return True

###############################################################################
#                                                                             #
#                                                                             #
#                              Data Processing                                #
#                                                                             #
#                                                                             #
###############################################################################

    def start_preprocess(self):
        """
        Start the analyse
        """
        if not self.check_input_file():
            return
        if not self.check_output_folder():
            return
        
        settings = SettingManager.get_instance()
        for count,_ in enumerate(self.slots):
            self.slots[count][1] = self.comboBoxes[count].currentText()
            settings.set_last_graph(count,self.slots[count][1])
            
        self.clear_graphs()
        self.completed = 0
        # Create the thread to run the analyse
        self.preprocess_thread = QThread()
        self.preprocess_worker = PreProcessWorker("PreProcessWorker",self.input_file_path.toPlainText())
        self.preprocess_worker.moveToThread(self.preprocess_thread)

        # Connect the signals
        # Start
        self.preprocess_thread.started.connect(self.preprocess_worker.run)
        # Progress
        self.preprocess_worker.status.connect(self.set_status)
        self.preprocess_worker.progress.connect(self.update_progress_bar)
        self.preprocess_worker.processed_obj.connect(self.set_obj)
        self.preprocess_worker.processed_obj.connect(self.process_raw_data)
        self.preprocess_worker.processed_obj.connect(self.process_discrete_data)
        # Finished
        self.preprocess_worker.finished.connect(self.preprocess_thread.quit)
        self.preprocess_worker.finished.connect(self.preprocess_worker.deleteLater)
        self.preprocess_thread.finished.connect(self.preprocess_thread.deleteLater)

        # Start the thread
        self.preprocess_thread.start()
        self.start_analyse_button.setEnabled(False)

    def process_raw_data(self, obj:ScannedObject):
        self.processrawdata_thread = QThread()
        self.processraw_worker = RawDataProcessWorker("RawDataProcessWorker",
                                                      obj,
                                                      self.output_folder_path.toPlainText(),
                                                      self.output_file_prefix.text(),
                                                      self.discretisation_value_selector.value())
        self.processraw_worker.moveToThread(self.processrawdata_thread)
        # Connect the signals
        # Start
        self.processrawdata_thread.started.connect(self.processraw_worker.run)
        # Progress
        self.processraw_worker.status.connect(self.set_status)
        self.processraw_worker.progress.connect(self.update_progress_bar)
        self.processraw_worker.processedData.connect(self.set_raw_data)
        # Finished
        self.processraw_worker.finished.connect(self.finish_analyse)
        self.processraw_worker.finished.connect(self.processrawdata_thread.quit)
        self.processraw_worker.finished.connect(self.processraw_worker.deleteLater)
        self.processrawdata_thread.finished.connect(self.processrawdata_thread.deleteLater)

        # Start the thread
        self.processrawdata_thread.start()

    def process_discrete_data(self, obj:ScannedObject):
        self.processdiscrete_thread = QThread()
        self.processdiscrete_worker = DiscreteDataProcessWorker("DiscreteDataProcessWorker",
                                                                obj,
                                                                self.output_folder_path.toPlainText(),
                                                                self.output_file_prefix.text(),
                                                                self.discretisation_value_selector.value())
        self.processdiscrete_worker.moveToThread(self.processdiscrete_thread)
        # Connect the signals
        # Start
        self.processdiscrete_thread.started.connect(self.processdiscrete_worker.run)
        # Progress
        self.processdiscrete_worker.status.connect(self.set_status)
        self.processdiscrete_worker.progress.connect(self.update_progress_bar)
        self.processdiscrete_worker.processedData.connect(self.set_discrete_data)
        # Finished
        self.processdiscrete_worker.finished.connect(self.finish_analyse)
        self.processdiscrete_worker.finished.connect(self.processdiscrete_thread.quit)
        self.processdiscrete_worker.finished.connect(self.processdiscrete_worker.deleteLater)
        self.processdiscrete_thread.finished.connect(self.processdiscrete_thread.deleteLater)
        
        # Start the thread
        self.processdiscrete_thread.start()

    def set_obj(self,obj:ScannedObject):
        self.obj = obj

    def set_discrete_data(self,discrete_data:dict):
        self.discrete_data = discrete_data

    def set_raw_data(self,raw_data:dict):
        self.raw_data = raw_data 

    def save_model(self):
        if self.obj is None:
            ErrorPopup("Aucune analyse effectuée. Aucun modèle à sauvegarder").show_popup()
            return
        file_path = QFileDialog.getSaveFileName(self,
                                                "Sauvegarder le modèle",
                                                "./",
                                                "Fichier OBJ (*.obj)")
        self.obj.export_obj(file_path[0])
        
###############################################################################
#                                                                             #
#                                                                             #
#                              Graphs management                              #
#                                                                             #
#                                                                             #
###############################################################################

    def toggle_graphs(self):
        """
        Show or hide the graphs
        """
        if self.show_graph_checkbox.isChecked():
            self.Graphs.show()
        else:
            self.Graphs.hide()

    def renderGraphs(self,obj:ScannedObject,raw_data:dict,discrete_data:dict):
        if not self.show_graph_checkbox.isChecked():
            return
        for slot in self.slots:
            current_slot = slot[0]
            graph_type = slot[1]
            if graph_type == "Mesh3D":
                current_slot.addWidget(render3D(obj,False).native)
            if graph_type == "Coupe XZ":
                current_slot.addWidget(cross_section(obj.get_x(),
                                                     obj.get_z(),
                                                     "Coupe XZ",
                                                     "X (en mm)",
                                                     "Z (en mm)",
                                                     False).native)
            if graph_type == "Coupe YZ":
                current_slot.addWidget(cross_section(obj.get_y(),
                                                     obj.get_z(),
                                                     "Coupe YZ",
                                                     "Y (en mm)",
                                                     "Z (en mm)",
                                                     False).native)
            if graph_type == "Evolution du rayon moyen":
                current_slot.addWidget(render2D(list(zip(discrete_data['Z moy (en mm)'],discrete_data['Rayon moyen (en mm)'])),
                                                "Evolution du rayon moyen en fonction de Z",
                                                "Z (en mm)",
                                                "Rayon moyen (en mm)\n",
                                                False).native)

    def clear_graphs(self):
        """
        Clear the graphs
        """
        if not self.show_graph_checkbox.isChecked():
            return
        for slot,_ in self.slots:
            for i in reversed(range(slot.count())):
                slot.itemAt(i).widget().setParent(None)


###############################################################################
#                                                                             #
#                                                                             #
#                            User interface updates                           #
#                                                                             #
#                                                                             #
###############################################################################

    def finish_analyse(self):
        """
        Finish the analyse
        """
        self.completed += 1
        if self.completed == self.total:
            self.status_text.setText("Done")
            self.analyse_progress_bar.setValue(100)
            self.renderGraphs(self.obj,self.raw_data,self.discrete_data)
            self.start_analyse_button.setEnabled(True)

    def update_progress_bar(self, value):
        """
        Update the progress bar
        """
        self.analyse_progress_bar.setValue(value)

    def set_status(self, status:str):
        """
        Set the status of the analyse
        """
        self.status_text.setText(status)

    def show_settings(self):
        """
        Show the settings window
        """
        self.settings_window.show()