# standard
from __future__ import annotations

from typing import Optional

# external
from pluggy import PluginManager  # type: ignore

# project
from openlog.datamodel.assay.generic_assay import AssayColumn
from openlog.plugins.assay.log_techniques import data_loader

# import default plugins
## connections
from openlog.plugins.connections import bdgeo_plugin, geotic_plugin, spatialite, xplordb
from openlog.plugins.hookspecs import AppHookSpecs, ConnectionHook, SerieTypeHook
from openlog.plugins.plugin_assay_column import PluginAssayColumn

## serie visualization
from openlog.plugins.visualization import (
    categorical_plugin,
    imagery_plugin,
    nominal_plugin,
    numerical_plugin,
    polar_plugin,
    spherical_plugin,
)
from openlog.plugins.visualization.log_techniques import (
    accessoire_plugin,
    forage_plugin,
    log_technique_plugin,
    remplissage_plugin,
    tubage_plugin,
)

# APP_NAME_DUPLICATE = "openlog-qgis-plugin"
APP_NAME_DUPLICATE = "openlog_qgis_plugin"


class AppPluginManager(PluginManager):
    """
    Our own custom subclass of PluginManager. We do this to add several convenience methods
    we use elsewhere in our application.
    """

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        # This is used as a cache
        self.__connection_hooks = None
        self.__serie_hooks = None
        self.__assay_generation_hooks = None

    def reset_cache(self) -> None:
        """Resets all cached properties"""
        self.__connection_hooks = None
        self.__serie_hooks = None
        self.__assay_generation_hooks = None

    def get_connections(self) -> tuple[ConnectionHook, ...]:
        if self.__connection_hooks is not None:
            return self.__connection_hooks
        return tuple(connection for connection in self.hook.connection())

    def get_serie_types(self) -> tuple[SerieTypeHook, ...]:
        if self.__serie_hooks is not None:
            return self.__serie_hooks
        return tuple(serie for serie in self.hook.serie_type())

    def get_one_serie_type(self, name: str) -> SerieTypeHook:

        serie_type_hooks = self.get_serie_types()
        hook = [hook for hook in serie_type_hooks if hook.name == name]
        if len(hook) > 0:
            return hook[0]
        else:
            return None

    def create_generic_assay_column(
        self,
        plugin_name: str,
        **kwargs,
    ) -> Optional[PluginAssayColumn]:
        """Create a PluginAssayColumn from a loaded plugin

        Args:
            plugin_name (str): plugin name to used
            **kwargs: Arbitrary keyword arguments. Define option for plugin use

        Returns:
            Optional[PluginAssayColumn]: created generic assay column, None if the plugin is not loaded
        """
        if self.__assay_generation_hooks is None:
            self.__assay_generation_hooks = {
                assay_column_config.name: assay_column_config
                for assay_column_config in self.hook.create_generic_assay_column()
            }
        if plugin_name in self.__assay_generation_hooks:
            return self.__assay_generation_hooks[
                plugin_name
            ].create_generic_assay_column(**kwargs)
        else:
            return None


def get_plugin_manager() -> AppPluginManager:
    """
    Plugin manager for the application. This function registers all plugin
    hooks, internal and external plugins.
    """
    # This is the plugin manager object we return that the application will use
    # to get information about register plugins.
    plugin_manager = AppPluginManager(APP_NAME_DUPLICATE)

    # Adds plugin hooks
    plugin_manager.add_hookspecs(AppHookSpecs)

    # Registers internal plugin hooks
    ## connections
    plugin_manager.register(spatialite)
    plugin_manager.register(xplordb)
    plugin_manager.register(bdgeo_plugin)
    plugin_manager.register(geotic_plugin)

    plugin_manager.register(numerical_plugin)
    plugin_manager.register(categorical_plugin)
    plugin_manager.register(nominal_plugin)
    plugin_manager.register(imagery_plugin)
    plugin_manager.register(polar_plugin)
    plugin_manager.register(spherical_plugin)

    plugin_manager.register(data_loader)
    plugin_manager.register(log_technique_plugin)
    plugin_manager.register(forage_plugin)
    plugin_manager.register(tubage_plugin)
    plugin_manager.register(accessoire_plugin)
    plugin_manager.register(remplissage_plugin)

    # This is the magic that allows our application to discover other plugins
    # installed alongside it
    plugin_manager.load_setuptools_entrypoints(APP_NAME_DUPLICATE)

    return plugin_manager
