# -*- coding: utf-8 -*-
"""Hero of the cinematic Home sections (docs/plan/skin-milionar/190-cinema.md).

The skin draws the hero and the card captions from properties of the selected widget item, so every text is ready
here: Kodi labels have no letter spacing, no shrink-to-fit and no ellipsis in text boxes. `movie`, `series`,
`episode` and `channel` turn a card (wcs/library2/rows.py, wcs/skin/widgets.py) into those texts, `apply` writes them
to the item. Light imports only:
this runs in the widget fast path.
"""
import re
from urllib.parse import quote

import xbmcaddon

ADDON_ID = 'plugin.video.milionar'
BACKDROP = 'https://image.tmdb.org/t/p/{}'
TMDB_SIZE = re.compile(r'^(https://image\.tmdb\.org/t/p/)[^/]+/')
BLUR = 'image://{}/transform?width=32&height=18'   # Kodi scales the image down itself (TextureCacheJob)
HAIR = '\u200a'                          # letter spacing of the kicker
PLOT_CHARS = 190                         # four lines of the hero text box
NOW_PLOT_CHARS = 140                     # three lines: a channel's first line names the episode
S_LEFT_MIN, S_LEFT_HOURS = 32549, 32550
S_SEASONS = (32551, 32552, 32553)        # one, few (2-4), many: the plural forms of cs, sk and en
S_TITLES = (32555, 32556, 32557)
S_CHANNEL = 32554
S_ON_NOW = 32701
S_GAME_PLOT = 32751                      # the line under the headline of the Milionář section


def _string(string_id, fallback):
    return xbmcaddon.Addon(ADDON_ID).getLocalizedString(string_id) or fallback


def track(text):
    """Hair spaces between the letters (the spaced kicker line)."""
    return (HAIR * 3 + ' ' + HAIR * 3).join(HAIR.join(word) for word in str(text or '').split())


