# -*- coding: utf-8 -*-
"""
This file is used for the generation of screenshots for py3status
documentation.

outside of pythons standard library there are the following requirements:

    Pillow==3.4.2
    fonttools

PIL may work if installed but is not supported.
"""

from __future__ import division

import ast
import os
import re

from hashlib import md5

from PIL import Image, ImageFont, ImageDraw
from fontTools.ttLib import TTFont


WIDTH = 650
TOP_BAR_HEIGHT = 5
BAR_HEIGHT = 24
X_OFFSET = 5
PADDING = 4

SEP_PADDING_LEFT = 4
SEP_PADDING_RIGHT = SEP_PADDING_LEFT + 1

SEP_BORDER = 4

FONT = 'DejaVuSansMono.ttf'

# Pillow does poor font rendering so we are best of creating huge text and then
# shrinking with anti-aliasing.  SCALE is how many times bigger we render the
# test
SCALE = 8

COLOR = '#FFFFFF'
COLOR_BG = '#000000'
COLOR_PY3STATUS = '#FFFFFF'
COLOR_SEP = '#666666'
COLOR_URGENT = '#FFFFFF'
COLOR_URGENT_BG = '#900000'

FONT_SIZE = BAR_HEIGHT - (PADDING * 2)
HEIGHT = TOP_BAR_HEIGHT + BAR_HEIGHT

# font, glyph_data want caching for performance
font = None
glyph_data = None


