# 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:
    import random
    import codecs
    import re
    import sys

    def theme_names():
        """
        Gets a list of all of the theme names we know about.
        """

        names = list(theme_data.THEME.keys())
        names.sort(key=lambda a : a.lower())

        return names

    def scheme_names(theme):
        """
        Gets a list of the color scheme names corresponding to the given
        theme.
        """

        names = list(theme_data.THEME[theme].keys())
        names.sort(key=lambda a : a.lower())

        return names

    def theme_yinitial():
        names = theme_names()

        if len(names) < 2:
            return 0

        return 1.0 * names.index(current_theme) / (len(names) - 1)

    def scheme_yinitial():
        names = scheme_names(current_theme)

        if len(names) < 2:
            return 0

        return 1.0 * names.index(current_scheme) / (len(names) - 1)

    def pick_theme(theme, scheme):
        """
        Returns a theme and scheme that are similar to `theme` and `scheme`.

        If the theme is known, picks it, otherwise picks a random theme. If
        the scheme is known for that theme, picks it, otherwise picks a
        random scheme that is known for the current theme.
        """

        if theme not in theme_data.THEME:
            theme = random.choice(list(theme_data.THEME))

        schemes = theme_data.THEME[theme]

        if scheme not in schemes:
            if theme in schemes:
                scheme = theme
            else:
                scheme = random.choice(list(schemes))

        return theme, scheme

    def implement_theme(theme, scheme):
        """
        Implement the current theme.

        This function uses non-public APIs.
        """

        global showing_theme, showing_scheme

        if theme == showing_theme and scheme == showing_scheme:
            return

        renpy.style.restore(style_backup)
        exec(theme_data.THEME[theme][scheme], globals(), globals())

        # Rebuild the style cache.
        renpy.style.rebuild(False)

        # Bust the render cache, so we re-evaluate the styles.
        renpy.display.interface.kill_textures()

        showing_theme = theme
        showing_scheme = scheme

        renpy.restart_interaction()

    showing_theme = None
    showing_scheme = None

    class SetTheme(Action):
        def __init__(self, theme):
            self.theme = theme

        def __call__(self):
            global current_theme
            global current_scheme

            current_theme, current_scheme = pick_theme(self.theme, current_scheme)

            implement_theme(current_theme, current_scheme)
            renpy.restart_interaction()

        def get_selected(self):
            return current_theme == self.theme

    class SetScheme(Action):
        def __init__(self, scheme):
            self.scheme = scheme

        def __call__(self):
            global current_theme
            global current_scheme

            current_theme, current_scheme = pick_theme(current_theme, self.scheme)

            implement_theme(current_theme, current_scheme)
            renpy.restart_interaction()

        def get_selected(self):
            return current_scheme == self.scheme

    class PreviewTheme(Action):

        def __init__(self, theme, scheme):
            self.theme = theme
            self.scheme = scheme

        def __call__(self):
            theme, scheme = pick_theme(self.theme, self.scheme)
            implement_theme(theme, scheme)

        def unhovered(self):
            if (showing_theme == self.theme and showing_scheme == self.scheme):
                implement_theme(current_theme, current_scheme)

    def value_changed(value):
        return None

    ##########################################################################
    # Code to update options.rpy

    def list_logical_lines(filename):
        """
        This reads in filename, and turns it into a list of logical
        lines.
        """

        f = codecs.open(filename, "rb", "utf-8")
        data = f.read()
        f.close()

        # The result.
        rv = [ ]

        # The current position we're looking at in the buffer.
        pos = 0

        # Looping over the lines in the file.
        while pos < len(data):

            # The line that we're building up.
            line = ""

            # The number of open parenthesis there are right now.
            parendepth = 0

            # Looping over the characters in a single logical line.
            while pos < len(data):

                c = data[pos]

                if c == '\n' and not parendepth:
                    rv.append(line)

                    pos += 1
                    # This helps out error checking.
                    line = ""
                    break

                # Backslash/newline.
                if c == "\\" and data[pos+1] == "\n":
                    pos += 2
                    line += "\\\n"
                    continue

                # Parenthesis.
                if c in ('(', '[', '{'):
                    parendepth += 1

                if c in ('}', ']', ')') and parendepth:
                    parendepth -= 1

                # Comments.
                if c == '#':
                    while data[pos] != '\n':
                        line += data[pos]
                        pos += 1

                    continue

                # Strings.
                if c in ('"', "'", "`"):
                    delim = c
                    line += c
                    pos += 1

                    escape = False

                    while pos < len(data):

                        c = data[pos]

                        if escape:
                            escape = False
                            pos += 1
                            line += c
                            continue

                        if c == delim:
                            pos += 1
                            line += c
                            break

                        if c == '\\':
                            escape = True

                        line += c
                        pos += 1

                        continue

                    continue

                line += c
                pos += 1

        if line:
            rv.append(line)

        return rv


    def switch_theme():
        """
        Switches the theme of the current project to the current theme
        and color scheme. (As set in current_theme and current_scheme.)
        """

        theme_code = theme_data.THEME[current_theme][current_scheme]

        # Did we change the file at all?
        changed = False

        filename = os.path.join(project.current.path, "game/options.rpy")

        with codecs.open(filename + ".new", "wb", "utf-8") as out:

            for l in list_logical_lines(filename):

                m = re.match(r'    theme.(\w+)\(', l)
                if (not changed) and m and (m.group(1) in theme_data.THEME_FUNCTIONS):
                    l = "    " + theme_code
                    changed = True

                out.write(l + "\n")

        if changed:
            try:
                os.unlink(filename + ".bak")
            except Exception:
                pass

            os.rename(filename, filename + ".bak")
            os.rename(filename + ".new", filename)
        else:
            os.unlink(filename + ".new")
            interface.error(_("Could not change the theme. Perhaps options.rpy was changed too much."))


        # Now give the theme's screen-ops function a chance to make any
        # necessary changes to the screens.rpy file
        filename = os.path.join(project.current.path, "game/screens.rpy")
        changed = False

        try:
            with codecs.open(filename + ".new", "wb", "utf-8") as out:
                lines = list_logical_lines(filename)

                lines = theme_data.THEME_SCREEN_OPERATIONS[current_theme](lines)

                if lines != None:
                    for l in lines:
                        out.write(l + "\n")
                    changed = True

            if changed:
                try:
                    os.unlink(filename + ".bak")
                except Exception:
                    pass

                os.rename(filename, filename + ".bak")
                os.rename(filename + ".new", filename)

        except Exception as inst:
            try:
                # just in case
                os.unlink(filename + ".new")
            except Exception:
                pass
            pass

    def make_style_backup():
        """
        Call this to back up the styles. This should be called in a
        translate python block in each translation.
        """

        global style_backup
        style_backup = renpy.style.backup()

    config.change_language_callbacks.append(make_style_backup)

