#! python3  # noqa: E265

# standard
import os
from functools import partial
from pathlib import Path

from pyplugin_installer.installer import QgsPluginInstaller

# PyQGIS
from qgis.core import QgsApplication, QgsExpression, QgsProject, QgsSettings
from qgis.gui import QgisInterface
from qgis.PyQt.Qt import QUrl
from qgis.PyQt.QtCore import QCoreApplication, Qt, QTimer
from qgis.PyQt.QtGui import QDesktopServices, QGuiApplication, QIcon
from qgis.PyQt.QtWidgets import QAction, QDockWidget, QFileDialog, QMenu

# project
from openlog.__about__ import DIR_PLUGIN_ROOT, __title__, __uri_documentation__
from openlog.gui.dlg_settings import PlgOptionsFactory
from openlog.gui.welcome.welcome import WelcomeScreen
from openlog.processing.provider import OpenLogProvider
from openlog.resources.styles.length3d_qgis_style import get_projected_length_of_3D_line
from openlog.toolbelt import PlgLogger, PlgTranslator

# Check dependency by loading plugin with external dependency
dependency_valid = True
import_error = ""
try:
    from openlog.core.desurveying import minimum_curvature_method
    from openlog.datamodel.assay.generic_assay import AssayDomainType
    from openlog.datamodel.connection.openlog_connection import OpenLogConnection
    from openlog.datamodel.connection.openlog_connection_factory import (
        OpenLogConnectionFactory,
    )
    from openlog.datamodel.connection.spatialite.spatialite_connection import (
        SpatialiteConnection,
    )
    from openlog.datamodel.connection.xplordb.xplordb_connection import (
        XplordbConnection,
    )
    from openlog.gui.assay_visualization.assay_widget import AssayWidget
    from openlog.gui.collar_creation.collar_creation_dialog import CollarCreationDialog
    from openlog.gui.connection.connection_dialog import ConnectionDialog
    from openlog.gui.create_database.database_creation import DatabaseCreationWizard
    from openlog.gui.import_assay.import_assay import AssayImportWizard
    from openlog.gui.import_data.database_import import (
        DatabaseImportWizard,
        ImportPages,
    )
    from openlog.gui.local_grid.local_grid_creation_dialog import (
        LocalGridCreationDialog,
    )
    from openlog.gui.survey_creation.survey_creation_dialog import SurveyCreationDialog
    from openlog.plugins.manager import get_plugin_manager
except ImportError:
    import site

    site.addsitedir(DIR_PLUGIN_ROOT / "embedded_external_libs")
    try:
        from openlog.core.desurveying import minimum_curvature_method
        from openlog.datamodel.assay.generic_assay import AssayDomainType
        from openlog.datamodel.connection.openlog_connection import OpenLogConnection
        from openlog.datamodel.connection.openlog_connection_factory import (
            OpenLogConnectionFactory,
        )
        from openlog.datamodel.connection.spatialite.spatialite_connection import (
            SpatialiteConnection,
        )
        from openlog.datamodel.connection.xplordb.xplordb_connection import (
            XplordbConnection,
        )
        from openlog.gui.assay_visualization.assay_widget import AssayWidget
        from openlog.gui.collar_creation.collar_creation_dialog import (
            CollarCreationDialog,
        )
        from openlog.gui.connection.connection_dialog import ConnectionDialog
        from openlog.gui.create_database.database_creation import DatabaseCreationWizard
        from openlog.gui.import_assay.import_assay import AssayImportWizard
        from openlog.gui.import_data.database_import import (
            DatabaseImportWizard,
            ImportPages,
        )
        from openlog.gui.local_grid.local_grid_creation_dialog import (
            LocalGridCreationDialog,
        )
        from openlog.gui.survey_creation.survey_creation_dialog import (
            SurveyCreationDialog,
        )
        from openlog.plugins.manager import get_plugin_manager

    except ImportError as exc:
        import_error = str(exc)
        dependency_valid = False

        OpenLogConnectionFactory = None

        XplordbConnection = None
        OpenLogConnection = None
        AssayDomainType = None

        ConnectionDialog = None

        AssayImportWizard = None
        AssayWidget = None

        SurveyCreationDialog = None
        DatabaseCreationWizard = None
        DatabaseImportWizard = None
        ImportPages = None
        CollarCreationDialog = None
        LocalGridCreationDialog = None

        minimum_curvature_method = None

BASE_SETTINGS_KEY = "/OpenLog/plugin"