def get_color_for_name(module_name):
    """
    Create a custom color for a given string.
    This allows the screenshots to each have a unique color but for that color
    to be consistent.
    """
    # all screenshots of the same module should be a uniform color
    module_name = module_name.split('-')[0]

    saturation = 0.5
    value = 243.2
    try:
        module_name = module_name.encode('utf-8')
    except:
        pass
    hue = int(md5(module_name).hexdigest(), 16) / 16**32
    hue *= 6
    hue += 3.708
    r, g, b = (
        (value, value - value * saturation * abs(1 - hue % 2), value - value *
         saturation) * 3)[5**int(hue) // 3 % 3::int(hue) % 2 + 1][:3]
    return '#' + '%02x' * 3 % (int(r), int(g), int(b))


def contains_bad_glyph(glyph_data, data):
    """
    Pillow only looks for glyphs in the font used so we need to make sure our
    font has the glygh.  Although we could substitute a glyph from another font
    eg symbola but this adds more complexity and is of limited value.
    """
    def check_glyph(char):
        for cmap in glyph_data['cmap'].tables:
            if cmap.isUnicode():
                if char in cmap.cmap:
                    return True
        return False

    for part in data:
        text = part.get('full_text', '')
        try:
            # for python 2
            text = text.decode('utf8')
        except:
            pass

        for char in text:
            if not check_glyph(ord(char)):
                # we have not found a character in the font
                print(u'%s (%s) missing' % (char, ord(char)))
                return True
    return False


def create_screenshot(name, data, path, font, module):
    """
    Create screenshot of py3status output and save to path
    """
    desktop_color = get_color_for_name(name)

    # if this screenshot is for a module then add modules name etc
    if module:
        data.append(
            {
                'full_text': name.split('-')[0],
                'color': desktop_color,
                'separator': True,
            }
        )
        data.append(
            {
                'full_text': 'py3status',
                'color': COLOR_PY3STATUS,
                'separator': True,
            }
        )

    img = Image.new('RGB', (WIDTH, HEIGHT), COLOR_BG)
    d = ImageDraw.Draw(img)

    # top bar
    d.rectangle((0, 0, WIDTH, TOP_BAR_HEIGHT), fill=desktop_color)
    x = X_OFFSET

    # add text and separators
    for part in reversed(data):
        text = part.get('full_text')
        color = part.get('color', COLOR)
        background = part.get('background')
        separator = part.get('separator')
        urgent = part.get('urgent')

        # urgent background
        if urgent:
            color = COLOR_URGENT
            background = COLOR_URGENT_BG

        size = font.getsize(text)

        if background:
            d.rectangle((WIDTH - x - (size[0] // SCALE),
                         TOP_BAR_HEIGHT + PADDING,
                         WIDTH - x - 1,
                         HEIGHT - PADDING,
                         ), fill=background)

        x += size[0] // SCALE

        txt = Image.new('RGB', size, background or COLOR_BG)
        d_text = ImageDraw.Draw(txt)
        d_text.text((0, 0), text, font=font, fill=color)
        # resize to actual size wanted and add to image
        txt = txt.resize((size[0] // SCALE, size[1] // SCALE), Image.ANTIALIAS)
        img.paste(txt, (WIDTH - x, TOP_BAR_HEIGHT + PADDING))

        if separator:
            x += SEP_PADDING_RIGHT
            d.line(((WIDTH - x, TOP_BAR_HEIGHT + PADDING),
                    (WIDTH - x, TOP_BAR_HEIGHT + 1 + PADDING + FONT_SIZE)),
                   fill=COLOR_SEP, width=1)
            x += SEP_PADDING_LEFT

    img.save(os.path.join(path, '%s.png' % name))
    print(' %s.png' % name)


def parse_sample_data(sample_data, module_name):
    """
    Parse sample output definitions and return a dict
    {screenshot_name: sample_output}
    """
    samples = {}
    name = None
    data = ''
    count = 0
    for line in sample_data.splitlines() + ['']:
        if line == '':
            if data:
                if name:
                    name = u'%s-%s-%s' % (module_name, count, name)
                else:
                    name = module_name
                try:
                    output = ast.literal_eval(data)
                    samples[name] = output
                except:
                    samples[name] = {
                        'color': '#990000',
                        'background': '#FFFF00',
                        'full_text': ' SAMPLE DATA ERROR ',
                    }
                name = None
                data = ''
                count += 1
            continue
        if name is None and data == '' and not line[0] in ['[', '{']:
            name = line
            continue
        else:
            data += line
    return samples


def get_samples():
    '''
    Look in all core modules and get any samples from the docstrings.
    return a dict {screenshot_name: sample_output}
    '''
    samples = {}
    module_dir = os.path.join(
        os.path.dirname(os.path.abspath(__file__)), 'modules')
    for file in sorted(os.listdir(module_dir)):
        if file.endswith('.py') and file != '__init__.py':
            module_name = file[:-3]
            with open(os.path.join(module_dir, file), 'r') as f:
                module = ast.parse(f.read())
                raw_docstring = ast.get_docstring(module)
                if raw_docstring is None:
                    continue
                parts = re.split('^SAMPLE OUTPUT$', raw_docstring, flags=re.M)
                if len(parts) == 1:
                    continue
                sample_data = parts[1]
                samples.update(parse_sample_data(sample_data, module_name))
    return samples


def process(name, data, module=True):
    """
    Process data to create a screenshot which will be saved in
    docs/screenshots/<name>.png
    If module is True the screenshot will include the name and py3status.
    """

    path = '../doc/screenshots'
    # create dir if not exists
    try:
        os.makedirs(path)
    except OSError:
        pass

    global font, glyph_data
    if font is None:
        font = ImageFont.truetype(FONT, FONT_SIZE * SCALE)
    if glyph_data is None:
        glyph_data = TTFont(font.path)

    # make sure that the data is in list form
    if not isinstance(data, list):
        data = [data]

    if contains_bad_glyph(glyph_data, data):
        print('** %s has characters not in %s **' % (name, font.getname()[0]))
    else:
        create_screenshot(name, data, path, font=font, module=module)


def create_screenshots(quiet=False):
    """
    create screenshots for all core modules.
    The screenshots directory will have all .png files deleted before new shots
    are created.
    """

    print('Creating screenshots...')
    samples = get_samples()
    for name, data in sorted(samples.items()):
        process(name, data)


if __name__ == '__main__':
    create_screenshots()
