import math
import re


class Color:
    def to_rgb(self):
        raise Exception("Must implement to_rgb() in subclasses")


class RGB:
    def __init__(self, r, g, b):
        self.r = r
        self.g = g
        self.b = b

    def to_rgb(self):
        return self


class HSL(Color):
    def __init__(self, h, s, l):  # noqa; E741
        self.h = h
        self.s = s
        self.l = l  # noqa; E741

    @staticmethod
    def _hue_to_rgb(t1, t2, hue):
        if hue < 0:
            hue += 6
        elif hue >= 6:
            hue -= 6

        if hue < 1:
            return (t2 - t1) * hue + t1
        elif hue < 3:
            return t2
        elif hue < 4:
            return (t2 - t1) * (4 - hue) + t1
        else:
            return t1

    def to_rgb(self):
        hue = self.h / 60.0
        if self.l <= 0.5:  # noqa; E741
            t2 = self.l * (self.s + 1)
        else:
            t2 = self.l + self.s - (self.l * self.s)
        t1 = self.l * 2 - t2
        r = self._hue_to_rgb(t1, t2, hue + 2)
        g = self._hue_to_rgb(t1, t2, hue)
        b = self._hue_to_rgb(t1, t2, hue - 2)
        return RGB(r, g, b)


class HWB(Color):
    def __init__(self, h, w, b):
        self.h = h
        self.w = w
        self.b = b

    @staticmethod
    def _hue_to_rgb(hue):
        if hue < 0:
            hue += 6
        elif hue >= 6:
            hue -= 6

        if hue < 1:
            return hue
        elif hue < 3:
            return 1
        elif hue < 4:
            return 4 - hue
        else:
            return 0

    def to_rgb(self):
        hue = self.h / 60.0
        t1 = 1 - self.w - self.b
        r = self._hue_to_rgb(hue + 2) * t1 + self.w
        g = self._hue_to_rgb(hue) * t1 + self.w
        b = self._hue_to_rgb(hue - 2) * t1 + self.w
        return RGB(r, g, b)


class CMYK(Color):
    def __init__(self, c, m, y, k):
        self.c = c
        self.m = m
        self.y = y
        self.k = k

    def to_rgb(self):
        r = 1.0 - min(1.0, self.c + self.k)
        g = 1.0 - min(1.0, self.m + self.k)
        b = 1.0 - min(1.0, self.y + self.k)
        return RGB(r, g, b)


class Gray(Color):
    def __init__(self, g):
        self.g = g

    def to_rgb(self):
        return RGB(self.g, self.g, self.g)


