# -*- coding: utf-8 -*-

# Copyright 2014-2016 Mir Calculate. http://www.calculate-linux.org
#
#  Licensed under the Apache License, Version 2.0 (the "License");
#  you may not use this file except in compliance with the License.
#  You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
#  Unless required by applicable law or agreed to in writing, software
#  distributed under the License is distributed on an "AS IS" BASIS,
#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#  See the License for the specific language governing permissions and
#  limitations under the License.

import re
from itertools import chain
from math import pow
from calculate.lib.utils.text import MultiReplace


class ConsoleCodesInfo(object):
    """
    Коды цветов
    """
    COLORCOUNT = 8
    BLACK, RED, GREEN, BROWN, BLUE, PURPLE, CYAN, WHITE = range(0, COLORCOUNT)
    DEFAULT = 9
    FOREGROUND = 30
    FOREGROUND_DEFAULT = 39
    FOREGROUND_END = 37
    BACKGROUND = 40
    BACKGROUND_DEFAULT = 49
    BACKGROUND_END = 47
    BOLD = 1
    HALFBRIGHT = 2
    UNDERLINE = 4
    NOUNDERLINE = 24
    NORMAL = 22
    RESET = 0
    FOREGROUND256 = 38
    COLOR256 = 5
    BACKGROUND256 = 48
    INVERT = 7
    NOINVERT = 27


class ConsoleColor256(object):
    """Объект перевода RGB цветов в консоль 256color"""

    colorList = (0, 95, 135, 175, 215, 255)
    colorMatch = [colorList[x] + (colorList[x + 1] - colorList[x]) / 2
                  for x in range(0, 5)] + [255]
    colorHex = ["%02x" % val for val in colorList]
    webMatch = re.compile("^#[0-9A-Fa-f]{6}$")
    grayMatch = [6 + x * 11 for x in xrange(0, 23)] + [255]

    @staticmethod
    def rgbToConsole(color):
        """Перевести RGB в 256 консоль

        >>> ConsoleColor256.rgbToConsole("#5f00ff")
        57
        >>> ConsoleColor256.rgbToConsole("not #rrggbb") is None
        True
        """
        color = ConsoleColor256.convertRgbToIntGroup(color)
        if color:
            # grayscale match
            if abs(color[0] - color[1]) + abs(color[0] - color[2]) < 5:
                for j, matchGray in enumerate(ConsoleColor256.grayMatch):
                    if color[0] <= matchGray:
                        return 232 + j
            # colortext match
            colorNumber = 16
            for i, _color in enumerate(color):
                for j, halfColor in enumerate(ConsoleColor256.colorMatch):
                    if _color <= halfColor:
                        colorNumber += j * 6 ** i
                        break
            return colorNumber
        return None

    @staticmethod
    def convertRgbToIntGroup(color):
        """Преобразовать #RRGGBB в кортеж целых (dec(RR),dec(GG),dec(BB))

        В случае ошибки возвращает None
        """
        if color and ConsoleColor256.webMatch.match(color):
            return [int(x, base=16) for x in
                    (color[5:7], color[3:5], color[1:3])]
        return None

    @staticmethod
    def consoleToRgb(color):
        """
        Перевести 256 консоль в #RGB


        >>> ConsoleColor256.consoleToRgb(216)
        '#ffaf87'
        >>> ConsoleColor256.consoleToRgb(15) is None
        True
        """
        if color >= 255:
            return "#ffffff"
        if color >= 232:
            return "#{0:02x}{0:02x}{0:02x}".format((color - 232) * 11)
        elif color >= 16:
            color -= 16
            return "#%s" % "".join(ConsoleColor256.colorHex[color / x % 6]
                                   for x in (36, 6, 1))
        else:
            return None


def bitProperty(bit):
    def set(self, value):
        self.attr &= ~bit
        self.attr |= bit if value else 0

    def get(self):
        return bool(self.attr & bit)

    return property(get, set)

