# OBSAH SOUBORU:
# - pomocne funkce tykajici se barev
#   - ziskani barvy z motivu
#   - nejblizsi zobrazitelna barva

import colorsys
import math
from math import sqrt
from colorsys import rgb_to_hls, hls_to_rgb

from openpyxl import Workbook
from openpyxl.styles import Color
from enums import EColor
import settings

RGBMAX = 0xff
HLSMAX = 240

# DULEZITE:
# HSB == HSV
# HSB != HSL
# rozdily mezi hsl, hsv a hsb jsou, pro me potreby staci umet prevest mezi hex, hsl a rgb.
# rgb je dobre pro interpolaci, je tam snadno predstavitelna. urcite jde interpolovat i v hsl,
# ale momentalne nevim jak to provest ani jak to vysvetlit. v budoucnu by se tim odstranilo par kroku. 

# hodnoty prevedene z balicku ninecolors. dle dokumentace xcolor je H od 0 do 360°, zbyle dve od 0 do 1
# prirazeni hodnot k enum variante:
sat0 = {'(0, 0.0, 0.14)': EColor.GRAY1, '(0, 0.0, 0.23)': EColor.GRAY2, '(0, 0.0, 0.3)': EColor.GRAY3, 
        '(0, 0.0, 0.38)': EColor.GRAY4, '(0, 0.0, 0.46)': EColor.GRAY5, '(0, 0.0, 0.55)': EColor.GRAY6,
        '(0, 0.0, 0.64)': EColor.GRAY7, '(0, 0.0, 0.75)': EColor.GRAY8, '(0, 0.0, 0.87)': EColor.GRAY9, 
        '(0, 0, 1.0)': EColor.WHITE}

hue0 = {'(0, 0.504, 0.173)': EColor.RED1, '(0, 0.504, 0.273)': EColor.RED2, '(0, 0.504, 0.359)': EColor.RED3, 
        '(0, 0.504, 0.446)': EColor.RED4, '(0, 0.501, 0.539)': EColor.RED5, '(0, 0.495, 0.624)': EColor.RED6, 
        '(0, 0.511, 0.714)': EColor.RED7, '(0, 0.497, 0.801)': EColor.RED8, '(0, 0.511, 0.898)': EColor.RED9, 
        '(0, 0, 1.0)': EColor.WHITE}

hue30 = {'(30, 0.504, 0.126)': EColor.BROWN1, '(30, 0.504, 0.206)': EColor.BROWN2, '(30, 0.504, 0.273)': EColor.BROWN3, 
         '(30, 0.504, 0.339)': EColor.BROWN4, '(30, 0.504, 0.412)': EColor.BROWN5, '(30, 0.504, 0.492)': EColor.BROWN6, 
         '(30, 0.495, 0.604)': EColor.BROWN7, '(30, 0.496, 0.722)': EColor.BROWN8, '(30, 0.499, 0.86)': EColor.BROWN9, 
         '(30, 0, 1.0)': EColor.WHITE}

hue60 = {'(60, 0.504, 0.1)': EColor.YELLOW1, '(60, 0.504, 0.153)': EColor.YELLOW2, '(60, 0.504, 0.206)': EColor.YELLOW3, 
         '(60, 0.504, 0.259)': EColor.YELLOW4, '(60, 0.504, 0.313)': EColor.YELLOW5, '(60, 0.504, 0.372)': EColor.YELLOW6, 
         '(60, 0.504, 0.439)': EColor.YELLOW7, '(60, 0.497, 0.543)': EColor.YELLOW8, '(60, 0.497, 0.761)': EColor.YELLOW9, 
         '(60, 0, 1.0)': EColor.WHITE}

