# -*- coding: utf-8 -*-
"""Offline tests of the settings of the Millionaire game (title card): the rows are the addon settings themselves.

What is checked: every row maps to a real setting of resources/settings.xml (same type, default, choices and range), every
Millionaire setting but the voice and the classic card's own ones has a row, values read / step / cycle / store the way
Kodi stores them, the language files have every string, and the window opens the screen, changes values with the
arrows and OK, keeps the dependent rows tied to the auto-play switch, and closes with Back.

Run: python3 tests/test_millionaire_settings.py
"""
import os
import re
import sys
import unittest

sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import kodi_stubs                                           # noqa: E402

kodi_stubs.install()
ADDON = kodi_stubs.ADDON_DIR

import test_millionaire_modern as modern                    # noqa: E402
from wcs.games import designs, game_settings as gs          # noqa: E402

LANGUAGES = ('cs_cz', 'sk_sk', 'en_gb')
# settings of the category "Hra Milionář" that are not rows: the voice (own screens) and what only the classic card used
NOT_ROWS = {'milionar_game_design', 'millionaire_show_correct_answer', 'millionaire_highlight_delay'}


def setUpModule():
    modern.setUpModule()


def tearDownModule():
    modern.tearDownModule()


def settings_xml():
    return open(os.path.join(ADDON, 'resources', 'settings.xml'), encoding='utf-8').read()


def category(label):
    text = settings_xml()
    start = text.index('<category label="{}">'.format(label))
    return text[start:text.index('</category>', start)]


def attributes(tag):
    return dict(re.findall(r'(\w+)="([^"]*)"', tag))


class FakeAddon:
    """The addon settings in memory: what Kodi's getSetting / setSetting do with strings."""
    store = {}

    def __init__(self, addon_id=None):
        pass

    def getSetting(self, key):
        return self.store.get(key, '')

    def setSetting(self, key, value):
        self.store[key] = value

    def getLocalizedString(self, string_id):
        return 'T{}'.format(string_id)

    def getAddonInfo(self, key):
        return ADDON if key == 'path' else 'plugin.video.milionar'


class FakeSettings(unittest.TestCase):
    def setUp(self):
        import xbmcaddon
        self._addon = xbmcaddon.Addon
        FakeAddon.store = {}
        xbmcaddon.Addon = FakeAddon

    def tearDown(self):
        import xbmcaddon
        xbmcaddon.Addon = self._addon


class RowsMatchTheAddonSettings(unittest.TestCase):
    """The screen and settings.xml cannot drift apart: a row is built from, and checked against, the real setting."""

    @classmethod
    def setUpClass(cls):
        cls.tags = {attributes(tag)['id']: attributes(tag)
                    for tag in re.findall(r'<setting [^>]*id="[^"]*"[^>]*>', category('32100'))}

    def test_every_row_is_a_setting_of_the_milionar_section(self):
        for row in gs.ROWS:
            self.assertIn(row.setting, self.tags, row.key)

    def test_type_default_choices_and_range_are_the_same(self):
        for row in gs.ROWS:
            tag = self.tags[row.setting]
            kind = {'select': 'select', 'bool': 'bool', 'slider': 'slider'}[row.kind]
            self.assertEqual(tag['type'], kind, row.key)
            expected = ('true' if row.default else 'false') if row.kind == 'bool' else str(row.default)
            self.assertEqual(tag['default'], expected, row.key)
            if row.kind == 'select':
                self.assertEqual(tag['lvalues'], '|'.join(str(i) for i in row.options), row.key)
            if row.kind == 'slider':
                self.assertEqual((int(tag['min']), int(tag['max'])), (row.low, row.high), row.key)
            self.assertEqual(tag['label'], str(row.label), row.key)

    def test_every_setting_but_the_voice_and_the_classic_ones_has_a_row(self):
        voice = re.compile(r'milionar_tts_')
        rows = {row.setting for row in gs.ROWS + gs.rows_for('story')}
        settings = {key for key, tag in self.tags.items() if tag.get('type') not in ('action', 'sep', 'lsep', 'text')}
        missing = {key for key in settings if not voice.match(key) and key not in NOT_ROWS} - rows
        self.assertEqual(missing, set(), 'a setting of the Milionář section the screen does not offer')

    def test_dependent_rows_follow_the_autoplay_switch_in_both_places(self):
        for row in gs.ROWS:
            if row.needs:
                self.assertEqual(row.needs, 'autoplay')
                self.assertTrue(self.tags[row.setting].get('visible', '').startswith('eq('), row.key)
            if row.key == 'autoplay':
                self.assertIsNone(row.needs)

    def test_every_string_exists_in_all_three_languages(self):
        needed = {35200, 35240, 35241, 35242, 35248, 35249, 35250, 35251, 35252, 35253}
        for row in gs.ROWS:
            needed |= {row.label, row.description, *row.options}
        for lang in LANGUAGES:
            po = open(os.path.join(ADDON, 'resources', 'language', 'resource.language.' + lang, 'strings.po'),
                      encoding='utf-8').read()
            have = {int(number) for number in re.findall(r'msgctxt "#(\d+)"', po)}
            self.assertEqual(sorted(needed - have), [], lang)

    def test_control_ids_are_the_design_clicks(self):
        for key, control in gs.CONTROLS.items():
            self.assertEqual(designs.MODERN.clicks[control], 'set_' + key)
        self.assertEqual(designs.MODERN.clicks[9604], 'settings')


