import secrets
from typing import Any

import numpy as np
import pyqtgraph as pg
import pyqtgraph.parametertree as ptree
import pyqtgraph.parametertree.parameterTypes as pTypes
from qgis.PyQt.QtGui import QColor

from openlog.__about__ import DIR_PLUGIN_ROOT
from openlog.core import pint_utilities
from openlog.datamodel.assay.generic_assay import (
    AssayColumn,
    AssayDataExtent,
    AssayDomainType,
    GenericAssay,
)
from openlog.datamodel.assay.uncertainty import UncertaintyType
from openlog.gui.assay_visualization.config import json_utils
from openlog.gui.assay_visualization.config.assay_column_visualization_config import (
    NumericalAssayColumnVisualizationConfig,
)
from openlog.gui.assay_visualization.extended.config import (
    ExtendedAssayColumnVisualizationConfig,
)
from openlog.gui.pyqtgraph.BoldBoolParameter import BoldBoolParameter
from openlog.gui.pyqtgraph.CustomActionParameter import CustomActionParameter
from openlog.plugins.visualization.numerical.item import (
    AssayBarGraphItem,
    AssayPlotDataItem,
    create_discrete_plot_item_from_extended_numerical,
    create_discrete_plot_items,
    create_extended_numerical_plot_items,
    create_extended_plot_item_from_discrete_numerical,
)
from openlog.plugins.visualization.numerical.saver import (
    DiscreteNumericalSaver,
    ExtendedNumericalSaver,
)

symbols = {"●": "o", "■": "s", "✖": "x", "◆": "d"}