hue90 = {'(90, 0.504, 0.106)': EColor.OLIVE1, '(90, 0.504, 0.166)': EColor.OLIVE2, '(90, 0.504, 0.219)': EColor.OLIVE3, 
         '(90, 0.504, 0.279)': EColor.OLIVE4, '(90, 0.504, 0.339)': EColor.OLIVE5, '(90, 0.504, 0.399)': EColor.OLIVE6, 
         '(90, 0.504, 0.472)': EColor.OLIVE7, '(90, 0.506, 0.616)': EColor.OLIVE8, '(90, 0.497, 0.801)': EColor.OLIVE9, 
         '(90, 0, 1.0)': EColor.WHITE}

hue120 = {'(120, 0.504, 0.106)': EColor.GREEN1, '(120, 0.504, 0.173)': EColor.GREEN2, '(120, 0.504, 0.233)': EColor.GREEN3,
          '(120, 0.504, 0.293)': EColor.GREEN4, '(120, 0.504, 0.352)': EColor.GREEN5, '(120, 0.504, 0.419)': EColor.GREEN6, 
          '(120, 0.504, 0.492)': EColor.GREEN7, '(120, 0.506, 0.656)': EColor.GREEN8, '(120, 0.49, 0.824)': EColor.GREEN9, 
          '(120, 0, 1.0)': EColor.WHITE}

hue150 = {'(150, 0.504, 0.106)': EColor.TEAL1, '(150, 0.504, 0.173)': EColor.TEAL2, '(150, 0.504, 0.226)': EColor.TEAL3,
          '(150, 0.504, 0.286)': EColor.TEAL4, '(150, 0.504, 0.346)': EColor.TEAL5, '(150, 0.504, 0.412)': EColor.TEAL6, 
          '(150, 0.504, 0.485)': EColor.TEAL7, '(150, 0.501, 0.64)': EColor.TEAL8, '(150, 0.503, 0.819)': EColor.TEAL9, 
          '(150, 0, 1.0)': EColor.WHITE}

hue180 = {'(180, 0.504, 0.106)': EColor.CYAN1, '(180, 0.504, 0.166)': EColor.CYAN2, '(180, 0.504, 0.219)': EColor.CYAN3, 
          '(180, 0.504, 0.279)': EColor.CYAN4, '(180, 0.504, 0.339)': EColor.CYAN5, '(180, 0.504, 0.399)': EColor.CYAN6, 
          '(180, 0.504, 0.472)': EColor.CYAN7, '(180, 0.5, 0.62)': EColor.CYAN8, '(180, 0.486, 0.805)': EColor.CYAN9, 
          '(180, 0, 1.0)': EColor.WHITE}

hue210 = {'(210, 0.504, 0.146)': EColor.AZURE1, '(210, 0.504, 0.233)': EColor.AZURE2, '(210, 0.504, 0.313)': EColor.AZURE3, 
          '(210, 0.504, 0.392)': EColor.AZURE4, '(210, 0.504, 0.472)': EColor.AZURE5, '(210, 0.498, 0.562)': EColor.AZURE6, 
          '(210, 0.5, 0.66)': EColor.AZURE7, '(210, 0.497, 0.761)': EColor.AZURE8, '(210, 0.505, 0.879)': EColor.AZURE9,
          '(210, 0, 1.0)': EColor.WHITE}

hue240 = {'(240, 0.504, 0.226)': EColor.BLUE1, '(240, 0.504, 0.352)': EColor.BLUE2, '(240, 0.504, 0.465)': EColor.BLUE3, 
          '(240, 0.493, 0.547)': EColor.BLUE4, '(240, 0.506, 0.616)': EColor.BLUE5, '(240, 0.506, 0.676)': EColor.BLUE6,
          '(240, 0.492, 0.744)': EColor.BLUE7, '(240, 0.49, 0.824)': EColor.BLUE8, '(240, 0.487, 0.902)': EColor.BLUE9, 
          '(240, 0, 1.0)': EColor.WHITE}

hue270 = {'(270, 0.504, 0.193)': EColor.VIOLET1, '(270, 0.504, 0.299)': EColor.VIOLET2, '(270, 0.504, 0.399)': EColor.VIOLET3, 
          '(270, 0.504, 0.499)': EColor.VIOLET4, '(270, 0.504, 0.577)': EColor.VIOLET5, '(270, 0.495, 0.644)': EColor.VIOLET6, 
          '(270, 0.496, 0.722)': EColor.VIOLET7, '(270, 0.486, 0.805)': EColor.VIOLET8, '(270, 0.511, 0.898)': EColor.VIOLET9, 
          '(270, 0, 1.0)': EColor.WHITE}

