# -*- coding: utf-8 -*-
"""Per-widget hero buttons of the cinematic Home sections (docs/plan/skin-milionar/220-hero-buttons.md).

The same principle as the context menu manager (wcs/skin/context.py): every widget has an ordered configuration
`{'main': [...], 'more': [...], 'hidden': [...]}` of the action ids of its context menu. `main` are the buttons of the
hero, `more` opens from the button "More options", `hidden` is offered nowhere. The hero shows at most LIMIT buttons,
"More options" included.

The buttons are written to the item (`milionar.hero.b<n>` builtin, `b<n>l` label, `b1i` = play icon) at the output
boundary (`context.attach`), in the widget fast path: no network and no store lookup there. The item builders register
the builtins they already have (`hero.direct`: play, detail, a Vinyl shuffle ...); every other action runs through
`skin_hero`, which resolves it with the context menu's own code when it is pressed.

A row without titles of its own (film / series genres) lends the hero a title of the first row that has one. While
such a row is the current one its own configuration applies: the lender's items carry that layout too, under
`milionar.hero.s<slot>.*` (`lent`), and the skin reads it instead of the item's own buttons.
"""
from urllib.parse import urlencode

import xbmc
import xbmcgui

from wcs.skin import context
from wcs.skin.hero_contract import BUTTON_SLOTS as LIMIT

BASE = 'plugin://plugin.video.milionar/'
MORE, EDIT = 'more', 'edit'
PROPERTY = 'milionar.hero.'
PLAY_ICON = ('play', 'next', 'live', 'play_all', 'restart', 'play_from', 'first', 'random', 'live_shuffle', 'shuffle')
S_TITLE, S_EDIT, S_TO_MAIN, S_TO_MORE, S_LIMIT, S_UNAVAILABLE, S_COLUMN, S_MORE = \
    32691, 32692, 32693, 32694, 32695, 32696, 32697, 32681
# the buttons a row kind has had so far (wcs/library2/clicks.ROW_KIND); the first one is the primary button
# "play" of a film or an episode and "next" of a series are the same button to the viewer: a row that shows both kinds
# of titles (a mixed row, a genre row's borrowed hero) needs only one of them configured
PLAY_ALIAS = {'play': 'next', 'next': 'play'}
MAIN = {'series': ('next', 'detail'), 'mixed': ('play', 'next', 'detail'),
        'game': context.GAME_HERO, 'demo': context.GAME_HERO,
        'tv_channel': ('live', 'open'), 'cinema_channel': ('live', 'open'),
        'vinyl_song': ('play', 'favorite'), 'vinyl_collection': ('play_all', 'shuffle', 'favorite'),
        'vinyl_genre': ('play_all', 'shuffle'), 'vinyl_radio': ('play_all',)}


def catalog(row):
    return tuple(action for action in context.catalog(row) if action not in context.CONTEXT_ONLY)


def defaults(row):
    """The buttons the hero had before the manager; Vinyl keeps the rest under More options, the others hide it."""
    from wcs.library2 import clicks
    allowed = catalog(row)
    if row.startswith('home_'):
        return {'main': list(allowed), 'more': [], 'hidden': []}
    main = [a for a in MAIN.get(clicks.kind_of(row), ('play', 'detail')) if a in allowed]
    if row.startswith('fy_'):
        main.append('fy_mode')                              # Pro tebe: Zobrazit: Vše / Filmy / Seriály
    rest = [a for a in allowed if a not in main]
    vinyl = row.startswith('vinyl_')
    return {'main': main, 'more': rest if vinyl else [], 'hidden': [] if vinyl else rest}


def limit(config):
    """At most LIMIT buttons, More options included: what does not fit goes to the front of `more`."""
    room = LIMIT if not config['more'] and len(config['main']) <= LIMIT else LIMIT - 1
    overflow = config['main'][room:]
    if overflow:
        config['main'], config['more'] = config['main'][:room], overflow + config['more']
    return bool(overflow)


