#  Copyright (c) 2005 Gavin E. Crooks
#
#  This software is distributed under the MIT Open Source License.
#  <http://www.opensource.org/licenses/mit-license.html>
#
#  Permission is hereby granted, free of charge, to any person obtaining a
#  copy of this software and associated documentation files (the "Software"),
#  to deal in the Software without restriction, including without limitation
#  the rights to use, copy, modify, merge, publish, distribute, sublicense,
#  and/or sell copies of the Software, and to permit persons to whom the
#  Software is furnished to do so, subject to the following conditions:
#
#  The above copyright notice and this permission notice shall be included
#  in all copies or substantial portions of the Software.
#
#  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
#  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
#  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
#  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
#  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
#  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
#  THE SOFTWARE.

""" Color specifications using CSS2 (Cascading Style Sheet) syntax."""

from typing import Any, List


class Color(object):
    """Color specifications using CSS2 (Cascading Style Sheet) syntax.

    http://www.w3.org/TR/REC-CSS2/syndata.html#color-units

    Usage:

    red = Color(255,0,0)
    red = Color(1., 0., 0.)
    red = Color.by_name("red")
    red = Color.from_rgb(1.,0.,0.)
    red = Color.from_rgb(255,0,0)
    red = Color.from_hsl(0.,1., 0.5)

    red = Color.from_string("red")
    red = Color.from_string("RED")
    red = Color.from_string("#F00")
    red = Color.from_string("#FF0000")
    red = Color.from_string("rgb(255, 0, 0)")
    red = Color.from_string("rgb(100%, 0%, 0%)")
    red = Color.from_string("hsl(0, 100%, 50%)")

    """

    def __init__(self, red: float, green: float, blue: float) -> None:
        if not (type(red) is type(green) is type(blue)):
            raise TypeError("Mixed floats and integers?")
        # Convert integer RBG values in [0, 255] to floats in [0, 1]
        if isinstance(red, int):
            red /= 255.0
        if isinstance(green, int):
            green /= 255.0
        if isinstance(blue, int):
            blue /= 255.0
        # Clip RBG values to [0, 1]
        self.red = max(0.0, min(red, 1.0))
        self.green = max(0.0, min(green, 1.0))
        self.blue = max(0.0, min(blue, 1.0))

    @staticmethod
    def names() -> List[str]:
        "Return a list of standard color names."
        return list(_std_colors.keys())

    @classmethod
    def from_rgb(cls, r: float, g: float, b: float) -> "Color":
        return cls(r, g, b)

    @classmethod
    def from_hsl(cls, hue_angle: float, saturation: float, lightness: float) -> "Color":
        def hue_to_rgb(v1: float, v2: float, vH: float) -> float:
            if vH < 0.0:
                vH += 1.0
            if vH > 1.0:
                vH -= 1.0  # pragma: no cover (Grandfathered in)
            if vH * 6.0 < 1.0:
                return v1 + (v2 - v1) * 6.0 * vH
            if vH * 2.0 < 1.0:
                return v2
            if vH * 3.0 < 2.0:
                return v1 + (v2 - v1) * ((2.0 / 3.0) - vH) * 6.0  # pragma: no cover
            return v1

        hue = (((hue_angle % 360.0) + 360.0) % 360.0) / 360.0

        if not (saturation >= 0.0 and saturation <= 1.0):
            raise ValueError(
                "Out-of-range saturation %f" % saturation
            )  # pragma: no cover
        if not (lightness >= 0.0 and lightness <= 1.0):
            raise ValueError(
                "Out-of-range lightness %f" % lightness
            )  # pragma: no cover

        if saturation == 0:
            # greyscale
            return cls.from_rgb(lightness, lightness, lightness)

        if lightness < 0.5:
            v2 = lightness * (1.0 + saturation)
        else:
            v2 = (lightness + saturation) - (saturation * lightness)

        v1 = 2.0 * lightness - v2
        r = hue_to_rgb(v1, v2, hue + (1.0 / 3.0))
        g = hue_to_rgb(v1, v2, hue)
        b = hue_to_rgb(v1, v2, hue - (1.0 / 3.0))

        return cls(r, g, b)

    @staticmethod
    def by_name(string: str) -> "Color":
        s = string.strip().lower().replace(" ", "")
        try:
            return _std_colors[s]
        except KeyError:
            raise ValueError("Unknown color name: %s" % s)

    @classmethod
    def from_string(cls, string: str) -> "Color":
        def to_frac(string: str) -> float:
            # string can be "255" or "100%"
            if string[-1] == "%":
                return float(string[0:-1]) / 100.0
            else:
                return float(string) / 255.0

        s = string.strip().lower().replace(" ", "").replace("_", "")

        if s in _std_colors:  # "red"
            return _std_colors[s]

        if s[0] == "#":  # "#fef"
            if len(s) == 4:
                r = int(s[1] + s[1], 16)
                g = int(s[2] + s[2], 16)
                b = int(s[3] + s[3], 16)
                return cls(r, g, b)
            elif len(s) == 7:  # "#ff00aa"
                r = int(s[1:3], 16)
                g = int(s[3:5], 16)
                b = int(s[5:7], 16)
                return cls(r, g, b)
            else:
                raise ValueError("Cannot parse string: %s" % s)

        if s[0:4] == "rgb(" and s[-1] == ")":
            rgb = s[4:-1].split(",")
            if len(rgb) != 3:
                raise ValueError("Cannot parse string a: %s" % s)
            return cls(to_frac(rgb[0]), to_frac(rgb[1]), to_frac(rgb[2]))

        if s[0:4] == "hsl(" and s[-1] == ")":
            hsl = s[4:-1].split(",")
            if len(hsl) != 3:
                raise ValueError("Cannot parse string a: %s" % s)
            return cls.from_hsl(int(hsl[0]), to_frac(hsl[1]), to_frac(hsl[2]))

        raise ValueError("Cannot parse string: %s" % s)

    def __eq__(self, other: Any) -> bool:
        if not isinstance(other, self.__class__):
            return False
        req = int(0.5 + 255.0 * self.red) == int(0.5 + 255.0 * other.red)
        beq = int(0.5 + 255.0 * self.blue) == int(0.5 + 255.0 * other.blue)
        geq = int(0.5 + 255.0 * self.green) == int(0.5 + 255.0 * other.green)
        return req and beq and geq

    def __repr__(self) -> str:
        return "Color(%f,%f,%f)" % (self.red, self.green, self.blue)


