#! python3  # noqa: E265

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

# PyQGIS
from qgis.core import QgsApplication, 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_homepage__
from openlog.gui.dlg_settings import PlgOptionsFactory
from openlog.gui.welcome.welcome import WelcomeScreen
from openlog.processing.provider import OpenLogProvider
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.bdgeo.bdgeo_connection import BDGeoConnection
    from openlog.datamodel.connection.geotic.geotic_connection import GeoticConnection
    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.bdgeo_connection_dialog import BDGeoConnectionDialog
    from openlog.gui.connection.connection_dialog import ConnectionDialog
    from openlog.gui.connection.geotic_connection_dialog import GeoticConnectionDialog
    from openlog.gui.connection.xplordb_connection_dialog import XplordbConnectionDialog
    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
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.bdgeo.bdgeo_connection import BDGeoConnection
        from openlog.datamodel.connection.geotic.geotic_connection import (
            GeoticConnection,
        )
        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.bdgeo_connection_dialog import BDGeoConnectionDialog
        from openlog.gui.connection.connection_dialog import ConnectionDialog
        from openlog.gui.connection.geotic_connection_dialog import (
            GeoticConnectionDialog,
        )
        from openlog.gui.connection.xplordb_connection_dialog import (
            XplordbConnectionDialog,
        )
        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,
        )

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

        OpenLogConnectionFactory = None

        BDGeoConnection = None
        SpatialiteConnection = None
        XplordbConnection = None
        OpenLogConnection = None
        GeoticConnection = None
        AssayDomainType = None

        BDGeoConnectionDialog = None
        GeoticConnectionDialog = None
        XplordbConnectionDialog = 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"
RECENT_SPATIALITE_KEY = "/recent_spatialite"
NB_RECENT_SPATIALITE = 5

