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


class DiscreteAssayColumnVisualizationConfig(NumericalAssayColumnVisualizationConfig):
    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=self.tr(""),
            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.pen
        pen.setBrush(QColor(color))

        if self.column.uncertainty.get_uncertainty_type() != UncertaintyType.UNDEFINED:
            self.uncertainty_visibility_param = ptree.Parameter.create(
                name=self.tr("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 = ptree.Parameter.create(name=self.tr("Point"), type="group")
        self.symbol_group.setOpts(expanded=False)
        self.symbol_parameter = pTypes.ListParameter(
            name=self.tr("Point"),
            type="str",
            value="",
            default="",
            limits=["", "o", "s", "x", "d"],
        )
        self.symbol_group.addChild(self.symbol_parameter)
        self.symbol_size_parameter = pTypes.SimpleParameter(
            name=self.tr("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=self.tr("Color"), value="blue", default="blue"
        )
        self.symbol_group.addChild(self.symbol_color_parameter)

        self.log_param = ptree.Parameter.create(
            name=self.tr("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=self.tr("X grid"),
            type="bool",
            value=False,
            default=False,
        )
        self.transformation_group.addChild(self.grid_x_param)
        self.grid_y_param = ptree.Parameter.create(
            name=self.tr("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 = []

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

        if (
            isinstance(self.plot_item, AssayPlotDataItem)
            and self.plot_item.topLevelWidget()
        ):
            self.plot_item.topLevelWidget().ctrl.xGridCheck.setChecked(x)
            self.plot_item.topLevelWidget().ctrl.yGridCheck.setChecked(y)

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

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

                    self.plot_item.topLevelWidget().ctrl.logXCheck.setChecked(checked)
                else:

                    self.plot_item.topLevelWidget().ctrl.logYCheck.setChecked(checked)

                self.plot_item.topLevelWidget().updateLogMode()

            # 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 from_json(self, data: dict) -> None:
        """
        Define DiscreteAssayColumnVisualizationConfig from json data (see to_dict)

        Args:
            data: json data

        """
        super().from_json(data)

        # Uncertainty
        if "uncertainty_visible" in data and self.uncertainty_visibility_param:
            self.uncertainty_visibility_param.setValue(data["uncertainty_visible"])

        # Symbol
        self.symbol_parameter.setValue(data["symbol"]["type"])
        self.symbol_size_parameter.setValue(data["symbol"]["size"])
        self.symbol_color_parameter.setValue(
            json_utils.color_from_json(data["symbol"]["color"])
        )

        # Color ramp (for now only name is supported)
        self.color_ramp_name_param.setValue(data["color_ramp"]["name"])
        if "min" in data["color_ramp"] and data["color_ramp"]["min"]:
            self.min_color_ramp_param.setValue(data["color_ramp"]["min"])
        if "max" in data["color_ramp"] and data["color_ramp"]["max"]:
            self.max_color_ramp_param.setValue(data["color_ramp"]["max"])
        self.pen_color_ramp_use_param.setValue(data["color_ramp"]["enable"])

        if "extended_config" in data:
            self.extended_config.from_json(data["extended_config"])
        if "as_extended" in data:
            self.as_extended = data["as_extended"]

    def to_dict(self) -> dict:
        """
        Convert configuration to dict (use for json export)

        Returns:
            dict: configuration as dict

        """
        result = super().to_dict()

        # Uncertainty
        if self.uncertainty_visibility_param:
            result["uncertainty_visible"] = self.uncertainty_visibility_param.value()

        # Symbol
        result["symbol"] = {}
        result["symbol"]["type"] = self.symbol_parameter.value()
        result["symbol"]["size"] = self.symbol_size_parameter.value()
        result["symbol"]["color"] = json_utils.color_to_dict(
            self.symbol_color_parameter.value()
        )

        # Color ramp
        result["color_ramp"] = {}
        result["color_ramp"]["name"] = self.color_ramp_name_param.value()
        # Min/max value only if defined
        if self.min_color_ramp_param.value():
            result["color_ramp"]["min"] = self.min_color_ramp_param.value()
        if self.max_color_ramp_param.value():
            result["color_ramp"]["max"] = self.max_color_ramp_param.value()
        result["color_ramp"]["enable"] = self.pen_color_ramp_use_param.value()
        self._update_color_ramp_from_name()

        if self.extended_config:
            result["extended_config"] = self.extended_config.to_dict()
            result["as_extended"] = self.as_extended

        return result

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

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

    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 stack_widget(self, plot_stacked: pg.PlotWidget) -> None:
        """
        Add current plot item to a new plot widget and remove them from current plot widget

        Args:
            plot_stacked (pg.PlotWidget): plot widget where item are added
        """
        super().stack_widget(plot_stacked)
        if self.plot_widget and not self.as_extended:
            for item in self._uncertainty_plot_items:
                self.plot_widget.removeItem(item)
                plot_stacked.addItem(item)

    def unstack_widget(self, plot_stacked: pg.PlotWidget) -> None:
        """
        Remove current plot item from a stacked plot widget and add them to current plot widget

        Args:
            plot_stacked (pg.PlotWidget): plot widget where items are available
        """
        super().unstack_widget(plot_stacked)
        if self.plot_widget and not self.as_extended:
            for item in self._uncertainty_plot_items:
                plot_stacked.removeItem(item)
                self.plot_widget.addItem(item)

    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):
        if self.extended_config:
            if self.as_extended:
                self.btn.setOpts(
                    icon=str(DIR_PLUGIN_ROOT / "resources" / "images" / "curve.svg"),
                    tooltip="Switch to line chart",
                )
                params.addChild(self.btn)
                self.extended_config.add_children_to_root_param(params)
            else:
                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)
        else:
            self._add_discrete_children_to_root_param(params)

    def _add_discrete_children_to_root_param(self, params: ptree.Parameter) -> None:
        super().add_children_to_root_param(params)
        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=self.tr("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)
        params.addChild(self.symbol_group)
        params.addChild(self.transformation_group)
        params.addChild(self.color_ramp_group)

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

        Args:
            plot_item: pg.PlotDataItem
        """
        super().set_plot_item(plot_item)
        if self.plot_item:
            # Define current parameters
            if self.symbol_parameter.value():
                self.plot_item.setSymbol(self.symbol_parameter.value())
            else:
                self.plot_item.setSymbol(None)
            self.plot_item.setSymbolSize(self.symbol_size_parameter.value())
            self.plot_item.setSymbolBrush(self.symbol_color_parameter.value())

            # Define color ramp min/max
            self.define_color_ramp_min_max_from_plot_item()

            # Update plot item according to discrete parameters
            self._set_log_mode()
            self._display_axis_grid()
            self._update_plot_item_color_ramp()

            self.symbol_parameter.sigValueChanged.connect(
                lambda params, changes: self.plot_item.setSymbol(changes)
                if changes
                else self.plot_item.setSymbol(None)
            )
            self.symbol_size_parameter.sigValueChanged.connect(
                lambda params, changes: self.plot_item.setSymbolSize(changes)
            )
            self.symbol_color_parameter.sigValueChanged.connect(
                lambda params, changes: self.plot_item.setSymbolBrush(changes)
            )
            self._update_plot_item_color_ramp()

    def define_color_ramp_min_max_from_plot_item(self, force: bool = False) -> None:
        """
        Define color ramp min/max from plot item data

        """
        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_color_ramp_param.setDefault(min_)
            self.max_color_ramp_param.setDefault(max_)

            # Update current min/max only if not defined
            if not self.min_color_ramp_param.value() or force:
                self.min_color_ramp_param.setValue(
                    min_, self._update_plot_item_color_ramp
                )

            if not self.max_color_ramp_param.value() or force:
                self.max_color_ramp_param.setValue(
                    max_, self._update_plot_item_color_ramp
                )

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

        """
        min_val = self.min_color_ramp_param.value()
        max_val = self.max_color_ramp_param.value()

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

        if self.plot_item:

            if self.log_param.value():
                index = 0 if self.plot_item.domain == AssayDomainType.DEPTH else 1
                data = self.plot_item.getData()[index]
                min_val, max_val = np.log10(min_val), np.log10(max_val)
                if np.isinf(min_val):
                    min_val = np.nanmin(data)
                if np.isinf(max_val):
                    max_val = np.nanmax(data)

            cm = self.color_ramp_parameter.value()
            pen = self.pen_params.pen
            if cm and self.pen_color_ramp_use_param.value():
                pen = cm.getPen(
                    span=(
                        min_val,
                        max_val,
                    ),
                    width=pen.width(),
                    orientation="horizontal"
                    if self.plot_item.domain == AssayDomainType.DEPTH
                    else "vertical",
                )
                self.plot_item.setPen(pen)
            else:
                self.plot_item.setPen(pen)

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

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

        if self.extended_config:
            self.extended_config.copy_from_config(other.extended_config)
            if self.as_extended != other.as_extended:
                self._switch_to_extended()
        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.color_ramp_name_param.setValue(
            self.color_ramp_name_param.value()
        )
        self.extended_config.color_ramp_parameter.setValue(
            self.color_ramp_parameter.value(),
            self.extended_config._update_plot_item_color_ramp,
        )
        self.extended_config.min_color_ramp_param.setValue(
            self.min_color_ramp_param.value(),
            self.extended_config._update_plot_item_color_ramp,
        )
        self.extended_config.max_color_ramp_param.setValue(
            self.max_color_ramp_param.value(),
            self.extended_config._update_plot_item_color_ramp,
        )

        self.extended_config.pen_color_ramp_use_param.setValue(
            self.pen_color_ramp_use_param.value(),
            self.extended_config._update_plot_item_color_ramp,
        )

    def _copy_transformation_params_to_child_config(self):
        self.extended_config.minimap_param.setValue(self.minimap_param.value())

    def _switch_to_extended(self) -> None:
        plot_widget = self.get_current_plot_widget()
        self.as_extended = not self.as_extended
        # copy color ramp parameters
        self._copy_color_ramp_params_to_child_config()
        self._copy_transformation_params_to_child_config()
        self.extended_config.plot_widget = plot_widget
        mode = "length"

        if self.assay and plot_widget:
            if self.plot_item:
                mode = self.plot_item.switcher.mode
                plot_widget.removeItem(self.plot_item)

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

            if not self.as_extended:
                plot_widget.removeItem(self.extended_config.plot_item)
                self.plot_item = create_discrete_plot_items(
                    plot_widget, self.assay, self.column.name, self
                )
                for plot_item in self._uncertainty_plot_items:
                    visible = (
                        self.uncertainty_visibility_param.value()
                        and self.visibility_param.value()
                    )
                    plot_item.setVisible(visible)
                self._set_log_mode()
                self._display_minimap()

            else:
                self.propagate_hole_and_assay_properties()
                self.plot_item = create_extended_plot_item_from_discrete_numerical(
                    plot_widget,
                    self.assay,
                    self.column.name,
                    self.extended_config,
                )
                self._set_log_mode()
                self.extended_config._display_minimap()
            self.plot_item.setVisible(self.visibility_param.value())
            self.plot_item.switcher.switch(mode)

        # Update parameters
        self.visibility_param.clearChildren()
        self.add_children_to_root_param(self.visibility_param)
        # Update color ramp
        self.extended_config._update_plot_item_color_ramp()

    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_color_ramp_param.value():
            min_ = pint_utilities.unit_conversion(
                from_unit, to_unit, self.min_color_ramp_param.value()
            )
            self.min_color_ramp_param.setValue(min_, self._update_plot_item_color_ramp)

            default_min = pint_utilities.unit_conversion(
                from_unit, to_unit, self.min_color_ramp_param.defaultValue()
            )
            self.min_color_ramp_param.setDefault(default_min)

        if self.max_color_ramp_param.value():
            max_ = pint_utilities.unit_conversion(
                from_unit, to_unit, self.max_color_ramp_param.value()
            )
            self.max_color_ramp_param.setValue(max_, self._update_plot_item_color_ramp)

            default_max = pint_utilities.unit_conversion(
                from_unit, to_unit, self.max_color_ramp_param.defaultValue()
            )
            self.max_color_ramp_param.setDefault(default_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
):
    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=self.tr(""),
            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
            )

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

        """
        if isinstance(self.plot_item, AssayBarGraphItem):
            cm = self.color_ramp_parameter.value()

            if cm and self.pen_color_ramp_use_param.value():
                values = (
                    self.plot_item.opts["width"]
                    if self.plot_item.opts["x1"] is None
                    else self.plot_item.opts["height"]
                )
                min_ = self.min_color_ramp_param.value()
                max_ = self.max_color_ramp_param.value()
                # min/max scaling to have 0-1 range
                mapper = cm.map((values - min_) / (max_ - min_))

                self.plot_item.setOpts(brushes=mapper)
            else:
                pen = self.pen_params.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 _update_color_ramp_from_name(self) -> None:
        """
        Update displayed color ramp from chosen name

        """
        cm = pg.colormap.get(self.color_ramp_name_param.value())
        self.color_ramp_parameter.setValue(cm)

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

        Args:
            plot_item: pg.PlotDataItem
        """
        super().set_plot_item(plot_item)
        if self.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.define_color_ramp_min_max_from_plot_item()
            self._update_plot_item_color_ramp()

    def define_color_ramp_min_max_from_plot_item(self, force: bool = False) -> None:
        """
        Define color ramp min/max from plot item data

        """
        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_color_ramp_param.setDefault(min_)
            self.max_color_ramp_param.setDefault(max_)

            # Update current min/max only if not defined
            if (not self.min_color_ramp_param.value() or force) and min_:
                self.min_color_ramp_param.setValue(
                    min_, self._update_plot_item_color_ramp
                )

            if (not self.max_color_ramp_param.value() or force) and max_:
                self.max_color_ramp_param.setValue(
                    max_, self._update_plot_item_color_ramp
                )

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

    def from_json(self, data: dict) -> None:
        """
        Define ExtendedNumericalAssayColumnVisualizationConfig from json data (see to_dict)

        Args:
            data: json data

        """
        super().from_json(data)

        if "discrete_config" in data:
            self.discrete_configuration.from_json(data["discrete_config"])
        if "as_discrete" in data:
            self.as_discrete = data["as_discrete"]

    def to_dict(self) -> dict:
        """
        Convert configuration to dict (use for json export)

        Returns:
            dict: configuration as dict

        """
        result = super().to_dict()

        if self.discrete_configuration:
            result["discrete_config"] = self.discrete_configuration.to_dict()
            result["as_discrete"] = self.as_discrete

        return result

    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.addChild(self.color_ramp_group)
        else:
            super().add_children_to_root_param(params)
            params.addChild(self.color_ramp_group)

    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 self.discrete_configuration:
            self.discrete_configuration.copy_from_config(other.discrete_configuration)
            if self.as_discrete != other.as_discrete:
                self._switch_to_discrete()

    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.color_ramp_name_param.setValue(
            self.color_ramp_name_param.value()
        )
        self.discrete_configuration.color_ramp_parameter.setValue(
            self.color_ramp_parameter.value(),
            self.discrete_configuration._update_plot_item_color_ramp,
        )
        self.discrete_configuration.min_color_ramp_param.setValue(
            self.min_color_ramp_param.value(),
            self.discrete_configuration._update_plot_item_color_ramp,
        )
        self.discrete_configuration.max_color_ramp_param.setValue(
            self.max_color_ramp_param.value(),
            self.discrete_configuration._update_plot_item_color_ramp,
        )
        self.discrete_configuration.pen_color_ramp_use_param.setValue(
            self.pen_color_ramp_use_param.value(),
            self.discrete_configuration._update_plot_item_color_ramp,
        )

    def _copy_transformation_params_to_child_config(self):
        self.discrete_configuration.minimap_param.setValue(self.minimap_param.value())

    def _switch_to_discrete(self) -> None:
        plot_widget = self.get_current_plot_widget()
        self.as_discrete = not self.as_discrete
        self.discrete_configuration.plot_widget = plot_widget
        self._copy_color_ramp_params_to_child_config()
        self._copy_transformation_params_to_child_config()
        mode = "length"

        if self.assay and plot_widget:

            if self.plot_item:
                mode = self.plot_item.switcher.mode
                plot_widget.removeItem(self.plot_item)
            if not self.as_discrete:
                plot_widget.removeItem(self.discrete_configuration.plot_item)
                self.plot_item = create_extended_numerical_plot_items(
                    plot_widget, self.assay, self.column.name, self
                )
            else:
                self.propagate_hole_and_assay_properties()
                self.plot_item = create_discrete_plot_item_from_extended_numerical(
                    plot_widget,
                    self.assay,
                    self.column.name,
                    self.discrete_configuration,
                )
                self.discrete_configuration._set_log_mode()
                self.discrete_configuration._display_minimap()

            self.plot_item.setVisible(self.visibility_param.value())
            self.plot_item.switcher.switch(mode)

        # Update parameters
        self.visibility_param.clearChildren()
        self.add_children_to_root_param(self.visibility_param)

    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)
