text: # Copyright (c) 2011-2020 Eric Froemling
  #
  # Permission is hereby granted, free of charge, to any person obtaining a copy
  # of this software and associated documentation files (the "Software"), to deal
  # in the Software without restriction, including without limitation the rights
  # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  # copies of the Software, and to permit persons to whom the Software is
  # furnished to do so, subject to the following conditions:
  #
  # The above copyright notice and this permission notice shall be included in
  # all copies or substantial portions of the Software.
  #
  # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  # SOFTWARE.
  # -----------------------------------------------------------------------------
  """Provides UI functionality for watching replays."""
  
  from __future__ import annotations
  
  import os
  from typing import TYPE_CHECKING, cast
  
  import _ba
  import ba
  
  if TYPE_CHECKING:
      from typing import Any, Optional, Tuple, Dict
  
  
  class WatchWindow(ba.Window):
      """Window for watching replays."""
  
      def __init__(self,
                   transition: Optional[str] = 'in_right',
                   origin_widget: ba.Widget = None):
          # pylint: disable=too-many-locals
          # pylint: disable=too-many-statements
          from bastd.ui import tabs
          ba.set_analytics_screen('Watch Window')
          scale_origin: Optional[Tuple[float, float]]
          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
          ba.app.main_window = "Watch"
          self._tab_data: Dict[str, Any] = {}
          self._my_replays_scroll_width: Optional[float] = None
          self._my_replays_watch_replay_button: Optional[ba.Widget] = None
          self._scrollwidget: Optional[ba.Widget] = None
          self._columnwidget: Optional[ba.Widget] = None
          self._my_replay_selected: Optional[str] = None
          self._my_replays_rename_window: Optional[ba.Widget] = None
          self._my_replay_rename_text: Optional[ba.Widget] = None
          self._r = 'watchWindow'
          self._width = 1240 if ba.app.small_ui else 1040
          x_inset = 100 if ba.app.small_ui else 0
          self._height = (578
                          if ba.app.small_ui else 670 if ba.app.med_ui else 800)
          self._current_tab: Optional[str] = None
          extra_top = 20 if ba.app.small_ui else 0
  
          super().__init__(root_widget=ba.containerwidget(
              size=(self._width, self._height + extra_top),
              transition=transition,
              toolbar_visibility='menu_minimal',
              scale_origin_stack_offset=scale_origin,
              scale=(1.3 if ba.app.small_ui else 0.97 if ba.app.med_ui else 0.8),
              stack_offset=(0, -10) if ba.app.small_ui else (
                  0, 15) if ba.app.med_ui else (0, 0)))
  
          if ba.app.small_ui and ba.app.toolbars:
              ba.containerwidget(edit=self._root_widget,
                                 on_cancel_call=self._back)
              self._back_button = None
          else:
              self._back_button = btn = ba.buttonwidget(
                  parent=self._root_widget,
                  autoselect=True,
                  position=(70 + x_inset, self._height - 74),
                  size=(140, 60),
                  scale=1.1,
                  label=ba.Lstr(resource='backText'),
                  button_type='back',
                  on_activate_call=self._back)
              ba.containerwidget(edit=self._root_widget, cancel_button=btn)
              ba.buttonwidget(edit=btn,
                              button_type='backSmall',
                              size=(60, 60),
                              label=ba.charstr(ba.SpecialChar.BACK))
  
          ba.textwidget(parent=self._root_widget,
                        position=(self._width * 0.5, self._height - 38),
                        size=(0, 0),
                        color=ba.app.title_color,
                        scale=1.5,
                        h_align="center",
                        v_align="center",
                        text=ba.Lstr(resource=self._r + '.titleText'),
                        maxwidth=400)
  
          tabs_def = [('my_replays',
                       ba.Lstr(resource=self._r + '.myReplaysText'))]
  
          scroll_buffer_h = 130 + 2 * x_inset
          tab_buffer_h = 750 + 2 * x_inset
  
          self._tab_buttons = tabs.create_tab_buttons(
              self._root_widget,
              tabs_def,
              pos=(tab_buffer_h * 0.5, self._height - 130),
              size=(self._width - tab_buffer_h, 50),
              on_select_call=self._set_tab)
  
          if ba.app.toolbars:
              ba.widget(edit=self._tab_buttons[tabs_def[-1][0]],
                        right_widget=_ba.get_special_widget('party_button'))
              if ba.app.small_ui:
                  bbtn = _ba.get_special_widget('back_button')
                  ba.widget(edit=self._tab_buttons[tabs_def[0][0]],
                            up_widget=bbtn,
                            left_widget=bbtn)
  
          self._scroll_width = self._width - scroll_buffer_h
          self._scroll_height = self._height - 180
  
          # not actually using a scroll widget anymore; just an image
          scroll_left = (self._width - self._scroll_width) * 0.5
          scroll_bottom = self._height - self._scroll_height - 79 - 48
          buffer_h = 10
          buffer_v = 4
          ba.imagewidget(parent=self._root_widget,
                         position=(scroll_left - buffer_h,
                                   scroll_bottom - buffer_v),
                         size=(self._scroll_width + 2 * buffer_h,
                               self._scroll_height + 2 * buffer_v),
                         texture=ba.gettexture('scrollWidget'),
                         model_transparent=ba.getmodel('softEdgeOutside'))
          self._tab_container: Optional[ba.Widget] = None
  
          self._restore_state()
  
      def _set_tab(self, tab: str) -> None:
          # pylint: disable=too-many-locals
          from bastd.ui import tabs
  
          if self._current_tab == tab:
              return
          self._current_tab = tab
  
          # We wanna preserve our current tab between runs.
          cfg = ba.app.config
          cfg['Watch Tab'] = tab
          cfg.commit()
  
          # Update tab colors based on which is selected.
          tabs.update_tab_button_colors(self._tab_buttons, tab)
  
          if self._tab_container:
              self._tab_container.delete()
          scroll_left = (self._width - self._scroll_width) * 0.5
          scroll_bottom = self._height - self._scroll_height - 79 - 48
  
          # A place where tabs can store data to get cleared when
          # switching to a different tab
          self._tab_data = {}
  
          if tab == 'my_replays':
              c_width = self._scroll_width
              c_height = self._scroll_height - 20
              sub_scroll_height = c_height - 63
              self._my_replays_scroll_width = sub_scroll_width = (
                  680 if ba.app.small_ui else 640)
  
              self._tab_container = cnt = ba.containerwidget(
                  parent=self._root_widget,
                  position=(scroll_left, scroll_bottom +
                            (self._scroll_height - c_height) * 0.5),
                  size=(c_width, c_height),
                  background=False,
                  selection_loop_to_parent=True)
  
              v = c_height - 30
              ba.textwidget(parent=cnt,
                            position=(c_width * 0.5, v),
                            color=(0.6, 1.0, 0.6),
                            scale=0.7,
                            size=(0, 0),
                            maxwidth=c_width * 0.9,
                            h_align='center',
                            v_align='center',
                            text=ba.Lstr(
                                resource='replayRenameWarningText',
                                subs=[('${REPLAY}',
                                       ba.Lstr(resource='replayNameDefaultText'))
                                      ]))
  
              b_width = 140 if ba.app.small_ui else 178
              b_height = (107
                          if ba.app.small_ui else 142 if ba.app.med_ui else 190)
              b_space_extra = (0 if ba.app.small_ui else
                               -2 if ba.app.med_ui else -5)
  
              b_color = (0.6, 0.53, 0.63)
              b_textcolor = (0.75, 0.7, 0.8)
              btnv = c_height - (48 if ba.app.small_ui else
                                 45 if ba.app.med_ui else 40) - b_height
              btnh = 40 if ba.app.small_ui else 40
              smlh = 190 if ba.app.small_ui else 225
              tscl = 1.0 if ba.app.small_ui else 1.2
              self._my_replays_watch_replay_button = btn1 = ba.buttonwidget(
                  parent=cnt,
                  size=(b_width, b_height),
                  position=(btnh, btnv),
                  button_type='square',
                  color=b_color,
                  textcolor=b_textcolor,
                  on_activate_call=self._on_my_replay_play_press,
                  text_scale=tscl,
                  label=ba.Lstr(resource=self._r + '.watchReplayButtonText'),
                  autoselect=True)
              ba.widget(edit=btn1, up_widget=self._tab_buttons[tab])
              if ba.app.small_ui and ba.app.toolbars:
                  ba.widget(edit=btn1,
                            left_widget=_ba.get_special_widget('back_button'))
              btnv -= b_height + b_space_extra
              ba.buttonwidget(parent=cnt,
                              size=(b_width, b_height),
                              position=(btnh, btnv),
                              button_type='square',
                              color=b_color,
                              textcolor=b_textcolor,
                              on_activate_call=self._on_my_replay_rename_press,
                              text_scale=tscl,
                              label=ba.Lstr(resource=self._r +
                                            '.renameReplayButtonText'),
                              autoselect=True)
              btnv -= b_height + b_space_extra
              ba.buttonwidget(parent=cnt,
                              size=(b_width, b_height),
                              position=(btnh, btnv),
                              button_type='square',
                              color=b_color,
                              textcolor=b_textcolor,
                              on_activate_call=self._on_my_replay_delete_press,
                              text_scale=tscl,
                              label=ba.Lstr(resource=self._r +
                                            '.deleteReplayButtonText'),
                              autoselect=True)
  
              v -= sub_scroll_height + 23
              self._scrollwidget = scrlw = ba.scrollwidget(
                  parent=cnt,
                  position=(smlh, v),
                  size=(sub_scroll_width, sub_scroll_height))
              ba.containerwidget(edit=cnt, selected_child=scrlw)
              self._columnwidget = ba.columnwidget(parent=scrlw, left_border=10)
  
              ba.widget(edit=scrlw,
                        autoselect=True,
                        left_widget=btn1,
                        up_widget=self._tab_buttons[tab])
              ba.widget(edit=self._tab_buttons[tab], down_widget=scrlw)
  
              self._my_replay_selected = None
              self._refresh_my_replays()
  
      def _no_replay_selected_error(self) -> None:
          ba.screenmessage(ba.Lstr(resource=self._r +
                                   '.noReplaySelectedErrorText'),
                           color=(1, 0, 0))
          ba.playsound(ba.getsound('error'))
  
      def _on_my_replay_play_press(self) -> None:
          if self._my_replay_selected is None:
              self._no_replay_selected_error()
              return
          _ba.increment_analytics_count('Replay watch')
  
          def do_it() -> None:
              try:
                  # reset to normal speed
                  _ba.set_replay_speed_exponent(0)
                  _ba.fade_screen(True)
                  assert self._my_replay_selected is not None
                  _ba.new_replay_session(_ba.get_replays_dir() + '/' +
                                         self._my_replay_selected)
              except Exception:
                  ba.print_exception("exception running replay session")
                  # drop back into a fresh main menu session
                  # in case we half-launched or something..
                  from bastd import mainmenu
                  _ba.new_host_session(mainmenu.MainMenuSession)
  
          _ba.fade_screen(False, endcall=ba.Call(ba.pushcall, do_it))
          ba.containerwidget(edit=self._root_widget, transition='out_left')
  
      def _on_my_replay_rename_press(self) -> None:
          if self._my_replay_selected is None:
              self._no_replay_selected_error()
              return
          c_width = 600
          c_height = 250
          self._my_replays_rename_window = cnt = ba.containerwidget(
              scale=1.8 if ba.app.small_ui else 1.55 if ba.app.med_ui else 1.0,
              size=(c_width, c_height),
              transition='in_scale')
          dname = self._get_replay_display_name(self._my_replay_selected)
          ba.textwidget(parent=cnt,
                        size=(0, 0),
                        h_align='center',
                        v_align='center',
                        text=ba.Lstr(resource=self._r + '.renameReplayText',
                                     subs=[('${REPLAY}', dname)]),
                        maxwidth=c_width * 0.8,
                        position=(c_width * 0.5, c_height - 60))
          self._my_replay_rename_text = txt = ba.textwidget(
              parent=cnt,
              size=(c_width * 0.8, 40),
              h_align='left',
              v_align='center',
              text=dname,
              editable=True,
              description=ba.Lstr(resource=self._r + '.replayNameText'),
              position=(c_width * 0.1, c_height - 140),
              autoselect=True,
              maxwidth=c_width * 0.7,
              max_chars=200)
          cbtn = ba.buttonwidget(
              parent=cnt,
              label=ba.Lstr(resource='cancelText'),
              on_activate_call=ba.Call(
                  lambda c: ba.containerwidget(edit=c, transition='out_scale'),
                  cnt),
              size=(180, 60),
              position=(30, 30),
              autoselect=True)
          okb = ba.buttonwidget(parent=cnt,
                                label=ba.Lstr(resource=self._r + '.renameText'),
                                size=(180, 60),
                                position=(c_width - 230, 30),
                                on_activate_call=ba.Call(
                                    self._rename_my_replay,
                                    self._my_replay_selected),
                                autoselect=True)
          ba.widget(edit=cbtn, right_widget=okb)
          ba.widget(edit=okb, left_widget=cbtn)
          ba.textwidget(edit=txt, on_return_press_call=okb.activate)
          ba.containerwidget(edit=cnt, cancel_button=cbtn, start_button=okb)
  
      def _rename_my_replay(self, replay: str) -> None:
          new_name = None
          try:
              if not self._my_replay_rename_text:
                  return
              new_name_raw = cast(
                  str, ba.textwidget(query=self._my_replay_rename_text))
              new_name = new_name_raw + '.brp'
              # ignore attempts to change it to what it already is
              # (or what it looks like to the user)
              if (replay != new_name
                      and self._get_replay_display_name(replay) != new_name_raw):
                  old_name_full = (_ba.get_replays_dir() + '/' +
                                   replay).encode('utf-8')
                  new_name_full = (_ba.get_replays_dir() + '/' +
                                   new_name).encode('utf-8')
                  # false alarm; ba.textwidget can return non-None val
                  # pylint: disable=unsupported-membership-test
                  if os.path.exists(new_name_full):
                      ba.playsound(ba.getsound('error'))
                      ba.screenmessage(
                          ba.Lstr(resource=self._r +
                                  '.replayRenameErrorAlreadyExistsText'),
                          color=(1, 0, 0))
                  elif any(char in new_name_raw for char in ['/', '\\', ':']):
                      ba.playsound(ba.getsound('error'))
                      ba.screenmessage(ba.Lstr(resource=self._r +
                                               '.replayRenameErrorInvalidName'),
                                       color=(1, 0, 0))
                  else:
                      _ba.increment_analytics_count('Replay rename')
                      os.rename(old_name_full, new_name_full)
                      self._refresh_my_replays()
                      ba.playsound(ba.getsound('gunCocking'))
          except Exception:
              ba.print_exception(
                  f"error renaming replay '{replay}' to '{new_name}'")
              ba.playsound(ba.getsound('error'))
              ba.screenmessage(ba.Lstr(resource=self._r +
                                       '.replayRenameErrorText'),
                               color=(1, 0, 0))
  
          ba.containerwidget(edit=self._my_replays_rename_window,
                             transition='out_scale')
  
      def _on_my_replay_delete_press(self) -> None:
          from bastd.ui import confirm
          if self._my_replay_selected is None:
              self._no_replay_selected_error()
              return
          confirm.ConfirmWindow(
              ba.Lstr(resource=self._r + '.deleteConfirmText',
                      subs=[('${REPLAY}',
                             self._get_replay_display_name(
                                 self._my_replay_selected))]),
              ba.Call(self._delete_replay, self._my_replay_selected), 450, 150)
  
      def _get_replay_display_name(self, replay: str) -> str:
          if replay.endswith('.brp'):
              replay = replay[:-4]
          if replay == '__lastReplay':
              return ba.Lstr(resource='replayNameDefaultText').evaluate()
          return replay
  
      def _delete_replay(self, replay: str) -> None:
          try:
              _ba.increment_analytics_count('Replay delete')
              os.remove((_ba.get_replays_dir() + '/' + replay).encode('utf-8'))
              self._refresh_my_replays()
              ba.playsound(ba.getsound('shieldDown'))
              if replay == self._my_replay_selected:
                  self._my_replay_selected = None
          except Exception:
              ba.print_exception("exception deleting replay '" + replay + "'")
              ba.playsound(ba.getsound('error'))
              ba.screenmessage(ba.Lstr(resource=self._r +
                                       '.replayDeleteErrorText'),
                               color=(1, 0, 0))
  
      def _on_my_replay_select(self, replay: str) -> None:
          self._my_replay_selected = replay
  
      def _refresh_my_replays(self) -> None:
          assert self._columnwidget is not None
          for child in self._columnwidget.get_children():
              child.delete()
          t_scale = 1.6
          try:
              names = os.listdir(_ba.get_replays_dir())
              # ignore random other files in there..
              names = [n for n in names if n.endswith('.brp')]
              names.sort(key=lambda x: x.lower())
          except Exception:
              ba.print_exception("error listing replays dir")
              names = []
  
          assert self._my_replays_scroll_width is not None
          assert self._my_replays_watch_replay_button is not None
          for i, name in enumerate(names):
              txt = ba.textwidget(
                  parent=self._columnwidget,
                  size=(self._my_replays_scroll_width / t_scale, 30),
                  selectable=True,
                  color=(1.0, 1, 0.4) if name == '__lastReplay.brp' else
                  (1, 1, 1),
                  always_highlight=True,
                  on_select_call=ba.Call(self._on_my_replay_select, name),
                  on_activate_call=self._my_replays_watch_replay_button.activate,
                  text=self._get_replay_display_name(name),
                  h_align='left',
                  v_align='center',
                  corner_scale=t_scale,
                  maxwidth=(self._my_replays_scroll_width / t_scale) * 0.93)
              if i == 0:
                  ba.widget(edit=txt, up_widget=self._tab_buttons['my_replays'])
  
      def _save_state(self) -> None:
          try:
              sel = self._root_widget.get_selected_child()
              if sel == self._back_button:
                  sel_name = 'Back'
              elif sel in list(self._tab_buttons.values()):
                  sel_name = 'Tab:' + list(self._tab_buttons.keys())[list(
                      self._tab_buttons.values()).index(sel)]
              elif sel == self._tab_container:
                  sel_name = 'TabContainer'
              else:
                  raise Exception("unrecognized selection")
              ba.app.window_states[self.__class__.__name__] = {
                  'sel_name': sel_name,
                  'tab': self._current_tab
              }
          except Exception:
              ba.print_exception('error saving state for', self.__class__)
  
      def _restore_state(self) -> None:
          try:
              try:
                  sel_name = ba.app.window_states[
                      self.__class__.__name__]['sel_name']
              except Exception:
                  sel_name = None
              try:
                  current_tab = ba.app.config['Watch Tab']
              except Exception:
                  current_tab = None
              if current_tab is None or current_tab not in self._tab_buttons:
                  current_tab = 'my_replays'
              self._set_tab(current_tab)
              if sel_name == 'Back':
                  sel = self._back_button
              elif sel_name == 'TabContainer':
                  sel = self._tab_container
              elif isinstance(sel_name, str) and sel_name.startswith('Tab:'):
                  sel = self._tab_buttons[sel_name.split(':')[-1]]
              else:
                  if self._tab_container is not None:
                      sel = self._tab_container
                  else:
                      sel = self._tab_buttons[current_tab]
              ba.containerwidget(edit=self._root_widget, selected_child=sel)
          except Exception:
              ba.print_exception('error restoring state for', self.__class__)
  
      def _back(self) -> None:
          from bastd.ui import mainmenu
          self._save_state()
          ba.containerwidget(edit=self._root_widget,
                             transition=self._transition_out)
          ba.app.main_menu_window = (mainmenu.MainMenuWindow(
              transition='in_left').get_root_widget())
  