PROJECT_CONNECTION_TYPE_KEY = "/project_connection/type"

# store method that will be monkeypatched for sqlalchemy .pyd issues when updating
_original_install_method = QgsPluginInstaller.installPlugin
_original_uninstall_method = QgsPluginInstaller.uninstallPlugin


def move_sqlalchemy_pyd_files():
    """
    Move sqlalchemy .pyd files in a temporary folder.
    In that way, files in plugin folder will be deleted without issues during upgrade.
    It means sqlalchemy will still use these files until next QGIS restart.
    """
    import tempfile

    import sqlalchemy

    # create folder in temp path
    tmp = tempfile.mkdtemp()
    modules = [
        sqlalchemy.cimmutabledict,
        sqlalchemy.cprocessors,
        sqlalchemy.cresultproxy,
    ]
    for module in modules:
        file = module.__file__
        if os.path.exists(file):
            os.rename(file, tmp + "/" + module.__name__ + ".pyd")


def install_plugin(self, key, quiet=False, stable=True):
    """
    On Windows we need to release lock on sqlalchemy library and unload plugin before
    performing an update. See https://github.com/MerginMaps/qgis-mergin-plugin/issues/504
    and https://github.com/MerginMaps/geodiff/issues/205
    """
    if key == "openlog" and os.name == "nt":
        try:
            move_sqlalchemy_pyd_files()
        except:
            pass

    _original_install_method(self, key, quiet, stable)


def uninstall_plugin(self, key, quiet=False):

    if key == "openlog" and os.name == "nt":
        try:
            move_sqlalchemy_pyd_files()
        except:
            pass

    _original_uninstall_method(self, key, quiet)