class TextState(object):
    """
    Параметры текста

    >>> ts = TextState()
    >>> ts.attr = TextState.Attributes.BOLD | TextState.Attributes.UNDERLINE
    >>> ts.bold
    True
    >>> ts.underline
    True
    >>> ts.halfbright
    False
    """

    class Attributes(object):
        NONE = 0
        BOLD = 1
        HALFBRIGHT = 2
        UNDERLINE = 4
        INVERT = 8

    class Colors(object):
        # обычные цвета
        BLACK = "black"
        RED = "red"
        GREEN = "green"
        BROWN = "brown"
        BLUE = "blue"
        PURPLE = "purple"
        CYAN = "cyan"
        GRAY = "gray"
        # яркие цвета
        DARK = "dark"
        LIGHT_RED = "lightred"
        LIGHT_GREEN = "lightgreen"
        YELLOW = "yellow"
        LIGHT_BLUE = "lightblue"
        LIGHT_PURPLE = "lightpurple"
        LIGHT_CYAN = "lightcyan"
        WHITE = "white"
        # темные цвета (консоль пониженной яркости)
        DARK_BLACK = "darkblack"
        DARK_RED = "darkred"
        DARK_GREEN = "darkgreen"
        DARK_BROWN = "darkbrown"
        DARK_BLUE = "darkblue"
        DARK_PURPLE = "darkpurple"
        DARK_CYAN = "darkcyan"
        DARK_GRAY = "darkgray"
        DEFAULT = None

    normalColors = [Colors.BLACK, Colors.RED, Colors.GREEN, Colors.BROWN,
                    Colors.BLUE, Colors.PURPLE, Colors.CYAN, Colors.GRAY]
    lightColors = [Colors.DARK, Colors.LIGHT_RED, Colors.LIGHT_GREEN,
                   Colors.YELLOW, Colors.LIGHT_BLUE, Colors.LIGHT_PURPLE,
                   Colors.LIGHT_CYAN, Colors.WHITE]
    darkColors = [Colors.DARK_BLACK, Colors.DARK_RED, Colors.DARK_GREEN,
                  Colors.DARK_BROWN, Colors.DARK_BLUE, Colors.DARK_PURPLE,
                  Colors.DARK_CYAN, Colors.DARK_GRAY]

    # текст с подчеркиванием
    underline = bitProperty(Attributes.UNDERLINE)
    # текст жирный
    bold = bitProperty(Attributes.BOLD)
    # пониженная яркость
    halfbright = bitProperty(Attributes.HALFBRIGHT)
    # инверсия
    invert = bitProperty(Attributes.INVERT)

    def __init__(self, foreground=Colors.DEFAULT,
                 background=Colors.DEFAULT,
                 attr=Attributes.NONE):
        self.foreground = foreground
        self.background = background
        self.attr = attr

    def clone(self):
        return self.__class__(self.foreground, self.background,
                              self.attr)

    def __cmp__(self, other):
        for i in ["foreground", "background", "attr"]:
            cmp_res = cmp(getattr(self, i), getattr(other, i))
            if cmp_res:
                return cmp_res
        return 0

    @classmethod
    def colors(cls):
        return chain(xrange(0, 8), [None])


class BaseColorMapping:
    # соответствие внутренних цветов консольным
    mapConsole_TS = {ConsoleCodesInfo.BLACK: TextState.Colors.BLACK,
                     ConsoleCodesInfo.RED: TextState.Colors.RED,
                     ConsoleCodesInfo.GREEN: TextState.Colors.GREEN,
                     ConsoleCodesInfo.BROWN: TextState.Colors.BROWN,
                     ConsoleCodesInfo.BLUE: TextState.Colors.BLUE,
                     ConsoleCodesInfo.PURPLE: TextState.Colors.PURPLE,
                     ConsoleCodesInfo.CYAN: TextState.Colors.CYAN,
                     ConsoleCodesInfo.WHITE: TextState.Colors.GRAY}

    mapTS_Console = {v: k for k, v in mapConsole_TS.items()}

    def __init__(self, base):
        self.mapConsole_TS = self.mapConsole_TS.copy()
        self.mapConsole_TS.update(base.mapConsole_TS)
        self.mapTS_Console = {v: k for k, v in self.mapConsole_TS.items()}