PROJECT_CONNECTION_TYPE_KEY = "/project_connection/type"


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.geotic_connect_action = None
        self.xplordb_connect_action = None

        self.spatialite_connect_action = None
        self.spatialite_open_action = None
        self.spatialite_menu = None

        self.bdgeo_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_homepage__))
        )

        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 ACTIONS
        self.xplordb_connect_action = QAction(
            QgsApplication.getThemeIcon("mIconPostgis.svg"),
            self.tr("xplordb"),
            self.iface.mainWindow(),
        )
        self.xplordb_connect_action.triggered.connect(self._xplordb_connect)
        self.connect_action.menu().addAction(self.xplordb_connect_action)

        self.geotic_connect_action = QAction(
            QgsApplication.getThemeIcon("mIconMssql.svg"),
            self.tr("Geotic"),
            self.iface.mainWindow(),
        )
        self.geotic_connect_action.triggered.connect(self._geotic_connect)
        self.connect_action.menu().addAction(self.geotic_connect_action)

        self.spatialite_connect_action = QAction(
            QgsApplication.getThemeIcon("mIconSpatialite.svg"),
            self.tr("Spatialite"),
            self.iface.mainWindow(),
        )
        self.connect_action.menu().addAction(self.spatialite_connect_action)

        # Menu for spatialite open and recent file
        self.spatialite_menu = QMenu(self.iface.mainWindow())
        self.spatialite_connect_action.setMenu(self.spatialite_menu)

        self.spatialite_open_action = QAction(
            QgsApplication.getThemeIcon("mIconSpatialite.svg"),
            self.tr("Open..."),
            self.iface.mainWindow(),
        )
        self.spatialite_open_action.triggered.connect(self._spatialite_connect)
        self.spatialite_menu.addAction(self.spatialite_open_action)

        settings = QgsSettings()
        recent_spatialite = settings.value(BASE_SETTINGS_KEY + RECENT_SPATIALITE_KEY)
        if recent_spatialite is not None:
            self._define_recent_spatialite_action(recent_spatialite)

        self.bdgeo_connect_action = QAction(
            QgsApplication.getThemeIcon("mIconPostgis.svg"),
            self.tr("BD Geo"),
            self.iface.mainWindow(),
        )
        self.bdgeo_connect_action.triggered.connect(self._bdgeo_connect)
        self.connect_action.menu().addAction(self.bdgeo_connect_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 holes"),
            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_)
            )
        )

        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()

    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 _define_recent_spatialite_action(self, file_list: [str]) -> None:
        """
        Define action for recent spatialite file open

        Args:
            file_list: [str] recent files
        """
        # Remove recent file actions
        actions = self.spatialite_menu.actions()
        for del_action in actions[1:]:
            self.spatialite_menu.removeAction(del_action)
        # Add action for files
        for file in file_list:
            self._add_recent_spatialite_action(file)

    def _add_recent_spatialite_action(self, file: str) -> None:
        """
        Add an action for spatialite file open

        Args:
            file: (str) filename
        """
        action = QAction(
            QgsApplication.getThemeIcon("mIconSpatialite.svg"),
            file,
            self.iface.mainWindow(),
        )
        action.triggered.connect(lambda clicked: self._open_spatialite_file(file))
        self.spatialite_menu.addAction(action)

    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)

    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()
                    )
            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:
            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_data.setEnabled(False)
            self.import_action_collar.setEnabled(False)
            self.import_action_survey.setEnabled(False)
            self.import_action_lith.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 _xplordb_connect(self) -> None:
        """
        Execute ConnectionDialog for xplordb database connection

        """
        widget = XplordbConnectionDialog(self.iface.mainWindow())
        if widget.exec():
            connection = widget.get_connection_model()
            self._define_current_connection(XplordbConnection(connection))

    def _geotic_connect(self) -> None:
        """
        Execute ConnectionDialog for geotic database connection

        """
        widget = GeoticConnectionDialog(self.iface.mainWindow())
        if widget.exec():
            connection = widget.get_connection_model()
            self._define_current_connection(GeoticConnection(connection))

    def _bdgeo_connect(self) -> None:
        """
        Execute ConnectionDialog for xplordb database connection

        """
        widget = BDGeoConnectionDialog(self.iface.mainWindow())
        if widget.exec():
            connection = widget.get_connection_model()
            self._define_current_connection(BDGeoConnection(connection))

    def _spatialite_connect(self) -> None:
        """
        Select a spatialite database

        """
        filename, filter_use = QFileDialog.getOpenFileName(
            self.iface.mainWindow(),
            self.tr("Select file"),
            "",
            "Spatialite database (*.db)",
        )
        if filename:
            self._open_spatialite_file(filename)

    def _open_spatialite_file(self, filename: str) -> None:
        """
        Open spatialite file and add it to recent spatialite file

        Args:
            filename: (str) filename
        """
        # Define current connection
        self._define_current_connection(
            SpatialiteConnection(Path(filename), new_file=False)
        )

        # Add new file to recent file
        settings = QgsSettings()
        recent_spatialite = settings.value(BASE_SETTINGS_KEY + RECENT_SPATIALITE_KEY)
        # Insert new file
        if recent_spatialite is None:
            recent_spatialite = []
        recent_spatialite.insert(0, filename)

        def unique(sequence):
            seen = set()
            return [x for x in sequence if not (x in seen or seen.add(x))]

        # Get unique files and limit number of file
        recent_spatialite = unique(recent_spatialite)
        recent_spatialite = recent_spatialite[:NB_RECENT_SPATIALITE]
        settings.setValue(BASE_SETTINGS_KEY + RECENT_SPATIALITE_KEY, recent_spatialite)

        # Update actions
        self._define_recent_spatialite_action(recent_spatialite)

    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

        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.renderer().clone()
                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.renderer().clone()
                if (
                    layer
                    != self.openlog_connection.get_layers_iface().get_collar_trace_layer()
                ):
                    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.setRenderer(collar_style)
                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)

            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.setRenderer(trace_style)
                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:
            surveys = self.openlog_connection.get_read_iface().get_surveys_from_collars(
                [hole_id]
            )
            if len(surveys) != 0:
                collar = self.openlog_connection.get_read_iface().get_collar(hole_id)
                geom = minimum_curvature_method(surveys, collar)
                self.openlog_connection.set_collar_desurveying(hole_id, geom)

        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(surveys) != 0)

        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,
            )