class ValuesTests(FakeSettings):
    def row(self, key):
        return gs.BY_KEY[key]

    def test_an_empty_or_broken_setting_reads_as_the_default(self):
        for row in gs.ROWS:
            self.assertEqual(gs.value_of(row), row.default, row.key)
        FakeAddon.store.update({'millionaire_difficulty': '9', 'milionar_autoplay_speed': 'x',
                                'milionar_autoplay': 'maybe'})
        self.assertEqual(gs.value_of(self.row('difficulty')), 1)
        self.assertEqual(gs.value_of(self.row('autoplay_speed')), 5)
        self.assertIs(gs.value_of(self.row('autoplay')), False)

    def test_values_are_stored_the_way_the_game_reads_them(self):
        gs.store(self.row('difficulty'), 2)
        gs.store(self.row('autoplay'), True)
        gs.store(self.row('autoplay_speed'), 8)
        self.assertEqual(FakeAddon.store, {'millionaire_difficulty': '2', 'milionar_autoplay': 'true',
                                           'milionar_autoplay_speed': '8'})
        from wcs.games import millionaire_prompts
        self.assertIn('TĚŽKÁ', millionaire_prompts._get_global_difficulty())

    def test_step_stops_at_the_ends_and_cycle_wraps(self):
        row = self.row('difficulty')
        self.assertEqual([gs.step(row, 1) for _ in range(3)], [2, 2, 2])
        self.assertEqual([gs.step(row, -1) for _ in range(4)], [1, 0, 0, 0])
        self.assertEqual([gs.cycle(row) for _ in range(4)], [1, 2, 0, 1])
        speed = self.row('autoplay_speed')
        self.assertEqual([gs.step(speed, -1) for _ in range(5)], [4, 3, 2, 2, 2])
        self.assertEqual(gs.step(speed, 1), 3)
        switch = self.row('autoplay')
        self.assertEqual([gs.cycle(switch) for _ in range(3)], [True, False, True])

    def test_the_prompts_read_what_the_rows_store(self):
        from wcs.games import millionaire_prompts
        for key, setting in (('len_welcome', 'welcome'), ('len_intro', 'intro'), ('len_result', 'result'),
                             ('len_transition', 'transition'), ('len_explanation', 'explanation')):
            gs.store(self.row(key), 3)
            self.assertEqual(FakeAddon.store[self.row(key).setting], '3', key)
        gs.store(self.row('style'), 4)
        from wcs.games.moderator_styles import get_current_style_index
        self.assertEqual(get_current_style_index(), 4)

    def test_the_choices_list_marks_the_current_value(self):
        text = gs.choices(self.row('difficulty'), translate=lambda i: 'T{}'.format(i))
        self.assertEqual(text.count('[CR]'), 2)
        self.assertRegex(text.split('[CR]')[1], r'E8B860.*•.*T32109')
        self.assertNotIn('•', text.split('[CR]')[0] + text.split('[CR]')[2])
        gs.store(self.row('autoplay'), True)
        on, off = gs.choices(self.row('autoplay'), translate=lambda i: 'T{}'.format(i)).split('[CR]')
        self.assertIn('T35248', on)
        self.assertIn('•', on)
        self.assertNotIn('•', off)
        self.assertEqual(gs.choices(self.row('autoplay_speed')), '2 – 10 s')

    def test_texts_and_visibility_of_the_dependent_rows(self):
        self.assertEqual(gs.text_of(self.row('difficulty')), 'T32109')
        self.assertEqual(gs.text_of(self.row('autoplay')), 'T35249')
        self.assertEqual(gs.text_of(self.row('autoplay_speed')), '5 s')
        props = gs.properties()
        for key in ('autoplay_fail_round', 'autoplay_speed', 'autoplay_lifelines'):
            self.assertEqual(props[gs.PREFIX + key + '.Show'], '', key)
        for key in ('difficulty', 'style', 'len_intro', 'autoplay'):
            self.assertEqual(props[gs.PREFIX + key + '.Show'], 'true', key)
        gs.cycle(self.row('autoplay'))
        props = gs.properties()
        self.assertEqual(props['WCS.Game.Set.autoplay'], 'T35248')
        for key in ('autoplay_fail_round', 'autoplay_speed', 'autoplay_lifelines'):
            self.assertEqual(props[gs.PREFIX + key + '.Show'], 'true', key)