class LightColorMapping(BaseColorMapping):
    # соответствие внутренних цветов консольным
    offset = ConsoleCodesInfo.COLORCOUNT
    mapConsole_TS = {
        ConsoleCodesInfo.BLACK + offset: TextState.Colors.DARK,
        ConsoleCodesInfo.RED + offset: TextState.Colors.LIGHT_RED,
        ConsoleCodesInfo.GREEN + offset: TextState.Colors.LIGHT_GREEN,
        ConsoleCodesInfo.BROWN + offset: TextState.Colors.YELLOW,
        ConsoleCodesInfo.BLUE + offset: TextState.Colors.LIGHT_BLUE,
        ConsoleCodesInfo.PURPLE + offset: TextState.Colors.LIGHT_PURPLE,
        ConsoleCodesInfo.CYAN + offset: TextState.Colors.LIGHT_CYAN,
        ConsoleCodesInfo.WHITE + offset: TextState.Colors.WHITE}

    mapTS_Console = {v: k for k, v in mapConsole_TS.items()}


def ConsoleCodeMapping(codeoffset, base):
    """
    Декоратор для преобразования кодов цвета в код цвета тона или фона

    Добавляет смещение к коду (3 -> 3 + codeoffset)
    """
    mapConsole_TS = {codeoffset + k: v for k, v in
                     base.mapConsole_TS.items()}
    return {v: k for k, v in mapConsole_TS.items()}


class SpanPalette:
    """
    Палитра для SpanCssOutput (черное на белом)
    """
    LOW_BRIGHT, NORMAL_BRIGHT, HIGH_BRIGHT = 0, 1, 2

    defaultColor = ["Black", "Black", "DarkGrey"]
    defaultBackground = "White"

    normalBright = dict(zip(TextState.normalColors,
                            ["Black", "DarkRed", "DarkGreen",
                             "Sienna", "DarkBlue", "DarkViolet",
                             "LightSeaGreen", "Grey"]))

    highBright = dict(zip(TextState.lightColors,
                          ["DarkGrey", "Red", "Green",
                           "Yellow", "RoyalBlue", "Magenta",
                           "Cyan", "White"]))

    lowBright = dict(zip(TextState.darkColors,
                         ["Black", "Maroon", "DarkGreen",
                          "SaddleBrown", "DarkBlue", "DarkViolet",
                          "LightSeaGreen", "Grey"]))

    mapHighNormal = dict(zip(TextState.normalColors,
                             TextState.lightColors))
    mapLowNormal = dict(zip(TextState.normalColors,
                            TextState.darkColors))

    def __init__(self):
        self.colorMap = dict(self.normalBright.items() +
                             self.highBright.items() +
                             self.lowBright.items())
        self.brightMap = {self.NORMAL_BRIGHT: {},
                          self.HIGH_BRIGHT: self.mapHighNormal,
                          self.LOW_BRIGHT: self.mapLowNormal}

    def brightTransform(self, color, bright):
        """
        Преобразовать основной цвет в зависимости от установленной яркости
        """
        mapper = self.brightMap.get(bright, {})
        return mapper.get(color, color)

    def getBackgroundColor(self, color=TextState.Colors.DEFAULT):
        if not color:
            return self.defaultBackground
        return self.colorMap.get(color, color)

    def getTextColor(self, color=TextState.Colors.DEFAULT,
                     bright=NORMAL_BRIGHT):
        """
        Получить соответствие цвета строкой
        """
        if not color:
            return self.defaultColor[bright]
        color = self.brightTransform(color, bright)
        return self.colorMap.get(color, color)

    def getBaseColorByRGB(self, rgb_color):
        """Получить ближайший базовый цвет, согласно палитре

        Например: #709080 -> TextState.Colors.DARK

        Args:
        rgb_color:  цвет #rrggbb (или словесный)

        Returns: TextState.Colors если подходящий цвет найден
        """

        def calculate_color_diff(Ri, Ro, Gi, Go, Bi, Bo):
            # вычислить отличие цветов
            kR, kG, kB = 30, 59, 25
            return kR * pow(Ri - Ro, 2) + kG * pow(Gi - Go, 2) + kB * pow(
                Bi - Bo, 2)

        color = ConsoleColor256.convertRgbToIntGroup(rgb_color)
        diffList = []
        if color:
            for key, val in self.colorMap.items():
                intVal = ConsoleColor256.convertRgbToIntGroup(val)
                if intVal:
                    diffList.append((
                        calculate_color_diff(*chain(*zip(color, intVal))), key))
            if diffList:
                # print diffList,sorted(diffList)[0]
                for diff, color in sorted(diffList):
                    return color
        # если вместо RGB в палитре цвет указан словом
        elif rgb_color in self.colorMap.values():
            for key, val in self.colorMap.items():
                if val == rgb_color:
                    return key
        return None


