import enum
import re
from math import atan2, cos, exp, pi, sin, sqrt

from ..configs import config_paths


class BaseColor(enum.Enum):

    def rgb(self, normed=False):
        r, g, b = self.value
        if normed:
            return r/255., g/255., b/255
        else:
            return r, g, b

    def hsl(self, normed=False):
        r, g, b = self.rgb(normed=True)

        col_max = max(r, g, b)
        col_min = min(r, g, b)

        delta = col_max - col_min
        col_sum = col_max + col_min

        lightness = col_sum / 2

        if delta == 0:
            hue = 1
            saturation = 0
        else:
            try:
                saturation = delta / (1 - abs(col_sum-1))
            except ZeroDivisionError:
                saturation = 0

            if col_max == r:
                hue = (g - b) / delta
            elif col_max == g:
                hue = (b - r) / delta + 2
            elif col_max == b:
                hue = (r - g) / delta + 4
            else:
                raise ValueError('convert to hsl failed')

            hue *= 60.
            if hue < 0:
                hue += 360

        if normed:
            return hue/360, saturation, lightness
        else:
            return hue, saturation*100, lightness*100

    def xyz(self, normed=True):
        rgb = list(self.rgb(normed=True))

        for i, val in enumerate(rgb):
            rgb[i] = ((val+0.055)/1.055)**2.4 if val > 0.04045 else val/12.92

        x = sum(i*j for i, j in zip([0.4124, 0.3576, 0.1805], rgb))
        y = sum(i*j for i, j in zip([0.2126, 0.7152, 0.0722], rgb))
        z = sum(i*j for i, j in zip([0.0193, 0.1192, 0.9505], rgb))

        if normed:
            return x, y, z
        else:
            return x*100, y*100, z*100

    def hsv(self, normed=False):
        r, g, b = self.rgb(normed=True)

        col_max = max(r, g, b)
        col_min = min(r, g, b)

        delta = col_max - col_min

        if delta == 0:
            hue = 0
            saturation = 0
        else:
            try:
                saturation = delta / col_max
            except ZeroDivisionError:
                saturation = 0

            if col_max == r:
                hue = (g - b) / delta
            elif col_max == g:
                hue = (b - r) / delta + 2
            elif col_max == b:
                hue = (r - g) / delta + 4
            else:
                raise ValueError('convert to hsv failed')

            hue *= 60.
            if hue < 0:
                hue += 360

        if normed:
            return hue / 360, saturation, col_max
        else:
            return hue, saturation * 100, col_max * 100

    def lab(self):
        x, y, z = self.xyz(normed=False)
        x /= 95.0489
        y /= 100
        z /= 108.884

        x = x**(1./3.) if x > 0.008856 else 7.787 * x + 16 / 116
        y = y**(1./3.) if y > 0.008856 else 7.787 * y + 16 / 116
        z = z**(1./3.) if z > 0.008856 else 7.787 * z + 16 / 116

        l = 116 * y - 16
        a = 500 * (x - y)
        b = 200 * (x - z)

        return l, a, b

    def cmyk(self, normed=False):
        r, g, b = self.rgb(normed=True)

        k = 1. - max(r, g, b)
        try:
            c = (1. - r - k) / (1. - k)
            m = (1. - g - k) / (1. - k)
            y = (1. - b - k) / (1. - k)
        except ZeroDivisionError:
            c = m = y = 0.

        if normed:
            return c, m, y, k
        else:
            return c*100, m*100, y*100, k*100

    def hex(self):
        r, g, b = self.value
        return f'#{r:02x}{g:02x}{b:02x}'

    @classmethod
    def from_rgb(cls, r, g, b, normed=False):
        if normed:
            r *= 255
            g *= 255
            b *= 255

        return cls((r, g, b))

    @classmethod
    def from_str(cls, hexstring):
        r = int(hexstring[1:3], 16)
        g = int(hexstring[3:5], 16)
        b = int(hexstring[5:7], 16)

        return cls((r, g, b))


class TUColorsA(BaseColor):
    TUDa1a = (93, 133, 195)
    TUDa2a = (0, 156, 218)
    TUDa3a = (80, 182, 149)
    TUDa4a = (175, 204, 80)
    TUDa5a = (221, 223, 72)
    TUDa6a = (255, 224, 92)
    TUDa7a = (248, 186, 60)
    TUDa8a = (238, 122, 52)
    TUDa9a = (233, 80, 62)
    TUDa10a = (201, 48, 142)
    TUDa11a = (128, 69, 151)


class TUColorsB(BaseColor):
    TUDa1b = (0, 90, 169)
    TUDa2b = (0, 131, 204)
    TUDa3b = (0, 157, 129)
    TUDa4b = (153, 192, 0)
    TUDa5b = (201, 212, 0)
    TUDa6b = (253, 202, 0)
    TUDa7b = (245, 163, 0)
    TUDa8b = (236, 101, 0)
    TUDa9b = (230, 0, 26)
    TUDa10b = (166, 0, 132)
    TUDa11b = (114, 16, 133)