hue300 = {'(300, 0.504, 0.153)': EColor.MAGENTA1, '(300, 0.504, 0.246)': EColor.MAGENTA2, 
          '(300, 0.504, 0.326)': EColor.MAGENTA3, '(300, 0.504, 0.406)': EColor.MAGENTA4, 
          '(300, 0.504, 0.492)': EColor.MAGENTA5, '(300, 0.505, 0.596)': EColor.MAGENTA6, 
          '(300, 0.493, 0.685)': EColor.MAGENTA7, '(300, 0.493, 0.783)': EColor.MAGENTA8, 
          '(300, 0.485, 0.884)': EColor.MAGENTA9, '(300, 0, 1.0)': EColor.WHITE}

hue330 = {'(330, 0.504, 0.166)': EColor.PURPLE1, '(330, 0.504, 0.259)': EColor.PURPLE2, '(330, 0.504, 0.346)': EColor.PURPLE3, 
          '(330, 0.504, 0.432)': EColor.PURPLE4, '(330, 0.499, 0.521)': EColor.PURPLE5, '(330, 0.506, 0.616)': EColor.PURPLE6, 
          '(330, 0.498, 0.701)': EColor.PURPLE7, '(330, 0.509, 0.796)': EColor.PURPLE8, '(330, 0.511, 0.898)': EColor.PURPLE9, 
          '(330, 0, 1.0)': EColor.WHITE}

# hodnoty pro pocitani nejblizsi barvy:
list_sat0 = [(0, 0.0, 0.14), (0, 0.0, 0.23), (0, 0.0, 0.3), (0, 0.0, 0.38), (0, 0.0, 0.46), (0, 0.0, 0.55), 
             (0, 0.0, 0.64), (0, 0.0, 0.75), (0, 0.0, 0.87), (0, 0, 1.0)]

list_hue0 = [(0, 0.504, 0.173), (0, 0.504, 0.273), (0, 0.504, 0.359), (0, 0.504, 0.446), (0, 0.501, 0.539), 
             (0, 0.495, 0.624), (0, 0.511, 0.714), (0, 0.497, 0.801), (0, 0.511, 0.898), (0, 0, 1.0)]

list_hue30 = [(30, 0.504, 0.126), (30, 0.504, 0.206), (30, 0.504, 0.273), (30, 0.504, 0.339), (30, 0.504, 0.412), 
              (30, 0.504, 0.492), (30, 0.495, 0.604), (30, 0.496, 0.722), (30, 0.499, 0.86), (30, 0, 1.0)]

list_hue60 = [(60, 0.504, 0.1), (60, 0.504, 0.153), (60, 0.504, 0.206), (60, 0.504, 0.259), (60, 0.504, 0.313), 
              (60, 0.504, 0.372), (60, 0.504, 0.439), (60, 0.497, 0.543), (60, 0.497, 0.761), (60, 0, 1.0)]

list_hue90 = [(90, 0.504, 0.106), (90, 0.504, 0.166), (90, 0.504, 0.219), (90, 0.504, 0.279), (90, 0.504, 0.339), 
              (90, 0.504, 0.399), (90, 0.504, 0.472), (90, 0.506, 0.616), (90, 0.497, 0.801), (90, 0, 1.0)]

list_hue120 = [(120, 0.504, 0.106), (120, 0.504, 0.173), (120, 0.504, 0.233), (120, 0.504, 0.293), 
               (120, 0.504, 0.352), (120, 0.504, 0.419), (120, 0.504, 0.492), (120, 0.506, 0.656), 
               (120, 0.49, 0.824), (120, 0, 1.0)]

