# 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

# import default plugins
## connections
from openlog.plugins.connections import geotic_plugin, spatialite, xplordb
from openlog.plugins.extensions.advanced_imports import advanced_imports_plugin

# premium
from openlog.plugins.extensions.ags_reader import ags_reader_plugin
from openlog.plugins.extensions.azimuth_sorting import azimuth_sorting_plugin
from openlog.plugins.extensions.barchart_projection import barchart_projection_plugin
from openlog.plugins.extensions.cross_symbology import cross_symbology_plugin
from openlog.plugins.extensions.crossplots import crossplots_plugin
from openlog.plugins.extensions.data_binning import data_binning_plugin
from openlog.plugins.extensions.depth_ticks import depth_ticks_plugin
from openlog.plugins.extensions.detection_limit import detection_limit_plugin
from openlog.plugins.extensions.fill_curve import fill_curve_plugin
from openlog.plugins.extensions.heterogenous_plots import heterogenous_plots_plugin
from openlog.plugins.extensions.inspector import inspector_plugin
from openlog.plugins.extensions.manage_assay import manage_assay_plugin
from openlog.plugins.extensions.stacked_charts import stacked_charts_plugin

# hooks
from openlog.plugins.hookspecs import (
    AdvancedImportsHook,
    AgsReaderHook,
    AppHookSpecs,
    AzimuthSortingHook,
    BarChartProjectionHook,
    ConnectionHook,
    CrossPlotsHook,
    CrossSymbologyHook,
    DataBinningHook,
    DataManagementHook,
    DepthTicksHook,
    DetectionLimitHook,
    FillCurveHook,
    HeterogenousPlotsHook,
    InspectorPluginHook,
    ManageAssayHook,
    SerieTypeHook,
    StackedChartsHook,
)
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,
)

# 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.__data_management_hooks = None
        self.__connection_hooks = None
        self.__serie_hooks = None
        self.__assay_generation_hooks = None

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

    def get_data_managers(self) -> tuple[DataManagementHook, ...]:
        if self.__data_management_hooks is not None:
            return self.__data_management_hooks
        return tuple(data_manager for data_manager in self.hook.data_management())

    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_inspector_plugin(self) -> InspectorPluginHook:

        return self.hook.inspector_plugin()[0]

    def get_manager_assay_plugin(self) -> ManageAssayHook:

        return self.hook.manage_assay_plugin()[0]

    def get_detection_limit_plugin(self) -> DetectionLimitHook:

        return self.hook.detection_plugin()[0]

    def get_azimuth_sorting_plugin(self) -> AzimuthSortingHook:

        return self.hook.azimuth_sorting_plugin()[0]

    def get_depth_ticks_plugin(self) -> DepthTicksHook:

        return self.hook.depth_ticks_plugin()[0]

    def get_heterogenous_plots_plugin(self) -> HeterogenousPlotsHook:

        return self.hook.heterogenous_plots_plugin()[0]

    def get_crossplots_plugin(self) -> CrossPlotsHook:

        return self.hook.crossplots_plugin()[0]

    def get_fill_curve_plugin(self) -> FillCurveHook:

        return self.hook.fill_curve_plugin()[0]

    def get_cross_symbology_plugin(self) -> CrossSymbologyHook:

        return self.hook.cross_symbology_plugin()[0]

    def get_barchart_projection_plugin(self) -> BarChartProjectionHook:

        return self.hook.barchart_projection_plugin()[0]

    def get_data_binning_plugin(self) -> DataBinningHook:

        return self.hook.data_binning_plugin()[0]

    def get_ags_reader_plugin(self) -> AgsReaderHook:

        return self.hook.ags_reader_plugin()[0]

    def get_advanced_imports_plugin(self) -> AdvancedImportsHook:

        return self.hook.advanced_imports_plugin()[0]

    def get_stacked_charts_plugin(self) -> StackedChartsHook:

        return self.hook.stacked_charts_plugin()[0]


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

    # premium extensions
    plugin_manager.register(inspector_plugin)
    plugin_manager.register(manage_assay_plugin)
    plugin_manager.register(detection_limit_plugin)
    plugin_manager.register(azimuth_sorting_plugin)
    plugin_manager.register(depth_ticks_plugin)
    plugin_manager.register(heterogenous_plots_plugin)
    plugin_manager.register(crossplots_plugin)
    plugin_manager.register(fill_curve_plugin)
    plugin_manager.register(cross_symbology_plugin)
    plugin_manager.register(barchart_projection_plugin)
    plugin_manager.register(data_binning_plugin)
    plugin_manager.register(ags_reader_plugin)
    plugin_manager.register(advanced_imports_plugin)
    plugin_manager.register(stacked_charts_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