class TUColorsC(BaseColor):
    TUDa1c = (0, 78, 138)
    TUDa2c = (0, 104, 157)
    TUDa3c = (0, 136, 119)
    TUDa4c = (127, 171, 22)
    TUDa5c = (177, 189, 0)
    TUDa6c = (215, 172, 0)
    TUDa7c = (210, 135, 0)
    TUDa8c = (204, 76, 3)
    TUDa9c = (185, 15, 34)
    TUDa10c = (149, 17, 105)
    TUDa11c = (97, 28, 115)


class TUColorsD(BaseColor):
    TUDa1d = (36, 53, 114)
    TUDa2d = (0, 78, 115)
    TUDa3d = (0, 113, 94)
    TUDa4d = (106, 139, 55)
    TUDa5d = (153, 166, 4)
    TUDa6d = (174, 142, 0)
    TUDa7d = (190, 111, 0)
    TUDa8d = (169, 73, 19)
    TUDa9d = (156, 28, 38)
    TUDa10d = (115, 32, 84)
    TUDa11d = (76, 34, 106)


class TUGrays(BaseColor):
    TUDa0a = (220, 220, 220)
    TUDa0b = (181, 181, 181)
    TUDa0c = (137, 137, 137)
    TUDa0d = (83, 83, 83)
    White = (255, 255, 255)
    Black = (0, 0, 0)


class RedBlue(BaseColor):
    C1 = (103, 0, 31)
    C2 = (178, 27, 43)
    C3 = (178, 24, 43)
    C4 = (244, 165, 130)
    C5 = (253, 219, 199)
    C6 = (220, 220, 220)
    C7 = (253, 219, 199)
    C8 = (146, 197, 222)
    C9 = (67, 147, 195)
    C10 = (33, 102, 172)
    C11 = (5, 48, 97)


class Tab10(BaseColor):
    TabBlue = TUColorsB.TUDa2b.value
    TabOrange = TUColorsA.TUDa8a.value
    TabGreen = TUColorsD.TUDa4d.value
    TabRed = TUColorsB.TUDa9b.value
    TabPurple = TUColorsA.TUDa11a.value
    TabBrown = TUColorsD.TUDa8d.value
    TabRose = TUColorsA.TUDa10a.value
    TabGrey = TUGrays.TUDa0a.value
    TabChartreuse = TUColorsC.TUDa5c.value
    TabTurquoise = TUColorsA.TUDa2a.value

class Tab20(BaseColor):
    TabBlue = (31, 119, 180)
    TabBlue2 = (174, 199, 232)
    TabOrange = (255, 127, 14)
    TabOrange2 = (255, 187, 120)
    TabGreen = (44, 160, 44)
    TabGreen2 = (152, 223, 138)
    TabRed = (214, 39, 40)
    TabRed2 = (255, 152, 150)
    TabPurple = (148, 103, 189)
    TabPurple2 = (197, 176, 213)
    TabBrown = (140, 86, 75)
    TabBrown2 = (196, 156, 148)
    TabRose = (227, 119, 194)
    TabRose2 = (247, 182, 210)
    TabGrey = (220, 220, 220)
    TabGrey2 = (199, 199, 199)
    TabChartreuse = (188, 189, 34)
    TabChartreuse2 = (219, 219, 141)
    TabTurquoise = (23, 190, 207)
    TabTurquoise2 = (158, 218, 229)


class ColorBlind(BaseColor):
    c0 = (0, 107, 164)
    c1 = (255, 128, 14)
    c2 = (171, 171, 171)
    c3 = (89, 89, 89)
    c4 = (95, 158, 209)
    c5 = (200, 82, 0)
    c6 = (137, 137, 137)
    c7 = (162, 200, 237)
    c8 = (255, 188, 121)
    c9 = (207, 207, 207)


class GraceColors(BaseColor):
    Red = (255, 0, 0)
    Green = (0, 255, 0)
    Blue = (0, 0, 255)
    Yellow = (255, 255, 0)
    Brown = (188, 143, 143)
    Grey = (220, 220, 220)
    Violet = (148, 0, 211)
    Cyan = (0, 255, 255)
    Magenta = (255, 0, 255)
    Orange = (255, 165, 0)
    Indigo = (114, 33, 188)
    Maroon = (103, 7, 72)
    Turquoise = (64, 224, 208)
    Green4 = (0, 139, 0)


class BlackWhite(BaseColor):
    White = (255, 255, 255)
    Black = (0, 0, 0)


TUColors = enum.Enum(
    value='TUColors',
    names={member.name: member.value for palette in [TUColorsA, TUColorsB, TUColorsC, TUColorsD] for member in palette},
    type=BaseColor,
)