class DiscreteAssayColumnVisualizationConfig(NumericalAssayColumnVisualizationConfig):
    saverClass = DiscreteNumericalSaver
    shadingIcon = str(
        DIR_PLUGIN_ROOT / "resources" / "images" / "openlog_icon_line_shading.svg"
    )

    def __init__(self, column: str, extended_config: Any = None):
        """
        Store visualization configuration for a discrete assay column.
        Can also access plot item if configuration created from visualization widget

        Configuration supported :
        - visibility_param (bool) : assay column visibility (default : True)
        - pen_params (QPen) : assay column pen (default : black)
        - uncertainty_visibility_param (bool) : uncertainty visibility (default : True) (available in uncertainty defined)

        Args:
            column: (str) assay column name
        """

        super().__init__(column)
        self.is_splittable = True
        self.is_discrete = True
        self.is_minmax_registered = True
        self.is_switchable_config = True
        self.is_categorical = False
        self.as_extended = False

        self.extended_config = extended_config
        self.btn = CustomActionParameter(
            name="",
            type="action",
            width=60,
            icon=str(DIR_PLUGIN_ROOT / "resources" / "images" / "histogram.svg"),
            tooltip="Switch to bar chart",
        )

        self.btn.sigActivated.connect(self._switch_to_extended)

        color = "#" + "".join([secrets.choice("0123456789ABCDEF") for _ in range(6)])
        pen = self.pen_params.get_pen()
        pen.setBrush(QColor(color))
        self.pen_params.init_from_pen(pen)

        if self.column.uncertainty.get_uncertainty_type() != UncertaintyType.UNDEFINED:
            self.uncertainty_visibility_param = ptree.Parameter.create(
                name="Uncertainty", type="bool", value=True, default=True
            )
            self.uncertainty_visibility_param.sigValueChanged.connect(
                self._uncertainty_visibility_changed
            )
        else:
            self.uncertainty_visibility_param = None

        self.symbol_group = BoldBoolParameter(name="Point", type="bool", default=False)
        self.symbol_group.setOpts(expanded=False)
        self.symbol_parameter = pTypes.ListParameter(
            name="Symbol",
            type="str",
            value="",
            default="",
            limits=["●", "■", "✖", "◆"],
        )
        self.symbol_group.addChild(self.symbol_parameter)
        self.symbol_size_parameter = pTypes.SimpleParameter(
            name="Size", type="int", value=5, default=5, min=1, max=20
        )
        self.symbol_group.addChild(self.symbol_size_parameter)
        self.symbol_color_parameter = pTypes.ColorParameter(
            name="Color", value="blue", default="blue"
        )
        self.symbol_group.addChild(self.symbol_color_parameter)

        self.log_param = ptree.Parameter.create(
            name="Log",
            type="bool",
            value=False,
            default=False,
        )
        self.transformation_group.addChild(self.log_param)
        self.log_param.sigValueChanged.connect(self._set_log_mode)
        self.grid_x_param = ptree.Parameter.create(
            name="X grid",
            type="bool",
            value=False,
            default=False,
        )
        self.transformation_group.addChild(self.grid_x_param)
        self.grid_y_param = ptree.Parameter.create(
            name="Y grid",
            type="bool",
            value=False,
            default=False,
        )
        self.transformation_group.addChild(self.grid_y_param)
        self.grid_x_param.sigValueChanged.connect(self._display_axis_grid)
        self.grid_y_param.sigValueChanged.connect(self._display_axis_grid)

        self._uncertainty_plot_items = []
        self._detection_limit_items = []
        self._fill_curve_item = None

        # load plugins
        self.load_numerical_plugins()

        # Connect to extended config unit param change to update current unit
        if self.extended_config:
            self.extended_config.unit_parameter.sigValueChanged.connect(
                self._extended_unit_updated
            )

            # synchronization if visibility change in extended_config attribute
            self.extended_config.visibility_param.sigValueChanged.connect(
                self._synchronize_visibility_param
            )

    def connect_slot_to_visibility_param(self, slot):
        """
        Connect slot also to extended config.
        """
        super().connect_slot_to_visibility_param(slot)
        if self.extended_config:
            self.extended_config.visibility_param.sigValueChanged.connect(slot)

    def disconnect_slot_to_visibility_param(self, slot):
        """
        Connect slot also to extended config.
        """
        super().disconnect_slot_to_visibility_param(slot)
        if self.extended_config:
            try:
                self.extended_config.visibility_param.sigValueChanged.disconnect(slot)
            except Exception:
                pass

    def after_plot_item_creation(self) -> None:
        """
        Method executed after plot_item creation.
        """
        # trigger for displaying fill curve
        if self.fill_curve_handler:
            self.fill_curve_handler.display_fill_curve()

    def load_numerical_plugins(self) -> None:
        """
        Load numerical plugins
        """
        # need to import here because of circular imports
        from openlog.plugins.manager import get_plugin_manager

        self.detection_limit_visibility_param = None
        self.detection_limit_handler = None
        self.fill_curve_param = None
        self.fill_curve_handler = None
        if (
            get_plugin_manager().get_detection_limit_plugin().enable
            and self.column.detection_limit.is_detection_limits_defined()
        ):

            self.detection_limit_visibility_param = ptree.Parameter.create(
                name="Detection limit", type="bool", value=False, default=False
            )
            self.detection_limit_handler = (
                get_plugin_manager().get_detection_limit_plugin().handler(self)
            )
            self.detection_limit_visibility_param.sigValueChanged.connect(
                self._detection_visibility_changed
            )

        if get_plugin_manager().get_fill_curve_plugin().enable:
            self.fill_curve_handler = (
                get_plugin_manager().get_fill_curve_plugin().handler(self)
            )
            self.fill_curve_param = self.fill_curve_handler.get_pyqtgraph_param()
            self.fill_curve_param.sigValueChanged.connect(self._fill_curve_changed)

    def set_assay_iface(self, assay_iface):
        super().set_assay_iface(assay_iface)
        self.color_ramp_handler.set_assay_iface(assay_iface)
        if self.fill_curve_handler:
            self.fill_curve_handler.set_assay_iface(assay_iface)

    def set_hole_id(self, hole_id):
        super().set_hole_id(hole_id)
        self.color_ramp_handler.set_hole_id(hole_id)
        if self.fill_curve_handler:
            self.fill_curve_handler.set_hole_id(hole_id)

    def set_children_configs(self, config_list):
        super().set_children_configs(config_list)
        self.color_ramp_handler.update_global_min_max()
        if self.fill_curve_handler:
            self.fill_curve_handler.update_global_min_max()

    def _fill_curve_changed(self) -> None:
        """
        Slot for fill curve displaying.
        """
        if self.fill_curve_handler is not None:
            self.fill_curve_handler.display_fill_curve()

    def _detection_visibility_changed(self):
        """
        Slot for detection limits displaying.
        """
        if self.detection_limit_handler is not None:
            self.detection_limit_handler.display_detection_limits()

    def _display_axis_grid(self) -> None:
        """
        Enable/disable x and/or y grid on pyqtgraph plot.
        """
        x = self.grid_x_param.value()
        y = self.grid_y_param.value()
        plot_widget = self.get_current_plot_widget()

        if isinstance(self.plot_item, AssayPlotDataItem) and plot_widget:
            plot_widget.plotItem.display_grids(x, y)

    def _disable_axis_grid(self) -> None:
        """
        Remove grids from plotwidget without affecting parameters. (used for switching)
        """
        plot_widget = self.get_current_plot_widget()
        if isinstance(self.plot_item, AssayPlotDataItem) and plot_widget:
            plot_widget.plotItem.display_grids(False, False)

    def _disable_log(self) -> None:
        """
        Remove log mode for plotwidget without affecting parameters. (used for switching)
        """
        plot_widget = self.get_current_plot_widget()
        if isinstance(self.plot_item, AssayPlotDataItem) and plot_widget:
            plot_widget.plotItem.transform_to_log(False, False)

    def _set_log_mode(self) -> None:
        """
        Synchronize log checkbox with pyqtgraph built-in control parameters.
        We have to call PlotItem.UpdateLogMode() after synchronization.
        """
        plot_widget = self.get_current_plot_widget()
        if isinstance(self.plot_item, AssayPlotDataItem):
            # checkbox value
            checked = self.log_param.value()

            # check/uncheck pyqtgraph params and call PlotItem.UpdateLogMode()
            if plot_widget:
                if self.plot_item.domain == AssayDomainType.DEPTH:

                    plot_widget.plotItem.transform_to_log(checked, False)
                else:

                    plot_widget.plotItem.transform_to_log(False, checked)

                plot_widget.plotItem.updateLogMode()

            # update fill curve
            if self.fill_curve_handler:
                self.fill_curve_handler.display_fill_curve()
            # update pen
            self._update_plot_item_color_ramp()

            # update minimap
            self._display_minimap()

    def _synchronize_visibility_param(self):
        self.visibility_param.setValue(self.extended_config.visibility_param.value())

    def enable_unit_param_display(self, enable: bool) -> None:
        super().enable_unit_param_display(enable)

    def set_assay(self, assay: GenericAssay) -> None:
        super().set_assay(assay)
        self._detection_visibility_changed()

    def _visibility_changed(self) -> None:
        """
        Update plot visibility when parameter is changed

        """
        super()._visibility_changed()
        for plot_item in self._uncertainty_plot_items:
            visible = (
                self.uncertainty_visibility_param.value()
                and self.visibility_param.value()
            )
            plot_item.setVisible(visible)

    def _pen_updated(self) -> None:
        """
        Update plot pen when parameter is changed : use color ramp or pen value

        """
        if self.plot_item:
            self._update_plot_item_color_ramp()

    def set_uncertainty_plot_items(self, plot_items: [pg.PlotDataItem]) -> None:
        """
        Define plot items containing current assay data uncertainty

        Args:
            plot_items: [pg.PlotDataItem]
        """
        self._uncertainty_plot_items = plot_items
        self._uncertainty_visibility_changed()

    def _uncertainty_visibility_changed(self) -> None:
        """
        Update plot uncertainty item visibility when parameter is changed

        """
        visible = (
            self.uncertainty_visibility_param.value() and self.visibility_param.value()
        )
        for plot_item in self._uncertainty_plot_items:
            plot_item.setVisible(visible)

    def add_children_to_root_param(self, params: ptree.Parameter):

        self.btn.setOpts(
            icon=str(DIR_PLUGIN_ROOT / "resources" / "images" / "histogram.svg"),
            tooltip="Switch to bar chart",
        )
        params.addChild(self.btn)
        self._add_discrete_children_to_root_param(params)

    def _add_discrete_children_to_root_param(self, params: ptree.Parameter) -> None:

        if self.uncertainty_visibility_param:
            # Need to recreate parameter or a exception is raised by pyqtgraph because checkbox widget can be deleted when cleaning root param
            self.uncertainty_visibility_param = ptree.Parameter.create(
                name="Uncertainty",
                type="bool",
                value=self.uncertainty_visibility_param.value(),
                default=True,
            )
            self.uncertainty_visibility_param.sigValueChanged.connect(
                self._uncertainty_visibility_changed
            )
            params.addChild(self.uncertainty_visibility_param)

        if self.detection_limit_visibility_param:
            self.detection_limit_visibility_param = ptree.Parameter.create(
                name="Detection limit",
                type="bool",
                value=self.detection_limit_visibility_param.value(),
                default=False,
            )
            self.detection_limit_visibility_param.sigValueChanged.connect(
                self._detection_visibility_changed
            )
            self._detection_visibility_changed()
            params.addChild(self.detection_limit_visibility_param)

        if self.fill_curve_param:
            params.addChild(self.fill_curve_param)

        params.addChild(self.symbol_group)
        params.addChild(self.transformation_group)
        super().add_children_to_root_param(params)
        params.insertChild(pos=self.transformation_group, child=self.color_ramp_group)

    def get_min_max_values(self) -> None:
        """
        Set min and max value in self's attributes.
        """
        if not isinstance(self.plot_item, AssayPlotDataItem):
            return
        if self.plot_item.xData is not None and self.plot_item.yData is not None:
            if self.plot_item.domain == AssayDomainType.DEPTH:
                min_ = min(self.plot_item.xData)
                max_ = max(self.plot_item.xData)
            else:
                min_ = min(self.plot_item.yData)
                max_ = max(self.plot_item.yData)

            self.min_value = min_
            self.max_value = max_

    def set_plot_item(self, plot_item: pg.PlotDataItem) -> None:
        """
        Define plot item containing current assay data

        Args:
            plot_item: pg.PlotDataItem
        """
        if self.as_extended:
            self.extended_config.set_plot_item(plot_item)
            return
        super().set_plot_item(plot_item)

        # trigger _update_plot_item_color_ramp for line shading update
        self.plot_item.switcher.switchSignal.connect(self._update_plot_item_color_ramp)
        if self.plot_item:

            # Update plot item according to discrete parameters
            self._set_log_mode()
            self._display_axis_grid()
            self._update_plot_item_color_ramp()
            self._update_symbols()
            self.symbol_group.sigValueChanged.connect(self._update_symbols)
            self.symbol_parameter.sigValueChanged.connect(self._update_symbols)
            self.symbol_size_parameter.sigValueChanged.connect(self._update_symbols)
            self.symbol_color_parameter.sigValueChanged.connect(self._update_symbols)
            self._update_plot_item_color_ramp()

    def _update_symbols(self) -> None:
        """
        Update displayed symbols.
        """
        if not self.plot_item:
            return
        if self.symbol_group.value():
            self.plot_item.setSymbol(symbols.get(self.symbol_parameter.value()))
            self.plot_item.setSymbolSize(self.symbol_size_parameter.value())
            self.plot_item.setSymbolBrush(self.symbol_color_parameter.value())
        else:
            self.plot_item.setSymbol(None)

    def _update_plot_item_color_ramp(self) -> None:
        """
        Update plot item color ramp from current parameter

        """
        if not self.plot_item:
            return

        pen = self.pen_params.get_pen()
        gradient_pen = self.color_ramp_handler.get_gradient_pen()
        if self.color_ramp_group.value():
            width = pen.width()
            style = pen.style()
            gradient_pen.setStyle(style)
            gradient_pen.setWidth(width)
            self.plot_item.setPen(gradient_pen)
        else:
            pen = self.pen_params.get_pen()
            self.plot_item.setPen(pen)

    def get_copiable_config(self, other_config):
        """
        If other_config is switched version of self.
        """
        if isinstance(other_config, ExtendedAssayColumnVisualizationConfig):
            self._switch_to_extended()
            return self.extended_config
        else:
            return self

    def get_default_config(self):
        """
        Return default config according to assay extent.
        """
        if self.assay:
            if self.assay.assay_definition.data_extent == AssayDataExtent.DISCRETE:
                return self
            else:
                return self.extended_config
        else:
            return self

    def copy_from_config(self, other) -> None:
        """
        Copy configuration from another configuration.
        If a plot item is associated it will be updated

        Args:
            other: configuration to be copy
        """

        super().copy_from_config(other)
        self._copy_color_ramp_from_config(other)

        if other.uncertainty_visibility_param and self.uncertainty_visibility_param:
            self.uncertainty_visibility_param.setValue(
                other.uncertainty_visibility_param.value()
            )

        if (
            other.detection_limit_visibility_param
            and self.detection_limit_visibility_param
        ):
            self.detection_limit_visibility_param.setValue(
                other.detection_limit_visibility_param.value()
            )
        if self.fill_curve_handler:
            self.fill_curve_handler.copy_lut_from_config(other.fill_curve_handler)
            self.fill_curve_param.setValue(other.fill_curve_param.value())
            self.fill_curve_handler.legend_param.setValue(
                other.fill_curve_handler.legend_param.value()
            )
            # trigger signal
            self.fill_curve_param.sigValueChanged.emit(
                self.fill_curve_param, self.fill_curve_param.value()
            )

        self.symbol_group.setValue(other.symbol_group.value())
        self.symbol_parameter.setValue(other.symbol_parameter.value())
        self.symbol_size_parameter.setValue(other.symbol_size_parameter.value())
        self.symbol_color_parameter.setValue(other.symbol_color_parameter.value())

        self.log_param.setValue(other.log_param.value())
        self.grid_x_param.setValue(other.grid_x_param.value())
        self.grid_y_param.setValue(other.grid_y_param.value())

    def propagate_hole_and_assay_properties(self) -> None:
        self.extended_config.hole_id = self.hole_id
        self.extended_config.hole_display_name = self.hole_display_name
        self.extended_config.assay_name = self.assay_name
        self.extended_config.assay_display_name = self.assay_display_name

    def _copy_color_ramp_params_to_child_config(self):

        self.extended_config.min_value = self.min_value
        self.extended_config.max_value = self.max_value
        if self.color_ramp_handler.min_param.value() is not None:
            self.extended_config.color_ramp_handler.min_param.setValue(
                self.color_ramp_handler.min_param.value()
            )
            self.extended_config.color_ramp_handler.max_param.setValue(
                self.color_ramp_handler.max_param.value()
            )

    def _copy_transformation_params_to_child_config(self):
        """
        Copy global options to extended config : minimap, title options
        """
        self.extended_config.minimap_param.setValue(self.minimap_param.value())
        self.extended_config.title_assay_param.setValue(self.title_assay_param.value())
        self.extended_config.title_collar_param.setValue(
            self.title_collar_param.value()
        )
        self.extended_config.title_column_param.setValue(
            self.title_column_param.value()
        )

    def _switch_to_extended(self) -> None:
        """
        Switch current plot to extended one.
        Plot widget stay the same.
        Plot item is removed from plot widget, then we add extended item.
        A signal is emitted to swap configs at AssayWidget level.
        """

        plot_widget = self.get_current_plot_widget()
        is_stacked = self.plot_stacked is not None
        self.extended_config.child_configs = self.child_configs
        self.extended_config.discrete_configuration = self
        self.extended_config.domain = self.domain
        self.extended_config.hole_id = self.hole_id
        self.extended_config.assay_name = self.assay_name
        self.extended_config.set_assay(self.assay)
        self.extended_config.set_assay_iface(self.assay_iface)
        self.propagate_hole_and_assay_properties()
        if self.assay:
            if self.extended_config.plot_item is None and self.hole_id != "":
                if self.assay.assay_definition.data_extent == AssayDataExtent.DISCRETE:
                    self.extended_config.plot_item = (
                        create_extended_plot_item_from_discrete_numerical(
                            None,
                            self.assay,
                            self.column.name,
                            self.extended_config,
                        )
                    )
                else:
                    self.extended_config.plot_item = (
                        create_extended_numerical_plot_items(
                            None,
                            self.assay,
                            self.column.name,
                            self.extended_config,
                        )
                    )
                self.extended_config.color_ramp_handler.plot_item = (
                    self.extended_config.plot_item
                )
            if plot_widget:
                # remove detection limits
                for item in self._detection_limit_items:
                    plot_widget.removeItem(item)

                if self._fill_curve_item:
                    plot_widget.removeItem(self._fill_curve_item)

                # delete detectionlimititems since old assayplotdataitem will be deleted
                self._detection_limit_items.clear()

                if self.plot_item:
                    mode = self.plot_item.switcher.mode
                    self._disable_axis_grid()
                    self._disable_log()
                    plot_widget.removeItem(self.plot_item)
                    self.plot_item.setVisible(self.visibility_param.value())
                    if self.extended_config.plot_item:
                        self.extended_config.plot_item.switcher.switch(mode)

                for plot_item in self._uncertainty_plot_items:
                    plot_widget.removeItem(plot_item)

                plot_widget.addItem(self.extended_config.plot_item)
                if not is_stacked:
                    plot_widget.set_assay_column_config(self.extended_config)

            if is_stacked:
                self.extended_config.plot_stacked = plot_widget
                self.plot_stacked = None
            else:
                self.extended_config.plot_widget = plot_widget
                self.plot_widget = None
            self._copy_transformation_params_to_child_config()
            self.extended_config._display_minimap()
            self._copy_color_ramp_params_to_child_config()

        # trigger color ramp to display binned data if needed
        self.extended_config.color_ramp_group.sigValueChanged.emit(
            self.extended_config.color_ramp_group,
            self.extended_config.color_ramp_group.value(),
        )
        self.switch_config.config_signal.emit(self, self.extended_config)

    def _extended_unit_updated(self) -> None:
        self.unit_parameter.setValue(self.extended_config.unit_parameter.value())

    def _update_plot_item_unit(self, from_unit: str, to_unit: str) -> None:
        # Update extended configuration
        if self.extended_config:
            self.extended_config.unit_parameter.setValue(to_unit)

        # Update plot widget unit
        if self.plot_widget:
            self.plot_widget.update_displayed_unit(to_unit)

        # Update current min/max only if defined
        if self.min_value:
            min_ = pint_utilities.unit_conversion(from_unit, to_unit, self.min_value)
            self.min_value = min_

        if self.max_value:
            max_ = pint_utilities.unit_conversion(from_unit, to_unit, self.max_value)
            self.max_value = max_

        if not self.plot_item:
            return

        if self.as_extended:
            return

        self.log(
            f"Conversion for assay {self.assay_name} column {self.column.name} and collar {self.hole_display_name} from {from_unit} to {to_unit}"
        )

        xData, yData = self.plot_item.getOriginalDataset()
        if self.assay.assay_definition.domain == AssayDomainType.DEPTH:
            xData = pint_utilities.unit_conversion(from_unit, to_unit, xData)
        else:
            yData = pint_utilities.unit_conversion(from_unit, to_unit, yData)
        self.plot_item.setData(x=xData, y=yData)

        for plot_item in self._uncertainty_plot_items:
            opts = plot_item.opts
            if self.assay.assay_definition.domain == AssayDomainType.DEPTH:
                opts["x"] = pint_utilities.unit_conversion(
                    from_unit, to_unit, opts["x"]
                )
                opts["right"] = pint_utilities.unit_conversion(
                    from_unit, to_unit, opts["right"]
                )
                opts["left"] = pint_utilities.unit_conversion(
                    from_unit, to_unit, opts["left"]
                )
            else:
                opts["y"] = pint_utilities.unit_conversion(
                    from_unit, to_unit, opts["y"]
                )
                opts["top"] = pint_utilities.unit_conversion(
                    from_unit, to_unit, opts["top"]
                )
                opts["bottom"] = pint_utilities.unit_conversion(
                    from_unit, to_unit, opts["bottom"]
                )

            plot_item.setData(**opts)
            visible = (
                self.uncertainty_visibility_param.value()
                and self.visibility_param.value()
            )
            plot_item.setVisible(visible)

        self._update_plot_item_color_ramp()


