"""
WiP.

Soon.
"""

# region [Imports]

import gc
import os
import re
import sys
import json
import queue
import math
import base64
import pickle
import random
import shelve
import dataclasses
import shutil
import asyncio
import logging
import sqlite3
import platform
import importlib
import subprocess
import unicodedata
import inspect

from time import sleep, process_time, process_time_ns, perf_counter, perf_counter_ns
from io import BytesIO, StringIO
from abc import ABC, ABCMeta, abstractmethod
from copy import copy, deepcopy
from enum import Enum, Flag, auto, unique
from time import time, sleep
from pprint import pprint, pformat
from pathlib import Path
from string import Formatter, digits, printable, whitespace, punctuation, ascii_letters, ascii_lowercase, ascii_uppercase
from timeit import Timer
from typing import TYPE_CHECKING, Union, Callable, Iterable, Optional, Mapping, Any, IO, TextIO, BinaryIO, Hashable, Generator, Literal, TypeVar, TypedDict, AnyStr, Annotated
from zipfile import ZipFile, ZIP_LZMA
from numbers import Real
from datetime import datetime, timezone, timedelta
from tempfile import TemporaryDirectory
from textwrap import TextWrapper, fill, wrap, dedent, indent, shorten
from functools import wraps, partial, lru_cache, singledispatch, total_ordering, cached_property
from importlib import import_module, invalidate_caches
from contextlib import contextmanager, asynccontextmanager, nullcontext, closing, ExitStack, suppress
from statistics import mean, mode, stdev, median, variance, pvariance, harmonic_mean, median_grouped
from collections import Counter, ChainMap, deque, namedtuple, defaultdict
from urllib.parse import urlparse
from importlib.util import find_spec, module_from_spec, spec_from_file_location
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor
from importlib.machinery import SourceFileLoader
from colorsys import rgb_to_hls, rgb_to_hsv, hls_to_rgb, hsv_to_rgb
import attr
from weakref import WeakValueDictionary
from gidapptools.general_helper.color.preset_colors.web_colors import get_webcolors_data
try:
    from PySide6.QtGui import QColor
    PYSIDE6_AVAILABLE = True
except ImportError:
    PYSIDE6_AVAILABLE = False
# endregion[Imports]

# region [TODO]


# endregion [TODO]

# region [Logging]


# endregion[Logging]

# region [Constants]

THIS_FILE_DIR = Path(__file__).parent.absolute()

# endregion[Constants]

INT_OR_FLOAT = Union[int, float]

COLOR_FLOAT_TYPE = Union[tuple[float, float, float], tuple[float, float, float, float]]

COLOR_INT_TYPE = Union[tuple[int, int, int], tuple[int, int, int, float]]


def _validate_color_float(instance: object, attribute: attr.Attribute, value: Optional[float]) -> None:

    if 0.0 > value > 1.0:

        raise ValueError(f"{attribute.name!r}-value can only be between 0 and 1, not {value!r}.")


def _alpha_converter(alpha: Union[int, float] = None) -> float:
    if alpha is None:
        return 1.0

    return float(alpha)


class BaseColor(ABC):
    float_round_n: int = 4

    def __iter__(self):
        return (getattr(self, field.name) for field in attr.fields(self.__class__) if field.metadata.get("is_core_color", False) is True)

    @abstractmethod
    def as_rgb_float(self, include_alpha: bool = True, alpha_as_int: bool = False) -> COLOR_FLOAT_TYPE:
        ...

    @abstractmethod
    def as_rgb_int(self, include_alpha: bool = True, alpha_as_int: bool = False) -> COLOR_INT_TYPE:
        ...

    @abstractmethod
    def as_hls(self, include_alpha: bool = True, alpha_as_int: bool = False) -> COLOR_FLOAT_TYPE:
        ...

    @abstractmethod
    def as_hsv(self, include_alpha: bool = True, alpha_as_int: bool = False) -> COLOR_FLOAT_TYPE:
        ...

    @cached_property
    def qcolor(self) -> Optional["QColor"]:
        if PYSIDE6_AVAILABLE:
            return QColor(*self.as_rgb_int(True, True))

    def to_q_style_rgba_string(self,) -> str:
        values = (str(i) for i in self.as_rgb_int(alpha_as_int=True))
        return "rgba(" + ', '.join(values) + ')'

    def __repr__(self) -> str:
        class_name = self.__class__.__name__.removesuffix("Color")
        _out = f"{class_name}(" + ', '.join(repr(x) for x in self)
        if self.name is not None:
            _out += f', name={self.name!r}'

        if self.aliases:
            _out += f", aliases={tuple(self.aliases)!r}"
        return _out + ')'

    def with_alpha(self, alpha: Union[int, float]) -> "BaseColor":
        if isinstance(alpha, int) or alpha > 1:
            alpha = round(alpha / 255, self.float_round_n)
        return self.__class__(*list(self)[:-1], alpha)