list_hue150 = [(150, 0.504, 0.106), (150, 0.504, 0.173), (150, 0.504, 0.226), (150, 0.504, 0.286), 
               (150, 0.504, 0.346), (150, 0.504, 0.412), (150, 0.504, 0.485), (150, 0.501, 0.64), 
               (150, 0.503, 0.819), (150, 0, 1.0)]

list_hue180 = [(180, 0.504, 0.106), (180, 0.504, 0.166), (180, 0.504, 0.219), (180, 0.504, 0.279), 
               (180, 0.504, 0.339), (180, 0.504, 0.399), (180, 0.504, 0.472), (180, 0.5, 0.62), 
               (180, 0.486, 0.805), (180, 0, 1.0)]

list_hue210 = [(210, 0.504, 0.146), (210, 0.504, 0.233), (210, 0.504, 0.313), (210, 0.504, 0.392), 
               (210, 0.504, 0.472), (210, 0.498, 0.562), (210, 0.5, 0.66), (210, 0.497, 0.761), 
               (210, 0.505, 0.879), (210, 0, 1.0)]

list_hue240 = [(240, 0.504, 0.226), (240, 0.504, 0.352), (240, 0.504, 0.465), (240, 0.493, 0.547), 
               (240, 0.506, 0.616), (240, 0.506, 0.676), (240, 0.492, 0.744), (240, 0.49, 0.824), 
               (240, 0.487, 0.902), (240, 0, 1.0)]

list_hue270 = [(270, 0.504, 0.193), (270, 0.504, 0.299), (270, 0.504, 0.399), (270, 0.504, 0.499), 
               (270, 0.504, 0.577), (270, 0.495, 0.644), (270, 0.496, 0.722), (270, 0.486, 0.805), 
               (270, 0.511, 0.898), (270, 0, 1.0)]

list_hue300 = [(300, 0.504, 0.153), (300, 0.504, 0.246), (300, 0.504, 0.326), (300, 0.504, 0.406), 
               (300, 0.504, 0.492), (300, 0.505, 0.596), (300, 0.493, 0.685), (300, 0.493, 0.783), 
               (300, 0.485, 0.884), (300, 0, 1.0)]

list_hue330 = [(330, 0.504, 0.166), (330, 0.504, 0.259), (330, 0.504, 0.346), (330, 0.504, 0.432), 
               (330, 0.499, 0.521), (330, 0.506, 0.616), (330, 0.498, 0.701), (330, 0.509, 0.796), 
               (330, 0.511, 0.898), (330, 0, 1.0)]

ninecolors_dict = {0: hue0, 30: hue30, 60: hue60, 90: hue90, 120: hue120, 150: hue150, 180: hue180, 210: hue210,
                   240: hue240, 270: hue270, 300: hue300, 330: hue330, 360: sat0}

color_list = {0: list_hue0, 30: list_hue30, 60: list_hue60, 90: list_hue90, 120: list_hue120, 150: list_hue150,
              180: list_hue180, 210: list_hue210, 240: list_hue240, 270: list_hue270, 300: list_hue300,
              330: list_hue330, 360: list_sat0}


