from typing import Any

import pyqtgraph as pg
import pyqtgraph.parametertree as ptree
import pyqtgraph.parametertree.parameterTypes as pTypes

from openlog.__about__ import DIR_PLUGIN_ROOT
from openlog.datamodel.assay.generic_assay import AssayColumn
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,
)
from openlog.gui.assay_visualization.extended.bar_symbology import BarSymbology
from openlog.gui.assay_visualization.extended.bar_symbology_dialog import (
    BarSymbologyDialog,
)
from openlog.gui.assay_visualization.extended.text_bar_graph_items import (
    TextBarGraphItems,
)
from openlog.gui.pyqtgraph.CustomActionParameter import CustomActionParameter


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.is_splittable = True
        self.is_discrete = True
        self.is_minmax_registered = False
        self.is_switchable_config = False
        self.is_categorical = True

        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)


class ExtendedCategoricalAssayColumnVisualizationConfig(AssayColumnVisualizationConfig):
    def __init__(self, column: AssayColumn):
        """
        Store visualization configuration for an extended assay categorical 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)
        - bar_symbology : symbology for each category
        - text_visibility (bool) : assay column category visibility (default : True)
        - text_color_param (QColor): assay column category text color (default : black)

        Args:
            column: (AssayColumn) assay column
        """
        super().__init__(column)

        self.is_splittable = True
        self.is_discrete = False
        self.is_minmax_registered = False
        self.is_switchable_config = False
        self.is_categorical = True

        self.text_bar_graph_item = None
        self.pen_params.setName(self.tr("Bar pen"))
        self.bar_symbology = BarSymbology()
        self.bar_symbology.pattern_col = column.name
        self.bar_symbology.color_col = column.name
        self.bar_symbology.scale_col = column.name
        self.text_visibility = ptree.Parameter.create(
            name=self.tr("Display category"), type="bool", value=True, default=True
        )
        self.text_color_param = pTypes.ColorParameter(
            name=self.tr("Text color"), value="black", default="black"
        )
        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 ExtendedCategoricalAssayColumnVisualizationConfig from json data (see to_dict)

        Args:
            data: json data

        """
        super().from_json(data)

        # Bar symbology
        self.bar_symbology = BarSymbology.from_json(data["bar_symbology"])

        # Text
        self.text_visibility.setValue(data["display_category"])
        self.text_color_param.setValue(json_utils.color_from_json(data["text_color"]))

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

        Returns:
            dict: configuration as dict

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

        # Bar symbology
        result["bar_symbology"] = self.bar_symbology.to_dict()

        # Text
        result["display_category"] = self.text_visibility.value()
        result["text_color"] = json_utils.color_to_dict(self.text_color_param.value())

        return result

    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:
            self.bar_symbology = plot_item.symbology_config

    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 self.text_bar_graph_item:
            self.plot_widget.removeItem(self.text_bar_graph_item)
            plot_stacked.addItem(self.text_bar_graph_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 self.text_bar_graph_item:
            plot_stacked.removeItem(self.text_bar_graph_item)
            self.plot_widget.addItem(self.text_bar_graph_item)

    def set_category_text_bar_graph_item(
        self, text_bar_graph_item: TextBarGraphItems
    ) -> None:
        """
        Define plot item containing category text for current assay data

        Args:
            text_bar_graph_item: TextBarGraphItems
        """
        self.text_bar_graph_item = text_bar_graph_item
        if self.text_bar_graph_item:
            # Define current parameters
            self.text_bar_graph_item.setPen(self.pen_params.value())
            self.text_bar_graph_item.setTextColor(self.text_color_param.value())

            # Connection to parameter changes
            self.pen_params.sigValueChanged.connect(
                lambda params, changes: self.text_bar_graph_item.setPen(changes)
            )

            self.text_color_param.sigValueChanged.connect(
                lambda params, changes: self.text_bar_graph_item.setTextColor(changes)
            )

            self.text_visibility.sigValueChanged.connect(
                lambda params, changes: self.text_bar_graph_item.setVisible(changes)
            )

    def add_children_to_root_param(self, params: ptree.Parameter):
        super().add_children_to_root_param(params)
        params.addChild(self.text_visibility)
        params.addChild(self.text_color_param)
        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.pen_params.setValue(other.pen_params.pen)

        self.bar_symbology.merge(other.bar_symbology, replace=True)

        self.text_visibility.setValue(other.text_visibility.value())
        self.text_color_param.setValue(other.text_color_param.value())

        if self.plot_item:
            self.plot_item.set_symbology(self.bar_symbology)
            self.plot_item.drawPicture()

    def _display_symbology_dialog(self):
        dialog = BarSymbologyDialog()
        dialog.set_assay(self.assay)
        dialog.set_symbology(self.bar_symbology)
        if dialog.exec():
            self.bar_symbology = dialog.get_symbology()
            if self.plot_item:
                self.plot_item.set_symbology(self.bar_symbology)
                self.plot_item.drawPicture()
            if self.plot_widget:
                self.plot_widget.plotItem.minimap.addItem(self.plot_item)