Colors = enum.Enum(
    value='Colors',
    names={member.name: member.value for palette in [TUColors, TUGrays, GraceColors] for member in palette},
    type=BaseColor,
)


def ciede2000(c1: BaseColor, c2: BaseColor):
    """
    Calculate difference between to colors with CIEDE2000 [1]_

    Args:
        c1: Color
        c2: Color

    [1]: G. Sharma, W.Wu, E. N. Dalal: The CIEDE2000 color-difference formula:
    Implementation notes, supplementary test data, and mathematical observations. Color (2004)
    https://doi.org/10.1002/col.20070

    """
    l1, a1, b1 = c1.lab()
    l2, a2, b2 = c2.lab()

    l_bar = (l1 + l2) / 2
    delta_l = l2 - l1

    c1 = sqrt(a1**2 + b1**2)
    c2 = sqrt(a2**2 + b2**2)
    c_bar = (c1 + c2) / 2

    c_scale = (1 - sqrt(c_bar**7 / (c_bar**7 + 25**7))) / 2
    a1_prime = a1 * (1 + c_scale)
    a2_prime = a2 * (1 + c_scale)

    c1_prime = sqrt(a1_prime**2 + b1**2)
    c2_prime = sqrt(a2_prime**2 + b2**2)
    c_bar_prime = (c1_prime + c2_prime) / 2
    delta_c = c2_prime - c1_prime

    h1 = (atan2(b1, a1) * 180 / pi + 360) % 360
    h2 = (atan2(b2, a2) * 180 / pi + 360) % 360

    if c1_prime * c2_prime == 0:
        dh = 0
    else:
        if abs(h1 - h2) <= 180:
            dh = h2 - h1
        else:
            if h2 > h1:
                dh = h2 - h1 - 360
            else:
                dh = h2 - h1 + 360

    delta_h = 2 * sqrt(c1_prime * c2_prime) * sin(dh * pi / 360)

    if c1 * c2 == 0:
        h_bar = h1 + h2
    else:
        dh_abs = abs(h1 - h2)
        h_bar = h1 + h2
        if dh_abs > 180:
            if h_bar < 360:
                h_bar += 360
            else:
                h_bar -= 360
        h_bar /= 2

    t = 1 - 0.17 * cos((h_bar - 30) * pi / 180) + 0.25 * cos(h_bar * pi / 90) + 0.32 * cos(
        (3 * h_bar - 6) * pi / 180) - 0.2 * cos((4 * h_bar - 63) * pi / 180)

    s_l = 1 + 0.015 * (l_bar - 50)**2 / sqrt(20 + (l_bar - 50)**2)
    s_c = 1 + 0.045 * c_bar_prime
    s_h = 1 + 0.015 * c_bar_prime * t

    r_t = -2 * sqrt(c_bar_prime**7 / (c_bar_prime**7 + 25**7)) * sin(pi / 3 * exp(-((h_bar - 275) / 25)**2))

    delta_e = (delta_l / s_l)**2 + (delta_c / s_c)**2 + (delta_h / s_h)**2 + r_t * delta_c / s_c * delta_h / s_h

    return sqrt(delta_e)


def get_palettes():
    palettes = {
        'TUDa': TUColors,
        'TUDa:a': TUColorsA,
        'TUDa:b': TUColorsB,
        'TUDa:c': TUColorsC,
        'TUDa:d': TUColorsD,
        'TUDa:gray': TUGrays,
        'Grace': GraceColors,
        'mpl': [Colors.TUDa2b, Colors.TUDa8a, Colors.TUDa4d, Colors.TUDa9b, Colors.TUDa11a,
                Colors.TUDa8d, Colors.TUDa10a, Colors.TUDa0a, Colors.TUDa5c, Colors.TUDa2a],
        'colorblind': [Colors.TUDa2c, Colors.TUDa8a, Colors.TUDa0b, Colors.TUDa0d, Colors.TUDa2a,
                       Colors.TUDa8c, Colors.TUDa0c, Colors.TUDa2b, Colors.TUDa7a, Colors.TUDa0a],
    }

    user_colors = config_paths() / 'colors.cfg'

    if user_colors.exists():
        with user_colors.open() as f:
            palette_open = False
            for line in f:
                if line.startswith('#--'):
                    color_name = line[4:-1]
                    palette_open = True
                    color_list = []
                    continue

                if palette_open:
                    m = re.match('(\d{1,3}.\d*), (\d{1,3}.\d*), (\d{1,3}.\d*)', line)
                    if m:
                        color_list.append(Colors.from_rgb(*(float(c) for c in m.groups())))
                    elif line == '\n':
                        palettes[color_name] = color_list
                        palette_open = False

    return palettes

available_cycles = get_palettes()