"""
PyMule color module for color creation and conversion operations.
"""
from math import floor
from .compat import *


__all__ = ["Color", "is_rgb_color", "is_rgba_color", "argb_to_color", "ARGB",
           "rgba_to_color", "RGBA", "string_to_color", "convert_to_color",
           "COLOR"]


class Color(object):
    """A simple RGBA-based color implementation."""
    def __init__(self, r=255, g=255, b=255, a=255):
        """Creates a Color with the specified RGBA values."""
        if r < 0 or r > 255:
            raise ValueError("r must be in the range [0; 255]")
        if g < 0 or g > 255:
            raise ValueError("r must be in the range [0; 255]")
        if b < 0 or b > 255:
            raise ValueError("r must be in the range [0; 255]")
        if a < 0 or a > 255:
            raise ValueError("r must be in the range [0; 255]")
        self._r = int(r)
        self._g = int(g)
        self._b = int(b)
        self._a = int(a)

    def __repr__(self):
        return "Color(r=%d, g=%d, b=%d, a=%d)" % \
            (self.r, self.g, self.b, self.a)

    def __copy__(self):
        return Color(self.r, self.g, self.b, self.a)

    def __eq__(self, color):
        return self.r == color.r and self.g == color.g and \
            self.b == color.b and self.a == color.a

    def __ne__(self, color):
        return self.r != color.r or self.g != color.g or \
            self.b != color.b or self.a != color.a

    def __int__(self):
        return (self.r << 24 | self.g << 16 | self.b << 8 | self.a)

    def __long__(self):
        return (self.r << 24 | self.g << 16 | self.b << 8 | self.a)

    def __float__(self):
        return (self.r << 24 | self.g << 16 | self.b << 8 | self.a) * 1.0

    def __index__(self):
        return (self.r << 24 | self.g << 16 | self.b << 8 | self.a)

    def __oct__(self):
        val = (self.r << 24 | self.g << 16 | self.b << 8 | self.a)
        return oct(val)

    def __hex__(self):
        val = (self.r << 24 | self.g << 16 | self.b << 8 | self.a)
        return hex(val)

    def __invert__(self):
        vals = (255 - self.r, 255 - self.g, 255 - self.b, 255 - self.a)
        return Color(vals[0], vals[1], vals[2], vals[3])

    def __mod__(self, color):
        vals = (self.r % color.r, self.g % color.g, self.b % color.b,
                self.a % color.a)
        return Color(vals[0], vals[1], vals[2], vals[3])

    def __div__(self, color):
        vals = [0, 0, 0, 0]
        if color.r != 0:
            vals[0] = self.r / color.r
        if color.g != 0:
            vals[1] = self.g / color.g
        if color.b != 0:
            vals[2] = self.b / color.b
        if color.a != 0:
            vals[3] = self.a / color.a
        return Color(vals[0], vals[1], vals[2], vals[3])

    def __truediv__(self, color):
        vals = [0, 0, 0, 0]
        if color.r != 0:
            vals[0] = self.r / color.r
        if color.g != 0:
            vals[1] = self.g / color.g
        if color.b != 0:
            vals[2] = self.b / color.b
        if color.a != 0:
            vals[3] = self.a / color.a
        return Color(vals[0], vals[1], vals[2], vals[3])

    def __mul__(self, color):
        vals = (min(self.r * color.r, 255), min(self.g * color.g, 255),
                min(self.b * color.b, 255), min(self.a * color.a, 255))
        return Color(vals[0], vals[1], vals[2], vals[3])

    def __sub__(self, color):
        vals = (max(self.r - color.r, 0), max(self.g - color.g, 0),
                max(self.b - color.b, 0), max(self.a - color.a, 0))
        return Color(vals[0], vals[1], vals[2], vals[3])

    def __add__(self, color):
        vals = (min(self.r + color.r, 255), min(self.g + color.g, 255),
                min(self.b + color.b, 255), min(self.a + color.a, 255))
        return Color(vals[0], vals[1], vals[2], vals[3])

    def __len__(self):
        return 4

    def __getitem__(self, index):
        return (self.r, self.g, self.b, self.a)[index]

    def __setitem__(self, index, val):
        tmp = [self.r, self.g, self.b, self.a]
        tmp[index] = val
        self.r = tmp[0]
        self.g = tmp[1]
        self.b = tmp[2]
        self.a = tmp[3]

    @property
    def r(self):
        """Gets or sets the red value of the Color."""
        return self._r

    @r.setter
    def r(self, val):
        """Gets or sets the red value of the Color."""
        if type(val) not in(int, long):
            raise TypeError("value must be an int")
        if val < 0 or val > 255:
            raise ValueError("The value must be in the range [0; 255]")
        self._r = val

    @property
    def g(self):
        """Gets or sets the green value of the Color."""
        return self._g

    @g.setter
    def g(self, val):
        """Gets or sets the green value of the Color."""
        if type(val) not in(int, long):
            raise TypeError("value must be an int")
        if val < 0 or val > 255:
            raise ValueError("The value must be in the range [0; 255]")
        self._g = val

    @property
    def b(self):
        """Gets or sets the blue value of the Color."""
        return self._b

    @b.setter
    def b(self, val):
        """Gets or sets the blue value of the Color."""
        if type(val) not in(int, long):
            raise TypeError("value must be an int")
        if val < 0 or val > 255:
            raise ValueError("The value must be in the range [0; 255]")
        self._b = val

    @property
    def a(self):
        """Gets or sets the alpha value of the Color."""
        return self._a

    @a.setter
    def a(self, val):
        """Gets or sets the alpha value of the Color."""
        if type(val) not in(int, long):
            raise TypeError("value must be an int")
        if val < 0 or val > 255:
            raise ValueError("The value must be in the range [0; 255]")
        self._a = val

    @property
    def hsva(self):
        """The Color as HSVA value."""
        rn = self.r / 255.0
        gn = self.g / 255.0
        bn = self.b / 255.0
        an = self.a / 255.0

        maxv = max(rn, gn, bn)
        minv = min(rn, gn, bn)
        diff = maxv - minv

        h = 0
        s = 0
        v = maxv * 100.0
        a = an * 100.0

        if maxv == minv:
            return(h, s, v, a)
        s = 100.0 * (maxv - minv) / maxv

        if maxv == rn:
            h = (60 * (gn - bn) / diff) % 360.0
        elif maxv == gn:
            h = (60 * (bn - rn) / diff) + 120.0
        else:
            h = (60 * (rn - gn) / diff) + 240.0
        if h < 0:
            h += 360.0
        return (h, s, v, a)

    @hsva.setter
    def hsva(self, value):
        """The Color as HSVA value."""
        h, s, v, a = value
        for x in (h, s, v, a):
            if type(x) not in(int, long, float):
                raise TypeError("HSVA values must be of type float")
        if not (0 <= s <= 100) or not (0 <= v <= 100) or \
                not (0 <= a <= 100) or not (0 <= h <= 360):
            raise ValueError("invalid HSVA value")

        self.a = int((a / 100.0) * 255)
        s /= 100.0
        v /= 100.0

        hi = int(floor(h / 60.0))
        f = (h / 60.0) - hi
        p = v * (1 - s)
        q = v * (1 - s * f)
        t = v * (1 - s * (1 - f))
        if hi == 0:
            self.r = int(v * 255)
            self.g = int(t * 255)
            self.b = int(p * 255)
        elif hi == 1:
            self.r = int(q * 255)
            self.g = int(v * 255)
            self.b = int(p * 255)
        elif hi == 2:
            self.r = int(p * 255)
            self.g = int(v * 255)
            self.b = int(t * 255)
        elif hi == 3:
            self.r = int(p * 255)
            self.g = int(q * 255)
            self.b = int(v * 255)
        elif hi == 4:
            self.r = int(t * 255)
            self.g = int(p * 255)
            self.b = int(v * 255)
        elif hi == 5:
            self.r = int(v * 255)
            self.g = int(p * 255)
            self.b = int(q * 255)
        else:
            raise OverflowError("invalid HSVA value")

    @property
    def hsla(self):
        """The Color a HSLA value."""
        rn = self.r / 255.0
        gn = self.g / 255.0
        bn = self.b / 255.0
        an = self.a / 255.0

        maxv = max(rn, gn, bn)
        minv = min(rn, gn, bn)
        diff = maxv - minv

        h = 0
        s = 0
        l = 50.0 * (maxv + minv)
        a = an * 100.0

        if maxv == minv:
            return(h, s, l, a)

        if l <= 50.0:
            s = diff / (maxv + minv) * 100.0
        else:
            s = diff / (2.0 - maxv - minv) * 100.0

        if maxv == rn:
            h = (60 * (gn - bn) / diff) % 360.0
        elif maxv == gn:
            h = (60 * (bn - rn) / diff) + 120.0
        else:
            h = (60 * (rn - gn) / diff) + 240.0
        if h < 0:
            h += 360.0
        return (h, s, l, a)

    @hsla.setter
    def hsla(self, value):
        """The Color a HSLA value."""
        h, s, l, a = value
        for x in (h, s, l, a):
            if type(x) not in (int, long, float):
                raise TypeError("HSLA values must be of type float")
        if not (0 <= s <= 100) or not (0 <= l <= 100) or \
                not (0 <= a <= 100) or not (0 <= h <= 360):
            raise ValueError("invalid HSLA value")

        self.a = int((a / 100.0) * 255)

        s /= 100.0
        l /= 100.0

        if s == 0:
            self.r = int(l * 255)
            self.g = int(l * 255)
            self.b = int(l * 255)
            return

        q = 0
        if l < 0.5:
            q = l * (1 + s)
        else:
            q = l + s - (l * s)
        p = 2 * l - q

        ht = h / 360.0

        # r
        h = ht + (1.0 / 3.0)
        if h < 0:
            h += 1
        elif h > 1:
            h -= 1

        if h < (1.0 / 6.0):
            self.r = int((p + ((q - p) * 6 * h)) * 255)
        elif h < 0.5:
            self.r = int(q * 255)
        elif h < (2.0 / 3.0):
            self.r = int((p + ((q - p) * 6 * (2.0 / 3.0 - h))) * 255)
        else:
            self.r = int(p * 255)

        # g
        h = ht
        if h < 0:
            h += 1
        elif h > 1:
            h -= 1

        if h < (1.0 / 6.0):
            self.g = int((p + ((q - p) * 6 * h)) * 255)
        elif h < 0.5:
            self.g = int(q * 255)
        elif h < (2.0 / 3.0):
            self.g = int((p + ((q - p) * 6 * (2.0 / 3.0 - h))) * 255)
        else:
            self.g = int(p * 255)

        # b
        h = ht - (1.0 / 3.0)
        if h < 0:
            h += 1
        elif h > 1:
            h -= 1

        if h < (1.0 / 6.0):
            self.b = int((p + ((q - p) * 6 * h)) * 255)
        elif h < 0.5:
            self.b = int(q * 255)
        elif h < (2.0 / 3.0):
            self.b = int((p + ((q - p) * 6 * (2.0 / 3.0 - h))) * 255)
        else:
            self.b = int(p * 255)

    @property
    def i1i2i3(self):
        """The Color as I1I2I3 value."""
        rn = self.r / 255.0
        gn = self.g / 255.0
        bn = self.b / 255.0

        i1 = (rn + gn + bn) / 3.0
        i2 = (rn - bn) / 2.0
        i3 = (2 * gn - rn - bn) / 4.0

        return(i1, i2, i3)

    @i1i2i3.setter
    def i1i2i3(self, value):
        """The Color as I1I2I3 value."""
        i1, i2, i3 = value
        for x in (i1, i2, i3):
            if type(x) not in (int, long, float):
                raise TypeError("I1I2I3 values must be of type float")
        if not (0 <= i1 <= 1) or not (-0.5 <= i2 <= 0.5) or \
                not (-0.5 <= i3 <= 0.5):
            raise ValueError("invalid I1I2I3 value")

        ab = i1 - i2 - 2 * i3 / 3.0
        ar = 2 * i2 + ab
        ag = 3 * i1 - ar - ab

        self.r = int(ar * 255)
        self.g = int(ag * 255)
        self.b = int(ab * 255)

    @property
    def cmy(self):
        """The Color as CMY value."""
        return (1.0 - self.r / 255.0,
                1.0 - self.g / 255.0,
                1.0 - self.b / 255.0)

    @cmy.setter
    def cmy(self, value):
        """The Color as CMY value."""
        c, m, y = value
        if (c < 0 or c > 1) or (m < 0 or m > 1) or (y < 0 or y > 1):
            raise ValueError("invalid CMY value")
        self.r = int((1.0 - c) * 255)
        self.g = int((1.0 - m) * 255)
        self.b = int((1.0 - y) * 255)

    def normalize(self):
        """Returns the RGBA values in a normalized form with the range
        [0;1] as tuple.
        """
        return (self.r / 255.0, self.g / 255.0, self.b / 255.0, self.a / 255.0)


