# -*- coding: utf-8 -*-
# This file is part of pygal
#
# A python svg graph plotting library
# Copyright © 2012-2025 Kozea
#
# This library is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option) any
# later version.
#
# This library is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with pygal. If not, see <http://www.gnu.org/licenses/>.
"""Charts styling classes"""

from __future__ import division

from itertools import chain

from pygal import colors
from pygal.colors import darken, is_foreground_light, lighten


class Style(object):
    """Styling class containing colors for the css generation"""

    plot_background = 'rgba(255, 255, 255, 1)'
    background = 'rgba(249, 249, 249, 1)'
    value_background = 'rgba(229, 229, 229, 1)'
    foreground = 'rgba(0, 0, 0, .87)'
    foreground_strong = 'rgba(0, 0, 0, 1)'
    foreground_subtle = 'rgba(0, 0, 0, .54)'

    # Monospaced font is highly encouraged
    font_family = ('Consolas, "Liberation Mono", Menlo, Courier, monospace')

    label_font_family = None
    major_label_font_family = None
    value_font_family = None
    value_label_font_family = None
    tooltip_font_family = None
    title_font_family = None
    legend_font_family = None
    no_data_font_family = None

    label_font_size = 10
    major_label_font_size = 10
    value_font_size = 16
    value_label_font_size = 10
    tooltip_font_size = 14
    title_font_size = 16
    legend_font_size = 14
    no_data_font_size = 64

    # Guide line dash array style
    guide_stroke_dasharray = '4,4'
    major_guide_stroke_dasharray = '6,6'
    guide_stroke_color = 'black'
    major_guide_stroke_color = 'black'

    opacity = '.7'
    opacity_hover = '.8'

    stroke_opacity = '.8'
    stroke_width = '1'
    stroke_opacity_hover = '.9'
    stroke_width_hover = '4'

    dot_opacity = '1'

    transition = '150ms'
    colors = (
        '#F44336',  # 0
        '#3F51B5',  # 4
        '#009688',  # 8
        '#FFC107',  # 13
        '#FF5722',  # 15
        '#9C27B0',  # 2
        '#03A9F4',  # 6
        '#8BC34A',  # 10
        '#FF9800',  # 14
        '#E91E63',  # 1
        '#2196F3',  # 5
        '#4CAF50',  # 9
        '#FFEB3B',  # 12
        '#673AB7',  # 3
        '#00BCD4',  # 7
        '#CDDC39',  # 11b
        '#9E9E9E',  # 17
        '#607D8B',  # 18
    )

    value_colors = ()
    ci_colors = ()

    def __init__(self, **kwargs):
        """Create the style"""
        self.__dict__.update(kwargs)
        self._google_fonts = set()
        if self.font_family.startswith('googlefont:'):
            self.font_family = self.font_family.replace('googlefont:', '')
            self._google_fonts.add(self.font_family.split(',')[0].strip())

        for name in dir(self):
            if name.endswith('_font_family'):
                fn = getattr(self, name)
                if fn is None:
                    setattr(self, name, self.font_family)
                elif fn.startswith('googlefont:'):
                    setattr(self, name, fn.replace('googlefont:', ''))
                    self._google_fonts.add(
                        getattr(self, name).split(',')[0].strip()
                    )

    def get_colors(self, prefix, len_):
        """Get the css color list"""

        def color(tupl):
            """Make a color css"""
            return ((
                '%s.color-{0}, %s.color-{0} a:visited {{\n'
                '  stroke: {1};\n'
                '  fill: {1};\n'
                '}}\n'
            ) % (prefix, prefix)).format(*tupl)

        def value_color(tupl):
            """Make a value color css"""
            return ((
                '%s .text-overlay .color-{0} text {{\n'
                '  fill: {1};\n'
                '}}\n'
            ) % (prefix, )).format(*tupl)

        def ci_color(tupl):
            """Make a value color css"""
            if not tupl[1]:
                return ''
            return (('%s .color-{0} .ci {{\n'
                     '  stroke: {1};\n'
                     '}}\n') % (prefix, )).format(*tupl)

        if len(self.colors) < len_:
            missing = len_ - len(self.colors)
            cycles = 1 + missing // len(self.colors)
            colors = []
            for i in range(0, cycles + 1):
                for color_ in self.colors:
                    colors.append(darken(color_, 33 * i / cycles))
                    if len(colors) >= len_:
                        break
                else:
                    continue
                break
        else:
            colors = self.colors[:len_]

        # Auto compute foreground value color when color is missing
        value_colors = []
        for i in range(len_):
            if i < len(self.value_colors) and self.value_colors[i] is not None:
                value_colors.append(self.value_colors[i])
            else:
                value_colors.append(
                    'white' if is_foreground_light(colors[i]) else 'black'
                )

        return '\n'.join(
            chain(
                map(color, enumerate(colors)),
                map(value_color, enumerate(value_colors)),
                map(ci_color, enumerate(self.ci_colors))
            )
        )

    def to_dict(self):
        """Convert instance to a serializable mapping."""
        config = {}
        for attr in dir(self):
            if not attr.startswith('_'):
                value = getattr(self, attr)
                if not hasattr(value, '__call__'):
                    config[attr] = value
        return config