screen theme_demo:

    window:
        style "gm_root"
        xpadding 5
        ypadding 5

        grid 1 1:
            xfill True
            style_group "prefs"

            vbox:

                frame:
                    style_group "pref"
                    has vbox

                    label _("Display")
                    textbutton _("Window") action SelectedIf(True)
                    textbutton _("Fullscreen") action ui.returns(None)
                    textbutton _("Planetarium") action None


                frame:
                    style_group "pref"
                    has vbox

                    label _("Sound Volume")
                    bar style "slider" value .75 range 1.0 changed value_changed

                    textbutton "Test":
                        action ui.returns(None)
                        style "soundtest_button"


init -2 python:
    style.pref_frame.xfill = True
    style.pref_frame.xmargin = 5
    style.pref_frame.top_margin = 5

    style.pref_vbox.xfill = True

    style.pref_button.size_group = "pref"
    style.pref_button.xalign = 1.0

    style.pref_slider.xmaximum = 192
    style.pref_slider.xalign = 1.0

    style.soundtest_button.xalign = 1.0

screen choose_theme:

    default scheme_yadjustment = ui.adjustment()
    default theme_yadjustment = ui.adjustment()

    default first = True

    python:
        if first:
            theme_yinitial_value = theme_yinitial()
            scheme_yinitial_value = scheme_yinitial()
        else:
            theme_yinitial_value = None
            scheme_yinitial_value = None

        first = False

    frame:
        style_group "l"
        style "l_root"

        window:

            has vbox

            label _("Choose Theme")

            hbox:
                yfill True

                # Theme selector.
                frame:
                    style "l_indent"
                    bottom_margin HALF_SPACER_HEIGHT
                    xmaximum 225

                    has vbox

                    label _("Theme") style "l_label_small"

                    viewport:
                        scrollbars "vertical"
                        mousewheel True
                        yadjustment theme_yadjustment
                        yinitial theme_yinitial_value

                        has vbox

                        for i in theme_names():
                            textbutton "[i]":
                                action SetTheme(i)
                                hovered PreviewTheme(i, current_scheme)
                                style "l_list2"


                # Color scheme selector.
                frame:
                    style "l_indent"
                    bottom_margin HALF_SPACER_HEIGHT
                    xmaximum 225

                    has vbox

                    label _("Color Scheme") style "l_label_small"

                    viewport:
                        scrollbars "vertical"
                        mousewheel True
                        yadjustment scheme_yadjustment
                        yinitial scheme_yinitial_value

                        has vbox

                        for i in scheme_names(current_theme):
                            textbutton "[i]":
                                action SetScheme(i)
                                hovered PreviewTheme(current_theme, i)
                                style "l_list2"


                # Preview
                frame:
                    style "l_default"
                    background Frame(PATTERN, 0, 0, tile=True)
                    xpadding 5
                    ypadding 5

                    xfill True
                    yfill True
                    xmargin 20
                    bottom_margin 6

                    use theme_demo

    textbutton _("Return") action Jump("front_page") style "l_left_button"
    textbutton _("Continue") action Return(True) style "l_right_button"


label choose_theme_callable:

    python:
        current_theme, current_scheme = pick_theme(None, None)
        implement_theme(current_theme, current_scheme)

    call screen choose_theme

    python hide:
        with interface.error_handling(_("changing the theme")):
            switch_theme()

    return

label choose_theme:
    call choose_theme_callable
    jump front_page