_std_colors = dict(
    aliceblue=Color(240, 248, 255),  # f0f8ff
    antiquewhite=Color(250, 235, 215),  # faebd7
    aqua=Color(0, 255, 255),  # 00ffff
    aquamarine=Color(127, 255, 212),  # 7fffd4
    azure=Color(240, 255, 255),  # f0ffff
    beige=Color(245, 245, 220),  # f5f5dc
    bisque=Color(255, 228, 196),  # ffe4c4
    black=Color(0, 0, 0),  # 000000
    blanchedalmond=Color(255, 235, 205),  # ffebcd
    blue=Color(0, 0, 255),  # 0000ff
    blueviolet=Color(138, 43, 226),  # 8a2be2
    brown=Color(165, 42, 42),  # a52a2a
    burlywood=Color(222, 184, 135),  # deb887
    cadetblue=Color(95, 158, 160),  # 5f9ea0
    chartreuse=Color(127, 255, 0),  # 7fff00
    chocolate=Color(210, 105, 30),  # d2691e
    coral=Color(255, 127, 80),  # ff7f50
    cornflowerblue=Color(100, 149, 237),  # 6495ed
    cornsilk=Color(255, 248, 220),  # fff8dc
    crimson=Color(220, 20, 60),  # dc143c
    cyan=Color(0, 255, 255),  # 00ffff
    darkblue=Color(0, 0, 139),  # 00008b
    darkcyan=Color(0, 139, 139),  # 008b8b
    darkgoldenrod=Color(184, 134, 11),  # b8860b
    darkgray=Color(169, 169, 169),  # a9a9a9
    darkgreen=Color(0, 100, 0),  # 006400
    darkgrey=Color(169, 169, 169),  # a9a9a9
    darkkhaki=Color(189, 183, 107),  # bdb76b
    darkmagenta=Color(139, 0, 139),  # 8b008b
    darkolivegreen=Color(85, 107, 47),  # 556b2f
    darkorange=Color(255, 140, 0),  # ff8c00
    darkorchid=Color(153, 50, 204),  # 9932cc
    darkred=Color(139, 0, 0),  # 8b0000
    darksalmon=Color(233, 150, 122),  # e9967a
    darkseagreen=Color(143, 188, 143),  # 8fbc8f
    darkslateblue=Color(72, 61, 139),  # 483d8b
    darkslategray=Color(47, 79, 79),  # 2f4f4f
    darkslategrey=Color(47, 79, 79),  # 2f4f4f
    darkturquoise=Color(0, 206, 209),  # 00ced1
    darkviolet=Color(148, 0, 211),  # 9400d3
    deeppink=Color(255, 20, 147),  # ff1493
    deepskyblue=Color(0, 191, 255),  # 00bfff
    dimgray=Color(105, 105, 105),  # 696969
    dimgrey=Color(105, 105, 105),  # 696969
    dodgerblue=Color(30, 144, 255),  # 1e90ff
    firebrick=Color(178, 34, 34),  # b22222
    floralwhite=Color(255, 250, 240),  # fffaf0
    forestgreen=Color(34, 139, 34),  # 228b22
    fuchsia=Color(255, 0, 255),  # ff00ff
    gainsboro=Color(220, 220, 220),  # dcdcdc
    ghostwhite=Color(248, 248, 255),  # f8f8ff
    gold=Color(255, 215, 0),  # ffd700
    goldenrod=Color(218, 165, 32),  # daa520
    gray=Color(128, 128, 128),  # 808080
    green=Color(0, 128, 0),  # 008000
    greenyellow=Color(173, 255, 47),  # adff2f
    grey=Color(128, 128, 128),  # 808080
    honeydew=Color(240, 255, 240),  # f0fff0
    hotpink=Color(255, 105, 180),  # ff69b4
    indianred=Color(205, 92, 92),  # cd5c5c
    indigo=Color(75, 0, 130),  # 4b0082
    ivory=Color(255, 255, 240),  # fffff0
    khaki=Color(240, 230, 140),  # f0e68c
    lavender=Color(230, 230, 250),  # e6e6fa
    lavenderblush=Color(255, 240, 245),  # fff0f5
    lawngreen=Color(124, 252, 0),  # 7cfc00
    lemonchiffon=Color(255, 250, 205),  # fffacd
    lightblue=Color(173, 216, 230),  # add8e6
    lightcoral=Color(240, 128, 128),  # f08080
    lightcyan=Color(224, 255, 255),  # e0ffff
    lightgoldenrodyellow=Color(250, 250, 210),  # fafad2
    lightgray=Color(211, 211, 211),  # d3d3d3
    lightgreen=Color(144, 238, 144),  # 90ee90
    lightgrey=Color(211, 211, 211),  # d3d3d3
    lightpink=Color(255, 182, 193),  # ffb6c1
    lightsalmon=Color(255, 160, 122),  # ffa07a
    lightseagreen=Color(32, 178, 170),  # 20b2aa
    lightskyblue=Color(135, 206, 250),  # 87cefa
    lightslategray=Color(119, 136, 153),  # 778899
    lightslategrey=Color(119, 136, 153),  # 778899
    lightsteelblue=Color(176, 196, 222),  # b0c4de
    lightyellow=Color(255, 255, 224),  # ffffe0
    lime=Color(0, 255, 0),  # 00ff00
    limegreen=Color(50, 205, 50),  # 32cd32
    linen=Color(250, 240, 230),  # faf0e6
    magenta=Color(255, 0, 255),  # ff00ff
    maroon=Color(128, 0, 0),  # 800000
    mediumaquamarine=Color(102, 205, 170),  # 66cdaa
    mediumblue=Color(0, 0, 205),  # 0000cd
    mediumorchid=Color(186, 85, 211),  # ba55d3
    mediumpurple=Color(147, 112, 219),  # 9370db
    mediumseagreen=Color(60, 179, 113),  # 3cb371
    mediumslateblue=Color(123, 104, 238),  # 7b68ee
    mediumspringgreen=Color(0, 250, 154),  # 00fa9a
    mediumturquoise=Color(72, 209, 204),  # 48d1cc
    mediumvioletred=Color(199, 21, 133),  # c71585
    midnightblue=Color(25, 25, 112),  # 191970
    mintcream=Color(245, 255, 250),  # f5fffa
    mistyrose=Color(255, 228, 225),  # ffe4e1
    moccasin=Color(255, 228, 181),  # ffe4b5
    navajowhite=Color(255, 222, 173),  # ffdead
    navy=Color(0, 0, 128),  # 000080
    oldlace=Color(253, 245, 230),  # fdf5e6
    olive=Color(128, 128, 0),  # 808000
    olivedrab=Color(107, 142, 35),  # 6b8e23
    orange=Color(255, 165, 0),  # ffa500
    orangered=Color(255, 69, 0),  # ff4500
    orchid=Color(218, 112, 214),  # da70d6
    palegoldenrod=Color(238, 232, 170),  # eee8aa
    palegreen=Color(152, 251, 152),  # 98fb98
    paleturquoise=Color(175, 238, 238),  # afeeee
    palevioletred=Color(219, 112, 147),  # db7093
    papayawhip=Color(255, 239, 213),  # ffefd5
    peachpuff=Color(255, 218, 185),  # ffdab9
    peru=Color(205, 133, 63),  # cd853f
    pink=Color(255, 192, 203),  # ffc0cb
    plum=Color(221, 160, 221),  # dda0dd
    powderblue=Color(176, 224, 230),  # b0e0e6
    purple=Color(128, 0, 128),  # 800080
    red=Color(255, 0, 0),  # ff0000
    rosybrown=Color(188, 143, 143),  # bc8f8f
    royalblue=Color(65, 105, 225),  # 4169e1
    saddlebrown=Color(139, 69, 19),  # 8b4513
    salmon=Color(250, 128, 114),  # fa8072
    sandybrown=Color(244, 164, 96),  # f4a460
    seagreen=Color(46, 139, 87),  # 2e8b57
    seashell=Color(255, 245, 238),  # fff5ee
    sienna=Color(160, 82, 45),  # a0522d
    silver=Color(192, 192, 192),  # c0c0c0
    skyblue=Color(135, 206, 235),  # 87ceeb
    slateblue=Color(106, 90, 205),  # 6a5acd
    slategray=Color(112, 128, 144),  # 708090
    slategrey=Color(112, 128, 144),  # 708090
    snow=Color(255, 250, 250),  # fffafa
    springgreen=Color(0, 255, 127),  # 00ff7f
    steelblue=Color(70, 130, 180),  # 4682b4
    tan=Color(210, 180, 140),  # d2b48c
    teal=Color(0, 128, 128),  # 008080
    thistle=Color(216, 191, 216),  # d8bfd8
    tomato=Color(255, 99, 71),  # ff6347
    turquoise=Color(64, 224, 208),  # 40e0d0
    violet=Color(238, 130, 238),  # ee82ee
    wheat=Color(245, 222, 179),  # f5deb3
    white=Color(255, 255, 255),  # ffffff
    whitesmoke=Color(245, 245, 245),  # f5f5f5
    yellow=Color(255, 255, 0),  # ffff00
    yellowgreen=Color(154, 205, 50),  # 9acd32
)