def is_rgb_color(v):
    """Checks, if the passed value is an item that could be converted to
    a RGB color.
    """
    try:
        if hasattr(v, "r") and hasattr(v, "g") and hasattr(v, "b"):
            if 0 <= int(v.r) <= 255 and 0 <= int(v.g) <= 255 and \
                    0 <= v.b <= 255:
                return True

        if len(v) >= 3:
            if 0 <= int(v[0]) <= 255 and 0 <= int(v[1]) <= 255 and \
                    0 < int(v[2]) < 255:
                return True
        return False
    except (TypeError, ValueError):
        return False


def is_rgba_color(v):
    """Checks, if the passed value is an item that could be converted to
    a RGBA color.
    """
    rgb = is_rgb_color(v)
    if not rgb:
        return False

    try:
        if hasattr(v, "a") and 0 <= int(v.a) <= 255:
            return True
        if len(v) >= 4 and 0 <= int(v[3]) <= 255:
            return True
        return False
    except (TypeError, ValueError):
        return False


def argb_to_color(v):
    """Converts an integer value to a Color, assuming the integer
    represents a 32-bit ARGB value.
    """
    v = long(v)

    a = ((v & 0xFF000000) >> 24)
    r = ((v & 0x00FF0000) >> 16)
    g = ((v & 0x0000FF00) >> 8)
    b = ((v & 0x000000FF))
    return Color(r, g, b, a)