def normalize(row, config=None):
    """Keep a stored order; actions the row gained since go where the defaults have them."""
    if row.startswith('home_'):
        return context.normalize(row, config)
    base = defaults(row)
    if not isinstance(config, dict):
        return base
    allowed, seen, result = catalog(row), set(), {}
    for group in ('main', 'more', 'hidden'):
        values = config.get(group, [])
        result[group] = []
        for action in values if isinstance(values, list) else []:
            if isinstance(action, str) and action in allowed and action not in seen:
                result[group].append(action)
                seen.add(action)
    for group in ('main', 'more', 'hidden'):
        result[group].extend(a for a in base[group] if a not in seen)
    limit(result)
    return result


def can(action, data, resumable=False):
    """Whether the item can have the action, from what the item carries (the context menu decides for good later)."""
    kind = data.get('type')
    if kind == 'vinyl':
        from wcs.vinyl.context import can as vinyl_can
        return vinyl_can(action, data)
    if kind == 'channel':
        return action in ('live', 'open', 'random', 'edit_channel', 'rename_channel', 'move_channel', 'delete_channel') \
            or (data.get('kind') == 'tv' and action in ('live_shuffle', 'first'))
    if not data.get('tmdb') or kind not in ('movie', 'tv', 'season', 'episode'):
        return False
    if kind in ('movie', 'episode'):
        allowed = {'play', 'restart', 'source', 'detail', 'watched', 'list', 'channel', 'similar', 'game'}
        allowed.update(('collection',) if kind == 'movie' else ('folder', 'season'))
        if resumable:
            allowed.add('hide_continue')
    else:
        allowed = {'next', 'next_source', 'detail', 'folder', 'watched', 'list', 'channel', 'similar', 'game'}
    if 'mupcoming' in data.get('path', '') or data.get('s') == '-1':
        allowed -= {'play', 'restart', 'source', 'next', 'next_source', 'watched'}
    if data.get('channel_id'):                              # a MyTV title: its playback and progress are the channel's
        allowed -= {'watched', 'hide_continue', 'restart', 'source', 'next_source'}
        allowed.add('channel_open')
    return action in allowed


def lazy(action, encoded, owner=''):
    """Builtin of a button without a ready one: `encoded` is the item's `milionar.ctxdata`; `owner`: the widget whose
    configuration laid the button out when it is not the item's own (a genre row's borrowed hero)."""
    return 'RunPlugin({}?action=skin_hero&do={}&data={}{})'.format(BASE, action, encoded, '&owner=' + owner if owner else '')


def write(item, buttons, prefix=''):
    """`buttons`: (label, builtin, action id) in the order of the hero; `prefix`: 's<slot>.' of a lent layout."""
    buttons = [button for button in buttons if button[0] and button[1]][:LIMIT]
    for n in range(1, LIMIT + 1):
        label, builtin = buttons[n - 1][:2] if n <= len(buttons) else ('', '')
        item.setProperty('{}{}b{}'.format(PROPERTY, prefix, n), builtin)
        item.setProperty('{}{}b{}l'.format(PROPERTY, prefix, n), label)
    item.setProperty(PROPERTY + prefix + 'b1i', 'play' if buttons and buttons[0][2] in PLAY_ICON else '')
    if prefix:
        item.setProperty(PROPERTY + prefix.rstrip('.'), '1')            # the layout exists (it may have no button)


def directs(item):
    """The actions the item's builder gave a builtin: action id -> (label, builtin), in the builder's order."""
    ids = [a for a in item.getProperty(PROPERTY + 'directs').split(',') if a]
    return {a: (item.getProperty(PROPERTY + 'dl.' + a), item.getProperty(PROPERTY + 'do.' + a)) for a in ids}


def compose(item, data, row, config, encoded, prefix='', owner=''):
    """Write the item's hero buttons from a widget's configuration (None: the row's defaults)."""
    if not item.getProperty(PROPERTY + 'title'):
        return
    ready = directs(item)
    if row.startswith('home_'):
        from wcs.skin import navigation
        ready = navigation.actions(row)
    resumable = item.getProperty(PROPERTY + 'resumable') == '1'
    config = normalize(row, config)
    buttons = []
    for action in config['main']:
        alias = PLAY_ALIAS.get(action)
        if action not in ready and alias in ready and alias not in config['main']:
            action = alias
        if action in ready:
            buttons.append(ready[action] + (action,))
        elif can(action, data, resumable):
            buttons.append((context.label(row, action), lazy(action, encoded, owner), action))
    if any(action in ready or can(action, data, resumable) for action in config['more']):
        buttons = buttons[:LIMIT - 1] + [(context.text(S_MORE), lazy(MORE, encoded, owner), MORE)]
    write(item, buttons, prefix)


