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.QtCore import QSize
from qgis.PyQt.QtGui import QColor, QIcon

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.assay_plot_data_item import AssayPlotDataItem
from openlog.gui.assay_visualization.categorical_symbology import CategoricalSymbology
from openlog.gui.assay_visualization.categorical_symbology_dialog import (
    CategoricalSymbologyDialog,
)
from openlog.gui.assay_visualization.config import json_utils
from openlog.gui.assay_visualization.config.assay_column_visualization_config import (
    AssayColumnVisualizationConfig,
    NumericalAssayColumnVisualizationConfig,
)
from openlog.gui.assay_visualization.plot_item_factory import AssayPlotItemFactory
from openlog.gui.pyqtgraph.CustomActionParameter import CustomActionParameter


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.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.transformation_group = ptree.Parameter.create(
            name=self.tr("Plot options"), type="group"
        )
        self.transformation_group.setOpts(expanded=False)
        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):
            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.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()

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

        if self.assay and plot_widget:
            if self.plot_item:
                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 = AssayPlotItemFactory.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()

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

        # 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 DiscreteCategoricalAssayColumnVisualizationConfig(AssayColumnVisualizationConfig):
    """
    Store visualization configuration for a discrete catgerical assay column.
        Can also access plot item if configuration created from visualization widget

        Configuration supported :

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

    def __init__(self, column: AssayColumn):
        super().__init__(column)
        self.as_extended = False
        self.pen_params = 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="o",
            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.symbol_parameter.sigValueChanged.connect(self.update_symbol)
        self.symbol_size_parameter.sigValueChanged.connect(self.update_symbol)
        self.symbol_color_parameter.sigValueChanged.connect(self.update_symbol)

        self.point_symbology = CategoricalSymbology()
        self.point_symbology.color_col = column.name

        self.define_symbology_button = CustomActionParameter(
            name=self.tr(""),
            type="action",
            width=60,
            icon=str(
                DIR_PLUGIN_ROOT / "resources" / "images" / "icon_manage_symbology.svg"
            ),
            tooltip="Set symbology",
        )
        self.define_symbology_button.sigActivated.connect(
            self._display_symbology_dialog
        )

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

        Args:
            data: json data

        """
        super().from_json(data)

        # Bar symbology
        self.point_symbology = CategoricalSymbology.from_json(data["point_symbology"])

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

        Returns:
            dict: configuration as dict

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

        # point symbology
        result["point_symbology"] = self.point_symbology.to_dict()

        return result

    def update_symbol(self):
        if self.plot_item and self.symbol_group:
            self.plot_item.setSymbol(self.symbol_parameter.value())
            self.plot_item.setSize(self.symbol_size_parameter.value())
            self.plot_item.setPen(self.symbol_color_parameter.value())

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

        Args:
            plot_item: pg.PlotDataItem
        """
        super().set_plot_item(plot_item)
        if self.plot_item:
            self.point_symbology = plot_item.symbology_config

    def add_children_to_root_param(self, params: ptree.Parameter):
        super().add_children_to_root_param(params)
        params.addChild(self.symbol_group)
        params.addChild(self.define_symbology_button)

    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.point_symbology.merge(other.point_symbology, replace=True)
        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.plot_item:
            self.plot_item.set_symbology(self.point_symbology)
            self.update_symbol()

    def _display_symbology_dialog(self):
        dialog = CategoricalSymbologyDialog()
        dialog.set_assay(self.assay)
        dialog.set_symbology(self.point_symbology)
        if dialog.exec():
            self.point_symbology = dialog.get_symbology()
            if self.plot_item:
                self.plot_item.set_symbology(self.point_symbology)