def runtime(minutes):
    minutes = int(minutes or 0)
    if minutes <= 0:
        return ''
    return '{}h {:02d}m'.format(minutes // 60, minutes % 60) if minutes >= 60 else '{}m'.format(minutes)


def counted(count, forms):
    """'10 sérií' with the plural form of `forms` (one, few, many); '' when the count is unknown."""
    count = int(count or 0)
    if count <= 0:
        return ''
    return _string(forms[0 if count == 1 else 1 if count <= 4 else 2], '{} ×').format(count)


def years(card):
    """'1994–2004' of an ended series, '2019–' of a running one, the first year when the status is unknown."""
    first, last = str(card.get('year') or ''), str(card.get('end_year') or '')
    if not first or card.get('ended') is None:
        return first
    if not card['ended']:
        return first + '–'
    return first if last in ('', first) else '{}–{}'.format(first, last)


def size(title):
    """Font step of the title: l (short), m, s (long)."""
    length = len(str(title or ''))
    return 'l' if length <= 14 else 'm' if length <= 22 else 's'


def plot(text, limit=PLOT_CHARS):
    text = ' '.join(str(text or '').split())
    if len(text) <= limit:
        return text
    return text[:limit].rsplit(' ', 1)[0].rstrip(' ,.;:-') + '…'


def left(position, total):
    """'23 min zbývá' of a playback in progress, '' otherwise."""
    position, total = float(position or 0), float(total or 0)
    if position <= 0 or total <= position:
        return ''
    minutes = max(1, int(round((total - position) / 60.0)))
    if minutes >= 60:
        return _string(S_LEFT_HOURS, '{}h {:02d}m').format(minutes // 60, minutes % 60)
    return _string(S_LEFT_MIN, '{} min').format(minutes)


def fanart(card):
    """The backdrop in the size of the screen (the row art is a small card image)."""
    path = card.get('backdrop') or ''
    if not path.startswith('/'):
        return card.get('fanart') or ''
    try:
        from wcs.skin.performance import enabled
        low = enabled()
    except ImportError:
        low = False
    return BACKDROP.format('w780' if low else 'original') + path


def blur(url):
    """The backdrop as a tiny texture made by Kodi's own texture cache (`image://<url>/transform?width=..`).

    The modern skin stretches it over the screen: a soft blur behind its glass panels with no image library on the
    device (docs/plan/skin-milionar/240-modern.md). A TMDb backdrop is fetched in its smallest size for it."""
    if not url:
        return ''
    return BLUR.format(quote(TMDB_SIZE.sub(r'\1w300/', url), safe=''))


def _texts(card, facts, caption='', note=''):
    """Texts of a card: `facts` follow the genre in the kicker, `caption` stands by the hero's progress bar and
    `note` on the right of a wide card."""
    title = card.get('title') or ''
    parts = [str(card.get('genre') or '').upper()] + [str(fact or '') for fact in facts]
    rating = float(card.get('rating') or 0)
    backdrop = fanart(card)
    return {'title': title, 'size': size(title), 'kicker': track(' · '.join(p for p in parts if p)),
            'rating': '{:.1f}'.format(rating) if rating else '', 'plot': plot(card.get('plot')), 'left': caption,
            'note': note, 'fanart': backdrop, 'blur': blur(backdrop), 'badge': '', 'now': ''}


def movie(card):
    remaining = left(card.get('resume'), card.get('resume_total'))
    return _texts(card, (card.get('year'), runtime(card.get('runtime'))), remaining, remaining)


def _series_facts(card):
    return counted(card.get('seasons'), S_SEASONS), years(card)


def series(card):
    return _texts(card, _series_facts(card))


def episode(card, episode, state, status=''):
    """An episode shows its series; `status` replaces the time left (the air date of an episode not out yet)."""
    code = 'S{} · E{}'.format(episode['s'], episode['e'])
    remaining = status or left(state.get('resume_pos'), state.get('resume_total'))
    return _texts(card, _series_facts(card), ' · '.join(p for p in (code, remaining) if p), code)


def channel(name, titles, backdrop=None, now=None):
    """A MyTV / MyCinema channel: its name, how many titles it has and which; `backdrop`: a card of one of them.

    `now` (MyTV): what the channel plays next {'series', 's', 'e', 'title', 'plot', 'state'}. The hero then has the
    lines of any other hero: where a title has its rating stands the badge "on now" with the series (`badge`, `now`),
    the text box starts with the episode and continues with its plot (the channel's titles when it has none), and an
    episode in progress gives the time left."""
    names = ', '.join(t for t in titles if t)
    card = dict(backdrop or {}, title=name, genre=_string(S_CHANNEL, 'TV'), plot=names, rating=0)
    if not now:
        return _texts(card, (counted(len(titles), S_TITLES),))
    code = 'S{} · E{}'.format(now['s'], now['e'])
    state = now.get('state') or {}
    remaining = left(state.get('resume_pos'), state.get('resume_total'))
    texts = _texts(card, (counted(len(titles), S_TITLES),), ' · '.join((code, remaining)) if remaining else '')
    first = ' · '.join(p for p in (code, ' '.join(str(now.get('title') or '').split())) if p)
    texts.update(badge=_string(S_ON_NOW, 'On now').upper(), now=now.get('series') or '',
                 plot='[CR]'.join(p for p in (first, plot(now.get('plot'), NOW_PLOT_CHARS) or plot(names, NOW_PLOT_CHARS)) if p))
    return texts


def challenge(item, title, kicker, backdrop, buttons, description=''):
    """A card of the Milionář section: the selected title, synopsis and challenge kind, and the
    Home buttons `buttons` [(action id, label, builtin)] of every card."""
    texts = {'title': title, 'size': size(title), 'kicker': kicker, 'rating': '', 'plot': plot(description) or _string(S_GAME_PLOT, ''),
             'left': '', 'note': '', 'fanart': backdrop, 'blur': blur(backdrop),
             'badge': _string(32752, '9 questions'),
             'now': _string(32753, '3 lifelines · 1 virtual million')}
    for key, value in texts.items():
        item.setProperty('milionar.hero.' + key, value)
    item.setProperty('milionar.hero.resumable', '')
    for action, label, builtin in buttons:
        direct(item, action, label, builtin)
    default_buttons(item)


S_CONTINUE, S_PLAY, S_INFO = 31902, 208, 31991   # skin strings of the hero: Pokračovat, Přehrát, Více informací   # skin strings of the hero: Pokračovat, Přehrát, Více informací


def direct(item, action, label, builtin):
    """Register a builtin the builder already has for a context action; the hero buttons use it as it is."""
    if not builtin or not action:
        return
    ids = [a for a in item.getProperty('milionar.hero.directs').split(',') if a]
    if action not in ids:
        item.setProperty('milionar.hero.directs', ','.join(ids + [action]))
    item.setProperty('milionar.hero.dl.' + action, label)
    item.setProperty('milionar.hero.do.' + action, builtin)


def default_buttons(item):
    """The buttons of an item nobody configured: the registered builtins in the builder's order."""
    from wcs.skin import hero_buttons
    hero_buttons.write(item, [values + (action,) for action, values in hero_buttons.directs(item).items()])


def apply(item, texts, play, detail, ids=('play', 'detail'), resumable=False, labels=None):
    """Write the texts and the builtins of the two hero buttons to the item's `milionar.hero.*` properties.

    `ids`: the context actions `play` and `detail` stand for (a series plays its `next` episode, a channel goes `live`
    and `open`s; '' registers none); `resumable`: the primary button reads Pokračovat; `labels`: other captions of the
    two. The hero shows these two until context.attach lays the buttons out by the widget's configuration
    (wcs/skin/hero_buttons.py)."""
    import xbmc
    for key, value in dict(texts, play=play, detail=detail).items():
        item.setProperty('milionar.hero.' + key, value)
    item.setProperty('milionar.hero.resumable', '1' if resumable else '')
    labels = labels or (xbmc.getLocalizedString(S_CONTINUE if resumable else S_PLAY), xbmc.getLocalizedString(S_INFO))
    direct(item, ids[0], labels[0], play)
    direct(item, ids[1], labels[1], detail)
    default_buttons(item)