@attr.s(auto_attribs=True, auto_detect=True, slots=True, weakref_slot=True, frozen=True, order=True, repr=False)
class RGBColor(BaseColor):
    red: float = attr.ib(validator=_validate_color_float, metadata={'is_core_color': True})
    green: float = attr.ib(validator=_validate_color_float, metadata={'is_core_color': True})
    blue: float = attr.ib(validator=_validate_color_float, metadata={'is_core_color': True})

    alpha: float = attr.ib(default=1.0, converter=_alpha_converter, validator=_validate_color_float, metadata={'is_core_color': True})

    name: str = attr.ib(default=None)
    aliases: Iterable[str] = attr.ib(factory=set, converter=attr.converters.default_if_none(factory=set))

    def as_rgb_float(self, include_alpha: bool = True, alpha_as_int: bool = False) -> COLOR_FLOAT_TYPE:
        _out = list(self)[:-1]
        if include_alpha is True:
            if alpha_as_int is True:
                _out.append(int(self.alpha * 255))
            else:
                _out.append(self.alpha)
        return tuple(_out)

    def as_rgb_int(self, include_alpha: bool = True, alpha_as_int: bool = False) -> COLOR_INT_TYPE:
        _out = [int(sub_col * 255) for sub_col in list(self)[:-1]]
        if include_alpha is True:
            if alpha_as_int is True:
                _out.append(int(self.alpha * 255))
            else:
                _out.append(self.alpha)
        return tuple(_out)

    def as_hls(self, include_alpha: bool = True, alpha_as_int: bool = False) -> COLOR_FLOAT_TYPE:
        _out = [round(col, self.float_round_n) for col in rgb_to_hls(*list(self)[:-1])]
        if include_alpha is True:
            if alpha_as_int is True:
                _out.append(int(self.alpha * 255))
            else:
                _out.append(self.alpha)
        return _out

    def as_hsv(self, include_alpha: bool = True, alpha_as_int: bool = False) -> COLOR_FLOAT_TYPE:
        _out = [round(col, self.float_round_n) for col in rgb_to_hsv(*list(self)[:-1])]
        if include_alpha is True:
            if alpha_as_int is True:
                _out.append(int(self.alpha * 255))
            else:
                _out.append(self.alpha)
        return _out


@attr.s(auto_attribs=True, auto_detect=True, slots=True, weakref_slot=True, frozen=True, order=True, repr=False)
class HLSColor(BaseColor):
    hue: float = attr.ib(validator=_validate_color_float, metadata={'is_core_color': True})
    saturation: float = attr.ib(validator=_validate_color_float, metadata={'is_core_color': True})
    lightness: float = attr.ib(validator=_validate_color_float, metadata={'is_core_color': True})

    alpha: float = attr.ib(default=1.0, converter=_alpha_converter, validator=_validate_color_float, metadata={'is_core_color': True})

    name: str = attr.ib(default=None)
    aliases: Iterable[str] = attr.ib(factory=set, converter=attr.converters.default_if_none(factory=set))

    def as_hls(self, include_alpha: bool = True, alpha_as_int: bool = False) -> COLOR_FLOAT_TYPE:
        _out = list(self)[:-1]
        if include_alpha is True:
            if alpha_as_int is True:
                _out.append(int(self.alpha * 255))
            else:
                _out.append(self.alpha)
        return tuple(_out)

    def as_rgb_float(self, include_alpha: bool = True, alpha_as_int: bool = False) -> COLOR_FLOAT_TYPE:
        _out = [round(col, self.float_round_n) for col in hls_to_rgb(*list(self)[:-1])]
        if include_alpha is True:
            if alpha_as_int is True:
                _out.append(int(self.alpha * 255))
            else:
                _out.append(self.alpha)
        return tuple(_out)

    def as_rgb_int(self, include_alpha: bool = True, alpha_as_int: bool = False) -> COLOR_INT_TYPE:
        _out = [int(sub_col * 255) for sub_col in hls_to_rgb(*list(self)[:-1])]
        if include_alpha is True:
            if alpha_as_int is True:
                _out.append(int(self.alpha * 255))
            else:
                _out.append(self.alpha)
        return tuple(_out)

    def as_hsv(self, include_alpha: bool = True, alpha_as_int: bool = False) -> COLOR_FLOAT_TYPE:
        _out = [round(col, self.float_round_n) for col in rgb_to_hsv(*hls_to_rgb(*list(self)[:-1]))]
        if include_alpha is True:
            if alpha_as_int is True:
                _out.append(int(self.alpha * 255))
            else:
                _out.append(self.alpha)
        return _out