ARGB = argb_to_color


def rgba_to_color(v):
    """Converts an integer value to a Color, assuming the integer
    represents a 32-bit RGBBA value.
    """
    v = long(v)

    r = ((v & 0xFF000000) >> 24)
    g = ((v & 0x00FF0000) >> 16)
    b = ((v & 0x0000FF00) >> 8)
    a = ((v & 0x000000FF))
    return Color(r, g, b, a)


RGBA = rgba_to_color


def string_to_color(s):
    """Converts a hex color string or color name to a Color value.

    Supported hex values are:

    #RGB
    #RGBA
    #RRGGBB
    #RRGGBBAA

    0xRGB
    0xRGBA
    0xRRGGBB
    0xRRGGBBAA
    """
    if type(s) is not str:
        raise TypeError("s must be a string")

    if not(s.startswith("#") or s.startswith("0x")):
        raise ValueError("value is not Color-compatible")

    if s.startswith("#"):
        s = s[1:]
    else:
        s = s[2:]

    r, g, b, a = 255, 255, 255, 255
    if len(s) in (3, 4):
        # A triple/quadruple in the form #ead == #eeaadd
        r = int(s[0], 16) << 4 | int(s[0], 16)
        g = int(s[1], 16) << 4 | int(s[1], 16)
        b = int(s[2], 16) << 4 | int(s[2], 16)
        if len(s) == 4:
            a = int(s[3], 16) << 4 | int(s[3], 16)
    elif len(s) in (6, 8):
        r = int(s[0], 16) << 4 | int(s[1], 16)
        g = int(s[2], 16) << 4 | int(s[3], 16)
        b = int(s[4], 16) << 4 | int(s[5], 16)
        if len(s) == 8:
            a = int(s[6], 16) << 4 | int(s[7], 16)
    else:
        raise ValueError("value is not Color-compatible")
    return Color(r, g, b, a)