DefaultStyle = Style


class DarkStyle(Style):
    """A dark style (old default)"""

    background = 'black'
    plot_background = '#111'
    foreground = '#999'
    foreground_strong = '#eee'
    foreground_subtle = '#555'
    opacity = '.8'
    opacity_hover = '.4'
    transition = '250ms'
    colors = (
        '#ff5995', '#b6e354', '#feed6c', '#8cedff', '#9e6ffe', '#899ca1',
        '#f8f8f2', '#bf4646', '#516083', '#f92672', '#82b414', '#fd971f',
        '#56c2d6', '#808384', '#8c54fe', '#465457'
    )


class LightStyle(Style):
    """A light style"""

    background = 'white'
    plot_background = 'rgba(0, 0, 255, 0.1)'
    foreground = 'rgba(0, 0, 0, 0.7)'
    foreground_strong = 'rgba(0, 0, 0, 0.9)'
    foreground_subtle = 'rgba(0, 0, 0, 0.5)'
    colors = (
        '#242424', '#9f6767', '#92ac68', '#d0d293', '#9aacc3', '#bb77a4',
        '#77bbb5', '#777777'
    )


class NeonStyle(DarkStyle):
    """Similar to DarkStyle but with more opacity and effects"""

    opacity = '.1'
    opacity_hover = '.75'
    transition = '1s ease-out'


class CleanStyle(Style):
    """A rather clean style"""

    background = 'transparent'
    plot_background = 'rgba(240, 240, 240, 0.7)'
    foreground = 'rgba(0, 0, 0, 0.9)'
    foreground_strong = 'rgba(0, 0, 0, 0.9)'
    foreground_subtle = 'rgba(0, 0, 0, 0.5)'
    colors = (
        'rgb(12,55,149)', 'rgb(117,38,65)', 'rgb(228,127,0)', 'rgb(159,170,0)',
        'rgb(149,12,12)'
    )


class DarkSolarizedStyle(Style):
    """Dark solarized popular theme"""

    background = '#073642'
    plot_background = '#002b36'
    foreground = '#839496'
    foreground_strong = '#fdf6e3'
    foreground_subtle = '#657b83'
    opacity = '.66'
    opacity_hover = '.9'
    transition = '500ms ease-in'
    colors = (
        '#b58900', '#cb4b16', '#dc322f', '#d33682', '#6c71c4', '#268bd2',
        '#2aa198', '#859900'
    )


class LightSolarizedStyle(DarkSolarizedStyle):
    """Light solarized popular theme"""

    background = '#fdf6e3'
    plot_background = '#eee8d5'
    foreground = '#657b83'
    foreground_strong = '#073642'
    foreground_subtle = '#073642'


class RedBlueStyle(Style):
    """A red and blue theme"""

    background = lighten('#e6e7e9', 7)
    plot_background = lighten('#e6e7e9', 10)
    foreground = 'rgba(0, 0, 0, 0.9)'
    foreground_strong = 'rgba(0, 0, 0, 0.9)'
    foreground_subtle = 'rgba(0, 0, 0, 0.5)'
    opacity = '.6'
    opacity_hover = '.9'
    colors = (
        '#d94e4c', '#e5884f', '#39929a', lighten('#d94e4c', 10),
        darken('#39929a', 15), lighten('#e5884f', 17), darken('#d94e4c', 10),
        '#234547'
    )