_x11_colors = {
    "aliceblue": (240, 248, 255),
    "antiquewhite": (250, 235, 215),
    "aqua": (0, 255, 255),
    "aquamarine": (127, 255, 212),
    "azure": (240, 255, 255),
    "beige": (245, 245, 220),
    "bisque": (255, 228, 196),
    "black": (0, 0, 0),
    "blanchedalmond": (255, 235, 205),
    "blue": (0, 0, 255),
    "blueviolet": (138, 43, 226),
    "brown": (165, 42, 42),
    "burlywood": (222, 184, 135),
    "cadetblue": (95, 158, 160),
    "chartreuse": (127, 255, 0),
    "chocolate": (210, 105, 30),
    "coral": (255, 127, 80),
    "cornflowerblue": (100, 149, 237),
    "cornsilk": (255, 248, 220),
    "crimson": (220, 20, 60),
    "cyan": (0, 255, 255),
    "darkblue": (0, 0, 139),
    "darkcyan": (0, 139, 139),
    "darkgoldenrod": (184, 134, 11),
    "darkgray": (169, 169, 169),
    "darkgreen": (0, 100, 0),
    "darkgrey": (169, 169, 169),
    "darkkhaki": (189, 183, 107),
    "darkmagenta": (139, 0, 139),
    "darkolivegreen": (85, 107, 47),
    "darkorange": (255, 140, 0),
    "darkorchid": (153, 50, 204),
    "darkred": (139, 0, 0),
    "darksalmon": (233, 150, 122),
    "darkseagreen": (143, 188, 143),
    "darkslateblue": (72, 61, 139),
    "darkslategray": (47, 79, 79),
    "darkslategrey": (47, 79, 79),
    "darkturquoise": (0, 206, 209),
    "darkviolet": (148, 0, 211),
    "deeppink": (255, 20, 147),
    "deepskyblue": (0, 191, 255),
    "dimgray": (105, 105, 105),
    "dimgrey": (105, 105, 105),
    "dodgerblue": (30, 144, 255),
    "firebrick": (178, 34, 34),
    "floralwhite": (255, 250, 240),
    "forestgreen": (34, 139, 34),
    "fuchsia": (255, 0, 255),
    "gainsboro": (220, 220, 220),
    "ghostwhite": (248, 248, 255),
    "gold": (255, 215, 0),
    "goldenrod": (218, 165, 32),
    "gray": (128, 128, 128),
    "grey": (128, 128, 128),
    "green": (0, 128, 0),
    "greenyellow": (173, 255, 47),
    "honeydew": (240, 255, 240),
    "hotpink": (255, 105, 180),
    "indianred": (205, 92, 92),
    "indigo": (75, 0, 130),
    "ivory": (255, 255, 240),
    "khaki": (240, 230, 140),
    "lavender": (230, 230, 250),
    "lavenderblush": (255, 240, 245),
    "lawngreen": (124, 252, 0),
    "lemonchiffon": (255, 250, 205),
    "lightblue": (173, 216, 230),
    "lightcoral": (240, 128, 128),
    "lightcyan": (224, 255, 255),
    "lightgoldenrodyellow": (250, 250, 210),
    "lightgray": (211, 211, 211),
    "lightgreen": (144, 238, 144),
    "lightgrey": (211, 211, 211),
    "lightpink": (255, 182, 193),
    "lightsalmon": (255, 160, 122),
    "lightseagreen": (32, 178, 170),
    "lightskyblue": (135, 206, 250),
    "lightslategray": (119, 136, 153),
    "lightslategrey": (119, 136, 153),
    "lightsteelblue": (176, 196, 222),
    "lightyellow": (255, 255, 224),
    "lime": (0, 255, 0),
    "limegreen": (50, 205, 50),
    "linen": (250, 240, 230),
    "magenta": (255, 0, 255),
    "maroon": (128, 0, 0),
    "mediumaquamarine": (102, 205, 170),
    "mediumblue": (0, 0, 205),
    "mediumorchid": (186, 85, 211),
    "mediumpurple": (147, 112, 219),
    "mediumseagreen": (60, 179, 113),
    "mediumslateblue": (123, 104, 238),
    "mediumspringgreen": (0, 250, 154),
    "mediumturquoise": (72, 209, 204),
    "mediumvioletred": (199, 21, 133),
    "midnightblue": (25, 25, 112),
    "mintcream": (245, 255, 250),
    "mistyrose": (255, 228, 225),
    "moccasin": (255, 228, 181),
    "navajowhite": (255, 222, 173),
    "navy": (0, 0, 128),
    "oldlace": (253, 245, 230),
    "olive": (128, 128, 0),
    "olivedrab": (107, 142, 35),
    "orange": (255, 165, 0),
    "orangered": (255, 69, 0),
    "orchid": (218, 112, 214),
    "palegoldenrod": (238, 232, 170),
    "palegreen": (152, 251, 152),
    "paleturquoise": (175, 238, 238),
    "palevioletred": (219, 112, 147),
    "papayawhip": (255, 239, 213),
    "peachpuff": (255, 218, 185),
    "peru": (205, 133, 63),
    "pink": (255, 192, 203),
    "plum": (221, 160, 221),
    "powderblue": (176, 224, 230),
    "purple": (128, 0, 128),
    "red": (255, 0, 0),
    "rosybrown": (188, 143, 143),
    "royalblue": (65, 105, 225),
    "saddlebrown": (139, 69, 19),
    "salmon": (250, 128, 114),
    "sandybrown": (244, 164, 96),
    "seagreen": (46, 139, 87),
    "seashell": (255, 245, 238),
    "sienna": (160, 82, 45),
    "silver": (192, 192, 192),
    "skyblue": (135, 206, 235),
    "slateblue": (106, 90, 205),
    "slategray": (112, 128, 144),
    "slategrey": (112, 128, 144),
    "snow": (255, 250, 250),
    "springgreen": (0, 255, 127),
    "steelblue": (70, 130, 180),
    "tan": (210, 180, 140),
    "teal": (0, 128, 128),
    "thistle": (216, 191, 216),
    "tomato": (255, 99, 71),
    "turquoise": (64, 224, 208),
    "violet": (238, 130, 238),
    "wheat": (245, 222, 179),
    "white": (255, 255, 255),
    "whitesmoke": (245, 245, 245),
    "yellow": (255, 255, 0),
    "yellowgreen": (154, 205, 50),
}

_ws_re = re.compile(r"\s+")
_token_re = re.compile(r"[A-Za-z_][A-Za-z0-9_]*")
_hex_re = re.compile(r"#([0-9a-f]{3}(?:[0-9a-f]{3})?)$")
_number_re = re.compile(r"[0-9]*(\.[0-9]*)")