class DarkPastelsPalette(SpanPalette):
    """
    Палитра идентичная Calculate в консоли (Dark Pastels)
    """
    defaultColor = ["#DCDCDC", "#DCDCDC", "#709080"]
    defaultBackground = "#2C2C2C"

    normalBright = dict(zip(TextState.normalColors,
                            ["#2C2C2C", "#705050", "#60B48A", "#DFAF8F",
                             "#9AB8D7", "#DC8CC3", "#8CD0D3", "#DCDCDC"]))
    highBright = dict(zip(TextState.lightColors,
                          ["#709080", "#DCA3A3", "#72D5A3", "#F0DFAF",
                           "#94BFF3", "#EC93D3", "#93E0E3", "#FFFFFF"]))


class WhitePalette(SpanPalette):
    """
    Палитра черное на белом
    """

    defaultColor = ["#141414", "#141414", "#272727"]
    defaultBackground = "#ffffff"

    normalBright = dict(zip(TextState.normalColors,
                            ["#141414", "#742523", "#237425", "#737423",
                             "#252374", "#742373", "#237374", "#c1c1c1"]))
    highBright = dict(zip(TextState.lightColors,
                          ["#272727", "#cc5755", "#55cc57", "#cacc55",
                           "#5755cc", "#cc55ca", "#55cacc", "#ffffff"]))


class GreyPaletteOld(SpanPalette):
    """
    Палитра черное на светло-сером
    """
    defaultColor = ["#101010", "#101010", "#505050"]
    defaultBackground = "#e4e1e0"

    normalBright = dict(zip(TextState.normalColors,
                            ["#101010", "#902c2b", "#2b902c", "#8f902b",
                             "#2c2b90", "#902b8f", "#2b8f90", "#b9b9b9"]))

    highBright = dict(zip(TextState.lightColors,
                          ["#303030", "#c6403d", "#3dc640", "#c3c63d",
                           "#403dc6", "#c63dc3", "#3dc3c6", "#e4e1e0"]))


class GreyPalette(SpanPalette):
    """
    Палитра черное на светло-сером
    """
    defaultColor = ["#101010", "#101010", "#505050"]
    defaultBackground = "#ffffff"

    normalBright = dict(zip(TextState.normalColors,
                            ["#101010", "#902c2b", "#2b902c", "#8f902b",
                             "#2c2b90", "#902b8f", "#2b8f90", "#b9b9b9"]))

    highBright = dict(zip(TextState.lightColors,
                          ["#303030", "#c6403d", "#3dc640", "#c3c63d",
                           "#403dc6", "#c63dc3", "#3dc3c6", "#ffffff"]))


class GuiPalette(SpanPalette):
    """
    Палитра черное на светло-сером
    """
    defaultColor = ["#101010", "#101010", "#505050"]
    defaultBackground = "#ffffff"

    normalBright = dict(zip(TextState.normalColors,
                            ["#101010", "#902c2b", "#2b902c", "#8f902b",
                             "#2c2b90", "#902b8f", "#2b8f90", "#b9b9b9"]))

    highBright = dict(zip(TextState.lightColors,
                          ["#303030", "#c6403d", "#3dc640", "#c3c63d",
                           "#403dc6", "#c63dc3", "#3dc3c6", "#ffffff"]))

class XmlFormat(object):
    """
    Константы для внутреннего формата XML
    """

    class Tags(object):
        BOLD = "b"
        UNDERLINE = "u"
        FONT = "font"
        HALFBRIGHT = "dark"
        INVERT = "invert"
        NEWLINE = "br"
        TAB = "tab"

    class FontAttributes(object):
        BACKGROUND = "bgColor"
        FOREGROUND = "color"

    escape_map = {'<': '&lt;', '>': '&gt;'}
    escaper = MultiReplace(escape_map)
    unescaper = MultiReplace({v: k for k, v in escape_map.items()})