class LightColorizedStyle(Style):
    """A light colorized style"""

    background = '#f8f8f8'
    plot_background = lighten('#f8f8f8', 3)
    foreground = '#333'
    foreground_strong = '#666'
    foreground_subtle = 'rgba(0, 0 , 0, 0.5)'
    opacity = '.5'
    opacity_hover = '.9'
    transition = '250ms ease-in'
    colors = (
        '#fe9592', '#534f4c', '#3ac2c0', '#a2a7a1', darken('#fe9592', 15),
        lighten('#534f4c', 15), lighten('#3ac2c0', 15), lighten('#a2a7a1', 15),
        lighten('#fe9592', 15), darken('#3ac2c0', 10)
    )


class DarkColorizedStyle(Style):
    """A dark colorized style"""

    background = darken('#3a2d3f', 5)
    plot_background = lighten('#3a2d3f', 2)
    foreground = 'rgba(255, 255, 255, 0.9)'
    foreground_strong = 'rgba(255, 255, 255, 0.9)'
    foreground_subtle = 'rgba(255, 255 , 255, 0.5)'
    opacity = '.2'
    opacity_hover = '.7'
    transition = '250ms ease-in'
    colors = (
        '#c900fe', '#01b8fe', '#59f500', '#ff00e4', '#f9fa00',
        darken('#c900fe', 20), darken('#01b8fe', 15), darken('#59f500', 20),
        darken('#ff00e4', 15), lighten('#f9fa00', 20)
    )


class TurquoiseStyle(Style):
    """A turquoise style"""

    background = darken('#1b8088', 15)
    plot_background = darken('#1b8088', 17)
    foreground = 'rgba(255, 255, 255, 0.9)'
    foreground_strong = 'rgba(255, 255, 255, 0.9)'
    foreground_subtle = 'rgba(255, 255 , 255, 0.5)'
    opacity = '.5'
    opacity_hover = '.9'
    transition = '250ms ease-in'
    colors = (
        '#93d2d9', '#ef940f', '#8C6243', '#fff', darken('#93d2d9', 20),
        lighten('#ef940f', 15), lighten('#8c6243', 15), '#1b8088'
    )


class LightGreenStyle(Style):
    """A light green style"""

    background = lighten('#f3f3f3', 3)
    plot_background = '#fff'
    foreground = '#333333'
    foreground_strong = '#666'
    foreground_subtle = '#222222'
    opacity = '.5'
    opacity_hover = '.9'
    transition = '250ms ease-in'
    colors = (
        '#7dcf30', '#247fab', lighten('#7dcf30', 10), '#ccc',
        darken('#7dcf30', 15), '#ddd', lighten('#247fab', 10),
        darken('#247fab', 15)
    )


class DarkGreenStyle(Style):
    """A dark green style"""

    background = darken('#251e01', 3)
    plot_background = darken('#251e01', 1)
    foreground = 'rgba(255, 255, 255, 0.9)'
    foreground_strong = 'rgba(255, 255, 255, 0.9)'
    foreground_subtle = 'rgba(255, 255, 255, 0.6)'
    opacity = '.6'
    opacity_hover = '.9'
    transition = '250ms ease-in'
    colors = (
        '#adde09', '#6e8c06', '#4a5e04', '#fcd202', '#C1E34D',
        lighten('#fcd202', 25)
    )


class DarkGreenBlueStyle(Style):
    """A dark green and blue style"""

    background = '#000'
    plot_background = lighten('#000', 8)
    foreground = 'rgba(255, 255, 255, 0.9)'
    foreground_strong = 'rgba(255, 255, 255, 0.9)'
    foreground_subtle = 'rgba(255, 255, 255, 0.6)'
    opacity = '.55'
    opacity_hover = '.9'
    transition = '250ms ease-in'
    colors = (
        lighten('#34B8F7', 15), '#7dcf30', '#247fab', darken('#7dcf30', 10),
        lighten('#247fab', 10), lighten('#7dcf30', 10), darken('#247fab', 10),
        '#fff'
    )


