#!/usr/bin/env python3
"""Generate the textures of the cinematic Home (docs/plan/skin-milionar/190-cinema.md).

Shapes only (panels, masks, the focus frame); every icon stays Estuary's own. White textures are tinted in the XML with
`colordiffuse`. Run after changing a shape; the PNGs are committed.
"""
import os

from PIL import Image, ImageChops, ImageDraw, ImageFilter

OUT = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', '..', 'resources', 'skin.milionar', 'media',
                   'milionar', 'cinema')
SS = 4                                   # supersampling
RADIUS = 10                              # one corner radius for every card, button and mask


def rounded(size, radius, corners=(True, True, True, True)):
    """Anti-aliased alpha mask (L) of a rounded rectangle; `corners`: tl, tr, br, bl."""
    w, h = size
    big = Image.new('L', (w * SS, h * SS), 0)
    draw = ImageDraw.Draw(big)
    draw.rounded_rectangle((0, 0, w * SS - 1, h * SS - 1), radius * SS, fill=255, corners=corners)
    return big.resize((w, h), Image.LANCZOS)


def solid(size, radius, color=(255, 255, 255), alpha=255, corners=(True, True, True, True)):
    mask = rounded(size, radius, corners).point(lambda v: v * alpha // 255)
    image = Image.new('RGBA', size, color + (0,))
    image.putalpha(mask)
    return image


def outlined(size, radius, fill, border, width=2):
    """Rounded panel with a thin inner border (the ghost button)."""
    outer = rounded(size, radius)
    inner = Image.new('L', size, 0)
    inner.paste(rounded((size[0] - 2 * width, size[1] - 2 * width), radius - width), (width, width))
    ring = ImageChops.subtract(outer, inner)
    image = Image.new('RGBA', size, fill[:3] + (0,))
    image.putalpha(inner.point(lambda v: v * fill[3] // 255))
    edge = Image.new('RGBA', size, border[:3] + (0,))
    edge.putalpha(ring.point(lambda v: v * border[3] // 255))
    return Image.alpha_composite(image, edge)


def _frame(outer, inner, size, glow):
    ring = ImageChops.subtract(outer, inner)
    halo = ImageChops.subtract(outer.filter(ImageFilter.GaussianBlur(glow)), inner).point(lambda v: v * 150 // 255)
    image = Image.new('RGBA', (size, size), (255, 255, 255, 0))
    image.putalpha(ImageChops.lighter(ring, halo))
    return image


def focus_frame(size=96, margin=16, width=3, glow=6, overlap=1, radius=RADIUS):
    """White ring with a soft outer glow around a card whose edge lies `margin` px inside the texture.

    The ring lies outside the card, with the card's own corner radius on its inner edge, so it covers no picture and
    no corner of the card shows outside it; `overlap` px reach under the card's edge so that no seam opens between
    the two when the skin is scaled."""
    box = size - 2 * margin
    outer = Image.new('L', (size, size), 0)
    outer.paste(rounded((box + 2 * width, box + 2 * width), radius + width), (margin - width, margin - width))
    inner = Image.new('L', (size, size), 0)
    inner.paste(rounded((box - 2 * overlap, box - 2 * overlap), radius - overlap), (margin + overlap, margin + overlap))
    return _frame(outer, inner, size, glow)


def smooth(t):
    t = min(1.0, max(0.0, t))
    return t * t * (3 - 2 * t)


def hero_fade(size=(486, 273), left=(0.20, 0.64), floor=0.12, bottom=(0.40, 0.74), edge=0.0):
    """Diffuse mask of the hero backdrop: dim on the left (under the text), gone at the bottom (under the rows);
    `edge` > 0 also fades its left edge in from nothing over that part of the width (no hard line where it starts)."""
    w, h = size
    image = Image.new('RGBA', size, (255, 255, 255, 255))
    pixels = image.load()
    for y in range(h):
        down = 1.0 - smooth((y / (h - 1.0) - bottom[0]) / (bottom[1] - bottom[0]))
        for x in range(w):
            across = floor + (1.0 - floor) * smooth((x / (w - 1.0) - left[0]) / (left[1] - left[0]))
            if edge:
                across *= smooth(x / (w - 1.0) / edge)
            pixels[x, y] = (255, 255, 255, int(round(255 * across * down)))
    return image


def shade(size=(464, 696), start=0.70, strength=0.85, radius=2 * RADIUS):
    """Dark foot of a poster (under its status badge), cut to the poster's rounded corners."""
    w, h = size
    ramp = Image.new('L', size, 0)
    pixels = ramp.load()
    for y in range(h):
        value = int(round(255 * strength * smooth((y / (h - 1.0) - start) / (1.0 - start))))
        for x in range(w):
            pixels[x, y] = value
    image = Image.new('RGBA', size, (0, 0, 0, 0))
    image.putalpha(ImageChops.multiply(ramp, rounded(size, radius)))
    return image


def disc(size):
    """Anti-aliased alpha mask (L) of a circle that fills the square."""
    big = Image.new('L', (size * SS, size * SS), 0)
    ImageDraw.Draw(big).ellipse((0, 0, size * SS - 1, size * SS - 1), fill=255)
    return big.resize((size, size), Image.LANCZOS)


def focus_ring(size=448, margin=32, width=6, glow=12, overlap=2):
    """The focus frame of a round card (focus_frame as a circle); twice the card's size."""
    box = size - 2 * margin
    outer = Image.new('L', (size, size), 0)
    outer.paste(disc(box + 2 * width), (margin - width, margin - width))
    inner = Image.new('L', (size, size), 0)
    inner.paste(disc(box - 2 * overlap), (margin + overlap, margin + overlap))
    return _frame(outer, inner, size, glow)


def grooves(size=384):
    """Subtle grooves and the spindle hole of a record, cut to the record's circle."""
    scale = size * SS / 192.0
    image = Image.new('RGBA', (size * SS, size * SS), (0, 0, 0, 0))
    draw = ImageDraw.Draw(image)
    for radius in range(30, 96, 7):
        draw.ellipse(tuple(int(v * scale) for v in (96 - radius, 96 - radius, 96 + radius, 96 + radius)),
                     outline=(255, 255, 255, 30), width=max(1, int(scale)))
    draw.ellipse(tuple(int(v * scale) for v in (89, 89, 103, 103)), fill=(5, 8, 14, 235))
    image = image.resize((size, size), Image.LANCZOS)
    image.putalpha(ImageChops.multiply(image.getchannel('A'), disc(size)))
    return image


def band(width, height, radius, corners=(True, True, True, True)):
    """Mask of a square picture shown through a wide rounded window (the middle `height` rows of the square).

    Kodi lays the diffuse mask over the whole picture, also over the part an `aspectratio` scale crops away, so a mask
    of the card's own size loses its corners. The card therefore draws the square cover uncropped and this mask opens
    the window."""
    image = Image.new('RGBA', (width, width), (255, 255, 255, 0))
    alpha = Image.new('L', (width, width), 0)
    alpha.paste(rounded((width, height), radius, corners), (0, (width - height) // 2))
    image.putalpha(alpha)
    return image


def build(radius=RADIUS, button=None, ghost=None, card=None, frame=None, poster_shade=(0.70, 0.85), ring=None, fade=None):
    """Every texture of the cinematic Home; skin.milionar.modern builds them again with its own shapes
    (gen_modern_textures.py): `radius` of cards and buttons, and the images of the button, the ghost button, the card
    and the focus frames (of a card, of a round card) when they are not the plain ones, and where a poster's dark foot starts and how dark it gets."""
    # Masks and shades are twice the size of their card (radius 2 x radius), like the film cards: sharp at 1440p.
    top = (True, True, False, False)
    big = 2 * radius
    circle = Image.new('RGBA', (384, 384), (255, 255, 255, 0))
    circle.putalpha(disc(384))
    return {
        'mask_circle.png': circle,
        'record_grooves.png': grooves(),
        'focus_circle.png': ring or focus_ring(),
        'mask_square.png': solid((432, 432), big, corners=top),
        'mask_music_wide.png': band(652, 368, big, corners=top),
        'mask_banner.png': band(980, 552, big),
        'mask_genre.png': band(496, 336, big),
        'mask_track.png': solid((168, 168), big),
        'mask_hero_cover.png': solid((336, 336), radius),
        'mask_detail_cover.png': solid((400, 400), radius),
        'mask_detail_track.png': solid((72, 72), radius),
        'shade_music.png': shade((980, 552), start=0.30, strength=0.96, radius=big),
        'shade_genre.png': shade((496, 336), start=0.40, strength=0.96, radius=big),
        'shade_poster.png': shade(start=poster_shade[0], strength=poster_shade[1], radius=big),
        'hero_fade.png': fade or hero_fade(),
        'button.png': button or solid((64, 64), radius),
        'button_ghost.png': ghost or outlined((64, 64), radius, (20, 27, 46, 184), (255, 255, 255, 72)),
        'focus.png': frame or focus_frame(radius=radius),
        'card.png': card or solid((64, 64), radius, (15, 21, 38), 240),
        'mask_poster.png': solid((464, 696), big),
        'mask_wide.png': solid((652, 366), big, corners=(True, True, False, False)),
        'badge.png': solid((40, 32), 6),
        'bar.png': solid((16, 6), 3),
    }


def save(images, folder):
    os.makedirs(folder, exist_ok=True)
    for name, image in images.items():
        path = os.path.join(folder, name)
        os.makedirs(os.path.dirname(path), exist_ok=True)
        image.save(path, optimize=True)
        print('wrote', name, image.size)


if __name__ == '__main__':
    save(build(), OUT)
