# -*- coding: utf-8 -*-
"""
Contains definition of the encounter builder window.

If run as the main script, will create and display a window allowing the user
to edit, save, and export an encounter of monsters to a file.
"""
# noinspection PyUnresolvedReferences
import math
# noinspection PyUnresolvedReferences
import os
# noinspection PyUnresolvedReferences
import sys
# noinspection PyUnresolvedReferences
import traceback
# noinspection PyUnresolvedReferences
import types
# noinspection PyUnresolvedReferences
import wx

from __init__ import __version__
from encounter_ui_helpers import *
import exporters
import file_utils
# noinspection PyUnresolvedReferences
import stat_stuff
import ui_helpers
from ui_helpers import *

# ----------
# Main encounter building panel.
# ----------


class EncounterBuilderPanel(wx.Panel):
    """
    Main panel for the encounter builder.
    """
    XP_BUDGET_STR = "Encounter XP Budget ({0:d}):"  # String to use with format() to display suggested encounter XP budget.
    XP_USED_STR = "Encounter XP ({0:d}):"  # String to use with format() to display the suggested XP total for the encounter.

    def __init__(self, frame: ui_helpers.BuilderFrame, parent: wx.Window):
        """
        Constructor for the window. Sets up and initializes UI elements.
        :param frame: EncounterBuilderFrame object that will hold this panel. Used to reference frame controls like the status bar.
        :param parent: The owner of this panel.
        """
        super(EncounterBuilderPanel, self).__init__(parent=parent, id=wx.ID_ANY)
        self.frame = frame

        self.last_save_file = ''
        self.last_rtf_file = ''
        # Control containers. Populated with _setup_ui().
        self.panels = types.SimpleNamespace()
        self.static_boxes = types.SimpleNamespace()
        # Controls. Populated with _setup_ui().
        self.buttons = types.SimpleNamespace()
        self.checkboxes = types.SimpleNamespace()
        self.checklists = types.SimpleNamespace()
        self.dropdowns = types.SimpleNamespace()
        self.labels = types.SimpleNamespace()
        self.lines = types.SimpleNamespace()
        self.textboxes = types.SimpleNamespace()
        self.sizers = types.SimpleNamespace()
        # Control collections. Populated with _setup_ui().
        self.monsters = {}  # Key: Monster ID, Value: SimpleNamespace: .file_name = string file name for the monster, .panel = MonsterBuilderPanel object.
        # .monster_id = ID of active monster or -1, .panel = Reference to active monster's MonsterBuilderPanel or a DummyMonsterBuilderPanel.
        self.active_monster = types.SimpleNamespace()
        # Guards to stop looping actions.
        self.laying_out = False
        self.repainting = False
        self.resizing = False

        self._setup_ui()
        self._initialize()

    def _setup_ui(self):
        """
        Sets up and places all UI elements on the window.
        """
        # Stop UI updates from happening while everything is getting set up for efficiency's sake.
        self.Freeze()
        # Font setup. This should cascade to all children of this panel.
        self.SetFont(ui_helpers.TEXT_FONT)

        self.Hide()

        window_sizer = wx.BoxSizer(orient=wx.VERTICAL)

        # Encounter properties panel. Name, Level, XP, etc.
        # ==========
        self.panels.properties = wx.Panel(parent=self, id=wx.ID_ANY)
        properties_sizer = wx.BoxSizer(orient=wx.HORIZONTAL)

        # First column: Encounter name & description.
        col_sizer = wx.BoxSizer(orient=wx.VERTICAL)
        sub_row_sizer = wx.BoxSizer(orient=wx.HORIZONTAL)
        self.labels.name, self.textboxes.name = labeled_textbox(parent=self.panels.properties,
                                                                label="Encounter Name:",
                                                                value="",
                                                                sizer=sub_row_sizer,
                                                                text_minsize=ui_helpers.TEXT_LARGE_DLG_SIZE,
                                                                label_style=wx.ALIGN_RIGHT,
                                                                text_prop=1)
        col_sizer.Add(sizer=sub_row_sizer, flag=wx.EXPAND)

        sub_row_sizer = wx.BoxSizer(orient=wx.HORIZONTAL)
        self.labels.description = ui_helpers.basic_label(parent=self.panels.properties, label="Encounter Description:" + ui_helpers.TEXT_FORMATTING_HELP, sizer=sub_row_sizer)
        sub_row_sizer.AddStretchSpacer()
        self.checkboxes.description = ui_helpers.basic_checkbox(parent=self.panels.properties, label="Include description in exports", sizer=sub_row_sizer)
        col_sizer.Add(sizer=sub_row_sizer, flag=wx.EXPAND)

        self.textboxes.description = wx.TextCtrl(parent=self.panels.properties, id=wx.ID_ANY, value="", size=ui_helpers.MULTI_TEXTBOX_SIZE, style=wx.TE_MULTILINE)
        self.textboxes.description.SetMinSize(ui_helpers.MULTI_TEXTBOX_SIZE)
        col_sizer.Add(window=self.textboxes.description, flag=wx.ALL | wx.EXPAND, border=ui_helpers.CTRL_BORDER)

        properties_sizer.Add(col_sizer, proportion=1, flag=wx.EXPAND)

        properties_sizer.Add(window=wx.StaticLine(parent=self.panels.properties, id=wx.ID_ANY, style=wx.LI_VERTICAL), flag=wx.EXPAND)

        # Second column: Encounter level, # players, Encounter XP, XP budget.
        col_sizer = wx.BoxSizer(orient=wx.VERTICAL)

        sub_row_sizer = wx.BoxSizer(orient=wx.HORIZONTAL)
        self.labels.level = basic_label(parent=self.panels.properties, label="Encounter Level:", sizer=sub_row_sizer, style=wx.ALIGN_RIGHT)
        self.dropdowns.level = basic_dropdown(parent=self.panels.properties, choices=[str(i) for i in range(1, 31)], sizer=sub_row_sizer, minsize=ui_helpers.DROPDOWN_SMALL_DLG_SIZE)
        self.dropdowns.level.Bind(wx.EVT_CHOICE, self._cb_level_changed)
        col_sizer.Add(sizer=sub_row_sizer, flag=wx.EXPAND)

        sub_row_sizer = wx.BoxSizer(orient=wx.HORIZONTAL)
        self.labels.player_count, self.textboxes.player_count = labeled_textbox(parent=self.panels.properties,
                                                                                label="# of Players:",
                                                                                value="",
                                                                                sizer=sub_row_sizer,
                                                                                text_minsize=ui_helpers.TEXT_SMALL_DLG_SIZE,
                                                                                label_style=wx.ALIGN_RIGHT,
                                                                                label_flag=wx.ALL,
                                                                                text_flag=wx.RIGHT | wx.TOP | wx.BOTTOM)
        self.textboxes.player_count.Bind(wx.EVT_TEXT, self._cb_player_count_changed)
        col_sizer.Add(sizer=sub_row_sizer, flag=wx.EXPAND)

        sub_row_sizer = wx.BoxSizer(orient=wx.HORIZONTAL)
        self.labels.xp_used = basic_label(parent=self.panels.properties, label=self.XP_USED_STR.format(0), sizer=sub_row_sizer, flag=wx.ALL)
        self.textboxes.xp_used = basic_textbox(parent=self.panels.properties, value="", sizer=sub_row_sizer, minsize=ui_helpers.TEXT_MED_DLG_SIZE, flag=wx.TOP | wx.BOTTOM)
        self.checkboxes.xp_used = basic_checkbox(parent=self.panels.properties, label="Custom", sizer=sub_row_sizer, flag=wx.ALL)
        self.checkboxes.xp_used.Bind(wx.EVT_CHECKBOX, self._cb_xp_used_clicked)
        col_sizer.Add(sizer=sub_row_sizer, flag=wx.EXPAND)

        sub_row_sizer = wx.BoxSizer(orient=wx.HORIZONTAL)
        self.labels.xp_budget = basic_label(parent=self.panels.properties, label=self.XP_BUDGET_STR.format(0), sizer=sub_row_sizer, style=wx.ALIGN_RIGHT, flag=wx.ALL)
        self.textboxes.xp_budget = basic_textbox(parent=self.panels.properties, value="", sizer=sub_row_sizer, minsize=ui_helpers.TEXT_MED_DLG_SIZE, flag=wx.TOP | wx.BOTTOM)
        self.checkboxes.xp_budget = basic_checkbox(parent=self.panels.properties, label="Custom", sizer=sub_row_sizer, flag=wx.ALL)
        self.checkboxes.xp_budget.Bind(wx.EVT_CHECKBOX, self._cb_xp_budget_clicked)
        col_sizer.Add(sizer=sub_row_sizer, flag=wx.EXPAND)

        properties_sizer.Add(sizer=col_sizer, flag=wx.EXPAND)

        self.panels.properties.SetSizer(properties_sizer)

        # Encounter management panel, holding controls to add & remove monsters and change which monster is displayed.
        # ==========
        monster_row_sizer = wx.BoxSizer(orient=wx.HORIZONTAL)

        self.panels.manage = MonsterManagementPanel(encounter_panel=self, parent=self)
        monster_row_sizer.Add(window=self.panels.manage, proportion=0)

        # Monster building panel. Holds all the monster builder panels and just shows one at a time.
        # ==========
        self.panels.monster_builder = wx.Panel(parent=self, id=wx.ID_ANY)
        self.sizers.monster_builder = wx.StaticBoxSizer(orient=wx.VERTICAL, parent=self.panels.monster_builder, label="Monster Editor")
        self.static_boxes.monster_builder = self.sizers.monster_builder.GetStaticBox()

        # Create a "blank" panel to fill in the space until the user creates or loads a monster.
        self.panels.blank_monster = DummyMonsterBuilderPanel(frame=self.frame, parent=self.static_boxes.monster_builder)
        # Set the dummy monster as the initial "active" one.
        self.active_monster.monster_id = -1
        self.active_monster.panel = self.panels.blank_monster

        self.sizers.monster_builder.Add(window=self.panels.blank_monster, flag=wx.EXPAND)
        self.panels.monster_builder.SetSizer(self.sizers.monster_builder)
        monster_row_sizer.Add(window=self.panels.monster_builder, proportion=1, flag=wx.EXPAND)

        # Final window sizer setup.
        # ==========
        window_sizer.Add(window=self.panels.properties, proportion=0, flag=wx.EXPAND, border=0)
        window_sizer.Add(sizer=monster_row_sizer, proportion=0, flag=wx.EXPAND, border=0)

        self.SetSizer(window_sizer)
        self.Thaw()

    def _initialize(self):
        """
        Gives UI elements their initial values.
        """
        stat_stuff.CALC_VALS.xp_budget = 0
        stat_stuff.CALC_VALS.xp_used = 0
        self.reset()

        self.layout()

        self.Show()

    # Callbacks
    # ---------

    # noinspection PyUnusedLocal
    def _cb_level_changed(self, event: wx.CommandEvent):
        """
        Callback handling when the user changes the encounter level.

        :param event: CommandEvent containing details about the event.
        """
        self.update_ui()

    # noinspection PyUnusedLocal
    def cb_monster_xp_changed(self, event: wx.CommandEvent):
        """
        Callback handling when the user changes a monster's XP value.

        :param event: CommandEvent containing details about the event.
        """
        self.update_ui()

    # noinspection PyUnusedLocal
    def _cb_player_count_changed(self, event: wx.CommandEvent):
        """
        Callback handling when the user changes the number of players in the encounter.

        :param event: CommandEvent containing details about the event.
        """
        self.update_ui()

    # noinspection PyUnusedLocal
    def _cb_xp_budget_clicked(self, event: wx.CommandEvent):
        """
        Callback handling when the user (un)checks the custom XP budget checkbox.

        :param event: CommandEvent containing details about the event.
        """
        self.update_ui()

    # noinspection PyUnusedLocal
    def _cb_xp_used_clicked(self, event: wx.CommandEvent):
        """
        Callback handling when the user (un)checks the custom XP checkbox.

        :param event: CommandEvent containing details about the event.
        """
        self.update_ui()

    # Action and helper methods
    # -------------------------

    def export_to_rtf(self):
        """
        Exports the data for this encounter to an RTF file.
        """
        default_rtf_name = self.textboxes.name.GetValue()
        default_path = os.getcwd()
        if len(self.last_rtf_file) > 0:
            default_rtf_name = os.path.splitext(os.path.basename(self.last_rtf_file))[0]
            default_path = os.path.dirname(file_utils.abs_path(self.last_rtf_file))
        rtf_file = wx.FileSelector(message='Export Encounter to RTF Document', default_path=default_path, default_filename=default_rtf_name, default_extension='.rtf', wildcard='RTF Documents (*.rtf)|*.rtf|All Files (*.*)|*.*', flags=wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT, parent=self.frame).strip()

        if len(rtf_file) > 0:
            done = False
            # noinspection PyBroadException
            try:
                if exporters.export_encounter_to_rtf(rtf_file, self):
                    self.frame.set_status('Encounter exported to file "{0}"'.format(rtf_file))
                    # Remember the last RTF file exported to for future use.
                    self.last_rtf_file = file_utils.abs_path(rtf_file)
                    done = True
            except Exception as ex:
                log_error('Error: Failed to export to RTF: {0}\n{1}', ex, traceback.format_exc())
                self.frame.set_status('Error: Could not export RTF data: {0}'.format(ex))
                done = True

            if not done:
                log_error('Error: Could not export data to file "{0}"', rtf_file)
                self.frame.set_status('Error: Could not export encounter to file "{0}"'.format(rtf_file))

    def layout(self):
        if not self.laying_out:
            self.laying_out = True
            self.panels.properties.Layout()
            self.panels.properties.Fit()
            self.panels.manage.Layout()
            self.panels.manage.Fit()
            self.panels.monster_builder.Layout()
            self.panels.monster_builder.Fit()
            for monster in self.monsters.values():
                monster.panel.Layout()
                monster.panel.Fit()
            self.Layout()
            self.frame.layout()
            self.Fit()
            self.laying_out = False

    def recalculate(self):
        """
        Calculates all derived and suggested values for the encounter based on current state.
        """
        stat_stuff.CALC_VALS.xp_budget = stat_stuff.XP[self.dropdowns.level.GetSelection()] * ui_helpers.get_textbox_int(self.textboxes.player_count)
        self.panels.manage.recalculate()

    def repaint(self):
        """
        Sizes and "redraws" the panel.
        Everything is taken care of in layout(), nothing to do here.
        """
        if not self.repainting:
            self.repainting = True
            self.repainting = False

    def reset(self):
        """
        Removes all monsters from the encounter and sets the encounter properties back to their defaults.
        """
        self.panels.manage.reset()
        self.textboxes.name.Clear()
        self.checkboxes.description.SetValue(True)  # Default to including encounter description.
        self.textboxes.description.Clear()
        self.dropdowns.level.SetSelection(0)
        self.textboxes.player_count.SetValue("1")
        self.checkboxes.xp_used.SetValue(False)  # Default to using suggested encounter XP.
        self.textboxes.xp_used.SetValue("")
        self.checkboxes.xp_budget.SetValue(False)  # Default to using suggested encounter XP budget.
        self.textboxes.xp_budget.SetValue("")
        self.last_save_file = ''
        self.last_rtf_file = ''
        # Set the "active" monster back to the dummy monster panel.
        self.panels.manage.select_monster(monster_id=-1)
        self.update_ui()
        self.set_label_vals()

    def resize(self):
        """
        Calls Layout() for everything and then Fit() to ensure everything has the correct UI size.
        """
        if not self.resizing:
            self.resizing = True
            self.layout()
            self.resizing = False

    def save(self) -> int:
        """
        Saves the data of the current encounter to a file. Uses the last file saved to,
        or asks the user for a file name if no file has been specified yet.
        Saves all monsters in the encounter as well, and only saves the encounter if all monsters saved properly.

        :return: 1 if all files saved successfully, 0 if a save was canceled, -1 if a save failed.
        """
        return self.save_as(self.last_save_file)

    def save_as(self, filename: str = '') -> int:
        """
        Saves the data of the current encounter to a file.
        Prompts the user for a file name to save to if filename is blank.
        Saves all monsters in the encounter as well, and only saves the encounter if all monsters saved properly.

        :return: 1 if all files saved successfully, 0 if a save was canceled, -1 if a save failed.
        """
        result = -1
        save_file = filename
        if filename == '':
            default_save_name = self.textboxes.name.GetValue()
            default_path = os.getcwd()
            if len(self.last_save_file) > 0:
                default_save_name = os.path.splitext(os.path.basename(self.last_save_file))[0]
                default_path = os.path.dirname(file_utils.abs_path(self.last_save_file))
            save_file = wx.FileSelector(message='Save Encounter to file', default_path=default_path, default_filename=default_save_name, default_extension='.enc', wildcard='Encounter Files (*.enc)|*.enc|All Files (*.*)|*.*', flags=wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT, parent=self.frame).strip()

        # Cancel save if the user cancels out of save file dialog.
        if len(save_file) == 0:
            return 0

        # Save each monster in the encounter to a file if the user wants that.
        if self.panels.manage.should_monsters_save_to_files():
            for monster in self.monsters.values():
                # If the save operation failed or was canceled, abort saving the encounter.
                monster_save_result = monster.panel.save()
                if monster_save_result < 0:
                    self.frame.set_status('WARNING: Encounter not saved, could not save monster "{0:s}"'.format(monster.panel.textboxes.name.GetValue()))
                    return -1
                elif monster_save_result == 0:
                    self.frame.set_status('WARNING: Encounter not saved, monster "{0:s}" save operation canceled'.format(monster.panel.textboxes.name.GetValue()))
                    return 0
                else:
                    # On success, remember the monster's file path & name.
                    monster.file_name = file_utils.abs_path(monster.panel.last_save_file)

        self.frame.set_status('Saving to file "' + save_file + '"...')
        try:
            if file_utils.save_encounter(save_file, self):
                self.frame.set_status('Encounter saved to file "' + save_file + '"')
                # Remember the file saved to so no prompt is necessary for future Save actions.
                self.last_save_file = save_file
                result = 1
            else:
                log_error('Error: Could not save encounter to file "' + save_file + '".')
                self.frame.set_status('Error: Could not save encounter to file "' + save_file + '".')
        except Exception as ex:
            log_error('Error: Failed to save encounter: {0}\n{1}', ex, traceback.format_exc())
            self.frame.set_status('Error: Could not save encounter: {0}'.format(ex))

        return result

    def set_label_vals(self):
        """
        Sets the "suggested" values and other hints in labels based on current calculated values.
        """
        self.labels.xp_budget.SetLabel(self.XP_BUDGET_STR.format(stat_stuff.CALC_VALS.xp_budget))
        self.labels.xp_used.SetLabel(self.XP_USED_STR.format(stat_stuff.CALC_VALS.xp_used))

    def update_ui(self):
        """
        Updates all UI fields after an encounter gets loaded or some UI-changing event happens.
        """
        self.recalculate()
        self.textboxes.xp_used.Enable(self.checkboxes.xp_used.IsChecked())
        if not self.textboxes.xp_used.IsEnabled():
            self.textboxes.xp_used.SetValue(str(stat_stuff.CALC_VALS.xp_used))
        self.textboxes.xp_budget.Enable(self.checkboxes.xp_budget.IsChecked())
        if not self.textboxes.xp_budget.IsEnabled():
            self.textboxes.xp_budget.SetValue(str(stat_stuff.CALC_VALS.xp_budget))
        self.set_label_vals()
        self.panels.manage.update_ui()
        self.resize()