class BlueStyle(Style):
    """A blue style"""

    background = darken('#f8f8f8', 3)
    plot_background = '#f8f8f8'
    foreground = 'rgba(0, 0, 0, 0.9)'
    foreground_strong = 'rgba(0, 0, 0, 0.9)'
    foreground_subtle = 'rgba(0, 0, 0, 0.6)'
    opacity = '.5'
    opacity_hover = '.9'
    transition = '250ms ease-in'
    colors = (
        '#00b2f0', '#43d9be', '#0662ab', darken('#00b2f0', 20),
        lighten('#43d9be', 20), lighten('#7dcf30', 10), darken('#0662ab', 15),
        '#ffd541', '#7dcf30', lighten('#00b2f0', 15), darken('#ffd541', 20)
    )


class SolidColorStyle(Style):
    """A light style with strong colors"""

    background = '#FFFFFF'
    plot_background = '#FFFFFF'
    foreground = '#000000'
    foreground_strong = '#000000'
    foreground_subtle = '#828282'
    opacity = '.8'
    opacity_hover = '.9'
    transition = '400ms ease-in'
    colors = (
        '#FF9900', '#DC3912', '#4674D1', '#109618', '#990099', '#0099C6',
        '#DD4477', '#74B217', '#B82E2E', '#316395', '#994499'
    )


styles = {
    'default': DefaultStyle,
    'dark': DarkStyle,
    'light': LightStyle,
    'neon': NeonStyle,
    'clean': CleanStyle,
    'light_red_blue': RedBlueStyle,
    'dark_solarized': DarkSolarizedStyle,
    'light_solarized': LightSolarizedStyle,
    'dark_colorized': DarkColorizedStyle,
    'light_colorized': LightColorizedStyle,
    'turquoise': TurquoiseStyle,
    'green': LightGreenStyle,
    'dark_green': DarkGreenStyle,
    'dark_green_blue': DarkGreenBlueStyle,
    'blue': BlueStyle,
    'solid_color': SolidColorStyle
}


class ParametricStyleBase(Style):
    """Parametric Style base class for all the parametric operations"""

    _op = None

    def __init__(self, color, step=10, max_=None, base_style=None, **kwargs):
        """
        Initialization of the parametric style.

        This takes several parameters:
          * a `step` which correspond on how many colors will be needed
          * a `max_` which defines the maximum amplitude of the color effect
          * a `base_style` which will be taken as default for everything
            except colors
          * any keyword arguments setting other style parameters
        """

        if self._op is None:
            raise RuntimeError('ParametricStyle is not instanciable')

        defaults = {}
        if base_style is not None:
            if isinstance(base_style, type):
                base_style = base_style()
            defaults.update(base_style.to_dict())
        defaults.update(kwargs)

        super(ParametricStyleBase, self).__init__(**defaults)

        if max_ is None:
            violency = {
                'darken': 50,
                'lighten': 50,
                'saturate': 100,
                'desaturate': 100,
                'rotate': 360
            }
            max_ = violency[self._op]

        def modifier(index):
            percent = max_ * index / (step - 1)
            return getattr(colors, self._op)(color, percent)

        self.colors = list(map(modifier, range(0, max(2, step))))


class LightenStyle(ParametricStyleBase):
    """Create a style by lightening the given color"""

    _op = 'lighten'


class DarkenStyle(ParametricStyleBase):
    """Create a style by darkening the given color"""

    _op = 'darken'


class SaturateStyle(ParametricStyleBase):
    """Create a style by saturating the given color"""

    _op = 'saturate'


class DesaturateStyle(ParametricStyleBase):
    """Create a style by desaturating the given color"""

    _op = 'desaturate'


class RotateStyle(ParametricStyleBase):
    """Create a style by rotating the given color"""

    _op = 'rotate'


parametric_styles = {
    'lighten': LightenStyle,
    'darken': DarkenStyle,
    'saturate': SaturateStyle,
    'desaturate': DesaturateStyle,
    'rotate': RotateStyle
}