class WindowTests(modern.ModernPanelCase, FakeSettings):
    def setUp(self):
        FakeSettings.setUp(self)

    def tearDown(self):
        FakeSettings.tearDown(self)

    @staticmethod
    def action(action_id):
        return type('Action', (), {'getId': lambda self: action_id})()

    def open(self):
        panel = self.panel('matrix')
        self.until(panel, 'title')
        panel.onClick(9604)
        return panel

    def test_the_button_opens_the_screen_and_focuses_the_first_row(self):
        panel = self.open()
        self.assertEqual(panel.getProperty('WCS.Game.Settings'), 'true')
        self.assertEqual(panel.focus, gs.CONTROLS['difficulty'])
        self.assertEqual(panel.getProperty('WCS.Game.Set.difficulty'), 'T32109')
        self.assertEqual((panel.getProperty('WCS.Game.SetTitle'), panel.getProperty('WCS.Game.SetValue'),
                          panel.getProperty('WCS.Game.SetDesc')), ('T32107', 'T32109', 'T35260'))
        self.assertIn('T32109', panel.getProperty('WCS.Game.SetChoices'))

    def test_the_arrows_change_the_value_and_ok_takes_the_next_one(self):
        panel = self.open()
        panel.focus = gs.CONTROLS['difficulty']
        panel.onAction(self.action(2))                                   # right
        self.assertEqual(FakeAddon.store['millionaire_difficulty'], '2')
        self.assertEqual(panel.getProperty('WCS.Game.Set.difficulty'), 'T32110')
        self.assertEqual(panel.getProperty('WCS.Game.SetValue'), 'T32110')
        panel.onAction(self.action(1))                                   # left
        panel.onAction(self.action(1))
        self.assertEqual(FakeAddon.store['millionaire_difficulty'], '0')
        panel.onClick(gs.CONTROLS['difficulty'])                         # OK
        self.assertEqual(FakeAddon.store['millionaire_difficulty'], '1')

    def test_focusing_a_row_shows_what_it_does(self):
        panel = self.open()
        panel.onFocus(gs.CONTROLS['len_result'])
        self.assertEqual(panel.getProperty('WCS.Game.SetTitle'), 'T35224')
        self.assertEqual(panel.getProperty('WCS.Game.SetDesc'), 'T35264')

    def test_the_autoplay_rows_appear_with_the_switch(self):
        panel = self.open()
        self.assertEqual(panel.getProperty('WCS.Game.Set.autoplay_speed.Show'), '')
        panel.onClick(gs.CONTROLS['autoplay'])
        self.assertEqual(FakeAddon.store['milionar_autoplay'], 'true')
        self.assertEqual(panel.getProperty('WCS.Game.Set.autoplay_speed.Show'), 'true')
        panel.focus = gs.CONTROLS['autoplay_speed']
        panel.onAction(self.action(1))
        self.assertEqual(FakeAddon.store['milionar_autoplay_speed'], '4')
        panel.onClick(gs.CONTROLS['autoplay'])
        self.assertEqual(panel.getProperty('WCS.Game.Set.autoplay_speed.Show'), '')

    def test_back_closes_the_screen_and_focuses_the_button_not_the_game(self):
        panel = self.open()
        panel.onAction(self.action(92))
        self.assertEqual(panel.getProperty('WCS.Game.Settings'), '')
        self.assertEqual(panel.focus, 9604)
        self.assertEqual(self.screen(panel), 'title')
        self.assertFalse(panel.closed)

    def test_the_arrows_do_nothing_outside_the_settings(self):
        panel = self.panel('matrix')
        self.until(panel, 'title')
        panel.focus = 9601
        panel.onAction(self.action(2))
        self.assertEqual(FakeAddon.store, {})

    def test_what_is_chosen_is_what_the_game_starts_with(self):
        panel = self.open()
        panel.focus = gs.CONTROLS['difficulty']
        panel.onAction(self.action(2))
        panel.onAction(self.action(92))
        panel.onClick(9601)
        self.assertEqual(FakeAddon.store['millionaire_difficulty'], '2')
        from wcs.games import millionaire_prompts
        self.assertIn('TĚŽKÁ', millionaire_prompts._get_global_difficulty())


if __name__ == '__main__':
    unittest.main()
