# -*- coding: utf-8 -*-
"""Initial audio/subtitle selection for every video started in Kodi."""
import json
import queue
import re
import threading
import time

import xbmc

STARTUP_TIMEOUT_S = 5
LANGUAGES = {
    'cs': {'cs', 'cz', 'cze', 'ces', 'czech', 'čeština', 'cestina', 'česky', 'český'},
    'en': {'en', 'eng', 'english', 'angličtina', 'anglictina'},
    'sk': {'sk', 'slo', 'slk', 'slovak', 'slovenčina', 'slovencina'},
}


def _language(stream, language):
    tag = (stream.get('language') or '').strip().casefold().replace('_', '-').split('-')[0]
    if tag and tag not in ('und', 'unknown'):
        return tag in LANGUAGES[language]
    # A name is useful only when the stream has no actual language tag.
    words = set(re.findall(r'\w+', (stream.get('name') or '').casefold()))
    return bool(words & LANGUAGES[language])


def _audio_rank(stream):
    name = (stream.get('name') or '').casefold()
    commentary = any(word in name for word in ('comment', 'koment', 'audiopopis', 'audio description', 'descriptive'))
    return (not (stream.get('isimpaired') or commentary), stream.get('channels', 0),
            bool(stream.get('isdefault')), -stream['index'])


def _audio(properties):
    streams = properties.get('audiostreams') or []
    for language in ('cs', 'en'):
        choices = [stream for stream in streams if _language(stream, language)]
        if choices:
            return max(choices, key=_audio_rank)
    # Keep the original/current track if neither Czech nor English is available; never prefer Slovak.
    choices = [stream for stream in streams if not _language(stream, 'sk')]
    current = (properties.get('currentaudiostream') or {}).get('index')
    return max(choices, key=lambda stream: (bool(stream.get('isoriginal')), stream['index'] == current,
                                            _audio_rank(stream))) if choices else None


def _subtitle(properties):
    choices = [stream for stream in properties.get('subtitles') or [] if _language(stream, 'cs')]
    return max(choices, key=lambda stream: (not stream.get('isforced'), not stream.get('isimpaired'),
                                            bool(stream.get('isdefault')), -stream['index'])) if choices else None


class LanguagePlayer(xbmc.Player):
    """Callbacks enqueue only. The service applies preferences once, then leaves manual changes alone."""

    def __init__(self, now=time.monotonic):
        super().__init__()
        self.events = queue.Queue()
        self.now = now
        self.deadline = None
        self.file = None
        self.applied = False

    def onAVStarted(self):
        self.events.put(('started', self.now()))

    def onPlayBackStopped(self):
        self.events.put(('stopped', self.now()))

    def onPlayBackEnded(self):
        self.events.put(('stopped', self.now()))

    def onPlayBackError(self):
        self.events.put(('stopped', self.now()))

    def tick(self):
        while not self.events.empty():
            event, when = self.events.get_nowait()
            self.deadline = when + STARTUP_TIMEOUT_S if event == 'started' else None
            self.file = None
            self.applied = False
        if self.deadline is None:
            return
        expired = self.now() >= self.deadline
        try:
            if not self.isPlayingVideo():
                self.deadline = None
                return
            playing = self.getPlayingFile()
            if self.file is not None and playing != self.file:
                self.deadline = None
                return
            self.file = playing
            reply = json.loads(xbmc.executeJSONRPC(json.dumps({
                'jsonrpc': '2.0', 'id': 1, 'method': 'Player.GetProperties',
                'params': {'playerid': 1, 'properties': ['audiostreams', 'currentaudiostream', 'subtitles',
                                                       'currentsubtitle', 'subtitleenabled']}})))
            properties = reply.get('result')
            if not isinstance(properties, dict) or not properties.get('audiostreams'):
                return
            audio = _audio(properties)
            czech = audio is not None and _language(audio, 'cs')
            subtitle = None if czech else _subtitle(properties)
            if not czech and subtitle is None and not properties.get('subtitles') and not expired and not self.applied:
                return  # allow embedded subtitles to become available during startup
            audio_matches = audio is None or audio['index'] == (properties.get('currentaudiostream') or {}).get('index')
            current_subtitle = (properties.get('currentsubtitle') or {}).get('index')
            subtitle_matches = (bool(properties.get('subtitleenabled')) == (subtitle is not None)
                                and (subtitle is None or subtitle['index'] == current_subtitle))
            if self.applied and audio_matches and subtitle_matches:
                self.deadline = None
                xbmc.log('[Milionář Playback] Initial audio/subtitles verified: {}; Czech subtitles: {}'.format(
                    'Czech' if czech else 'original', subtitle is not None), xbmc.LOGINFO)
                return
            if not audio_matches:
                self.setAudioStream(audio['index'])
            if subtitle and not subtitle_matches:
                self.setSubtitleStream(subtitle['index'])
            self.showSubtitles(subtitle is not None)
            if not self.applied:
                self.deadline = max(self.deadline, self.now() + 1)
            self.applied = True
        except (RuntimeError, ValueError, TypeError) as exc:
            if expired:
                xbmc.log('[Milionář Playback] Initial language selection failed: {}'.format(
                    type(exc).__name__), xbmc.LOGWARNING)
        finally:
            if self.deadline is not None and self.now() >= self.deadline:
                xbmc.log('[Milionář Playback] Initial audio/subtitles could not be verified before timeout', xbmc.LOGWARNING)
                self.deadline = None

    def run(self):
        monitor = xbmc.Monitor()
        while not monitor.abortRequested():
            self.tick()
            if monitor.waitForAbort(0.1 if self.deadline is not None else 0.5):
                break


def start():
    # Register the callbacks before addon autostart can launch its first video.
    player = LanguagePlayer()
    threading.Thread(target=player.run, name='milionar-playback-language', daemon=True).start()