class ExtendedNumericalAssayColumnVisualizationConfig(
    ExtendedAssayColumnVisualizationConfig, NumericalAssayColumnVisualizationConfig
):
    saverClass = ExtendedNumericalSaver
    shadingIcon = str(
        DIR_PLUGIN_ROOT / "resources" / "images" / "openlog_icon_bar_shading.svg"
    )

    def __init__(self, column: str, discrete_configuration: Any = None):
        """
        Store visualization configuration for an extended numerical assay column.
        Can also access plot item if configuration created from visualization widget

        Configuration supported :
        - visibility_param (bool) : assay_name column visibility (default : True)
        - pen_params (QPen) : assay_name column pen (default : black)
        - bar_color_param (QColor): assay_name column bar color (default : white)

        Args:
            column: (str) assay_name column name
        """
        super().__init__(column)
        self.is_splittable = True
        self.is_discrete = False
        self.is_minmax_registered = True
        self.is_switchable_config = True
        self.is_categorical = False
        self.as_discrete = False

        self.discrete_configuration = discrete_configuration
        self.btn = CustomActionParameter(
            name="",
            type="action",
            width=60,
            icon=str(DIR_PLUGIN_ROOT / "resources" / "images" / "icon_line_chart.svg"),
            tooltip="Switch to line chart",
        )
        self.btn.sigActivated.connect(self._switch_to_discrete)

        # Connect to discrete config unit param change to update current unit
        if self.discrete_configuration:
            self.discrete_configuration.unit_parameter.sigValueChanged.connect(
                self._discrete_unit_updated
            )

            # synchronization if visibility change in extended_config attribute
            self.discrete_configuration.visibility_param.sigValueChanged.connect(
                self._synchronize_visibility_param
            )

        # rename shading module
        self.color_ramp_group.setName("Bar shading")

    def after_plot_item_creation(self) -> None:
        """
        Method executed after plot_item creation.
        """
        self.color_ramp_handler.aggregate()

    def connect_slot_to_visibility_param(self, slot):
        """
        Connect slot also to extended config.
        """
        super().connect_slot_to_visibility_param(slot)
        if self.discrete_configuration:
            self.discrete_configuration.visibility_param.sigValueChanged.connect(slot)

    def disconnect_slot_to_visibility_param(self, slot):
        """
        Connect slot also to extended config.
        """
        super().disconnect_slot_to_visibility_param(slot)
        if self.discrete_configuration:
            try:
                self.discrete_configuration.visibility_param.sigValueChanged.disconnect(
                    slot
                )
            except Exception:
                pass

    def set_children_configs(self, config_list):
        super().set_children_configs(config_list)
        self.color_ramp_handler.update_global_min_max()

    def set_assay_iface(self, assay_iface):
        super().set_assay_iface(assay_iface)
        self.color_ramp_handler.set_assay_iface(assay_iface)
        if self.discrete_configuration:
            self.discrete_configuration.domain = self.domain

    def set_hole_id(self, hole_id):
        self.color_ramp_handler.set_hole_id(hole_id)
        super().set_hole_id(hole_id)
        if self.discrete_configuration:
            self.discrete_configuration.set_hole_id(hole_id)

    def _update_plot_item_color_ramp(self) -> None:
        """
        Update plot item color ramp from current parameter

        """
        if self.plot_item is None:
            return

        if self.color_ramp_group.value():
            colors = self.color_ramp_handler.get_colors()
            self.color_ramp_handler.plot_item.setOpts(brushes=colors)
            self.color_ramp_handler.aggregate()
        else:
            pen = self.pen_params.get_pen()
            color = self.bar_color_param.value()
            # remove brushes to have brush working
            self.plot_item.setOpts(brushes=None)
            self.plot_item.setOpts(brush=color)
            self.plot_item.setOpts(pen=pen)

    def get_min_max_values(self) -> None:

        if not isinstance(self.plot_item, AssayBarGraphItem):
            return

        if self.plot_item.opts is not None:
            # depth
            if (
                self.plot_item.opts["x1"] is None
                and len(self.plot_item.opts["width"]) > 0
            ):
                min_ = min(self.plot_item.opts["width"])
                max_ = max(self.plot_item.opts["width"])
            # time
            elif (
                self.plot_item.opts["y1"] is None
                and len(self.plot_item.opts["height"]) > 0
            ):
                min_ = min(self.plot_item.opts["height"])
                max_ = max(self.plot_item.opts["height"])
            else:
                min_ = max_ = None

            self.min_value = min_
            self.max_value = max_

    def set_plot_item(self, plot_item: pg.PlotDataItem) -> None:
        """
        Define plot item containing current assay data

        Args:
            plot_item: pg.PlotDataItem
        """
        if self.as_discrete:
            self.discrete_configuration.set_plot_item(plot_item)
            return

        super().set_plot_item(plot_item)
        if self.plot_item:
            self.color_ramp_handler.plot_item = plot_item
            # Define current parameters
            self.plot_item.setBrush(self.bar_color_param.value())

            # Connection to parameter changes
            self.bar_color_param.sigValueChanged.connect(
                lambda params, changes: self.plot_item.setBrush(changes)
            )

            self._update_plot_item_color_ramp()

    def _synchronize_visibility_param(self):
        self.visibility_param.setValue(
            self.discrete_configuration.visibility_param.value()
        )

    def enable_unit_param_display(self, enable: bool) -> None:
        super().enable_unit_param_display(enable)
        # if self.discrete_configuration:
        #     self.discrete_configuration.enable_unit_param_display(enable)

    def set_assay(self, assay: GenericAssay) -> None:
        super().set_assay(assay)
        # if self.discrete_configuration:
        #     self.discrete_configuration.set_assay(assay)

    def add_children_to_root_param(self, params: ptree.Parameter):
        if self.discrete_configuration:
            if self.as_discrete:
                self.btn.setOpts(
                    icon=str(
                        DIR_PLUGIN_ROOT / "resources" / "images" / "histogram.svg"
                    ),
                    tooltip="Switch to bar chart",
                )
                params.addChild(self.btn)
                self.discrete_configuration.add_children_to_root_param(params)
            else:
                self.btn.setOpts(
                    icon=str(DIR_PLUGIN_ROOT / "resources" / "images" / "curve.svg"),
                    tooltip="Switch to line chart",
                )
                params.addChild(self.btn)
                super().add_children_to_root_param(params)
                params.insertChild(
                    pos=self.transformation_group, child=self.color_ramp_group
                )
        else:

            super().add_children_to_root_param(params)
            params.insertChild(
                pos=self.transformation_group, child=self.color_ramp_group
            )

    def get_copiable_config(self, other_config):
        """
        If other_config is switched version of self.
        """
        if isinstance(other_config, DiscreteAssayColumnVisualizationConfig):
            self._switch_to_discrete()
            return self.discrete_configuration
        else:
            return self

    def get_default_config(self):
        """
        Return default config according to assay extent.
        """
        if self.assay:
            if self.assay.assay_definition.data_extent == AssayDataExtent.EXTENDED:
                return self
            else:
                return self.discrete_configuration
        else:
            return self

    def copy_from_config(self, other) -> None:
        """
        Copy configuration from another configuration.
        If a plot item is associated it will be updated

        Args:
            other: configuration to be copy
        """
        super().copy_from_config(other)
        self._copy_color_ramp_from_config(other)

    def propagate_hole_and_assay_properties(self) -> None:
        self.discrete_configuration.hole_id = self.hole_id
        self.discrete_configuration.hole_display_name = self.hole_display_name
        self.discrete_configuration.assay_name = self.assay_name
        self.discrete_configuration.assay_display_name = self.assay_display_name

    def _copy_color_ramp_params_to_child_config(self):

        self.discrete_configuration.min_value = self.min_value
        self.discrete_configuration.max_value = self.max_value
        if self.color_ramp_handler.min_param.value() is not None:
            self.discrete_configuration.color_ramp_handler.min_param.setValue(
                self.color_ramp_handler.min_param.value()
            )
            self.discrete_configuration.color_ramp_handler.max_param.setValue(
                self.color_ramp_handler.max_param.value()
            )

            if self.discrete_configuration.fill_curve_handler:
                self.discrete_configuration.fill_curve_handler.min_param.setValue(
                    self.color_ramp_handler.min_param.value()
                )
                self.discrete_configuration.fill_curve_handler.max_param.setValue(
                    self.color_ramp_handler.max_param.value()
                )

    def _copy_transformation_params_to_child_config(self):
        """
        Copy global options to discrete config : minimap, title options, grids, log
        """
        self.discrete_configuration.minimap_param.setValue(self.minimap_param.value())
        self.discrete_configuration.title_assay_param.setValue(
            self.title_assay_param.value()
        )
        self.discrete_configuration.title_collar_param.setValue(
            self.title_collar_param.value()
        )
        self.discrete_configuration.title_column_param.setValue(
            self.title_column_param.value()
        )

    def _switch_to_discrete(self) -> None:

        plot_widget = self.get_current_plot_widget()
        is_stacked = self.plot_stacked is not None
        self.discrete_configuration.child_configs = self.child_configs
        self.discrete_configuration.extended_config = self
        self.discrete_configuration.domain = self.domain
        self.discrete_configuration.hole_id = self.hole_id
        self.discrete_configuration.assay_name = self.assay_name
        self.discrete_configuration.set_assay(self.assay)
        self.discrete_configuration.set_assay_iface(self.assay_iface)

        self.propagate_hole_and_assay_properties()
        if self.assay:
            if self.discrete_configuration.plot_item is None and self.hole_id != "":
                if self.assay.assay_definition.data_extent == AssayDataExtent.EXTENDED:
                    self.discrete_configuration.plot_item = (
                        create_discrete_plot_item_from_extended_numerical(
                            None,
                            self.assay,
                            self.column.name,
                            self.discrete_configuration,
                        )
                    )
                else:
                    self.discrete_configuration.plot_item = create_discrete_plot_items(
                        None,
                        self.assay,
                        self.column.name,
                        self.discrete_configuration,
                    )
                self.discrete_configuration.color_ramp_handler.plot_item = (
                    self.discrete_configuration.plot_item
                )
            if plot_widget:

                if self.plot_item:
                    mode = self.plot_item.switcher.mode
                    plot_widget.removeItem(self.plot_item)
                    # remove binned item if displayed
                    if (
                        self.color_ramp_handler.binned_item
                        in plot_widget.plotItem.items
                    ):
                        plot_widget.removeItem(self.color_ramp_handler.binned_item)

                    self.plot_item.setVisible(self.visibility_param.value())
                    if self.discrete_configuration.plot_item:
                        self.discrete_configuration.plot_item.switcher.switch(mode)

                plot_widget.addItem(self.discrete_configuration.plot_item)
                # add uncertainties if present
                if len(self.discrete_configuration._uncertainty_plot_items) > 0:
                    plot_widget.addItem(
                        self.discrete_configuration._uncertainty_plot_items[0]
                    )
                if not is_stacked:
                    plot_widget.set_assay_column_config(self.discrete_configuration)

            if is_stacked:
                self.discrete_configuration.plot_stacked = plot_widget
                self.plot_stacked = None
            else:
                self.discrete_configuration.plot_widget = plot_widget
                self.plot_widget = None

            if self.discrete_configuration.fill_curve_handler:
                self.discrete_configuration.fill_curve_handler.display_fill_curve()

            self._copy_transformation_params_to_child_config()
            self.discrete_configuration._display_minimap()
            self.discrete_configuration._set_log_mode()
            self.discrete_configuration._display_axis_grid()
            self._copy_color_ramp_params_to_child_config()

        self.switch_config.config_signal.emit(self, self.discrete_configuration)

    def _discrete_unit_updated(self) -> None:
        self.unit_parameter.setValue(self.discrete_configuration.unit_parameter.value())

    def _update_plot_item_unit(self, from_unit: str, to_unit: str) -> None:
        # Update discrete configuration
        if self.discrete_configuration:
            self.discrete_configuration.unit_parameter.setValue(to_unit)

        # Update plot widget unit
        if self.plot_widget:
            self.plot_widget.update_displayed_unit(to_unit)

        if not self.plot_item:
            return

        if self.as_discrete:
            return

        self.log(
            f"Conversion for assay {self.assay_name} column {self.column.name} and collar {self.hole_display_name} from {from_unit} to {to_unit}"
        )

        if self.assay.assay_definition.domain == AssayDomainType.DEPTH:
            y_val = pint_utilities.unit_conversion(
                from_unit, to_unit, self.plot_item.opts.get("width")
            )
            self.plot_item.setOpts(width=y_val)
        else:
            y_val = pint_utilities.unit_conversion(
                from_unit, to_unit, self.plot_item.opts.get("height")
            )
            self.plot_item.setOpts(height=y_val)
