from typing import Union

from osgeo import gdal
from qgis.core import QgsApplication
from qgis.PyQt.QtWidgets import QApplication

from SEILAPLAN.gui.adjustment_dialog import AdjustmentDialog
from SEILAPLAN.gui.progress_dialog import ProgressDialog
from SEILAPLAN.gui.seilaplan_plugin_dialog import SeilaplanPluginDialog
from SEILAPLAN.tools.config_handler import ConfigHandler
from SEILAPLAN.tools.processing_thread import ProcessingTask


class SeilaplanRun:
    """Handles the program flow, manages settings, configuration and keeps a
    hold on the plugin state and calculation results between different dialog
    windows. Allows running the plugin multiple times in parallel."""

    def __init__(self, interface):
        self.iface = interface

        gdal.UseExceptions()

        # Each run of the plugin has its own ConfigHandler instance
        self.confHandler: ConfigHandler = ConfigHandler()

        self.projectWindow = None
        self.workerThread = None
        self.progressDialog = None
        self.adjustmentWindow = None

    def showProjectWindow(self):
        if not self.projectWindow:
            # Initialize first dialog window
            self.projectWindow = SeilaplanPluginDialog(
                self.iface, self.confHandler, self.onCloseProjectWindow
            )
            # Set up dialog for first show
            self.projectWindow.setupContentForFirstRun()
        else:
            # Updating dialog state when coming back from the adjustment window
            self.projectWindow.setupContent()

        # Show dialog and start event loop. No exec() because we don't want to
        #  block the event loop in case a second Seilaplan instance is started.
        self.projectWindow.show()

    def onCloseProjectWindow(self, runOptimization: Union[bool, None]):
        """Gets called by the Project dialog on closing."""
        if runOptimization:
            # Continue with optimization algorithm
            self.startOptimization()
        elif runOptimization is False:
            # Continue by skipping over optimization and go straight to the
            #  adjustment window
            result, resultQuality = self.confHandler.prepareResultWithoutOptimization()
            self.showAdjustmentWindow(result, resultQuality)
        else:  # None: runOptimization not set --> user canceled dialog
            self.close()

    def startOptimization(self):
        # Create separate thread for calculations so QGIS stays responsive
        self.workerThread = ProcessingTask(self.confHandler.project)

        # To see progress, a new dialog window shows a progress bar
        self.progressDialog = ProgressDialog(
            self.iface.mainWindow(), self.onCloseProgressWindow
        )
        self.progressDialog.setThread(self.workerThread)

        # Add task to task manager of QGIS and start the calculations
        QgsApplication.taskManager().addTask(self.workerThread)

        # Show progress bar and start event loop
        self.progressDialog.show()

    def onCloseProgressWindow(self, continueToAdjustment: Union[bool, None]):
        """Gets called by the Progress dialog on closing."""
        # Get result if calculation was successful
        result, resultQuality = None, None
        if continueToAdjustment:
            result, resultQuality = self.workerThread.getResult()

        # Cleanup
        self.progressDialog.deleteLater()
        del self.workerThread

        if continueToAdjustment:
            self.showAdjustmentWindow(result, resultQuality)
        elif continueToAdjustment is False:
            # User chose to go back to project window
            self.showProjectWindow()
        else:  # None: continueToAdjustment not set --> user canceled dialog
            self.close()

    def showAdjustmentWindow(self, result, resultQuality):
        # Show adjustment window to modify calculated cable line
        self.adjustmentWindow = AdjustmentDialog(
            self.iface, self.confHandler, self.onCloseAdjustmentWindow
        )
        self.adjustmentWindow.initData(result, resultQuality)
        self.adjustmentWindow.show()

    def onCloseAdjustmentWindow(self, returnToProjectWindow: Union[bool, None]):
        """Gets called by the Adjustment dialog on closing."""
        if returnToProjectWindow:
            self.adjustmentWindow.deleteLater()
            # Reset configuration
            self.confHandler.reset()
            self.showProjectWindow()
        else:  # User clicked cancel or close
            self.close()

    def close(self):
        # Save user settings
        self.confHandler.updateUserSettings()
        # Restore the default cursor, just in case
        QApplication.restoreOverrideCursor()

        # Cleanup any markers in map and delete dialogs
        for dialog in [
            self.projectWindow,
            self.progressDialog,
            self.adjustmentWindow,
        ]:
            if dialog:
                try:
                    dialog.cleanUp(True)
                    dialog.deleteLater()
                except RuntimeError:
                    # Already deleted
                    pass
