from dataclasses import dataclass, field
from typing import List, Union

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

from openlog.core import pint_utilities
from openlog.gui.assay_visualization.config.assay_column_visualization_config import (
    AssayColumnVisualizationConfig,
)
from openlog.gui.assay_visualization.config.assay_visualization_config import (
    AssayVisualizationConfig,
)
from openlog.gui.assay_visualization.stacked.stacked_utils import (
    config_list_default_unit,
    is_config_list_valid_for_stack,
)
from openlog.toolbelt.translator import PlgTranslator


@dataclass
class StackedConfiguration:
    """
    StackedConfiguration
    Contains list of AssayColumnVisualizationConfig
    Can also access plot and plot item if configuration created from visualization widget

    """

    def __init__(self) -> None:
        self.tr = PlgTranslator().tr
        self.is_visible: bool = True
        self.plot: pg.PlotWidget = None
        self.name: str = ""
        self.config_list: List[AssayColumnVisualizationConfig] = []
        self.unit_parameter = pTypes.SimpleParameter(
            name=self.tr("Conversion"),
            type="str",
            value="",
            default="",
        )
        self.unit_parameter.sigValueChanged.connect(self._update_conversion_unit)
        self._conversion_unit = ""

    def set_plot(self, plot: pg.PlotWidget) -> None:
        """
        Define plot containing current stacked data

        Args:
            plot: pg.PlotWidget
        """
        self.plot = plot

    def is_valid(self) -> bool:
        """
        Check if StackedConfiguration is valid :
        - only one unit used
        - only one series type used
        - only valid series type used

        Returns: True if StackedConfiguration is valid, False otherwise

        """
        return is_config_list_valid_for_stack(self.config_list)

    def get_pyqtgraph_params(self) -> List[ptree.Parameter]:
        """
        Get pyqtgraph param to display in pyqtgraph ParameterTree

        Returns: List[ptree.Parameter] containing all configuration params

        """
        params = []

        if pint_utilities.is_pint_unit(self._conversion_unit):
            params.append(self.unit_parameter)

        return params

    def _update_conversion_unit(self) -> None:
        new_conversion_unit = self.unit_parameter.value()

        # Check if conversion possible, if not restore previous value
        if not pint_utilities.can_convert(new_conversion_unit, self._conversion_unit):
            self.unit_parameter.setValue(self._conversion_unit)
            return
        elif new_conversion_unit != self._conversion_unit:
            # Propagate unit change to assay column
            for config in self.config_list:
                config.set_conversion_unit(new_conversion_unit)

            self._conversion_unit = new_conversion_unit

            # Update stack plot widget unit
            if self.plot:
                self.plot.update_displayed_unit(self._conversion_unit)

    def add_column_config(self, config: AssayColumnVisualizationConfig) -> None:
        """
        Add assay column configuration

        Args:
            config: AssayColumnVisualizationConfig
        """

        # Check if config already available
        if not self.config_list.count(config):
            self.config_list.append(config)

        display_unit = False

        # Define conversion unit
        default_unit = config_list_default_unit(self.config_list)
        display_unit = default_unit and pint_utilities.is_pint_unit(default_unit)
        if display_unit:
            self._conversion_unit = default_unit
            self.unit_parameter.setDefault(default_unit)
            if not self.unit_parameter.value():
                self.unit_parameter.setValue(default_unit)

        for col in self.config_list:
            col.enable_unit_param_display(not display_unit)

    def copy_from_config(
        self,
        other: Union[AssayVisualizationConfig, AssayColumnVisualizationConfig],
        only_visible: bool = False,
    ) -> None:
        """
        Copy configuration from another configuration.

        Args:
            other(Union[AssayVisualizationConfig, AssayColumnVisualizationConfig]): configuration to be copy
            only_visible : bool : True if only visible configuration must be used, False for all (default False)
        """
        if only_visible and not self.is_visible:
            return
        if isinstance(other, AssayVisualizationConfig):
            for col in other.column_config.values():
                self.copy_from_column_config(col, only_visible=only_visible)
        if isinstance(other, AssayColumnVisualizationConfig):
            self.copy_from_column_config(other)

    def copy_from_column_config(
        self,
        other: AssayColumnVisualizationConfig,
        only_visible: bool = False,
    ) -> None:
        """
        Copy column configuration from another configuration.

        Args:
            other(AssayColumnVisualizationConfig): configuration to be copy
            only_visible : bool : True if only visible configuration must be used, False for all (default False)
        """
        for col in self.config_list:
            # Update configuration if same assay and column
            if (
                col.assay_name == other.assay_name
                and col.column.name == other.column.name
            ):
                if only_visible and not col.visibility_param.value():
                    continue
                col.copy_from_config(other)


@dataclass
class StackedConfigurationList:
    def __init__(self):
        """
        Container of StackedConfiguration.
        List can be accessed with list attributes.

        Define several method for list modification and query

        """
        self.list = []

    def remove_config(self, config: StackedConfiguration) -> None:
        """
        Remove configuration from list
        If plot is associated to configuration, plot is also deleted

        Args:
            config: (StackedConfiguration) configuration
        """

        if config.plot is not None:
            # Update visibility even if plot is deleted later
            # so we can count number of visible plot if main thread as not process event
            config.plot.setVisible(False)
            config.plot.deleteLater()
        self.list.remove(config)
