# -*- coding: utf-8 -*-
"""What the skin asks the running service to do at once (docs/plan/skin-optimization, part D).

A context menu used to start the plugin: a new script run that imports and compiles its modules again (Kodi keeps no
bytecode), a fraction of a second on a desktop and many seconds on a weak box. The service has everything loaded, so
the Home keymap (resources/keymaps/milionar-home.xml) and DialogContextMenu.xml send
`NotifyAll(plugin.video.milionar,<method>)` instead and the menu is built and shown from here. The menu's own quick
actions also run in the process that shows it (wcs.skin.context.run_chosen).

The same way the skin runs every builtin of a card, a hero button or one of its own buttons:
`NotifyAll(plugin.video.milionar,run:<builtin>)`. The builtin arrives here as it is and wcs.skin.context.run_chosen does
it: a quick action of ours (wcs/skin/local.py) in a thread of the service, anything else as the builtin it is. While
this listener runs, Window(Home).Property(milionar.service) is set; without it the skin runs the builtin itself, so a
click is never lost (before the service is up, or without it).

The plugin actions of the same names stay (main.py): they do the same when Kodi starts the plugin for them.
"""
import threading

import xbmc
from wcs.skin import userfirst

# notification -> function of wcs.skin.context
METHODS = {'skin_context': 'home_context', 'skin_context_native': 'native_context'}
# what a menu needs, imported once so that the first menu does not wait for it
MODULES = ('wcs.skin.local', 'wcs.skin.context', 'wcs.skin.slots', 'wcs.skin.hero_buttons', 'wcs.skin.context_profiles', 'wcs.skin.navigation',
           'wcs.library2.menu', 'wcs.library2.clicks', 'wcs.library2.ctx', 'wcs.library2.play', 'wcs.library2.urls')
RUN = 'run:'                                # + a builtin
ALIVE = 'milionar.service'                  # Home property: the skin may ask (gen_slots.ALIVE)
PUMP = 0.02                                 # seconds between looks for a notification while the user can press the key
_busy = threading.Lock()


def _run(method):
    if not _busy.acquire(blocking=False):
        return                                        # a menu is already being shown: a second key press is not another one
    try:
        from wcs.skin import context
        getattr(context, METHODS[method])()
    except Exception as exc:
        xbmc.log('[Milionář Skin] {} failed: {}: {}'.format(method, type(exc).__name__, exc), xbmc.LOGERROR)
    finally:
        _busy.release()


def _run_builtin(command):
    try:
        from wcs.skin import context
        context.run_chosen(command)
    except Exception as exc:
        xbmc.log('[Milionář Skin] {} failed: {}: {}'.format(command[:80], type(exc).__name__, exc), xbmc.LOGERROR)


def handle(method):
    """A notification of the addon; True when it was one of ours (it runs in its own thread)."""
    if method.startswith(RUN) or method in METHODS:
        userfirst.mark()                              # the background work of the service steps aside for it
    if method.startswith(RUN):
        threading.Thread(target=_run_builtin, args=(method[len(RUN):],), name='milionar-run', daemon=True).start()
        return True
    if method not in METHODS:
        return False
    threading.Thread(target=_run, args=(method,), name='milionar-' + method, daemon=True).start()
    return True


def _warm_up(monitor):
    if monitor.waitForAbort(4):                       # Home and its rows first
        return
    import importlib
    for name in MODULES:
        try:
            importlib.import_module(name)
        except Exception as exc:
            xbmc.log('[Milionář Skin] {} not loaded: {}: {}'.format(name, type(exc).__name__, exc), xbmc.LOGWARNING)
        if monitor.waitForAbort(0.05):
            return


class _Monitor(xbmc.Monitor):
    def onNotification(self, sender, method, data):
        if sender == 'plugin.video.milionar':
            handle(method[6:] if method.startswith('Other.') else method)


def _listen():
    """Kodi hands a notification to Python only in the thread that made the monitor, and only while that thread
    waits in waitForAbort (in steps of 100 ms). This thread does nothing else and waits in short steps, so the key
    answers within PUMP; while something plays nobody needs it."""
    import xbmcgui
    monitor = _Monitor()
    home = xbmcgui.Window(10000)
    home.setProperty(ALIVE, '1')
    try:
        checked, step = 0.0, PUMP
        while not monitor.waitForAbort(step):
            checked += step
            if checked >= 1.0:
                checked = 0.0
                step = 0.25 if xbmc.getCondVisibility('Player.HasMedia') else PUMP
    finally:
        home.clearProperty(ALIVE)                     # the skin runs its builtins itself again


def start(monitor):
    threading.Thread(target=_warm_up, args=(monitor,), name='milionar-commands', daemon=True).start()
    threading.Thread(target=_listen, name='milionar-listen', daemon=True).start()