@attr.s(auto_attribs=True, auto_detect=True, slots=True, weakref_slot=True, frozen=True, order=True, repr=False)
class HSVColor(BaseColor):
    hue: float = attr.ib(validator=_validate_color_float, metadata={'is_core_color': True})
    saturation: float = attr.ib(validator=_validate_color_float, metadata={'is_core_color': True})
    value: float = attr.ib(validator=_validate_color_float, metadata={'is_core_color': True})

    alpha: float = attr.ib(default=1.0, converter=_alpha_converter, validator=attr, metadata={'is_core_color': True})

    name: str = attr.ib(default=None)
    aliases: Iterable[str] = attr.ib(factory=set, converter=attr.converters.default_if_none(factory=set))

    def as_hsv(self, include_alpha: bool = True, alpha_as_int: bool = False) -> COLOR_FLOAT_TYPE:
        _out = list(self)[:-1]
        if include_alpha is True:
            if alpha_as_int is True:
                _out.append(int(self.alpha * 255))
            else:
                _out.append(self.alpha)
        return tuple(_out)

    def as_hls(self, include_alpha: bool = True, alpha_as_int: bool = False) -> COLOR_FLOAT_TYPE:
        _out = [round(col, self.float_round_n) for col in rgb_to_hls(*hsv_to_rgb(*list(self)[:-1]))]
        if include_alpha is True:
            if alpha_as_int is True:
                _out.append(int(self.alpha * 255))
            else:
                _out.append(self.alpha)
        return _out

    def as_rgb_float(self, include_alpha: bool = True, alpha_as_int: bool = False) -> COLOR_FLOAT_TYPE:
        _out = [round(col, self.float_round_n) for col in hsv_to_rgb(*list(self)[:-1])]
        if include_alpha is True:
            if alpha_as_int is True:
                _out.append(int(self.alpha * 255))
            else:
                _out.append(self.alpha)
        return tuple(_out)

    def as_rgb_int(self, include_alpha: bool = True, alpha_as_int: bool = False) -> COLOR_INT_TYPE:
        _out = [int(sub_col * 255) for sub_col in hsv_to_rgb(*list(self)[:-1])]
        if include_alpha is True:
            if alpha_as_int is True:
                _out.append(int(self.alpha * 255))
            else:
                _out.append(self.alpha)
        return tuple(_out)


color_class_map: dict[str, type[BaseColor]] = {"rgb": RGBColor,
                                               "hls": HLSColor,
                                               "hsv": HSVColor}


class ColorRegistry:

    def __init__(self) -> None:
        self.colors_by_name: dict[str:RGBColor] = {}
        self.colors_by_rgba_value: dict[tuple[float, float, float, float]:RGBColor] = {}

    def _add_color(self, color: RGBColor) -> None:
        if color.name:
            self.colors_by_name[color.name.casefold()] = color
        for alias in color.aliases:
            self.colors_by_name[alias.casefold()] = color

        self.colors_by_rgba_value[color.as_rgb_float(True, False)] = color

    def get_color(self, r: float, g: float, b: float, alpha: float = 1.0, name: str = None, aliases: Iterable[str] = None) -> RGBColor:
        if name is not None:
            if color := self.colors_by_name.get(name.casefold()):
                return color
        if color := self.colors_by_rgba_value.get((r, g, b, alpha)):
            return color

        color = RGBColor(r, g, b, alpha, name, aliases)
        self._add_color(color)
        return color

    def color_factory(self, r: Union[int, float], g: Union[int, float], b: Union[int, float], alpha: float = 1.0, name: str = None, aliases: Iterable[str] = None):

        if any(sub_v > 1 for sub_v in (r, g, b)) or all(isinstance(sub_v, int) for sub_v in (r, g, b)):
            values = tuple(round(sub_v / 255, BaseColor.float_round_n) for sub_v in (r, g, b))

        if alpha is not None and alpha > 1:
            alpha = round(alpha / 255, BaseColor.float_round_n)
        return self.get_color(*values, alpha=alpha, name=name, aliases=aliases)

    def __call__(self, r: Union[int, float], g: Union[int, float], b: Union[int, float], alpha: float = 1.0, name: str = None, aliases: Iterable[str] = None) -> Any:
        return self.color_factory(r, g, b, alpha, name, aliases)

    def get_color_by_name(self, name: str) -> "RGBColor":
        return self.colors_by_name[name.casefold()]


Color = ColorRegistry()
for p_color in get_webcolors_data():
    Color(**p_color)

# region[Main_Exec]
if __name__ == '__main__':
    pass
# endregion[Main_Exec]
