# Copyright 2004-2025 Tom Rothamel <pytom@bishoujo.us>
#
# 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.

init python:

    ######################################################################
    # First up, we define a bunch of configuration variable, which the
    # user can change.

    # The contents of the game menu choices.
    config.game_menu = [
            ( "return", u"Return", ui.jumps("_return"), 'True'),
            ( "skipping", u"Begin Skipping", ui.jumps("_return_skipping"), 'config.allow_skipping and not renpy.context()._main_menu'),
            ( "prefs", u"Preferences", _intra_jumps("_prefs_screen", "intra_transition"), 'True' ),
            ( "save", u"Save Game", _intra_jumps("_save_screen", "intra_transition"), 'not renpy.context()._main_menu' ),
            ( "load", u"Load Game", _intra_jumps("_load_screen", "intra_transition"), 'True'),
            ( "mainmenu", u"Main Menu", lambda : _mainmenu_prompt(), 'not renpy.context()._main_menu' ),
            ( "quit", u"Quit", lambda : _quit_prompt("quit"), 'True' ),
            ]

    # If not None, a map from the names of the game menu
    # navigation buttons to new fixed positions for them on
    # the screen.
    config.game_menu_positions = None

    # The number of columns of files to show at once.
    config.file_page_cols = 2

    # The number of rows of files to show at once.
    config.file_page_rows = 5

    # The number of pages to add quick access buttons for.
    config.file_quick_access_pages = 5

    # The positions of file picker components.
    config.file_picker_positions = None


    # This lets us disable the file pager. (So we only have one
    # page of files.)
    config.disable_file_pager = False

    # This lets us disable the thumbnails in the file pager.
    config.disable_thumbnails = False

    # How we format time.
    config.time_format = "%b %d, %H:%M"

    # How we format loade_save slot formats.
    config.file_entry_format = "%(time)s\n%(save_name)s"

    # If True, we will be prompted before loading a game. (This can
    # be changed from inside the game code, so that one can load from
    # the first few screens but not after that.)
    _load_prompt = True


    _game_menu_screen = "_save_screen"

    ######################################################################
    # Next, support code.

    # This is used to store scratch data that's used by the
    # library, but shouldn't be saved out as part of the savegame.
    _scratch = object()

    # This returns a window containing the game menu navigation
    # buttons, set up to jump to the appropriate screen sections.
    #
    # This can be overridden by user code. It's called with the
    # name of the selected screen... one of "mainmenu", "prefs",
    # "save", "load", or "quit", at least for the default game
    # menu. If None, then it's an indication that none of the
    # nav buttons should be shown.
    def _game_nav(screen, buttons=True):

        global _screen
        _screen = screen

        ui.add(renpy.Keymap(toggle_fullscreen = renpy.toggle_fullscreen))
        ui.add(renpy.Keymap(game_menu=ui.jumps("_noisy_return")))

        ui.window(style=style.gm_root[screen])
        ui.null()

        if not screen:
            return

        if not config.game_menu_positions:
            ui.window(style='gm_nav_frame')
            ui.vbox(focus='gm_nav', style='gm_nav_vbox')

        for i, (key, label, clicked, enabled) in enumerate(config.game_menu):

            disabled = False

            if not eval(enabled):
                disabled = True
                clicked = None

            if config.game_menu_positions:
                kwargs = config.game_menu_positions.get(label, { })
            else:
                kwargs = { }

            _button_factory(label, "gm_nav",
                            selected=(key==screen),
                            disabled=disabled,
                            clicked=clicked,
                            properties=kwargs)

        if not config.game_menu_positions:
            ui.close()

    # This is called from the game menu to interact with the
    # user.  It suppresses all of the underlays and overlays.
    def _game_interact():

        return ui.interact(suppress_underlay=True,
                           suppress_overlay=True,
                           mouse="gamemenu")


    # This renders a slot with a file in it, in the file picker.
    def _render_savefile(index, name, filename, extra_info, screenshot, mtime, newest, clickable, **positions):

        if clickable:
            clicked = ui.returns(("return", (filename, True)))
        else:
            clicked = None

        ui.button(style=style.file_picker_entry[index],
                  clicked=clicked,
                  **positions)

        ui.hbox(style=style.file_picker_entry_box[index])

        if not config.disable_thumbnails:
            ui.add(screenshot)

        if newest:
            ui.text(name + ". ", style=style.file_picker_new[index])
        else:
            ui.text(name + ". ", style=style.file_picker_old[index])


        s = config.file_entry_format % dict(
            time=renpy.time.strftime(config.time_format,
                                     renpy.time.localtime(mtime)),
            save_name=extra_info)

        ui.text(s, style=style.file_picker_extra_info[index])

        ui.close()

    # This renders an empty slot in the file picker.
    def _render_new_slot(index, name, filename, clickable, **positions):

        if clickable:
            clicked=ui.returns(("return", (filename, False)))
            enable_hover = True
        else:
            clicked = None
            enable_hover = True

        ui.button(style=style.file_picker_entry[index],
                  clicked=clicked,
                  enable_hover=enable_hover,
                  **positions)

        ui.hbox(style=style.file_picker_entry_box[index])

        if not config.disable_thumbnails:
            ui.null(width=config.thumbnail_width,
                    height=config.thumbnail_height)


        ui.text(name + ". ", style=style.file_picker_old[index])

        ui.text(_(u"Empty Slot."), style=style.file_picker_empty_slot[index])
        ui.close()

    _scratch.file_picker_page = None

    # The names of the various pages that the file picker knows
    # about.
    def _file_picker_pages():

        rv = [ ]

        if config.has_autosave:
            rv.append("Auto")

        if config.has_quicksave:
            rv.append("Quick")

        for i in range(1, config.file_quick_access_pages + 1):
            rv.append(str(i))

        return rv


    # This function is given a page, and should map it to the names
    # of the files on that page.
    def _file_picker_page_files(page):

        per_page = config.file_page_cols * config.file_page_rows
        rv = [ ]

        if config.has_autosave:
            if page == 0:
                for i in range(1, per_page + 1):
                    rv.append(("auto-" + str(i), "a" + str(i), True))

                return rv
            else:
                page -= 1

        if config.has_quicksave:
            if page == 0:
                for i in range(1, per_page + 1):
                    rv.append(("quick-" + str(i), "q" + str(i), True))

                return rv
            else:
                page -= 1

        for i in range(per_page * page + 1, per_page * page + 1 + per_page):
            rv.append(("%d" % i, "%d" % i, False))

        return rv

    # Given a filename, returns the page that filename is on.
    def _file_picker_file_page(filename):

        per_page = config.file_page_cols * config.file_page_rows

        base = 0

        if config.has_autosave:
            if filename.startswith("auto-"):
                return base
            else:
                base += 1

        if config.has_quicksave:
            if filename.startswith("quick-"):
                return base
            else:
                base += 1

        return base + int((int(filename) - 1) / per_page)


    def _file_picker_process_screenshot(s):
        return s

    # This displays a file picker that can chose a save file from
    # the list of save files.
    def _file_picker(selected, save):

        # The number of slots in a page.
        file_page_length = config.file_page_cols * config.file_page_rows

        saved_games = renpy.list_saved_games(regexp=r'(auto-|quick-)?[0-9]+')

        newest = None
        newest_mtime = None
        save_info = { }

        for fn, extra_info, screenshot, mtime in saved_games:
            screenshot = _file_picker_process_screenshot(screenshot)
            save_info[fn] = (extra_info, screenshot, mtime)

            if not fn.startswith("auto-") and mtime > newest_mtime:
                newest = fn
                newest_mtime = mtime

        if config.file_picker_positions:
            positions = config.file_picker_positions
        else:
            positions = { }

        # The index of the first entry in the page.
        fpp = _scratch.file_picker_page

        if fpp is None:

            if newest:
                fpp = _file_picker_file_page(newest)
            else:
                fpp = _file_picker_file_page("1")


        while True:

            if fpp < 0:
                fpp = 0

            _scratch.file_picker_page = fpp

            # Show navigation
            _game_nav(selected)

            if not config.file_picker_positions:

                ui.window(style='file_picker_frame')
                ui.vbox(style='file_picker_frame_vbox') # whole thing.

            if not config.disable_file_pager:

                ### file_picker_navbox thick_hbox
                # (box) The box containing the naviation (next/previous)
                # buttons in the file picker.

                ### file_picker_nav_button menu_button
                # (window, hover) The style that is used for enabled file
                # picker navigation buttons.

                ### file_picker_nav_button_text menu_button_text
                # (text) The style that is used for the label of enabled
                # file picker navigation buttons.

                # Draw the navigation.
                if not config.file_picker_positions:
                    ui.hbox(style='file_picker_navbox') # nav buttons.

                def tb(cond, label, clicked, selected):
                    _button_factory(label,
                                    "file_picker_nav",
                                    disabled=not cond,
                                    clicked=clicked,
                                    selected=selected,
                                    properties=positions.get("nav_" + label, { }))

                # Previous
                tb(fpp > 0, _(u'Previous'), ui.returns(("fppdelta", -1)), selected=False)

                # Quick Access
                for i, name in enumerate(_file_picker_pages()):
                    tb(True, name, ui.returns(("fppset", i)), fpp == i)

                # Next
                tb(True, _(u'Next'), ui.returns(("fppdelta", +1)), False)

                # Done with nav buttons.
                if not config.file_picker_positions:
                    ui.close()

            # This draws a single slot.
            def entry(name, filename, offset, ro):
                place = positions.get("entry_" + str(offset + 1), { })

                if filename not in save_info:
                    clickable = save and not ro

                    _render_new_slot(offset, name, filename, clickable, **place)
                else:
                    clickable = not save or not ro

                    extra_info, screenshot, mtime = save_info[filename]
                    _render_savefile(offset, name, filename, extra_info, screenshot,
                                     mtime, newest == filename, clickable, **place)

            if not config.file_picker_positions:
                ui.grid(config.file_page_cols,
                        config.file_page_rows,
                        style='file_picker_grid',
                        transpose=True) # slots

            for i, (filename, name, ro) in enumerate(_file_picker_page_files(fpp)):
                entry(name, filename, i, ro)

            if not config.file_picker_positions:
                ui.close() # slots
                ui.close() # whole thing

            result = _game_interact()
            type, value = result

            if type == "return":
                return value

            if type == "fppdelta":
                fpp += value

            if type == "fppset":
                fpp = value


    # This renders a yes/no prompt, as part of the game menu. If
    # screen is None, then it omits the game menu navigation.
    def _yesno_prompt(screen, message):
        """
        @param screen: The screen button that should be highlighted when this prompt is shown. If None, then no game menu navigation is shown.

        @param message: The message that is shown to the user to prompt them to answer yes or no.

        This function returns True if the user clicks Yes, or False if the user clicks No.
        """

        renpy.transition(config.intra_transition)

        _game_nav(screen)

        ui.window(style='yesno_frame')
        ui.vbox(style='yesno_frame_vbox')

        _label_factory(message, "yesno")

        ui.hbox(style='yesno_button_hbox')

        # The extra nulls are because we want equal whitespace surrounding
        # the two buttons. It should work as long as we have xfill=True
        _button_factory(u"Yes", 'yesno', clicked=ui.returns(True))
        _button_factory(u"No", 'yesno', clicked=ui.returns(False))

        ui.close()
        ui.close()

        rv =  _game_interact()
        renpy.transition(config.intra_transition)
        return rv

    config.old_names["Are you sure you want to quit?"] = "Are you sure you want to quit the game?"

    def _quit_prompt(screen="quit"):

        def prompt():
           return _yesno_prompt(screen, u"Are you sure you want to quit?")

        if renpy.invoke_in_new_context(prompt):
            renpy.quit()
        else:
            return

    config.old_names["Are you sure you want to return to the main menu?\nThis will lose unsaved progress."] = "Are you sure you want to return to the main menu?\nThis will end your game."

    def _mainmenu_prompt(screen="mainmenu"):

        def prompt():
           return _yesno_prompt(screen, u"Are you sure you want to return to the main menu?\nThis will lose unsaved progress.")

        if renpy.invoke_in_new_context(prompt):
            renpy.full_restart(transition=config.game_main_transition)
        else:
            return

label _compat_confirm_quit:
    $ _quit_prompt(None)
    return


init -1160:
    $ config.old_names["Loading will lose unsaved progress.\nAre you sure you want to do this?"] = "Loading a new game will end your current game.\nAre you sure you want to do this?"

label _load_screen:

    if renpy.has_label("_load_screen_hook"):
        call expression "_load_screen_hook" from _call__load_screen_hook

    python hide:

        fn, exists = _file_picker("load", False )

        if not renpy.context()._main_menu and _load_prompt:
            if _yesno_prompt("load", u"Loading will lose unsaved progress.\nAre you sure you want to do this?"):
                renpy.load(fn)
        else:
            renpy.load(fn)

    jump _load_screen

label _save_screen:

    if renpy.has_label("_save_screen_hook"):
        call expression "_save_screen_hook" from _call__save_screen_hook

    $ _fn, _exists = _file_picker("save", True)

    if not _exists or _yesno_prompt("save", u"Are you sure you want to overwrite your save?"):
        python hide:

            if save_name:
                full_save_name = save_name
            else:
                full_save_name = ""

            renpy.save(_fn, full_save_name)

    jump _save_screen