def hsl_to_ninecolors(h: int, s: float, l: float, allow_black: bool = False):
    """Nalezeni nejblizsi barvy definovane v ninecolors.
    [28, 0.6, 0.6] -> EColor.BROWN5"""

    if l < 0.07:
        return EColor.BLACK if allow_black else EColor.NOT_SET
    elif l > 0.95:
        return EColor.WHITE

    # najdu nejblizsi odstin, tj, nejblizsi nasobek 30. Pro hodnoty blizke 30/2 zaokhrouhluju vzdy nahoru, aby
    # to bylo konzistentni a 2 blizke barvy v lin interpolaci pak nemely totalne jine odstiny.
    # hodnota 13.5 je zkuseny odhad
    if h % 30 > 13.5:
        interp_h = 30 * math.ceil(h / 30)
    else:
        interp_h = 30 * round(h / 30)

    if interp_h >= 360 or interp_h < 0:
        interp_h = 0

    # h == 0 a h == 360 v hsv predstavuji totez, takze toho vyuziju k rozliseni sede (h,s==0) od cervene (h==0)
    if interp_h == 0 and s < 0.05:
        interp_h = 360

    # vezmu si seznam barev ve kterem hledat
    color_cmp = color_list[interp_h]
    color_diffs = []

    # v nejblizsim odstinu hledam nejblizsi vzdalenost soucasne od S a B (S a V)
    # jsme v polorovine, takze staci pouzit pythagoras
    for color in color_cmp:
        hh, ss, ll = color

        color_diff = sqrt((ll-l)**2 + ss**2 + s**2 - (2*(s*ss*math.cos(math.radians(hh-h)))))
        color_diffs.append((color_diff, color))

    # ziskam nejblizsi barvu ([0] je pak ta diference)
    closest = min(color_diffs)[1]

    # vratim jeji nicecolors ekvivalent
    return ninecolors_dict[interp_h][str(closest)]


def get_cell_color_nine(wb: Workbook, clr: Color, allow_black: bool = False):
    """Objekt typu Color -> ninecolors"""

    ltx_color = EColor.NOT_SET
    try:
        theme = clr.theme
        tint = clr.tint

        # pouziva se motiv
        if isinstance(theme, int):
            h, s, l = theme_and_tint_to_hsl(wb, theme, tint)
            ltx_color = hsl_to_ninecolors(h * 360, s, l, allow_black)

        # pouziva se rgb
        elif str(clr.rgb)[0] != 'V':
            h, s, l = hex_to_hsl(clr.rgb[2:], allow_black)
            ltx_color = hsl_to_ninecolors(h * 360, s, l, allow_black)

        else:
            return EColor.BLACK if allow_black else EColor.NOT_SET

        return ltx_color

    except AttributeError:
        # pokud neni zadna barva nastavena, muze nastat clr == None, tedy ze neexistuje clr.theme a
        # clr.tint, v tom pripade proste nedelame nic, vime, ze text bude cerny nebo pozadi bile, coz nepotrebuje zasah.
        return EColor.NOT_SET


def get_rgb_from_color_obj(wb: Workbook, clr: Color):
    """Z objektu typu Color vytahne barvu v rgb. RGB je potreba pro linearni interpolaci, v hsv i v hsl by neslo
    interpolovat linearne (po primce), byla by to doslova interpolace po nejake 3D horske draze."""

    clr_rgb = 'FFFFFF'
    try:
        theme = clr.theme
        tint = clr.tint

        # pouziva se index
        if isinstance(theme, int):
            rgb = get_theme_colors(wb)[theme]
            h, l, s = rgb_to_ms_hls(rgb)
            clr_rgb = rgb_to_hex(ms_hls_to_rgb(h, tint_luminance(tint, l), s))

        # pouziva se rgb
        elif clr.rgb[0] != 'V':
            clr_rgb = clr.rgb[2:]
    except AttributeError:
        return 'FFFFFF'

    return clr_rgb


def theme_and_tint_to_ninecolors(wb, theme, tint, allow_black=False):
    h, s, l = theme_and_tint_to_hsl(wb, theme, tint)
    return hsl_to_ninecolors(h * 360, s * HLSMAX, l * HLSMAX, allow_black)


def theme_and_tint_to_hsl(wb, theme, tint):
    """Given a workbook, a theme number and a tint return a hsl based color"""
    rgb = get_theme_colors(wb)[theme]
    h, l, s = rgb_to_ms_hls(rgb)
    return h/HLSMAX, s/HLSMAX, tint_luminance(tint, l)/HLSMAX

def hex_to_rgb(hex_str: str, normalise: bool = False):
    """0080FF -> [0, 128, 255]
    if normalize: 0080FF -> [0, 0.5, 1]"""

    if len(hex_str) != 6:
        return [0, 0, 0]

    color = []

    if normalise:
        for i in (0, 2, 4):
            color.append(int(hex_str[i:i + 2], 16) / 255.0)
        return color

    for i in (0, 2, 4):
        color.append(int(hex_str[i:i + 2], 16))

    return color