class OpenlogPlugin:
    """
    OpenlogPlugin. Can be used to access data from current openlog connection

    """

    def __init__(self, iface: QgisInterface):
        """Constructor.

        :param iface: An interface instance that will be passed to this class which \
        provides the hook by which you can manipulate the QGIS application at run time.
        :type iface: QgsInterface
        """
        self.iface = iface
        self.provider = None
        self.menu = None
        self.log = PlgLogger().log
        self.options_factory = None
        self.action_help = None
        self.action_settings = None

        self.database_action = None

        self.import_action_collar = None
        self.import_action_survey = None

        self.import_assay_action = None

        self.xplordb_create_action = None
        self.spatialite_create_action = None

        self.connect_action = None

        self.local_grid_action = None
        self.add_collar_action = None
        self.selected_colar_desurv_action = None
        self.selected_colar_survey_definition_action = None
        self.depth_assay_dock_action = None
        self.time_assay_dock_action = None
        self._plugin_actions = []

        self.depth_assay_visu_dock = None
        self.depth_assay_visu_widget = None
        self.time_assay_visu_dock = None
        self.time_assay_visu_widget = None

        # translation
        plg_translation_mngr = PlgTranslator()
        translator = plg_translation_mngr.get_translator()
        if translator:
            QCoreApplication.installTranslator(translator)
        self.tr = plg_translation_mngr.tr

        self.collar_creation_dialog = None
        self.local_grid_creation_dialog = None
        self.openlog_connection = None

        self.context_menu_connect = None
        self.project_loaded_connect = None
        self.project_cleared_connect = None
        self.project_filename_changed_connect = None
        self.project_load_timer = None

        self.splash_active = True

    def get_openlog_connection(self) -> OpenLogConnection:
        """
        Returns current openlog connection.

        Can be used to access data from connection

        Returns: (OpenLogConnection)

        """
        return self.openlog_connection

    def check_dependencies(self) -> None:
        """Check if all dependencies are satisfied. If not, warn the user and disable plugin."""
        # if import failed
        if not dependency_valid:
            self.log(
                message=self.tr(
                    "Error importing dependencies : {}.\n Plugin disabled."
                ).format(import_error),
                log_level=2,
                push=True,
                button=True,
                duration=0,
            )

            # add tooltip over menu
            msg_disable = self.tr(
                "OpenLog Plugin disabled. Please install all dependencies and then restart QGIS."
            )

            self.menu.setEnabled(False)
            self.menu.setToolTip(msg_disable)

            # disable plugin actions
            for widget in self._plugin_actions:
                widget.setEnabled(False)
                widget.setToolTip(msg_disable)
        else:
            self.log(message=self.tr("Dependencies satisfied"), log_level=3)

    def initGui(self):
        """Set up plugin UI elements."""

        # settings page within the QGIS preferences menu
        self.options_factory = PlgOptionsFactory()
        self.iface.registerOptionsWidgetFactory(self.options_factory)

        self.menu = QMenu("OpenLog", self.iface.mainWindow())
        self.iface.mainWindow().menuBar().addMenu(self.menu)

        # -- Actions
        self.action_help = QAction(
            QIcon(":/images/themes/default/mActionHelpContents.svg"),
            self.tr("Help", context="OpenlogPlugin"),
            self.iface.mainWindow(),
        )
        self.action_help.triggered.connect(
            partial(QDesktopServices.openUrl, QUrl(__uri_documentation__))
        )

        self.action_settings = QAction(
            QgsApplication.getThemeIcon("console/iconSettingsConsole.svg"),
            self.tr("Settings"),
            self.iface.mainWindow(),
        )
        self.action_settings.triggered.connect(
            lambda: self.iface.showOptionsDialog(
                currentPage="mOptionsPage{}".format(__title__)
            )
        )

        self.database_action = QAction(
            QgsApplication.getThemeIcon("mIconDbSchema.svg"),
            self.tr("Database management"),
        )
        menu = QMenu(self.iface.mainWindow())
        self.database_action.setMenu(menu)

        #  IMPORT ACTIONS
        self.import_action_collar = QAction(
            QIcon(str(DIR_PLUGIN_ROOT / "resources" / "images" / "import_collar.svg")),
            self.tr("Import collar csv"),
            self.iface.mainWindow(),
        )
        self.import_action_collar.setEnabled(False)
        self.import_action_collar.triggered.connect(self._import_collar)

        self.database_action.menu().addAction(self.import_action_collar)

        self.import_action_survey = QAction(
            QIcon(str(DIR_PLUGIN_ROOT / "resources" / "images" / "import_survey.svg")),
            self.tr("Import survey csv"),
            self.iface.mainWindow(),
        )
        self.import_action_survey.setEnabled(False)
        self.import_action_survey.triggered.connect(self._import_survey)

        self.database_action.menu().addAction(self.import_action_survey)

        self.import_assay_action = QAction(
            QIcon(str(DIR_PLUGIN_ROOT / "resources" / "images" / "import_assay.svg")),
            self.tr("Import downhole data csv"),
            self.iface.mainWindow(),
        )
        self.import_assay_action.setEnabled(False)
        self.import_assay_action.triggered.connect(self._import_assay)

        self.database_action.menu().addAction(self.import_assay_action)

        #  CREATE ACTIONS
        self.database_action.menu().addSeparator()

        self.xplordb_create_action = QAction(
            QgsApplication.getThemeIcon("mActionAddPostgisLayer.svg"),
            self.tr("Create new xplordb database"),
            self.iface.mainWindow(),
        )
        self.xplordb_create_action.triggered.connect(self._xplordb_create)
        self.database_action.menu().addAction(self.xplordb_create_action)

        self.spatialite_create_action = QAction(
            QgsApplication.getThemeIcon("mActionAddSpatiaLiteLayer.svg"),
            self.tr("Create new spatialite database"),
            self.iface.mainWindow(),
        )
        self.spatialite_create_action.triggered.connect(self._spatialite_create)
        self.database_action.menu().addAction(self.spatialite_create_action)

        self.connect_action = QAction(
            QgsApplication.getThemeIcon("mIconConnect.svg"),
            self.tr("Connect to database"),
        )
        menu = QMenu(self.iface.mainWindow())
        self.connect_action.setMenu(menu)

        #  CONNECT ACTION through plugins

        plugin_manager = get_plugin_manager()
        for plugin_connection in plugin_manager.get_connections():
            action = plugin_connection.connection_action(self)
            self.connect_action.menu().addAction(action)

        # TOOL ACTION
        self.local_grid_action = QAction(
            QIcon(":/images/themes/default/grid.svg"),
            self.tr("Local grid"),
            self.iface.mainWindow(),
        )
        self.local_grid_action.triggered.connect(self._local_grid)

        self.add_collar_action = QAction(
            QIcon(str(DIR_PLUGIN_ROOT / "resources" / "images" / "add_collar.svg")),
            self.tr("Add collar"),
            self.iface.mainWindow(),
        )
        self.add_collar_action.triggered.connect(self._add_collar)
        self.add_collar_action.setEnabled(False)

        self.selected_colar_desurv_action = QAction(
            QIcon(str(DIR_PLUGIN_ROOT / "resources" / "images" / "desurvey.svg")),
            self.tr("Desurvey"),
            self.iface.mainWindow(),
        )
        self.selected_colar_desurv_action.triggered.connect(
            self._selected_collar_desurveying
        )
        self.selected_colar_desurv_action.setEnabled(False)

        self.selected_colar_survey_definition_action = QAction(
            QIcon(str(DIR_PLUGIN_ROOT / "resources" / "images" / "define_survey.svg")),
            self.tr("Edit surveys"),
            self.iface.mainWindow(),
        )
        self.selected_colar_survey_definition_action.triggered.connect(
            self._selected_collar_survey_definition
        )
        self.selected_colar_survey_definition_action.setEnabled(False)

        # -- Menu
        self._add_plugin_actions(self.action_settings)
        self._add_plugin_actions(self.action_help)
        self._add_plugin_actions(self.database_action)
        self._add_plugin_actions(self.connect_action)
        self._add_plugin_actions(self.local_grid_action)
        self._add_plugin_actions(self.add_collar_action)
        self._add_plugin_actions(self.selected_colar_desurv_action)
        self._add_plugin_actions(self.selected_colar_survey_definition_action)

        # Custom context menu for mapcanvas
        self.context_menu_connect = (
            self.iface.mapCanvas().contextMenuAboutToShow.connect(
                lambda menu_, event_: self.canvas_menu_about_to_show(menu_, event_)
            )
        )

        # register custom expression for layer style
        QgsExpression.registerFunction(get_projected_length_of_3D_line)

        if dependency_valid:
            # -- Dock widgets
            self.depth_assay_visu_dock = QDockWidget(
                self.tr("Display depth data"), self.iface.mainWindow()
            )
            self.depth_assay_visu_widget = AssayWidget(
                AssayDomainType.DEPTH, self.iface.mainWindow(), self.openlog_connection
            )
            self.depth_assay_visu_dock.setWidget(self.depth_assay_visu_widget)
            self.iface.addDockWidget(Qt.RightDockWidgetArea, self.depth_assay_visu_dock)
            self.depth_assay_visu_dock.close()
            self.depth_assay_dock_action = self.depth_assay_visu_dock.toggleViewAction()
            self.depth_assay_dock_action.setEnabled(True)
            self._add_plugin_actions(self.depth_assay_dock_action)

            self.time_assay_visu_dock = QDockWidget(
                self.tr("Display time data"), self.iface.mainWindow()
            )
            self.time_assay_visu_widget = AssayWidget(
                AssayDomainType.TIME, self.iface.mainWindow(), self.openlog_connection
            )
            self.time_assay_visu_dock.setWidget(self.time_assay_visu_widget)
            self.iface.addDockWidget(Qt.RightDockWidgetArea, self.time_assay_visu_dock)
            self.time_assay_visu_dock.close()
            self.time_assay_dock_action = self.time_assay_visu_dock.toggleViewAction()
            self.time_assay_dock_action.setEnabled(True)
            self._add_plugin_actions(self.time_assay_dock_action)

        # No need to go further if the dependencies are not installed
        self.check_dependencies()

        self.project_loaded_connect = QgsProject.instance().readProject.connect(
            self._qgis_project_loaded
        )

        self.project_filename_changed_connect = (
            QgsProject.instance().fileNameChanged.connect(
                self._qgis_project_filename_changed
            )
        )

        self.project_cleared_connect = QgsProject.instance().cleared.connect(
            self._qgis_project_cleared
        )
        self.initProcessing()

        # monkeypatch for windows (locked dll issue when upgrading OpenLog)
        if os.name == "nt":
            QgsPluginInstaller.installPlugin = install_plugin
            QgsPluginInstaller.uninstallPlugin = uninstall_plugin

    def initProcessing(self):
        self.provider = OpenLogProvider()
        QgsApplication.processingRegistry().addProvider(self.provider)

    def _qgis_project_loaded(self) -> None:
        """
        Restore connection if available in QgsProject settings

        """
        # Restore cursor to remove waiting cursor
        QGuiApplication.restoreOverrideCursor()

        connection_type = QgsProject.instance().readEntry(
            "OpenLog", BASE_SETTINGS_KEY + PROJECT_CONNECTION_TYPE_KEY
        )[0]
        if connection_type:
            connection = OpenLogConnectionFactory().create_connection_from_project(
                connection_type=connection_type,
                base_settings_key=BASE_SETTINGS_KEY,
                parent=self.iface.mainWindow(),
            )
            if connection:
                # Need to use timer for connection definition because we can't remove layer from project during
                # project load
                # If we remove layers a few millisecond later it's fine
                # QgsProject.instance().readProject signal seems to be fired too soon
                self.project_load_timer = QTimer(self.iface.mainWindow())
                self.project_load_timer.setSingleShot(True)
                self.project_load_timer.timeout.connect(
                    lambda: self._define_current_connection(connection)
                )
                self.project_load_timer.start(100)

    def _qgis_project_filename_changed(self) -> None:
        """
        Define new project title when filename is changed

        """
        if self.openlog_connection:
            new_title = f"<{QgsProject.instance().fileName()}> {self.openlog_connection.get_mainwindow_title()}"
            QgsProject.instance().setTitle(new_title)
            # Need to force dirty changed signal emission because there is a blocker implemented in QGIS
            # If not done, title is not updated
            QgsProject.instance().isDirtyChanged.emit(True)

    def _qgis_project_cleared(self) -> None:
        """
        Clear current openlog connection when project cleared

        """
        if self.openlog_connection:
            self._define_current_connection(None)

    def _add_plugin_actions(self, action: QAction):
        """
        Add action to plugin menu and store in actions list

        Args:
            action: QAction
        """
        self._plugin_actions.append(action)
        self.menu.addAction(action)

    def unload(self):
        """Cleans up when plugin is disabled/uninstalled."""

        # remove actions and clean up menu
        for action in self._plugin_actions:
            self.menu.removeAction(action)
            del action

        if self.menu:
            self.iface.mainWindow().menuBar().removeAction(self.menu.menuAction())
            del self.menu

        if self.depth_assay_visu_dock:
            self.iface.removeDockWidget(self.depth_assay_visu_dock)
            del self.depth_assay_visu_dock

        if self.time_assay_visu_dock:
            self.iface.removeDockWidget(self.time_assay_visu_dock)
            del self.time_assay_visu_dock

        self._unload_loaded_layer()

        # -- Clean up preferences panel in QGIS settings
        self.iface.unregisterOptionsWidgetFactory(self.options_factory)

        if self.context_menu_connect:
            self.iface.mapCanvas().contextMenuAboutToShow.disconnect(
                self.context_menu_connect
            )

        if self.project_loaded_connect:
            QgsProject.instance().readProject.disconnect(self.project_loaded_connect)

        if self.project_cleared_connect:
            QgsProject.instance().cleared.disconnect(self.project_cleared_connect)

        # -- Unregister processing
        QgsApplication.processingRegistry().removeProvider(self.provider)

        # unregister custom expression
        QgsExpression.unregisterFunction(get_projected_length_of_3D_line.name())

    def _unload_loaded_layer(self):
        """
        Unload openlog layer from map

        """
        if self.openlog_connection:
            try:
                if self.openlog_connection.get_layers_iface().get_collar_layer():
                    QgsProject.instance().removeMapLayer(
                        self.openlog_connection.get_layers_iface().get_collar_layer()
                    )
                if self.openlog_connection.get_layers_iface().get_collar_trace_layer():
                    QgsProject.instance().removeMapLayer(
                        self.openlog_connection.get_layers_iface().get_collar_trace_layer()
                    )

                if self.openlog_connection.get_layers_iface().get_planned_trace_layer():
                    QgsProject.instance().removeMapLayer(
                        self.openlog_connection.get_layers_iface().get_planned_trace_layer()
                    )
            except RuntimeError:
                self.log(message=self.tr("OpenLog connection layers can't be removed."))

    def canvas_menu_about_to_show(self, menu: QMenu, event):
        """
        Add collars action to canvas custom menu

        Args:
            menu: QMenu for map canvas
            event:
        """
        menu.addAction(self.selected_colar_desurv_action)
        menu.addAction(self.selected_colar_survey_definition_action)
        menu.addAction(self.depth_assay_dock_action)
        menu.addAction(self.time_assay_dock_action)

    def _import_assay(self) -> None:
        """
        Execute AssayImportWizard for assay import

        """
        wizard = AssayImportWizard(self.openlog_connection, self.iface.mainWindow())
        wizard.exec()

    def _import_collar(self) -> None:
        """
        Execute DatabaseImportWizard for collar import

        """
        wizard = DatabaseImportWizard(
            self.openlog_connection,
            self.iface.mainWindow(),
            import_pages=ImportPages.COLLAR,
        )
        wizard.exec()

    def _import_survey(self) -> None:
        """
        Execute DatabaseImportWizard for survey import

        """
        wizard = DatabaseImportWizard(
            self.openlog_connection,
            self.iface.mainWindow(),
            import_pages=ImportPages.SURVEY,
        )
        wizard.exec()

    def _xplordb_create(self) -> None:
        """
        Execute DatabaseCreationWizard for xplordb database creation

        """
        wizard = DatabaseCreationWizard(self.iface.mainWindow())
        wizard.exec()

    def _spatialite_create(self) -> None:
        """
        Select file for spatialite db creation

        """
        filename, filter_use = QFileDialog.getSaveFileName(
            self.iface.mainWindow(),
            self.tr("Select file"),
            "openlog_spatialite.db",
            "Spatialite database (*.db)",
        )
        if filename:
            self._define_current_connection(
                SpatialiteConnection(Path(filename), new_file=True)
            )

    def _define_current_connection(self, connection: OpenLogConnection):
        """
        Define current openlog connection :
        - add open log layers
        - define import actions availability depending on connection

        Args:
            connection: OpenLogConnection
        """
        self._unload_loaded_layer()
        self.openlog_connection = connection

        if self.openlog_connection:
            # if user-defined project, don't override project name
            if QgsProject.instance().fileName() == "":
                new_title = f"<{QgsProject.instance().fileName()}> {self.openlog_connection.get_mainwindow_title()}"
                QgsProject.instance().setTitle(new_title)
                # Need to force dirty changed signal emission because there is a blocker implemented in QGIS
                # If not done, title is not updated
                QgsProject.instance().isDirtyChanged.emit(True)
                self.openlog_connection.save_to_qgis_project(BASE_SETTINGS_KEY)
                QgsProject.instance().writeEntry(
                    "OpenLog",
                    BASE_SETTINGS_KEY + PROJECT_CONNECTION_TYPE_KEY,
                    type(self.openlog_connection).__name__,
                )

        self._add_openlog_layers()
        if self.openlog_connection:
            import_collar_enabled = (
                self.openlog_connection.get_write_iface().can_import_collar()
            )
            self.add_collar_action.setEnabled(import_collar_enabled)
            self.import_action_collar.setEnabled(import_collar_enabled)
            self.import_action_survey.setEnabled(import_collar_enabled)

            self.import_assay_action.setEnabled(
                self.openlog_connection.get_assay_iface().can_import_assay()
            )
        else:
            self.add_collar_action.setEnabled(False)
            self.import_action_collar.setEnabled(False)
            self.import_action_survey.setEnabled(False)
            self.import_assay_action.setEnabled(False)

        self.depth_assay_visu_widget.set_openlog_connection(connection)
        self.time_assay_visu_widget.set_openlog_connection(connection)

    def _display_splash(self, count: int):
        if count in [1, 5, 10]:
            ws = WelcomeScreen()
            ws.exec()

    def _count_openlog_use(self) -> None:
        """
        Update the number of use of Openlog.
        It is stored in plugin root (use_count.txt)
        """
        if not self.splash_active:
            return

        path = str(DIR_PLUGIN_ROOT / "use_count.txt")
        if os.path.exists(path):
            with open(path, "r") as f:
                count = f.readline()
            count = int(count) + 1
            with open(path, "w") as f:
                f.write(str(count))
        else:
            with open(path, "w") as f:
                f.write(str(1))
            count = 1
        self._display_splash(count)

    def _add_openlog_layers(self) -> None:
        """
        Add openlog layers from current connection

        """
        self._count_openlog_use()
        self._load_openlog_layers()

    def _load_openlog_layers(self) -> None:
        # check if current project is a user-defined project
        is_project = QgsProject.instance().fileName() != ""
        collar_style = None
        trace_style = None
        planned_trace_style = None

        if self.openlog_connection:

            # remove Collar and Trace layers if exists
            for layer in QgsProject.instance().mapLayersByName(
                self.openlog_connection.get_layers_iface().get_collar_layer_name()
            ):
                # get existing collar style
                collar_style = layer.styleManager().style(
                    layer.styleManager().currentStyle()
                )
                if (
                    layer
                    != self.openlog_connection.get_layers_iface().get_collar_layer()
                ):

                    QgsProject.instance().removeMapLayer(layer.id())

            for layer in QgsProject.instance().mapLayersByName(
                self.openlog_connection.get_layers_iface().get_collar_trace_layer_name()
            ):
                # get existing trace style
                trace_style = layer.styleManager().style(
                    layer.styleManager().currentStyle()
                )
                if (
                    layer
                    != self.openlog_connection.get_layers_iface().get_collar_trace_layer()
                ):
                    QgsProject.instance().removeMapLayer(layer.id())

            for layer in QgsProject.instance().mapLayersByName(
                self.openlog_connection.get_layers_iface().get_planned_trace_layer_name()
            ):
                # get existing trace style
                planned_trace_style = layer.styleManager().style(
                    layer.styleManager().currentStyle()
                )
                if (
                    layer
                    != self.openlog_connection.get_layers_iface().get_planned_trace_layer_name()
                ):
                    QgsProject.instance().removeMapLayer(layer.id())

            # add openlog_connection Collar and Trace layers
            if self.openlog_connection.get_layers_iface().get_collar_layer():

                default_style = (
                    self.openlog_connection.get_layers_iface().get_collar_layer_style_file()
                )
                layer = self.openlog_connection.get_layers_iface().get_collar_layer()

                if collar_style and is_project:
                    layer.styleManager().removeStyle(
                        layer.styleManager().currentStyle()
                    )
                    layer.styleManager().addStyle("def", collar_style)
                    layer.styleManager().setCurrentStyle("def")
                    layer.triggerRepaint()
                else:
                    layer.loadNamedStyle(str(default_style))
                QgsProject.instance().addMapLayer(layer)
                self.openlog_connection.get_layers_iface().get_collar_layer().selectionChanged.connect(
                    self._collar_feature_selected
                )

                # Show attribute table
                settings = QgsSettings()
                prev_value = settings.value("qgis/dockAttributeTable")
                settings.setValue("qgis/dockAttributeTable", True)
                self.iface.showAttributeTable(
                    self.openlog_connection.get_layers_iface().get_collar_layer()
                )
                settings.setValue("qgis/dockAttributeTable", prev_value)

            # add effective trace layer
            if self.openlog_connection.get_layers_iface().get_collar_trace_layer():
                default_style = (
                    self.openlog_connection.get_layers_iface().get_collar_trace_layer_style_file()
                )
                layer = (
                    self.openlog_connection.get_layers_iface().get_collar_trace_layer()
                )

                if trace_style and is_project:
                    layer.styleManager().removeStyle(
                        layer.styleManager().currentStyle()
                    )
                    layer.styleManager().addStyle("def", trace_style)
                    layer.styleManager().setCurrentStyle("def")
                    layer.triggerRepaint()
                else:
                    layer.loadNamedStyle(str(default_style))
                QgsProject.instance().addMapLayer(layer)

            # add planned trace layer
            if self.openlog_connection.get_layers_iface().get_planned_trace_layer():
                default_style = (
                    self.openlog_connection.get_layers_iface().get_planned_trace_layer_style_file()
                )
                layer = (
                    self.openlog_connection.get_layers_iface().get_planned_trace_layer()
                )

                if planned_trace_style and is_project:
                    layer.styleManager().removeStyle(
                        layer.styleManager().currentStyle()
                    )
                    layer.styleManager().addStyle("def", planned_trace_style)
                    layer.styleManager().setCurrentStyle("def")
                    layer.triggerRepaint()
                else:
                    layer.loadNamedStyle(str(default_style))
                QgsProject.instance().addMapLayer(layer)

    def _local_grid(self) -> None:
        """
        Show LocalGridCreationDialog for local grid definition

        """
        if not self.local_grid_creation_dialog:
            self.local_grid_creation_dialog = LocalGridCreationDialog(
                self.openlog_connection, self.iface.mainWindow()
            )
            # Connection to dialog finished for temporary layer remove and openlog layer repaint trigger
            self.local_grid_creation_dialog.finished.connect(
                self._local_grid_creation_finished
            )

        self.local_grid_creation_dialog.show()

    def _add_collar(self) -> None:
        """
        Show CollarCreationDialog for collar add

        """
        if not self.collar_creation_dialog:
            self.collar_creation_dialog = CollarCreationDialog(
                self.openlog_connection, self.iface.mainWindow()
            )
            # Connection to dialog finished for temporary layer remove and openlog layer repaint trigger
            self.collar_creation_dialog.finished.connect(self._collar_add_finished)

        self.collar_creation_dialog.show()

    def _local_grid_creation_finished(self) -> None:
        """
        Delete CollarCreationDialog use for collar add and remove temporary layer used.

        Trigger repaint for openlog layer for new collar

        """
        if self.local_grid_creation_dialog:
            self.local_grid_creation_dialog.remove_temp_layer()
            self.local_grid_creation_dialog.deleteLater()
            self.local_grid_creation_dialog = None

            self.iface.mapCanvas().redrawAllLayers()
            self.iface.mapCanvas().refresh()

    def _collar_add_finished(self) -> None:
        """
        Delete CollarCreationDialog use for collar add and remove temporary layer used.

        Trigger repaint for openlog layer for new collar

        """
        if self.collar_creation_dialog:
            self.collar_creation_dialog.remove_temp_layer()
            self.collar_creation_dialog.deleteLater()
            self.collar_creation_dialog = None

            self.iface.mapCanvas().redrawAllLayers()
            self.iface.mapCanvas().refresh()

    def _selected_collar_desurveying(self):
        """
        It takes the hole_ids of the selected features in the collar layer and uses them to get the
        surveys from the openlog database.
        Then it uses the minimum curvature method to calculate the desurveying of the hole.
        """
        hole_ids = [
            f["hole_id"]
            for f in self.openlog_connection.get_layers_iface()
            .get_collar_layer()
            .selectedFeatures()
        ]
        for hole_id in hole_ids:

            # effective surveys
            surveys = self.openlog_connection.get_read_iface().get_surveys_from_collars(
                [hole_id]
            )
            collar = self.openlog_connection.get_read_iface().get_collar(hole_id)
            if len(surveys) != 0:
                # geom_trace (exact geometry)
                geom = minimum_curvature_method(surveys, collar, False)
                self.openlog_connection.set_collar_desurveying(
                    hole_id, geom, "geom_trace"
                )
                self.openlog_connection.commit()
                # effective_geom (display purpose)
                geom = minimum_curvature_method(surveys, collar, True)
                self.openlog_connection.set_collar_desurveying(
                    hole_id, geom, "effective_geom"
                )
                self.openlog_connection.commit()

            # planned survey : planned_trace (exact geometry)
            geom = minimum_curvature_method(None, collar, False)
            self.openlog_connection.set_collar_desurveying(
                hole_id, geom, "planned_trace"
            )
            self.openlog_connection.commit()

            # planned survey : planned_geom (display purpose)
            geom = minimum_curvature_method(None, collar, True)
            self.openlog_connection.set_collar_desurveying(
                hole_id, geom, "planned_geom"
            )
            self.openlog_connection.commit()

        self.iface.mapCanvas().redrawAllLayers()
        self.iface.mapCanvas().refresh()

    def _selected_collar_survey_definition(self) -> None:
        """
        If there are selected collar, display survey creation dialog and re-calculate the desurveying

        """
        hole_ids = [
            f["hole_id"]
            for f in self.openlog_connection.get_layers_iface()
            .get_collar_layer()
            .selectedFeatures()
        ]
        survey_creation_dialog = SurveyCreationDialog(
            self.openlog_connection, self.iface.mainWindow()
        )
        survey_creation_dialog.set_selected_collar(hole_ids)
        # survey_creation_dialog.exec()
        if (
            survey_creation_dialog.exec()
            and self.openlog_connection.selected_collar_surveying_available()
        ):
            self._selected_collar_desurveying()

    def _collar_feature_selected(self) -> None:
        """
        If there are selected collars with associated surveys, then enable the desurvey action
        """
        self.selected_colar_desurv_action.setEnabled(False)
        self.selected_colar_survey_definition_action.setEnabled(False)

        if self.openlog_connection.selected_collar_surveying_available():
            collar_id = [
                f["hole_id"]
                for f in self.openlog_connection.get_layers_iface()
                .get_collar_layer()
                .selectedFeatures()
            ]
            # surveys = self.openlog_connection.get_read_iface().get_surveys_from_collars(
            #    collar_id
            # )
            self.selected_colar_desurv_action.setEnabled(
                len(collar_id) != 0
                and not isinstance(self.openlog_connection, XplordbConnection)
            )

        if (
            self.openlog_connection.get_write_iface().selected_collar_survey_definition_available()
        ):
            self.selected_colar_survey_definition_action.setEnabled(True)

    def run(self):
        """Main process.

        :raises Exception: if there is no item in the feed
        """
        try:
            self.log(
                message=self.tr(
                    text="Everything ran OK.",
                    context="OpenlogPlugin",
                ),
                log_level=3,
                push=False,
            )
        except Exception as err:
            self.log(
                message=self.tr(
                    text="Houston, we've got a problem: {}".format(err),
                    context="OpenlogPlugin",
                ),
                log_level=2,
                push=True,
            )
