# Released under the MIT License. See LICENSE for details.
#
"""Provides a window for browsing and launching game playlists."""

from __future__ import annotations

import logging
import copy
import math

import bascenev1 as bs
import bauiv1 as bui


class PlaylistBrowserWindow(bui.Window):
    """Window for starting teams games."""

    def __init__(
        self,
        sessiontype: type[bs.Session],
        transition: str | None = 'in_right',
        origin_widget: bui.Widget | None = None,
    ):
        # pylint: disable=too-many-statements
        # pylint: disable=cyclic-import
        from bauiv1lib.playlist import PlaylistTypeVars

        # If they provided an origin-widget, scale up from that.
        scale_origin: tuple[float, float] | None
        if origin_widget is not None:
            self._transition_out = 'out_scale'
            scale_origin = origin_widget.get_screen_space_center()
            transition = 'in_scale'
        else:
            self._transition_out = 'out_right'
            scale_origin = None

        assert bui.app.classic is not None

        # Store state for when we exit the next game.
        if issubclass(sessiontype, bs.DualTeamSession):
            bui.app.ui_v1.set_main_menu_location('Team Game Select')
            bui.set_analytics_screen('Teams Window')
        elif issubclass(sessiontype, bs.FreeForAllSession):
            bui.app.ui_v1.set_main_menu_location('Free-for-All Game Select')
            bui.set_analytics_screen('FreeForAll Window')
        else:
            raise TypeError(f'Invalid sessiontype: {sessiontype}.')
        self._pvars = PlaylistTypeVars(sessiontype)

        self._sessiontype = sessiontype

        self._customize_button: bui.Widget | None = None
        self._sub_width: float | None = None
        self._sub_height: float | None = None

        self._ensure_standard_playlists_exist()

        # Get the current selection (if any).
        self._selected_playlist = bui.app.config.get(
            self._pvars.config_name + ' Playlist Selection'
        )

        uiscale = bui.app.ui_v1.uiscale
        self._width = 1100.0 if uiscale is bui.UIScale.SMALL else 800.0
        x_inset = 150 if uiscale is bui.UIScale.SMALL else 0
        self._height = (
            480
            if uiscale is bui.UIScale.SMALL
            else 510 if uiscale is bui.UIScale.MEDIUM else 580
        )

        top_extra = 20 if uiscale is bui.UIScale.SMALL else 0

        super().__init__(
            root_widget=bui.containerwidget(
                size=(self._width, self._height + top_extra),
                transition=transition,
                toolbar_visibility='menu_full',
                scale_origin_stack_offset=scale_origin,
                scale=(
                    1.69
                    if uiscale is bui.UIScale.SMALL
                    else 1.05 if uiscale is bui.UIScale.MEDIUM else 0.9
                ),
                stack_offset=(
                    (0, -26) if uiscale is bui.UIScale.SMALL else (0, 0)
                ),
            )
        )

        self._back_button: bui.Widget | None = bui.buttonwidget(
            parent=self._root_widget,
            position=(59 + x_inset, self._height - 70),
            size=(120, 60),
            scale=1.0,
            on_activate_call=self._on_back_press,
            autoselect=True,
            label=bui.Lstr(resource='backText'),
            button_type='back',
        )
        bui.containerwidget(
            edit=self._root_widget, cancel_button=self._back_button
        )
        txt = self._title_text = bui.textwidget(
            parent=self._root_widget,
            position=(self._width * 0.5, self._height - 41),
            size=(0, 0),
            text=self._pvars.window_title_name,
            scale=1.3,
            res_scale=1.5,
            color=bui.app.ui_v1.heading_color,
            h_align='center',
            v_align='center',
        )
        if uiscale is bui.UIScale.SMALL and bui.app.ui_v1.use_toolbars:
            bui.textwidget(edit=txt, text='')

        bui.buttonwidget(
            edit=self._back_button,
            button_type='backSmall',
            size=(60, 54),
            position=(59 + x_inset, self._height - 67),
            label=bui.charstr(bui.SpecialChar.BACK),
        )

        if uiscale is bui.UIScale.SMALL and bui.app.ui_v1.use_toolbars:
            self._back_button.delete()
            self._back_button = None
            bui.containerwidget(
                edit=self._root_widget, on_cancel_call=self._on_back_press
            )
            scroll_offs = 33
        else:
            scroll_offs = 0
        self._scroll_width = self._width - (100 + 2 * x_inset)
        self._scroll_height = self._height - (
            146
            if uiscale is bui.UIScale.SMALL and bui.app.ui_v1.use_toolbars
            else 136
        )
        self._scrollwidget = bui.scrollwidget(
            parent=self._root_widget,
            highlight=False,
            size=(self._scroll_width, self._scroll_height),
            position=(
                (self._width - self._scroll_width) * 0.5,
                65 + scroll_offs,
            ),
        )
        bui.containerwidget(edit=self._scrollwidget, claims_left_right=True)
        self._subcontainer: bui.Widget | None = None
        self._config_name_full = self._pvars.config_name + ' Playlists'
        self._last_config = None

        # Update now and once per second.
        # (this should do our initial refresh)
        self._update()
        self._update_timer = bui.AppTimer(
            1.0, bui.WeakCall(self._update), repeat=True
        )

    def _ensure_standard_playlists_exist(self) -> None:
        plus = bui.app.plus
        assert plus is not None

        # On new installations, go ahead and create a few playlists
        # besides the hard-coded default one:
        if not plus.get_v1_account_misc_val('madeStandardPlaylists', False):
            plus.add_v1_account_transaction(
                {
                    'type': 'ADD_PLAYLIST',
                    'playlistType': 'Free-for-All',
                    'playlistName': bui.Lstr(
                        resource='singleGamePlaylistNameText'
                    )
                    .evaluate()
                    .replace(
                        '${GAME}',
                        bui.Lstr(
                            translate=('gameNames', 'Death Match')
                        ).evaluate(),
                    ),
                    'playlist': [
                        {
                            'type': 'bs_death_match.DeathMatchGame',
                            'settings': {
                                'Epic Mode': False,
                                'Kills to Win Per Player': 10,
                                'Respawn Times': 1.0,
                                'Time Limit': 300,
                                'map': 'Doom Shroom',
                            },
                        },
                        {
                            'type': 'bs_death_match.DeathMatchGame',
                            'settings': {
                                'Epic Mode': False,
                                'Kills to Win Per Player': 10,
                                'Respawn Times': 1.0,
                                'Time Limit': 300,
                                'map': 'Crag Castle',
                            },
                        },
                    ],
                }
            )
            plus.add_v1_account_transaction(
                {
                    'type': 'ADD_PLAYLIST',
                    'playlistType': 'Team Tournament',
                    'playlistName': bui.Lstr(
                        resource='singleGamePlaylistNameText'
                    )
                    .evaluate()
                    .replace(
                        '${GAME}',
                        bui.Lstr(
                            translate=('gameNames', 'Capture the Flag')
                        ).evaluate(),
                    ),
                    'playlist': [
                        {
                            'type': 'bs_capture_the_flag.CTFGame',
                            'settings': {
                                'map': 'Bridgit',
                                'Score to Win': 3,
                                'Flag Idle Return Time': 30,
                                'Flag Touch Return Time': 0,
                                'Respawn Times': 1.0,
                                'Time Limit': 600,
                                'Epic Mode': False,
                            },
                        },
                        {
                            'type': 'bs_capture_the_flag.CTFGame',
                            'settings': {
                                'map': 'Roundabout',
                                'Score to Win': 2,
                                'Flag Idle Return Time': 30,
                                'Flag Touch Return Time': 0,
                                'Respawn Times': 1.0,
                                'Time Limit': 600,
                                'Epic Mode': False,
                            },
                        },
                        {
                            'type': 'bs_capture_the_flag.CTFGame',
                            'settings': {
                                'map': 'Tip Top',
                                'Score to Win': 2,
                                'Flag Idle Return Time': 30,
                                'Flag Touch Return Time': 3,
                                'Respawn Times': 1.0,
                                'Time Limit': 300,
                                'Epic Mode': False,
                            },
                        },
                    ],
                }
            )
            plus.add_v1_account_transaction(
                {
                    'type': 'ADD_PLAYLIST',
                    'playlistType': 'Team Tournament',
                    'playlistName': bui.Lstr(
                        translate=('playlistNames', 'Just Sports')
                    ).evaluate(),
                    'playlist': [
                        {
                            'type': 'bs_hockey.HockeyGame',
                            'settings': {
                                'Time Limit': 0,
                                'map': 'Hockey Stadium',
                                'Score to Win': 1,
                                'Respawn Times': 1.0,
                            },
                        },
                        {
                            'type': 'bs_football.FootballTeamGame',
                            'settings': {
                                'Time Limit': 0,
                                'map': 'Football Stadium',
                                'Score to Win': 21,
                                'Respawn Times': 1.0,
                            },
                        },
                    ],
                }
            )
            plus.add_v1_account_transaction(
                {
                    'type': 'ADD_PLAYLIST',
                    'playlistType': 'Free-for-All',
                    'playlistName': bui.Lstr(
                        translate=('playlistNames', 'Just Epic')
                    ).evaluate(),
                    'playlist': [
                        {
                            'type': 'bs_elimination.EliminationGame',
                            'settings': {
                                'Time Limit': 120,
                                'map': 'Tip Top',
                                'Respawn Times': 1.0,
                                'Lives Per Player': 1,
                                'Epic Mode': 1,
                            },
                        }
                    ],
                }
            )
            plus.add_v1_account_transaction(
                {
                    'type': 'SET_MISC_VAL',
                    'name': 'madeStandardPlaylists',
                    'value': True,
                }
            )
            plus.run_v1_account_transactions()

    def _refresh(self) -> None:
        # FIXME: Should tidy this up.
        # pylint: disable=too-many-statements
        # pylint: disable=too-many-branches
        # pylint: disable=too-many-locals
        # pylint: disable=too-many-nested-blocks
        from efro.util import asserttype
        from bascenev1 import get_map_class, filter_playlist

        if not self._root_widget:
            return
        if self._subcontainer is not None:
            self._save_state()
            self._subcontainer.delete()

        # Make sure config exists.
        if self._config_name_full not in bui.app.config:
            bui.app.config[self._config_name_full] = {}

        items = list(bui.app.config[self._config_name_full].items())

        # Make sure everything is unicode.
        items = [
            (i[0].decode(), i[1]) if not isinstance(i[0], str) else i
            for i in items
        ]

        items.sort(key=lambda x2: asserttype(x2[0], str).lower())
        items = [['__default__', None]] + items  # default is always first

        count = len(items)
        columns = 3
        rows = int(math.ceil(float(count) / columns))
        button_width = 230
        button_height = 230
        button_buffer_h = -3
        button_buffer_v = 0

        self._sub_width = self._scroll_width
        self._sub_height = (
            40.0 + rows * (button_height + 2 * button_buffer_v) + 90
        )
        assert self._sub_width is not None
        assert self._sub_height is not None
        self._subcontainer = bui.containerwidget(
            parent=self._scrollwidget,
            size=(self._sub_width, self._sub_height),
            background=False,
        )

        children = self._subcontainer.get_children()
        for child in children:
            child.delete()

        assert bui.app.classic is not None
        bui.textwidget(
            parent=self._subcontainer,
            text=bui.Lstr(resource='playlistsText'),
            position=(40, self._sub_height - 26),
            size=(0, 0),
            scale=1.0,
            maxwidth=400,
            color=bui.app.ui_v1.title_color,
            h_align='left',
            v_align='center',
        )

        index = 0
        appconfig = bui.app.config

        mesh_opaque = bui.getmesh('level_select_button_opaque')
        mesh_transparent = bui.getmesh('level_select_button_transparent')
        mask_tex = bui.gettexture('mapPreviewMask')

        h_offs = 225 if count == 1 else 115 if count == 2 else 0
        h_offs_bottom = 0

        uiscale = bui.app.ui_v1.uiscale
        for y in range(rows):
            for x in range(columns):
                name = items[index][0]
                assert name is not None
                pos = (
                    x * (button_width + 2 * button_buffer_h)
                    + button_buffer_h
                    + 8
                    + h_offs,
                    self._sub_height
                    - 47
                    - (y + 1) * (button_height + 2 * button_buffer_v),
                )
                btn = bui.buttonwidget(
                    parent=self._subcontainer,
                    button_type='square',
                    size=(button_width, button_height),
                    autoselect=True,
                    label='',
                    position=pos,
                )

                if (
                    x == 0
                    and bui.app.ui_v1.use_toolbars
                    and uiscale is bui.UIScale.SMALL
                ):
                    bui.widget(
                        edit=btn,
                        left_widget=bui.get_special_widget('back_button'),
                    )
                if (
                    x == columns - 1
                    and bui.app.ui_v1.use_toolbars
                    and uiscale is bui.UIScale.SMALL
                ):
                    bui.widget(
                        edit=btn,
                        right_widget=bui.get_special_widget('party_button'),
                    )
                bui.buttonwidget(
                    edit=btn,
                    on_activate_call=bui.Call(
                        self._on_playlist_press, btn, name
                    ),
                    on_select_call=bui.Call(self._on_playlist_select, name),
                )
                bui.widget(edit=btn, show_buffer_top=50, show_buffer_bottom=50)

                if self._selected_playlist == name:
                    bui.containerwidget(
                        edit=self._subcontainer,
                        selected_child=btn,
                        visible_child=btn,
                    )

                if self._back_button is not None:
                    if y == 0:
                        bui.widget(edit=btn, up_widget=self._back_button)
                    if x == 0:
                        bui.widget(edit=btn, left_widget=self._back_button)

                print_name: str | bui.Lstr | None
                if name == '__default__':
                    print_name = self._pvars.default_list_name
                else:
                    print_name = name
                bui.textwidget(
                    parent=self._subcontainer,
                    text=print_name,
                    position=(
                        pos[0] + button_width * 0.5,
                        pos[1] + button_height * 0.79,
                    ),
                    size=(0, 0),
                    scale=button_width * 0.003,
                    maxwidth=button_width * 0.7,
                    draw_controller=btn,
                    h_align='center',
                    v_align='center',
                )

                # Poke into this playlist and see if we can display some of
                # its maps.
                map_images = []
                try:
                    map_textures = []
                    map_texture_entries = []
                    if name == '__default__':
                        playlist = self._pvars.get_default_list_call()
                    else:
                        if (
                            name
                            not in appconfig[
                                self._pvars.config_name + ' Playlists'
                            ]
                        ):
                            print(
                                'NOT FOUND ERR',
                                appconfig[
                                    self._pvars.config_name + ' Playlists'
                                ],
                            )
                        playlist = appconfig[
                            self._pvars.config_name + ' Playlists'
                        ][name]
                    playlist = filter_playlist(
                        playlist,
                        self._sessiontype,
                        remove_unowned=False,
                        mark_unowned=True,
                        name=name,
                    )
                    for entry in playlist:
                        mapname = entry['settings']['map']
                        maptype: type[bs.Map] | None
                        try:
                            maptype = get_map_class(mapname)
                        except bui.NotFoundError:
                            maptype = None
                        if maptype is not None:
                            tex_name = maptype.get_preview_texture_name()
                            if tex_name is not None:
                                map_textures.append(tex_name)
                                map_texture_entries.append(entry)
                        if len(map_textures) >= 6:
                            break

                    if len(map_textures) > 4:
                        img_rows = 3
                        img_columns = 2
                        scl = 0.33
                        h_offs_img = 30
                        v_offs_img = 126
                    elif len(map_textures) > 2:
                        img_rows = 2
                        img_columns = 2
                        scl = 0.35
                        h_offs_img = 24
                        v_offs_img = 110
                    elif len(map_textures) > 1:
                        img_rows = 2
                        img_columns = 1
                        scl = 0.5
                        h_offs_img = 47
                        v_offs_img = 105
                    else:
                        img_rows = 1
                        img_columns = 1
                        scl = 0.75
                        h_offs_img = 20
                        v_offs_img = 65

                    v = None
                    for row in range(img_rows):
                        for col in range(img_columns):
                            tex_index = row * img_columns + col
                            if tex_index < len(map_textures):
                                entry = map_texture_entries[tex_index]

                                owned = not (
                                    (
                                        'is_unowned_map' in entry
                                        and entry['is_unowned_map']
                                    )
                                    or (
                                        'is_unowned_game' in entry
                                        and entry['is_unowned_game']
                                    )
                                )

                                tex_name = map_textures[tex_index]
                                h = pos[0] + h_offs_img + scl * 250 * col
                                v = pos[1] + v_offs_img - scl * 130 * row
                                map_images.append(
                                    bui.imagewidget(
                                        parent=self._subcontainer,
                                        size=(scl * 250.0, scl * 125.0),
                                        position=(h, v),
                                        texture=bui.gettexture(tex_name),
                                        opacity=1.0 if owned else 0.25,
                                        draw_controller=btn,
                                        mesh_opaque=mesh_opaque,
                                        mesh_transparent=mesh_transparent,
                                        mask_texture=mask_tex,
                                    )
                                )
                                if not owned:
                                    bui.imagewidget(
                                        parent=self._subcontainer,
                                        size=(scl * 100.0, scl * 100.0),
                                        position=(h + scl * 75, v + scl * 10),
                                        texture=bui.gettexture('lock'),
                                        draw_controller=btn,
                                    )
                        if v is not None:
                            v -= scl * 130.0

                except Exception:
                    logging.exception('Error listing playlist maps.')

                if not map_images:
                    bui.textwidget(
                        parent=self._subcontainer,
                        text='???',
                        scale=1.5,
                        size=(0, 0),
                        color=(1, 1, 1, 0.5),
                        h_align='center',
                        v_align='center',
                        draw_controller=btn,
                        position=(
                            pos[0] + button_width * 0.5,
                            pos[1] + button_height * 0.5,
                        ),
                    )

                index += 1

                if index >= count:
                    break
            if index >= count:
                break
        self._customize_button = btn = bui.buttonwidget(
            parent=self._subcontainer,
            size=(100, 30),
            position=(34 + h_offs_bottom, 50),
            text_scale=0.6,
            label=bui.Lstr(resource='customizeText'),
            on_activate_call=self._on_customize_press,
            color=(0.54, 0.52, 0.67),
            textcolor=(0.7, 0.65, 0.7),
            autoselect=True,
        )
        bui.widget(edit=btn, show_buffer_top=22, show_buffer_bottom=28)
        self._restore_state()

    def on_play_options_window_run_game(self) -> None:
        """(internal)"""
        if not self._root_widget:
            return
        bui.containerwidget(edit=self._root_widget, transition='out_left')

    def _on_playlist_select(self, playlist_name: str) -> None:
        self._selected_playlist = playlist_name

    def _update(self) -> None:
        # make sure config exists
        if self._config_name_full not in bui.app.config:
            bui.app.config[self._config_name_full] = {}

        cfg = bui.app.config[self._config_name_full]
        if cfg != self._last_config:
            self._last_config = copy.deepcopy(cfg)
            self._refresh()

    def _on_playlist_press(
        self, button: bui.Widget, playlist_name: str
    ) -> None:
        # pylint: disable=cyclic-import
        from bauiv1lib.playoptions import PlayOptionsWindow

        # Make sure the target playlist still exists.
        exists = (
            playlist_name == '__default__'
            or playlist_name in bui.app.config.get(self._config_name_full, {})
        )
        if not exists:
            return

        self._save_state()
        PlayOptionsWindow(
            sessiontype=self._sessiontype,
            scale_origin=button.get_screen_space_center(),
            playlist=playlist_name,
            delegate=self,
        )

    def _on_customize_press(self) -> None:
        # pylint: disable=cyclic-import
        from bauiv1lib.playlist.customizebrowser import (
            PlaylistCustomizeBrowserWindow,
        )

        # no-op if our underlying widget is dead or on its way out.
        if not self._root_widget or self._root_widget.transitioning_out:
            return

        self._save_state()
        bui.containerwidget(edit=self._root_widget, transition='out_left')
        assert bui.app.classic is not None
        bui.app.ui_v1.set_main_menu_window(
            PlaylistCustomizeBrowserWindow(
                origin_widget=self._customize_button,
                sessiontype=self._sessiontype,
            ).get_root_widget(),
            from_window=self._root_widget,
        )

    def _on_back_press(self) -> None:
        # pylint: disable=cyclic-import
        from bauiv1lib.play import PlayWindow

        # no-op if our underlying widget is dead or on its way out.
        if not self._root_widget or self._root_widget.transitioning_out:
            return

        # Store our selected playlist if that's changed.
        if self._selected_playlist is not None:
            prev_sel = bui.app.config.get(
                self._pvars.config_name + ' Playlist Selection'
            )
            if self._selected_playlist != prev_sel:
                cfg = bui.app.config
                cfg[self._pvars.config_name + ' Playlist Selection'] = (
                    self._selected_playlist
                )
                cfg.commit()

        self._save_state()
        bui.containerwidget(
            edit=self._root_widget, transition=self._transition_out
        )
        assert bui.app.classic is not None
        bui.app.ui_v1.set_main_menu_window(
            PlayWindow(transition='in_left').get_root_widget(),
            from_window=self._root_widget,
        )

    def _save_state(self) -> None:
        try:
            sel = self._root_widget.get_selected_child()
            if sel == self._back_button:
                sel_name = 'Back'
            elif sel == self._scrollwidget:
                assert self._subcontainer is not None
                subsel = self._subcontainer.get_selected_child()
                if subsel == self._customize_button:
                    sel_name = 'Customize'
                else:
                    sel_name = 'Scroll'
            else:
                raise RuntimeError('Unrecognized selected widget.')
            assert bui.app.classic is not None
            bui.app.ui_v1.window_states[type(self)] = sel_name
        except Exception:
            logging.exception('Error saving state for %s.', self)

    def _restore_state(self) -> None:
        try:
            assert bui.app.classic is not None
            sel_name = bui.app.ui_v1.window_states.get(type(self))
            if sel_name == 'Back':
                sel = self._back_button
            elif sel_name == 'Scroll':
                sel = self._scrollwidget
            elif sel_name == 'Customize':
                sel = self._scrollwidget
                bui.containerwidget(
                    edit=self._subcontainer,
                    selected_child=self._customize_button,
                    visible_child=self._customize_button,
                )
            else:
                sel = self._scrollwidget
            bui.containerwidget(edit=self._root_widget, selected_child=sel)
        except Exception:
            logging.exception('Error restoring state for %s.', self)