def hex_to_hsl(hexrgb: str, allow_black: bool = False):
    h, l, s = colorsys.rgb_to_hls(*hex_to_rgb(hexrgb, True))
    return h, s, l


# prevzato z https://stackoverflow.com/questions/64858700/convert-hsl-to-hsv-color-codes-and-vice-versa-in-python
def hsv_to_hsl(h, s, v):
    """Barva v HSV (resp. v HSB) => Barva v HSL"""
    l = v * (1 - s / 2)
    s = 0 if l in (0, 1) else (v - l) / min(l, 1 - l)
    return h, s, l


def hsl_to_hsv(h, s, l):
    """Barva v HSL => Barva v HSV (resp. v HSB)"""
    v = l + s * min(l, 1 - l)
    s = 0 if v == 0 else 2 * (1 - l / v)
    return h, s, v

# odsud az po konec souboru je vse ukradnuto ze stackowerflow. tomu kodu docela rozumim, ale sam bych s nim v zivote
# neprisel. jde se na to nejak odkazat?
# (https://stackoverflow.com/questions/58429823/getting-excel-cell-background-themed-color-as-hex-with-openpyxl)

def tint_luminance(tint, lum):
    """Tints a HLSMAX based luminance"""
    # See: http://ciintelligence.blogspot.co.uk/2012/02/converting-excel-theme-color-and-tint.html
    if tint < 0:
        return int(round(lum * (1.0 + tint)))
    else:
        return int(round(lum * (1.0 - tint) + (HLSMAX - HLSMAX * (1.0 - tint))))


def rgb_to_ms_hls(red, green=None, blue=None):
    if green is None:
        if isinstance(red, str):
            if len(red) > 6:
                red = red[-6:]
            blue = int(red[4:], 16) / RGBMAX
            green = int(red[2:4], 16) / RGBMAX
            red = int(red[0:2], 16) / RGBMAX
        else:
            red, green, blue = red
    h, l, s = rgb_to_hls(red, green, blue)
    return int(round(h * HLSMAX)), int(round(l * HLSMAX)), int(round(s * HLSMAX))


def ms_hls_to_rgb(hue, lightness=None, saturation=None):
    """Converts HLSMAX based HLS values to rgb values in the range (0,1)"""
    if lightness is None:
        hue, lightness, saturation = hue
    return hls_to_rgb(hue / HLSMAX, lightness / HLSMAX, saturation / HLSMAX)


def rgb_to_hex(red, green=None, blue=None):
    """Converts (0,1) based RGB values to a hex string 'rrggbb'"""
    if green is None:
        red, green, blue = red
    return ('%02x%02x%02x' % (int(round(red * RGBMAX)), int(round(green * RGBMAX)), int(round(blue * RGBMAX)))).upper()


def get_theme_colors(wb):
    """Gets theme colors from the workbook"""
    # see: https://groups.google.com/forum/#!topic/openpyxl-users/I0k3TfqNLrc
    from openpyxl.xml.functions import QName, fromstring
    xlmns = 'http://schemas.openxmlformats.org/drawingml/2006/main'
    root = fromstring(wb.loaded_theme)
    themeEl = root.find(QName(xlmns, 'themeElements').text)
    colorSchemes = themeEl.findall(QName(xlmns, 'clrScheme').text)
    firstColorScheme = colorSchemes[0]

    colors = []

    for c in ['lt1', 'dk1', 'lt2', 'dk2', 'accent1', 'accent2', 'accent3', 'accent4', 'accent5', 'accent6']:
        accent = firstColorScheme.find(QName(xlmns, c).text)
        for i in list(accent):  # walk all child nodes, rather than assuming [0]
            # barvy okna (v 99 % pripadu cerna a bila)
            if 'window' in i.attrib['val']:
                colors.append(i.attrib['lastClr'])

            # barvy motivu
            else:
                colors.append(i.attrib['val'])

    return colors