def convert_to_color(v):
    """Tries to convert the passed value to a Color object.

    If the color is an integer value, it is assumed to be in ARGB layout.
    """
    if isinstance(v, Color):
        return v

    if type(v) is str:
        return string_to_color(v)
    if type(v) in (int, long):
        return argb_to_color(v)

    r, g, b, a = 0, 0, 0, 0
    if hasattr(v, "r") and hasattr(v, "g") and hasattr(v, "b"):
        if 0 <= int(v.r) <= 255 and 0 <= int(v.g) <= 255 and \
                0 <= v.b <= 255:
            r = int(v.r)
            g = int(v.g)
            b = int(v.b)
            if hasattr(v, "a") and 0 <= int(v.a) <= 255:
                a = int(v.a)
        else:
            raise ValueError("value is not Color-compatible")
        return Color(r, g, b, a)

    try:
        length = len(v)
    except:
        raise ValueError("value is not Color-compatible")
    if length < 3:
        raise ValueError("value is not Color-compatible")
    if 0 <= int(v[0]) <= 255 and 0 <= int(v[1]) <= 255 and \
            0 <= int(v[2]) <= 255:
        r = int(v[0])
        g = int(v[1])
        b = int(v[2])
        if length >= 4 and 0 <= int(v[3]) <= 255:
            a = int(v[3])
        return Color(r, g, b, a)

    raise ValueError("value is not Color-compatible")


COLOR = convert_to_color
