"""
/***************************************************************************
 qgSurf - plugin for Quantum GIS

 Processing of geological planes and surfaces

                              -------------------
        begin                : 2011-12-21
        copyright            : (C) 2011-2026 by Mauro Alberti
        email                : alberti.m65@gmail.com

 ***************************************************************************/

# licensed under the terms of GNU GPL 3

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 3 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/
"""

# -*- coding: utf-8 -*-

from builtins import object

import os

from qgis.core import QgsProject
from qgis.PyQt.QtCore import *

from . import resources

from geogst.external_wrappers.yaml.io import *

from .apps.qt.tools import *
from .apps.qgis.messages import *

from .configurations.general_params import *
from .utils.qt_helpers import is_qt_object_deleted, safe_qt_access, safe_disconnect_signal

from .tools.BestFitPlaneTool import BestFitPlaneWidget
from .tools.DEMPlaneIntersectionTool import DemPlaneIntersectionWidget
from .tools.PlaneDistancesTool import PlaneDistancesWidget
from .tools.StereoplotTool import StereoplotWidget
from .tools.GeoprofilerTool import GeoProfilerWidget
from .tools.AboutDialog import AboutDialog


class QgSurfGui(object):

    def __init__(self, interface):

        self.plugin_name = "qgSurf"

        self.interface = interface
        self.main_window = self.interface.mainWindow()
        self.canvas = self.interface.mapCanvas()

        self.plugin_folder = os.path.dirname(__file__)
        self.config_fldrpth = os.path.join(
            self.plugin_folder,
            config_folder_name,
        )

        plugin_config_file = os.path.join(
            self.config_fldrpth,
            plugin_params_flnm,
        )

        dPluginParams = read_yaml(plugin_config_file)

        self.version = dPluginParams["version"]
        self.tools = dPluginParams["tools"]

        self.bestfitplane_toolpars = self.tools["bestfitplane_tool_params"]
        self.demplaneinters_toolpars = self.tools["demplaneinters_tool_params"]
        self.planedistances_toolpars = self.tools["planedistances_tool_params"]
        self.geoprofiler_toolpars = self.tools["geoprofiler_tool_params"]
        self.stereonet_toolpars = self.tools["stereonet_tool_params"]
        self.about_toolpars = self.tools["about_dlg_params"]

        db_config_file = os.path.join(
            self.config_fldrpth,
            db_params_flnm,
        )

        db_params = read_yaml(db_config_file)

        self.sqlite_db_params = db_params["sqlite_db"]

        self.best_fit_plane_module_open = False
        self.dem_plane_intersection_module_open = False
        self.plane_distances_module_open = False
        self.geoprofiler_module_open = False
        self.stereoplot_module_open = False

    def initGui(self):

        self.bestfitplane_geoproc = make_qaction(
            tool_params=self.bestfitplane_toolpars,
            plugin_name=plugin_name,
            icon_folder=icon_folder,
            parent=self.main_window)
        self.bestfitplane_geoproc.triggered.connect(self.RunBestFitPlaneGeoproc)
        self.interface.addPluginToMenu(
            plugin_name,
            self.bestfitplane_geoproc)

        self.demplaneinters_geoproc = make_qaction(
            tool_params=self.demplaneinters_toolpars,
            plugin_name=plugin_name,
            icon_folder=icon_folder,
            parent=self.main_window)
        self.demplaneinters_geoproc.triggered.connect(self.RunDemPlaneIntersectionGeoproc)
        self.interface.addPluginToMenu(
            plugin_name,
            self.demplaneinters_geoproc)

        self.planedistances_geoproc = make_qaction(
            tool_params=self.planedistances_toolpars,
            plugin_name=plugin_name,
            icon_folder=icon_folder,
            parent=self.main_window)
        self.planedistances_geoproc.triggered.connect(self.RunPlaneDistancesGeoproc)
        self.interface.addPluginToMenu(
            plugin_name,
            self.planedistances_geoproc)

        self.geoprofiler_geoproc = make_qaction(
            tool_params=self.geoprofiler_toolpars,
            plugin_name=plugin_name,
            icon_folder=icon_folder,
            parent=self.main_window)
        self.geoprofiler_geoproc.triggered.connect(self.RunGeoprofilerGeoproc)
        self.interface.addPluginToMenu(
            plugin_name,
            self.geoprofiler_geoproc)

        self.stereonet_geoproc = make_qaction(
            tool_params=self.stereonet_toolpars,
            plugin_name=plugin_name,
            icon_folder=icon_folder,
            parent=self.main_window)
        self.stereonet_geoproc.triggered.connect(self.RunStereonetGeoproc)
        self.interface.addPluginToMenu(
            plugin_name,
            self.stereonet_geoproc)

        self.qgsurf_about = make_qaction(
            tool_params=self.about_toolpars,
            plugin_name=plugin_name,
            icon_folder=icon_folder,
            parent=self.main_window)
        self.qgsurf_about.triggered.connect(self.RunQgsurfAbout)
        self.interface.addPluginToMenu(
            plugin_name,
            self.qgsurf_about)

        self.geoprocessings = [
            self.bestfitplane_geoproc,
            self.demplaneinters_geoproc,
            self.planedistances_geoproc,
            self.geoprofiler_geoproc,
            self.stereonet_geoproc,
            self.qgsurf_about]

    def RunBestFitPlaneGeoproc(self):

        if self.best_fit_plane_module_open:
            warn_qgis(
                self.plugin_name,
                "Best fit plane widget already open"
            )
            return

        if QgsProject.instance().crs().isGeographic():
            warn_qgis(
                self.plugin_name,
                "Before using the BestFitPlane module please set the project crs to a non-geographical one"
            )
            return

        BestFitPlaneDockWidget = QDockWidget(
            f"{plugin_name} - {self.bestfitplane_toolpars["module_name"]}",
            self.main_window,
        )

        BestFitPlaneDockWidget.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose)
        BestFitPlaneDockWidget.setAllowedAreas(Qt.DockWidgetArea.LeftDockWidgetArea | Qt.DockWidgetArea.RightDockWidgetArea)

        self.BestFitPlaneQwidget = BestFitPlaneWidget(
            plugin_folder=os.path.dirname(__file__),
            module_name=self.bestfitplane_toolpars["module_name"],
            canvas=self.canvas,
            plugin_qaction=self.bestfitplane_geoproc,
            db_tables_params=self.sqlite_db_params)
        BestFitPlaneDockWidget.setWidget(self.BestFitPlaneQwidget)
        BestFitPlaneDockWidget.destroyed.connect(self.BestFitPlaneCloseEvent)
        self.interface.addDockWidget(Qt.DockWidgetArea.RightDockWidgetArea, BestFitPlaneDockWidget)

        self.best_fit_plane_module_open = True

    def RunDemPlaneIntersectionGeoproc(self):

        if self.dem_plane_intersection_module_open:
            warn_qgis(
                self.plugin_name,
                "DEM-plane intersections widget already open"
            )
            return

        DemPlaneIntersectionDockWidget = QDockWidget(
            "{} - {}".format(plugin_name, self.demplaneinters_toolpars["module_name"]),
            self.main_window)
        DemPlaneIntersectionDockWidget.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose)
        DemPlaneIntersectionDockWidget.setAllowedAreas(Qt.DockWidgetArea.LeftDockWidgetArea | Qt.DockWidgetArea.RightDockWidgetArea)

        self.DemPlaneIntersectionQwidget = DemPlaneIntersectionWidget(
            plugin_folder=os.path.dirname(__file__),
            module_name=self.demplaneinters_toolpars["module_name"],
            canvas=self.canvas,
            plugin_qaction=self.demplaneinters_geoproc)
        DemPlaneIntersectionDockWidget.setWidget(self.DemPlaneIntersectionQwidget)
        DemPlaneIntersectionDockWidget.destroyed.connect(self.DemPlaneIntersectionCloseEvent)
        self.interface.addDockWidget(Qt.DockWidgetArea.RightDockWidgetArea, DemPlaneIntersectionDockWidget)

        self.dem_plane_intersection_module_open = True

    def RunPlaneDistancesGeoproc(self):

        if self.plane_distances_module_open:
            warn_qgis(
                self.plugin_name,
                "Plane distances widget already open"
            )
            return

        PlaneDistancesDockWidget = QDockWidget(
            f"{plugin_name} - {self.planedistances_toolpars['module_name']}",
            self.main_window,
        )
        PlaneDistancesDockWidget.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose)
        PlaneDistancesDockWidget.setAllowedAreas(Qt.DockWidgetArea.LeftDockWidgetArea | Qt.DockWidgetArea.RightDockWidgetArea)

        self.PlaneDistancesQwidget = PlaneDistancesWidget(
            plugin_folder=os.path.dirname(__file__),
            module_name=self.planedistances_toolpars["module_name"],
            canvas=self.canvas,
            plugin_qaction=self.planedistances_geoproc)
        PlaneDistancesDockWidget.setWidget(self.PlaneDistancesQwidget)
        PlaneDistancesDockWidget.destroyed.connect(self.PlaneDistancesCloseEvent)

        area = Qt.DockWidgetArea.RightDockWidgetArea
        self.main_window.addDockWidget(area, PlaneDistancesDockWidget)
        #self.interface.addDockWidget(Qt.RightDockWidgetArea, PlaneDistancesDockWidget)

        self.plane_distances_module_open = True

    def RunGeoprofilerGeoproc(self):

        self.wdgtGeoprofiler = GeoProfilerWidget(
            current_directory=os.path.dirname(__file__),
            plugin_name=plugin_name,
            canvas=self.canvas,
            parent=self.main_window,
        )

        #self.wdgtGeoprofiler.destroyed.connect(self.GeoprofilerCloseEvent)

        # show dialog
        self.wdgtGeoprofiler.show()

        self.geoprofiler_module_open = True

    def RunStereonetGeoproc(self):

        if self.stereoplot_module_open:
            warn_qgis(
                self.plugin_name,
                "Geologic stereonets widget already open"
            )
            return

        dwgtStereoplotDockWidget = QDockWidget(
            "{} - {}".format(plugin_name,
            self.stereonet_toolpars["module_name"]),
            self.main_window,
        )

        dwgtStereoplotDockWidget.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose)
        dwgtStereoplotDockWidget.setAllowedAreas(Qt.DockWidgetArea.LeftDockWidgetArea | Qt.DockWidgetArea.RightDockWidgetArea)

        self.wdgtStereoplot = StereoplotWidget(
            module_name=self.stereonet_toolpars["module_name"],
            canvas=self.canvas,
            settings_name=settings_name,
            plugin_folder=self.plugin_folder)

        dwgtStereoplotDockWidget.setWidget(self.wdgtStereoplot)
        dwgtStereoplotDockWidget.destroyed.connect(self.StereoplotCloseEvent)
        self.interface.addDockWidget(Qt.DockWidgetArea.RightDockWidgetArea, dwgtStereoplotDockWidget)

        self.stereoplot_module_open = True

    def RunQgsurfAbout(self):

        qgsurf_about_dlg = AboutDialog(
            version=self.version)

        qgsurf_about_dlg.show()
        qgsurf_about_dlg.exec()

    def BestFitPlaneCloseEvent(self, event):

        # Check if widget still exists before accessing it
        if not hasattr(self, 'BestFitPlaneQwidget') or is_qt_object_deleted(self.BestFitPlaneQwidget):
            self.best_fit_plane_module_open = False
            return

        # Save references before cleanup to avoid accessing destroyed widget
        widget = self.BestFitPlaneQwidget
        markers = []
        emit_point_tool = None
        
        # Safely extract references
        if not is_qt_object_deleted(widget):
            markers = safe_qt_access(
                widget,
                lambda w: list(w.best_fit_plane_point_markers) if hasattr(w, 'best_fit_plane_point_markers') and w.best_fit_plane_point_markers else [],
                []
            )
            emit_point_tool = safe_qt_access(
                widget,
                lambda w: w.emit_point_from_canvas if hasattr(w, 'emit_point_from_canvas') else None,
                None
            )

        # Remove markers using widget's safe method if available
        if not is_qt_object_deleted(widget):
            safe_qt_access(
                widget,
                lambda w: (
                    [w._safe_remove_marker(mrk) for mrk in markers] if hasattr(w, '_safe_remove_marker') else None
                ),
                None
            )

        # Disconnect signals safely
        if not is_qt_object_deleted(widget):
            safe_disconnect_signal(
                QgsProject.instance().layerWasAdded,
                safe_qt_access(widget, lambda w: w.refresh_raster_layer_list, None)
            )
            safe_disconnect_signal(
                QgsProject.instance().layerRemoved,
                safe_qt_access(widget, lambda w: w.refresh_raster_layer_list, None)
            )
            safe_disconnect_signal(
                QgsProject.instance().layerWasAdded,
                safe_qt_access(widget, lambda w: w.refresh_layer_xy_list_in_combobox, None)
            )
            safe_disconnect_signal(
                QgsProject.instance().layerRemoved,
                safe_qt_access(widget, lambda w: w.refresh_layer_xy_list_in_combobox, None)
            )
            
            if emit_point_tool is not None and not is_qt_object_deleted(emit_point_tool):
                safe_disconnect_signal(
                    safe_qt_access(emit_point_tool, lambda t: t.canvasClicked, None),
                    safe_qt_access(widget, lambda w: w.add_input_qgis_xy_point_to_listing, None)
                )
                safe_disconnect_signal(
                    safe_qt_access(emit_point_tool, lambda t: t.leftClicked, None),
                    safe_qt_access(widget, lambda w: w.add_input_qgis_xy_point_to_listing, None)
                )

        self.best_fit_plane_module_open = False

    def DemPlaneIntersectionCloseEvent(self, event):

        # Check if widget still exists before accessing it
        if not hasattr(self, 'DemPlaneIntersectionQwidget') or is_qt_object_deleted(self.DemPlaneIntersectionQwidget):
            self.dem_plane_intersection_module_open = False
            return

        # Save references before cleanup to avoid accessing destroyed widget
        widget = self.DemPlaneIntersectionQwidget
        intersection_markers = []
        source_markers = []
        map_tool = None
        
        # Safely extract references
        if not is_qt_object_deleted(widget):
            intersection_markers = safe_qt_access(
                widget,
                lambda w: list(w.intersections_markers_list) if hasattr(w, 'intersections_markers_list') and w.intersections_markers_list else [],
                []
            )
            source_markers = safe_qt_access(
                widget,
                lambda w: list(w.source_point_marker_list) if hasattr(w, 'source_point_marker_list') and w.source_point_marker_list else [],
                []
            )
            map_tool = safe_qt_access(
                widget,
                lambda w: w.map_tool if hasattr(w, 'map_tool') else None,
                None
            )

        # Remove markers using widget's safe method if available
        if not is_qt_object_deleted(widget):
            safe_qt_access(
                widget,
                lambda w: (
                    [w._safe_remove_marker(mrk) for mrk in intersection_markers + source_markers] if hasattr(w, '_safe_remove_marker') else None
                ),
                None
            )

        # Disconnect signals safely
        if map_tool is not None and not is_qt_object_deleted(map_tool) and not is_qt_object_deleted(widget):
            safe_disconnect_signal(
                safe_qt_access(map_tool, lambda t: t.canvasClicked, None),
                safe_qt_access(widget, lambda w: w.update_source_point_location, None)
            )

        self.dem_plane_intersection_module_open = False

    def PlaneDistancesCloseEvent(self, event):

        # Check if widget still exists before accessing it
        if not hasattr(self, 'PlaneDistancesQwidget') or is_qt_object_deleted(self.PlaneDistancesQwidget):
            self.plane_distances_module_open = False
            return

        # Save references before cleanup to avoid accessing destroyed widget
        widget = self.PlaneDistancesQwidget
        source_markers = []
        map_tool = None
        
        # Safely extract references
        if not is_qt_object_deleted(widget):
            source_markers = safe_qt_access(
                widget,
                lambda w: list(w.source_point_marker_list) if hasattr(w, 'source_point_marker_list') and w.source_point_marker_list else [],
                []
            )
            map_tool = safe_qt_access(
                widget,
                lambda w: w.map_tool if hasattr(w, 'map_tool') else None,
                None
            )

        # Remove markers using widget's safe method if available
        if not is_qt_object_deleted(widget):
            safe_qt_access(
                widget,
                lambda w: (
                    [w._safe_remove_marker(mrk) for mrk in source_markers] if hasattr(w, '_safe_remove_marker') else None
                ),
                None
            )

        # Disconnect signals safely
        if map_tool is not None and not is_qt_object_deleted(map_tool) and not is_qt_object_deleted(widget):
            safe_disconnect_signal(
                safe_qt_access(map_tool, lambda t: t.canvasClicked, None),
                safe_qt_access(widget, lambda w: w.update_source_point_location, None)
            )

        self.plane_distances_module_open = False

    def GeoprofilerCloseEvent(self):

        # GeoProfilerWidget already handles cleanup in closeEvent() method
        # which calls disconnect_project_signal()
        # Just reset the flag
        self.geoprofiler_module_open = False

    def StereoplotCloseEvent(self):

        # StereoplotWidget has Qt.WA_DeleteOnClose=False, so it might not be destroyed automatically
        # Check if widget still exists and cleanup if needed
        if hasattr(self, 'wdgtStereoplot') and self.wdgtStereoplot is not None:
            try:
                # Make sure a canvas rectangle-link map tool, if active, doesn't
                # outlive the dock widget it was driven from.
                if hasattr(self.wdgtStereoplot, '_deactivate_rectangle_link'):
                    self.wdgtStereoplot._deactivate_rectangle_link()
                # StereoplotWidget doesn't seem to have signals that need disconnection
                # but we can emit the window_closed signal if needed
                if hasattr(self.wdgtStereoplot, 'window_closed'):
                    # Signal is defined but may not be connected, so we don't need to disconnect
                    pass
            except (RuntimeError, AttributeError):
                # Widget might be destroyed, skip cleanup
                pass
        
        self.stereoplot_module_open = False

    def unload(self):

        for geoprocessing in self.geoprocessings:

            self.interface.removePluginMenu(
                plugin_name,
                geoprocessing)
