"""Story runtime: one cancellable preparation worker, a pure engine, shared modal/voice."""
import threading

import xbmc
import xbmcaddon

from wcs.games import game_settings
from wcs.games.story import view
from wcs.games.story.content import DemoSource, Unavailable
from wcs.games.story.engine import Engine
from wcs.games.voice import TtsVoice, SilentVoice


class StoryAdapter:
    def __init__(self, dialog, subject, demo_id='', source=None):
        self.dialog, self.subject = dialog, subject
        if source is None:
            if demo_id:
                source = DemoSource(demo_id)
            else:
                from wcs.games.story.ai import AiSource
                source = AiSource()
        self.source = source
        if isinstance(source, DemoSource):
            self.subject = source.scoped_subject(subject)
        self.voice = SilentVoice() if source.name == 'demo' else TtsVoice()
        self.engine = None
        self.difficulty = 'medium'
        self.error = ''
        self._screen = 'title'
        self._active = True
        self._generation = 0
        self._lock = threading.RLock()

    def start(self):
        self._publish()

    def screen(self):
        return self._screen

    def focus_target(self):
        return {'title': 9601, 'rules': 9781, 'loading': 9783, 'question': 9790, 'feedback': 9794,
                'win': 9794, 'gameover': 9794, 'results': 9794, 'error': 9784}.get(self._screen, 9601)

    def _publish(self, focus=False):
        if not self._active:
            return
        addon = xbmcaddon.Addon()
        if self._screen == 'title':
            self.difficulty = ('easy', 'medium', 'hard')[game_settings.value_of(game_settings.STORY_ROW)]
        data = view.view(self.engine, self._screen, self.difficulty, addon.getLocalizedString, self.error)
        data['SourceNote'] = addon.getLocalizedString(35424) if self.source.name == 'demo' else ''
        if self.source.name == 'demo' and self.subject.season_number is not None and self.subject.episode_number is not None:
            data['SourceNote'] += ' · S{:02}E{:02}'.format(self.subject.season_number, self.subject.episode_number)
        for key, value in data.items():
            self.dialog.setProperty('WCS.Story.' + key, value)
        self.dialog.setProperty('WCS.Game.Screen', self._screen)
        self.dialog.setProperty('WCS.Game.ButtonsReady', '' if self._screen == 'loading' else 'true')
        if focus:
            self.dialog._delayed_focus(self.focus_target(), delay=0.05)

    def act(self, intent):
        with self._lock:
            if not self._active:
                return
            if intent == 'story_primary':
                intent = 'next' if self._screen == 'feedback' else 'again'
            if intent in ('start', 'again', 'retry') and self._screen in ('title', 'rules', 'win', 'gameover', 'error'):
                self._prepare()
            elif intent == 'rules' and self._screen == 'title':
                self._screen = 'rules'
                self._publish(True)
            elif intent.startswith('answer') and self._screen == 'question':
                if self.engine.answer(int(intent[6:])):
                    self._screen = {'lost': 'gameover', 'won': 'win'}.get(self.engine.state, 'feedback')
                    self._publish(True)
                    self.voice.feedback('', '', self.engine.scene.reveal)
            elif intent == 'next' and self.engine and self.engine.advance():
                self.voice.stop()
                self._screen = 'question'
                self._publish(True)
                self._speak_scene()
            elif intent in ('title', 'home', 'end'):
                self.dialog.leave(intent)

    def _prepare(self):
        self.voice.stop()
        if self.engine:
            self.engine.close()
        self.engine = None
        self.error = ''
        self.difficulty = ('easy', 'medium', 'hard')[game_settings.value_of(game_settings.STORY_ROW)]
        self._generation += 1
        generation, difficulty = self._generation, self.difficulty
        self._screen = 'loading'
        self._publish(True)

        def worker():
            try:
                pack = self.source.prepare(self.subject, difficulty)
                error = ''
            except Unavailable as exc:
                pack, error = None, str(exc)
            except Exception as exc:
                pack = None
                error = xbmcaddon.Addon().getLocalizedString(35421)
                # Do not store the pack, transport response or credentials in logs.
                xbmc.log('[Story] preparation failed: ' + type(exc).__name__, xbmc.LOGWARNING)
            with self._lock:
                if not self._active or generation != self._generation:
                    return
                self.error = error
                self.engine = Engine(pack) if pack else None
                self._screen = 'question' if pack else 'error'
                self._publish(True)
                if pack:
                    self._speak_scene()
        threading.Thread(target=worker, daemon=True).start()

    def _speak_scene(self):
        scene = self.engine.scene
        self.voice.question(scene.setup, scene.question, scene.options)

    def retire(self):
        with self._lock:
            if not self._active:
                return
            self._active = False
            self._generation += 1
            if self.engine:
                self.engine.close()
                self.engine = None
            self.voice.stop()

    def cleanup(self):
        self.retire()
        for key in view.FIELDS:
            self.dialog.clearProperty('WCS.Story.' + key)