# ----------
# Application window.
# ----------

class EncounterBuilderFrame(ui_helpers.BuilderFrame):
    """
    Main window of the application if running as an encounter builder.
    """

    def __init__(self):
        """
        Constructor for the window. Sets up and initializes UI elements.
        """
        super(EncounterBuilderFrame, self).__init__(parent=None, id=wx.ID_ANY, title="D&D 4th Edition Encounter Builder v{0:s}".format(__version__), style=wx.DEFAULT_FRAME_STYLE & ~(wx.RESIZE_BORDER | wx.MAXIMIZE_BOX))
        # Set up the default control sizes & font now that text size & scaling info is available.
        ui_helpers.setup_ui_helper_constants(self.GetCharWidth(), self.GetCharHeight())

        # Used to track a CallLater object to clear the status bar of messages.
        self.status_clear_timer = None

        self.encounter_panel = None
        self.menubar = None
        self.menus = None
        self.menuitems = None
        self.sizer = None
        self.status_bar = None

        # Flag to help prevent unnecessary recursive redraws.
        self.laying_out = False

        self._setup_ui()
        self._initialize()

    def _setup_ui(self):
        """
        Sets up and places all UI elements on the window.
        """
        # The GUI window icon has to be set after creation, so do it during setup.
        try:
            self.icon = wx.Icon(name=file_utils.path_rel_to_program('EncounterBuilderIcon.ico'), type=wx.BITMAP_TYPE_ICO)
            if self.icon.IsOk():
                self.SetIcon(self.icon)
        except IOError as e:
            ui_helpers.log_error("Could not load icon for GUI: {0:s}", e)

        self.encounter_panel = EncounterBuilderPanel(frame=self, parent=self)
        self.menubar = wx.MenuBar()
        self.menus = types.SimpleNamespace()
        self.menuitems = types.SimpleNamespace()
        self.sizer = wx.BoxSizer(orient=wx.VERTICAL)

        self.Bind(event=wx.EVT_CLOSE, handler=self._cb_close)

        # Menus.
        # ==========
        # File menu.
        self.menus.file = wx.Menu()
        self.menuitems.new = self.menus.file.Append(wx.ID_NEW)
        self.Bind(event=wx.EVT_MENU, handler=self._cb_new, source=self.menuitems.new)
        self.menuitems.open = self.menus.file.Append(wx.ID_OPEN)
        self.Bind(event=wx.EVT_MENU, handler=self._cb_open, source=self.menuitems.open)
        self.menuitems.save = self.menus.file.Append(wx.ID_SAVE)
        self.Bind(event=wx.EVT_MENU, handler=self._cb_save, source=self.menuitems.save)
        self.menuitems.saveas = self.menus.file.Append(wx.ID_SAVEAS)
        self.Bind(event=wx.EVT_MENU, handler=self._cb_save_as, source=self.menuitems.saveas)
        self.menus.file.AppendSeparator()
        self.menuitems.exportrtf = wx.MenuItem(parentMenu=self.menus.file, id=wx.ID_ANY, text="Export to &RTF\tCtrl+R", helpString="Export current encounter to an RTF file")
        self.Bind(event=wx.EVT_MENU, handler=self._cb_export_rtf, source=self.menuitems.exportrtf)
        self.menus.file.Append(self.menuitems.exportrtf)
        self.menus.file.AppendSeparator()
        self.menuitems.exit = self.menus.file.Append(wx.ID_EXIT, 'E&xit', 'Exit application')
        self.Bind(event=wx.EVT_MENU, handler=self._cb_close, source=self.menuitems.exit)
        self.menubar.Append(self.menus.file, '&File')
        self.SetMenuBar(self.menubar)

        # Status bar.
        # ==========
        self.status_bar = self.CreateStatusBar(number=1, style=wx.STB_DEFAULT_STYLE, id=wx.ID_ANY)

        # Final window sizer setup.
        # ==========
        self.sizer.Add(self.encounter_panel, proportion=1, flag=wx.ALL | wx.EXPAND, border=0)

        self.SetSizerAndFit(self.sizer)

        self.Bind(event=wx.EVT_SIZE, handler=self._cb_resize)

    def _initialize(self):
        """
        Gives UI elements their initial values.
        """
        self.new()

        self.layout()

    # Callbacks
    # ---------

    # noinspection PyUnusedLocal
    def _cb_close(self, event: wx.CloseEvent):
        """
        Callback to handle closing the window.

        :param event: CloseEvent object containing details about the event.
        """
        # Make sure the status update timer is cleared before leaving to avoid race condition.
        self.clear_status()
        self.Destroy()

    # noinspection PyUnusedLocal
    def _cb_export_rtf(self, event: wx.CommandEvent):
        """
        Callback handling the user wanting to export the monster to an RTF file.

        :param event: CommandEvent object containing details about the event.
        """
        self.encounter_panel.export_to_rtf()

    # noinspection PyUnusedLocal
    def _cb_new(self, event: wx.CommandEvent):
        """
        Callback handling the user wanting to create a new monster.

        :param event: CommandEvent object containing details about the event.
        """
        self.new()

    # noinspection PyUnusedLocal
    def _cb_open(self, event: wx.CommandEvent):
        """
        Callback handling the user wanting to open a monster file to edit.

        :param event: CommandEvent object containing details about the event.
        """
        self.open()

    # noinspection PyUnusedLocal
    def _cb_resize(self, event: wx.SizeEvent):
        """
        Callback to handle resizing the window.

        :param event: SizeEvent object containing details about the event.
        """
        self.resize()

    # noinspection PyUnusedLocal
    def _cb_save(self, event: wx.CommandEvent):
        """
        Callback handling the user wanting to (re-)save the monster to a file.

        :param event: CommandEvent object containing details about the event.
        """
        self.encounter_panel.save()

    # noinspection PyUnusedLocal
    def _cb_save_as(self, event: wx.CommandEvent):
        """
        Callback handling the user wanting to save the monster to a file.

        :param event: CommandEvent object containing details about the event.
        """
        self.encounter_panel.save_as()

    # Action and helper methods
    # -------------------------

    def clear_status(self):
        """
        Forcibly clears the contents of the status bar and cancels any waiting status clearing timer.
        """
        self.SetStatusText("")
        if self.status_clear_timer is not None and self.status_clear_timer.IsRunning():
            self.status_clear_timer.Stop()
            self.status_clear_timer = None

    def layout(self):
        """
        Redo the layout of the frame. Useful if UI elements were added/deleted/resized.
        """
        if not self.laying_out:
            self.laying_out = True
            self.Layout()
            self.Fit()
            if self.encounter_panel is not None:
                self.encounter_panel.layout()
            self.laying_out = False

    def new(self):
        """
        Resets all controls to default values, clears out any buildable lists or special properties.
        """
        self.set_status('Creating new encounter...')
        self.encounter_panel.reset()

        self.set_status('New encounter created.')

    def open(self):
        """
        Opens a saved encounter file for editing, replacing all the contents of this frame
        with whatever's in the file.
        """
        if len(self.encounter_panel.textboxes.name.GetValue()) > 0:
            default_open_name = self.encounter_panel.textboxes.name.GetValue() + '.enc'
        else:
            default_open_name = ''
        default_path = os.getcwd()
        if len(self.encounter_panel.last_save_file) > 0:
            default_open_name = os.path.basename(self.encounter_panel.last_save_file)
            default_path = os.path.dirname(file_utils.abs_path(self.encounter_panel.last_save_file))
        open_file = wx.FileSelector(message='Choose Encounter file to open', default_path=default_path, default_filename=default_open_name, default_extension='.enc', wildcard='Encounter Files (*.enc)|*.enc|All Files (*.*)|*.*', flags=wx.FD_OPEN | wx.FD_FILE_MUST_EXIST, parent=self).strip()

        # Cancel open if the user cancels out of open file dialog.
        if len(open_file) == 0:
            return

        self.set_status('Opening encounter file "' + open_file + '"...')
        self.encounter_panel.reset()
        self.laying_out = True
        try:
            # Returns a tuple: (T/F if the dictionary was unpacked successfully, # of monsters unpacked, # of monsters (if any) that had to be unpacked from backup data).
            result = file_utils.load_encounter(open_file, self.encounter_panel)
            if result[0]:
                msg_str = 'Encounter opened from file "' + open_file + '"'
                if result[2] > 0:
                    msg_str += ", WARNING: {0:d} {1:s} loaded from backup data!".format(result[2], "monster" if result[1] == 1 else "monsters")
                self.set_status(msg_str)
            else:
                log_error('Error: Could not open encounter from file "' + open_file + '".')
                self.set_status('Error: Could not open encounter from file "' + open_file + '".')
        except Exception as ex:
            log_error('Error: Failed to open encounter: {0}\n{1}', ex, traceback.format_exc())
            self.set_status('Error: Could not open encounter: {0}'.format(ex))
        self.laying_out = False
        self.layout()

    def repaint(self):
        """
        Sizes and "redraws" the window.
        """
        pass

    def resize(self):
        """
        Handles the window being resized by telling all children to rearrange themselves.

        The encounter panel's resize() method automatically calls our repaint(), so we can omit it here.
        """
        self.layout()
        if self.encounter_panel is not None:
            self.encounter_panel.resize()

    def set_status(self, status_text: str, clear_later: bool = True):
        """
        Sets the status bar to the given text string and sets a timer to clear the status bar later.

        :param status_text: The text to show on the status bar.
        :param clear_later: If True (the default) will set a timer to clear the status bar later, otherwise will leave the text there.
        """
        self.SetStatusText(status_text)
        if self.status_clear_timer is not None and self.status_clear_timer.IsRunning():
            self.status_clear_timer.Stop()
            self.status_clear_timer = None
        if clear_later:
            self.status_clear_timer = wx.CallLater(ui_helpers.STATUS_MESSAGE_TIME, self.clear_status)


# noinspection PyUnusedLocal
def main(argv):
    """
    Main function to run the program as an encounter editor.

    :param argv: Command-line args as sys.argv would provide.
    """
    main_app = wx.App()
    main_frame = EncounterBuilderFrame()
    main_frame.Show()
    main_app.MainLoop()


if __name__ == "__main__":
    main(sys.argv)