class ColorParser:
    def __init__(self, s):
        self._string = s
        self._pos = 0

    def skipws(self):
        m = _ws_re.match(self._string, self._pos)
        if m:
            self._pos = m.end(0)

    def expect(self, s, context=""):
        if (
            len(self._string) - self._pos < len(s)
            or self._string[self._pos : self._pos + len(s)] != s
        ):
            raise ValueError(f'bad color "{self._string}" - expected "{s}"{context}')
        self._pos += len(s)

    def expectEnd(self):
        if self._pos != len(self._string):
            raise ValueError('junk at end of color "%s"' % self._string)

    def getToken(self):
        m = _token_re.match(self._string, self._pos)
        if m:
            token = m.group(0)

            self._pos = m.end(0)
            return token
        return None

    def parseNumber(self, context=""):
        m = _number_re.match(self._string, self._pos)
        if m:
            self._pos = m.end(0)
            return float(m.group(0))
        raise ValueError(f'bad color "{self._string}" - expected a number{context}')

    def parseColor(self):
        self.skipws()

        token = self.getToken()
        if token:
            if token == "rgb":
                return self.parseRGB()
            elif token == "hsl":
                return self.parseHSL()
            elif token == "hwb":
                return self.parseHWB()
            elif token == "cmyk":
                return self.parseCMYK()
            elif token == "gray" or token == "grey":
                return self.parseGray()

            try:
                r, g, b = _x11_colors[token]
            except KeyError:
                raise ValueError('unknown color name "%s"' % token)

            self.expectEnd()

            return RGB(r / 255.0, g / 255.0, b / 255.0)

        m = _hex_re.match(self._string, self._pos)
        if m:
            hrgb = m.group(1)

            if len(hrgb) == 3:
                r = int("0x" + 2 * hrgb[0], 16)
                g = int("0x" + 2 * hrgb[1], 16)
                b = int("0x" + 2 * hrgb[2], 16)
            else:
                r = int("0x" + hrgb[0:2], 16)
                g = int("0x" + hrgb[2:4], 16)
                b = int("0x" + hrgb[4:6], 16)

            self._pos = m.end(0)
            self.skipws()

            self.expectEnd()

            return RGB(r / 255.0, g / 255.0, b / 255.0)

        raise ValueError('bad color syntax "%s"' % self._string)

    def parseRGB(self):
        self.expect("(", 'after "rgb"')
        self.skipws()

        r = self.parseValue()

        self.skipws()
        self.expect(",", 'in "rgb"')
        self.skipws()

        g = self.parseValue()

        self.skipws()
        self.expect(",", 'in "rgb"')
        self.skipws()

        b = self.parseValue()

        self.skipws()
        self.expect(")", 'at end of "rgb"')

        self.skipws()
        self.expectEnd()

        return RGB(r, g, b)

    def parseHSL(self):
        self.expect("(", 'after "hsl"')
        self.skipws()

        h = self.parseAngle()

        self.skipws()
        self.expect(",", 'in "hsl"')
        self.skipws()

        s = self.parseValue()

        self.skipws()
        self.expect(",", 'in "hsl"')
        self.skipws()

        l = self.parseValue()  # noqa; E741

        self.skipws()
        self.expect(")", 'at end of "hsl"')

        self.skipws()
        self.expectEnd()

        return HSL(h, s, l)

    def parseHWB(self):
        self.expect("(", 'after "hwb"')
        self.skipws()

        h = self.parseAngle()

        self.skipws()
        self.expect(",", 'in "hwb"')
        self.skipws()

        w = self.parseValue()

        self.skipws()
        self.expect(",", 'in "hwb"')
        self.skipws()

        b = self.parseValue()

        self.skipws()
        self.expect(")", 'at end of "hwb"')

        self.skipws()
        self.expectEnd()

        return HWB(h, w, b)

    def parseCMYK(self):
        self.expect("(", 'after "cmyk"')
        self.skipws()

        c = self.parseValue()

        self.skipws()
        self.expect(",", 'in "cmyk"')
        self.skipws()

        m = self.parseValue()

        self.skipws()
        self.expect(",", 'in "cmyk"')
        self.skipws()

        y = self.parseValue()

        self.skipws()
        self.expect(",", 'in "cmyk"')
        self.skipws()

        k = self.parseValue()

        self.skipws()
        self.expect(")", 'at end of "cmyk"')

        self.skipws()
        self.expectEnd()

        return CMYK(c, m, y, k)

    def parseGray(self):
        self.expect("(", 'after "gray"')
        self.skipws()

        g = self.parseValue()

        self.skipws()
        self.expect(")", 'at end of "gray')

        self.skipws()
        self.expectEnd()

        return Gray(g)

    def parseValue(self):
        n = self.parseNumber()
        self.skipws()
        if self._string[self._pos] == "%":
            n = n / 100.0
            self.pos += 1
        return n

    def parseAngle(self):
        n = self.parseNumber()
        self.skipws()
        tok = self.getToken()
        if tok == "rad":
            n = n * 180.0 / math.pi
        elif tok == "grad" or tok == "gon":
            n = n * 0.9
        elif tok != "deg":
            raise ValueError('bad angle unit "%s"' % tok)
        return n


_color_re = re.compile(
    r"\s*(#|rgb|hsl|hwb|cmyk|gray|grey|%s)" % "|".join(_x11_colors.keys())
)


def isAColor(s):
    return _color_re.match(s)


def parseColor(s):
    return ColorParser(s).parseColor()