def owns_hero(row):
    """A row of titles feeds the hero itself; a folder-only row (film / series genres) borrows a title."""
    from wcs.library2 import clicks
    return bool(clicks.kind_of(row))


def stored(sec, slot):
    """The saved configuration of a Home slot (None: none saved)."""
    return context.decode(xbmc.getInfoLabel('Skin.String(mil.{}.{}.hero)'.format(sec, slot)))


def lent(sec, slot):
    """What the other slots of the section lay out on this slot's items: {slot: (row, configuration, widget id)} of
    the rows without titles of their own that have a saved configuration."""
    from wcs.skin import slots
    result = {}
    for other in range(1, slots.SLOTS + 1):
        if str(other) == str(slot):
            continue
        prefix = 'Skin.String(mil.{}.{}.'.format(sec, other)
        row = xbmc.getInfoLabel(prefix + 'type)')
        if not row or row == 'off' or owns_hero(row):
            continue
        config = context.decode(xbmc.getInfoLabel(prefix + 'hero)'))
        if config is not None:
            result[other] = (row, config, xbmc.getInfoLabel(prefix + 'uid)') or '{}:{}'.format(sec, other))
    return result


def run(params):
    """`skin_hero`: a hero button without a ready builtin, the More options popup, or the editor."""
    do = params.get('do', '')
    data = context.unpack(params.get('data', ''))
    if do == EDIT:
        from wcs.skin import slots
        slots.edit_saved_hero(params.get('ctx') or data.get('ctx', ''))
        return
    _sec, _slot, widget, row, available = context.resolve(data)
    owner = params.get('owner', '')
    if owner:                                               # the layout of another widget (a genre row's borrowed hero)
        _sec, _slot, widget = context.find_widget(owner)
        row = widget['type'] if widget else row
        if row.startswith('home_'):
            from wcs.skin import navigation
            available = navigation.actions(row)
    if do == MORE:
        config = normalize(row, widget.get('hero') if widget else None)
        entries = [available[a] for a in config['more'] if a in available]
        if widget:
            entries.append((context.text(S_EDIT), context.builtin({'action': 'skin_hero', 'do': EDIT, 'ctx': owner or data['ctx']})))
        if not entries:
            return
        from wcs.skin.menu_anchor import anchored
        home = xbmcgui.Window(10000)
        home.setProperty('milionar.ctx.active', '1')
        try:
            with anchored():
                index = xbmcgui.Dialog().contextmenu([label for label, _command in entries])
        finally:
            home.clearProperty('milionar.ctx.active')
        if index >= 0:
            xbmc.executebuiltin(entries[index][1])
    elif do in available or PLAY_ALIAS.get(do) in available:
        xbmc.executebuiltin(available[do if do in available else PLAY_ALIAS[do]][1])
    else:
        xbmcgui.Dialog().notification('Milionář', context.text(S_UNAVAILABLE))


def focused_widget(control):
    """The widget whose buttons the hero of a focused hero button shows: the current row's (also a genre row, whose
    hero borrows a title), else the first row with titles. Its identity, or ''."""
    from wcs.skin.hero_contract import BASES, BUTTON_OFFSETS, BUTTON_PLAIN
    from wcs.skin import slots
    if not control.isdigit():
        return ''
    for sec, base in BASES.items():
        if int(control) - base in BUTTON_OFFSETS + (BUTTON_PLAIN,):
            data = 'Container({}).ListItem.Property(milionar.ctxdata)'
            current = xbmc.getInfoLabel('Window(Home).Property(milionar.row.{})'.format(sec))
            identity = context.unpack(xbmc.getInfoLabel(data.format(current))).get('ctx', '') if current else ''
            if identity:
                return identity
            for lid in (str(base + n * 100) for n in range(1, slots.SLOTS + 1)):
                if xbmc.getInfoLabel('Container({}).ListItem.Property({}title)'.format(lid, PROPERTY)):
                    return context.unpack(xbmc.getInfoLabel(data.format(lid))).get('ctx', '')
    return ''
