import json
import secrets

from openlog.datamodel.assay.generic_assay import GenericAssay


class CategoricalSymbology:

    """
    Base class for mapping categorical symbology.
    Default symbology parameter for this base class is color.
    """

    def __init__(self) -> None:
        self.color_col = ""
        self.color_map = {}

    @classmethod
    def from_json(cls, data):
        """
        Define BarSymbology from json data (see to_json)

        Args:
            data: json data

        Returns: BarSymbology

        """
        res = cls()
        res.color_col = data["color_col"]
        for k, v in data["color_map"].items():
            res.color_map[k] = str(v)
        return res

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

        Returns:
            dict: configuration as dict

        """
        result = {}

        result["color_col"] = self.color_col
        result["color_map"] = self.color_map
        return result

    def to_json(self) -> str:
        """
        Write BarSymbolog as JSON data

        Returns: JSON data

        """
        return json.dumps(
            self.to_dict(),
            default=lambda o: o.__dict__,
            sort_keys=True,
            indent=4,
        )

    @staticmethod
    def _set_lowercase_keys(map: dict) -> dict:
        """
        Turn dictionnary keys into lowercase

        Returns: dict
        """
        result = {k.lower(): v for k, v in map.items()}
        return result

    def merge(self, other, replace: bool = True) -> None:
        """
        Merge map from a BarSymbology

        Args:
            other: (BarSymbology)
        """

        self.color_col = other.color_col
        color_map_lower = self._set_lowercase_keys(self.color_map)

        for key, val in other.color_map.items():
            if key.lower() not in color_map_lower or replace:
                self.color_map[key] = val

    def add_color(self, key: str, color: str = None) -> None:
        """
        Add a key symbology to map (if no symbology defined get_random_symbology used)

        Args:
            key: (str) key
            color: (str) color (default : None)
        """
        if color is None:
            self.color_map[key] = "#" + "".join(
                [secrets.choice("0123456789ABCDEF") for _ in range(6)]
            )
        else:
            self.color_map[key] = color

    def get_color(self, key: str) -> str:
        """
        Get color

        Args:
            key: (str) key

        Returns: (str) html color as string

        """
        key = key.lower()
        color_map_lower = self._set_lowercase_keys(self.color_map)
        if key in color_map_lower:
            color = color_map_lower[key]
        else:
            # Random HTML color (6 values)
            color = "#" + "".join(
                [secrets.choice("0123456789ABCDEF") for _ in range(6)]
            )
        return color

    def init_from_assay(self, assay: GenericAssay) -> None:
        # Get all values from assay
        val_dict = {}

        if self.color_col and self.color_col not in val_dict:
            y = assay.get_distinct_col_values(self.color_col)
            val_dict[self.color_col] = y

        self.color_map.clear()
        if self.color_col:
            for category in val_dict[self.color_col]:
                if category not in self.color_map:
                    self.add_color(str(category))
