# Released under the MIT License. See LICENSE for details.
#
"""Provides a window for configuring play options."""

import logging
from typing import TYPE_CHECKING, override

from bacommon.analytics import ClassicAnalyticsEvent
import bascenev1 as bs
import bauiv1 as bui
from bauiv1 import builtinassets
from bauiv1 import _commonassets, classicassets

from bauiv1lib.popup import PopupWindow

if TYPE_CHECKING:
    from typing import Any

    from bauiv1lib.play import PlaylistSelectContext


class PlayOptionsWindow(PopupWindow):
    """A popup window for configuring play options."""

    def __init__(
        self,
        *,
        sessiontype: type[bs.Session],
        playlist: str,
        scale_origin: tuple[float, float],
        delegate: Any = None,
        playlist_select_context: PlaylistSelectContext | None = None,
    ):
        # pylint: disable=too-many-statements
        # FIXME: Tidy this up.
        # pylint: disable=too-many-branches
        # pylint: disable=too-many-locals
        from bascenev1 import filter_playlist, get_map_class
        from bauiv1lib.playlist import PlaylistTypeVars
        from bauiv1lib.config import ConfigNumberEdit

        ui = bui.app.ui_v1

        self._r = 'gameListWindow'
        self._delegate = delegate
        self._pvars = PlaylistTypeVars(sessiontype)
        self._transitioning_out = False

        self._playlist_select_context = playlist_select_context

        self._do_randomize_val = bui.app.config.get(
            self._pvars.config_name + ' Playlist Randomize', 0
        )

        self._sessiontype = sessiontype
        self._playlist = playlist

        self._width = 500.0
        self._height = 370.0 - 50.0

        # In teams games, show the custom names/colors button.
        if self._sessiontype is bs.DualTeamSession:
            self._height += 50.0

        self._row_height = 45.0

        # Grab our maps to display.
        mesh_opaque = classicassets.meshes.level_select_button_opaque.get()
        mesh_transparent = (
            classicassets.meshes.level_select_button_transparent.get()
        )
        mask_tex = classicassets.textures.map_preview_mask.get()

        # Poke into this playlist and see if we can display some of its
        # maps.
        map_textures = []
        map_texture_entries = []
        rows = 0
        columns = 0
        game_count = 0
        scl = 0.35
        c_width_total = 0.0
        try:
            max_columns = 5
            name = playlist
            if name == '__default__':
                plst = self._pvars.get_default_list_call()
            else:
                try:
                    plst = bui.app.config[
                        self._pvars.config_name + ' Playlists'
                    ][name]
                except Exception:
                    print(
                        'ERROR INFO: self._config_name is:',
                        self._pvars.config_name,
                    )
                    print(
                        'ERROR INFO: playlist names are:',
                        list(
                            bui.app.config[
                                self._pvars.config_name + ' Playlists'
                            ].keys()
                        ),
                    )
                    raise
            plst = filter_playlist(
                plst,
                self._sessiontype,
                remove_unowned=False,
                mark_unowned=True,
                name=name,
            )
            game_count = len(plst)
            for entry in plst:
                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:
                    map_tex = maptype.get_preview_texture()
                    if map_tex is not None:
                        map_textures.append(map_tex)
                        map_texture_entries.append(entry)
            rows = (max(0, len(map_textures) - 1) // max_columns) + 1
            columns = min(max_columns, len(map_textures))

            if len(map_textures) == 1:
                scl = 1.1
            elif len(map_textures) == 2:
                scl = 0.7
            elif len(map_textures) == 3:
                scl = 0.55
            else:
                scl = 0.35
            self._row_height = 128.0 * scl
            c_width_total = scl * 250.0 * columns
            if map_textures:
                self._height += self._row_height * rows

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

        show_shuffle_check_box = game_count > 1

        if show_shuffle_check_box:
            self._height += 40

        uiscale = ui.uiscale
        scale = (
            1.69
            if uiscale is bui.UIScale.SMALL
            else 1.1 if uiscale is bui.UIScale.MEDIUM else 0.85
        )
        # Creates our _root_widget.
        super().__init__(
            position=scale_origin, size=(self._width, self._height), scale=scale
        )

        playlist_name: str | bui.Lstr | bui.LangStr = (
            self._pvars.default_list_name
            if playlist == '__default__'
            else playlist
        )
        self._title_text = bui.textwidget(
            parent=self.root_widget,
            position=(self._width * 0.5, self._height - 89 + 51),
            size=(0, 0),
            text=playlist_name,
            scale=1.4,
            color=(1, 1, 1),
            maxwidth=self._width * 0.7,
            h_align='center',
            v_align='center',
        )

        self._cancel_button = bui.buttonwidget(
            parent=self.root_widget,
            position=(25, self._height - 53),
            size=(50, 50),
            scale=0.7,
            label=bui.charstr(bui.SpecialChar.CLOSE),
            textcolor=(1, 1, 1),
            color=(0.42, 0.73, 0.2),
            on_activate_call=self._on_cancel_press,
            autoselect=True,
        )
        bui.widget(edit=self._cancel_button, allow_preserve_selection=False)

        h_offs_img = self._width * 0.5 - c_width_total * 0.5
        v_offs_img = self._height - 118 - scl * 125.0 + 50
        bottom_row_buttons = []
        self._have_at_least_one_owned = False

        for row in range(rows):
            for col in range(columns):
                tex_index = row * columns + col
                if tex_index < len(map_textures):
                    map_tex = map_textures[tex_index]
                    h = h_offs_img + scl * 250 * col
                    v = v_offs_img - self._row_height * row
                    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']
                        )
                    )

                    if owned:
                        self._have_at_least_one_owned = True

                    try:
                        # The settings-display twin and the unlock
                        # composite below are both still legacy, so keep
                        # an Lstr-typed local for the composite's sub and
                        # let `desc` hold either flavor for display.
                        descbase = bui.getclass(
                            entry['type'], subclassof=bs.GameActivity
                        ).get_settings_display_string(entry)
                        desc: bui.Lstr | bui.LangStr = descbase
                        if not owned:
                            desc = bui.Lstr(
                                value='${DESC}\n${UNLOCK}',
                                subs=[
                                    ('${DESC}', descbase),
                                    (
                                        '${UNLOCK}',
                                        bui.Lstr(
                                            resource='unlockThisInTheStoreText'
                                        ),
                                    ),
                                ],
                            )
                        desc_color = (0, 1, 0) if owned else (1, 0, 0)
                    except Exception:
                        desc = _commonassets.strings.status.invalid
                        desc_color = (1, 0, 0)

                    btn = bui.buttonwidget(
                        parent=self.root_widget,
                        size=(scl * 240.0, scl * 120.0),
                        position=(h, v),
                        texture=(
                            map_tex
                            if owned
                            else classicassets.textures.empty.get()
                        ),
                        mesh_opaque=mesh_opaque if owned else None,
                        on_activate_call=bui.CallStrict(
                            bui.screenmessage, desc, desc_color
                        ),
                        label='',
                        color=(1, 1, 1),
                        autoselect=True,
                        extra_touch_border_scale=0.0,
                        mesh_transparent=mesh_transparent if owned else None,
                        mask_texture=mask_tex if owned else None,
                    )
                    bui.widget(edit=btn, allow_preserve_selection=False)

                    if row == 0 and col == 0:
                        bui.widget(edit=self._cancel_button, down_widget=btn)
                    if row == rows - 1:
                        bottom_row_buttons.append(btn)
                    if not owned:
                        # Ewww; buttons don't currently have alpha so in this
                        # case we draw an image over our button with an empty
                        # texture on it.
                        bui.imagewidget(
                            parent=self.root_widget,
                            size=(scl * 260.0, scl * 130.0),
                            position=(h - 10.0 * scl, v - 4.0 * scl),
                            draw_controller=btn,
                            color=(1, 1, 1),
                            texture=map_tex,
                            mesh_opaque=mesh_opaque,
                            opacity=0.25,
                            mesh_transparent=mesh_transparent,
                            mask_texture=mask_tex,
                        )

                        bui.imagewidget(
                            parent=self.root_widget,
                            size=(scl * 100, scl * 100),
                            draw_controller=btn,
                            position=(h + scl * 70, v + scl * 10),
                            texture=classicassets.textures.lock.get(),
                        )

        y_offs = 50 if show_shuffle_check_box else 0

        # Series Length
        y_offs2 = 40 if self._sessiontype is bs.DualTeamSession else 0
        self._series_length_numedit = ConfigNumberEdit(
            parent=self.root_widget,
            position=(100, 200 + y_offs + y_offs2),
            configkey=(
                'FFA' if self._sessiontype is bs.FreeForAllSession else 'Teams'
            )
            + ' Series Length',
            displayname=(
                classicassets.strings.play_options.points_to_win
                if self._sessiontype is bs.FreeForAllSession
                else classicassets.strings.play_options.series_length
            ),
            minval=1.0,
            maxval=100.0 if self._sessiontype is bs.FreeForAllSession else 99.0,
            increment=1.0 if self._sessiontype is bs.FreeForAllSession else 2.0,
            fallback_value=(
                24 if self._sessiontype is bs.FreeForAllSession else 7
            ),
            f=0,
        )

        # Team names/colors.
        self._custom_colors_names_button: bui.Widget | None
        if self._sessiontype is bs.DualTeamSession:
            self._custom_colors_names_button = bui.buttonwidget(
                parent=self.root_widget,
                position=(100, 195 + y_offs),
                size=(290, 35),
                on_activate_call=bui.WeakCallStrict(
                    self._custom_colors_names_press
                ),
                autoselect=True,
                textcolor=(0.8, 0.8, 0.8),
                label=classicassets.strings.play_options.team_names_colors,
            )
            bui.widget(
                edit=self._custom_colors_names_button,
                allow_preserve_selection=False,
            )
        else:
            self._custom_colors_names_button = None

        # Shuffle.
        def _cb_callback(val: bool) -> None:
            self._do_randomize_val = val
            cfg = bui.app.config
            cfg[self._pvars.config_name + ' Playlist Randomize'] = (
                self._do_randomize_val
            )
            cfg.commit()

        if show_shuffle_check_box:
            self._shuffle_check_box = bui.checkboxwidget(
                parent=self.root_widget,
                position=(110, 200),
                scale=1.0,
                size=(250, 30),
                autoselect=True,
                text=classicassets.strings.play_options.shuffle_game_order,
                maxwidth=300,
                textcolor=(0.8, 0.8, 0.8),
                value=self._do_randomize_val,
                on_value_change_call=_cb_callback,
            )

        # Show tutorial.
        show_tutorial = bool(bui.app.config.get('Show Tutorial', True))

        def _cb_callback_2(val: bool) -> None:
            cfg = bui.app.config
            cfg['Show Tutorial'] = val
            cfg.commit()

        self._show_tutorial_check_box = bui.checkboxwidget(
            parent=self.root_widget,
            position=(110, 151),
            scale=1.0,
            size=(250, 30),
            autoselect=True,
            text=classicassets.strings.play_options.show_tutorial,
            maxwidth=300,
            textcolor=(0.8, 0.8, 0.8),
            value=show_tutorial,
            on_value_change_call=_cb_callback_2,
        )

        # Grumble: current autoselect doesn't do a very good job
        # with checkboxes.
        if self._custom_colors_names_button is not None:
            for btn in bottom_row_buttons:
                bui.widget(
                    edit=btn, down_widget=self._custom_colors_names_button
                )
            if show_shuffle_check_box:
                bui.widget(
                    edit=self._custom_colors_names_button,
                    down_widget=self._shuffle_check_box,
                )
                bui.widget(
                    edit=self._shuffle_check_box,
                    up_widget=self._custom_colors_names_button,
                )
            else:
                bui.widget(
                    edit=self._custom_colors_names_button,
                    down_widget=self._show_tutorial_check_box,
                )
                bui.widget(
                    edit=self._show_tutorial_check_box,
                    up_widget=self._custom_colors_names_button,
                )

        self._ok_button = bui.buttonwidget(
            parent=self.root_widget,
            position=(70, 44),
            size=(200, 45),
            scale=1.8,
            text_res_scale=1.5,
            on_activate_call=self._on_ok_press,
            autoselect=True,
            label=(
                _commonassets.strings.actions.ok
                if self._playlist_select_context is not None
                else classicassets.strings.ui.play
            ),
        )
        bui.widget(edit=self._ok_button, allow_preserve_selection=False)

        bui.widget(
            edit=self._ok_button, up_widget=self._show_tutorial_check_box
        )

        bui.containerwidget(
            edit=self.root_widget,
            start_button=self._ok_button,
            cancel_button=self._cancel_button,
            selected_child=self._ok_button,
        )

        # Update now and once per second.
        self._update_timer = bui.AppTimer(
            1.0, bui.WeakCallStrict(self._update), repeat=True
        )
        self._update()

    def _custom_colors_names_press(self) -> None:
        from bauiv1lib.teamnamescolors import TeamNamesColorsWindow

        plus = bui.app.plus
        assert plus is not None

        assert bui.app.classic is not None
        assert self._custom_colors_names_button
        TeamNamesColorsWindow(
            scale_origin=(
                self._custom_colors_names_button.get_screen_space_center()
            )
        )

    def _does_target_playlist_exist(self) -> bool:
        if self._playlist == '__default__':
            return True
        return self._playlist in bui.app.config.get(
            self._pvars.config_name + ' Playlists', {}
        )

    def _update(self) -> None:
        # All we do here is make sure our targeted playlist still exists,
        # and close ourself if not.
        if not self._does_target_playlist_exist():
            self._transition_out()

    def _transition_out(self, transition: str = 'out_scale') -> None:
        if not self._transitioning_out:
            self._transitioning_out = True
            bui.containerwidget(edit=self.root_widget, transition=transition)

    @override
    def on_popup_cancel(self) -> None:
        builtinassets.audio.swish.get().play()
        self._transition_out()

    def _on_cancel_press(self) -> None:
        self._transition_out()

    def _on_ok_press(self) -> None:
        # 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

        # Disallow if our playlist has disappeared.
        if not self._does_target_playlist_exist():
            return

        # Disallow if we have no unlocked games.
        if not self._have_at_least_one_owned:
            builtinassets.audio.error.get().play()
            bui.screenmessage(
                classicassets.strings.play_options.no_valid_games,
                color=(1, 0, 0),
            )
            return

        cfg = bui.app.config
        cfg[self._pvars.config_name + ' Playlist Selection'] = self._playlist

        # Head back to the gather window in playlist-select mode or
        # start the game in regular mode.
        if self._playlist_select_context is not None:
            if self._sessiontype is bs.FreeForAllSession:
                typename = 'ffa'
            elif self._sessiontype is bs.DualTeamSession:
                typename = 'teams'
            else:
                raise RuntimeError('Only teams and ffa currently supported')
            cfg['Private Party Host Session Type'] = typename
            builtinassets.audio.gun_cocking.get().play()

            self._transition_out(transition='out_left')
            if self._delegate is not None:
                self._delegate.on_play_options_window_run_game()
        else:
            bui.fade_screen(False, endcall=self._run_selected_playlist)
            bui.lock_all_input()
            self._transition_out(transition='out_left')
            if self._delegate is not None:
                self._delegate.on_play_options_window_run_game()

        cfg.commit()

    def _run_selected_playlist(self) -> None:
        bui.unlock_all_input()

        # Save our place in the UI that we'll return to when done.
        if bs.app.classic is not None:
            bs.app.classic.save_ui_state()

        # Log analytics for when teams/ffa sessions are started from the
        # UI.
        if self._sessiontype is bs.FreeForAllSession:
            bui.app.analytics.submit_event(
                ClassicAnalyticsEvent(
                    ClassicAnalyticsEvent.EventType.START_FFA_SESSION
                )
            )
        elif self._sessiontype is bs.DualTeamSession:
            bui.app.analytics.submit_event(
                ClassicAnalyticsEvent(
                    ClassicAnalyticsEvent.EventType.START_TEAMS_SESSION
                )
            )

        try:
            bs.new_host_session(self._sessiontype)
        except Exception:
            from bascenev1lib import mainmenu

            logging.exception('Error running session %s.', self._sessiontype)

            # Drop back into a main menu session.
            bs.new_host_session(mainmenu.MainMenuSession)
