# Copyright 2004-2021 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.

# This file contains code that helps support the development of Ren'Py
# games.

# List styles.

screen _developer:

    zorder 1001
    modal True

    frame:
        style_prefix ""

        has side "t c b":
            spacing gui._scale(10)

        label _(u"Developer Menu")

        fixed:
            vbox:

                textbutton _("Interactive Director (D)"):
                    action [ director.Start(), Hide("_developer") ]
                textbutton _("Reload Game (Shift+R)"):
                    action renpy.reload_script
                textbutton _("Console (Shift+O)"):
                    action [ Hide("_developer"), _console.enter ]
                textbutton _("Variable Viewer"):
                    action ui.callsinnewcontext("_debugger_screen")
                textbutton _("Image Location Picker"):
                    action ui.callsinnewcontext("_image_location_picker")
                textbutton _("Filename List"):
                    action ui.callsinnewcontext("_filename_list")

                if not renpy.get_screen("_image_load_log"):
                    textbutton _("Show Image Load Log (F4)"):
                        action Show("_image_load_log")
                else:
                    textbutton _("Hide Image Load Log (F4)"):
                        action Hide("_image_load_log")

                textbutton _("Image Attributes"):
                    action ui.callsinnewcontext("_image_attributes")

        hbox:
            spacing gui._scale(25)

            textbutton _(u"Return"):
                action Hide("_developer")
                size_group "developer"


screen _image_attributes():

    frame:
        style_prefix ""
        right_padding 0

        has side "c b":
            spacing gui._scale(10)


        vbox:
            label _("Image Attributes")


            for name in renpy.get_hidden_tags():
                $ attributes = " ".join(renpy.get_attributes(name))
                text _("[name] [attributes] (hidden)")

            for name in renpy.get_showing_tags():
                $ attributes = " ".join(renpy.get_attributes(name))
                text _("[name] [attributes]")

        hbox:
            spacing gui._scale(25)

            textbutton _(u"Return"):
                action Return(True)


    pass

label _image_attributes:

    call _enter_game_menu

    call screen _image_attributes

    return

screen _variable_viewer(all_entries, deleted_entries):

    zorder 1010
    modal True
    default show_deleted = True
    python:
        if show_deleted:
            entries = all_entries
        else:
            entries = [(n, v) for n, v in all_entries if n not in deleted_entries]

    frame:
        style_prefix ""
        right_padding 0

        has side "t c b":
            spacing gui._scale(10)

        label _("Variable Viewer")

        viewport:
            scrollbars "both"
            child_size (4000, 0)
            yfill True
            xfill True
            mousewheel True

            vbox:

                if not entries:
                    text _("Nothing to inspect.")

                for vn, value in entries:
                    text "[vn] = [value!q]"

        hbox:
            spacing gui._scale(25)

            textbutton _(u"Return"):
                action Return(True)
            if all_entries and deleted_entries:
                textbutton (_(u"Hide deleted") if show_deleted else _(u"Show deleted")):
                    action ToggleScreenVariable("show_deleted")


label _debugger_screen:

    call _enter_game_menu

    python hide:

        import repr
        aRepr = repr.Repr()
        aRepr.maxstring = 120

        entries = [ ]
        deleted_entries = set()

        for sn, d in renpy.python.store_dicts.items():

            if sn.startswith("store._"):
                continue

            for vn in d.ever_been_changed:

                if vn.startswith("__00"):
                    continue

                if vn.startswith("_") and not vn.startswith("__"):
                    continue

                if vn not in d:
                    value = "deleted"
                else:
                    value = aRepr.repr(d[vn])

                if vn == "nvl_list":
                    continue

                name = (sn + "." + vn)[6:]

                if vn not in d:
                    deleted_entries.add(name)

                entries.append((name, value))

        entries.sort(key=lambda e : e[0])

        renpy.call_screen("_variable_viewer", all_entries=entries, deleted_entries=deleted_entries)

    return

label _theme_test:

    call _enter_game_menu

    python hide:

        # Never gets pickled
        def role(b):
            if b:
                return "selected_"
            else:
                return ""

        toggle_var = True
        adj = ui.adjustment(100, 25, page=25, adjustable=True)

        while True:

            ui.window(style=style.gm_root)
            ui.null()

            # Buttons
            ui.hbox(box_spacing=10, xpos=10, ypos=10)

            ui.vbox(box_spacing=10)

            sg = "theme_test"

            ui.frame(style='menu_frame')
            ui.vbox()
            layout.label("Button", None)
            ui.textbutton("Button", size_group=sg, clicked=ui.returns("gndn"))
            ui.textbutton("Button (Selected)", size_group=sg, clicked=ui.returns("gndn"), role=role(True))
            ui.textbutton("Small", clicked=ui.returns("gndn"), style='small_button')
            ui.close()

            ui.frame(style='menu_frame')
            ui.vbox()
            layout.label("Radio Button", None)
            ui.textbutton("True", size_group=sg, clicked=ui.returns("set"), role=role(toggle_var), style='radio_button')
            ui.textbutton("False", size_group=sg, clicked=ui.returns("unset"), role=role(not toggle_var), style='radio_button')
            ui.close()

            ui.frame(style='menu_frame')
            ui.vbox()
            layout.label("Check Button", None)
            ui.textbutton("Check Button", size_group=sg, clicked=ui.returns("toggle"), role=role(toggle_var), style='check_button')
            ui.close()

            ui.frame(style='menu_frame')
            ui.vbox(box_spacing=2)
            ui.bar(adjustment=adj, style='bar', xmaximum=200)
            ui.bar(adjustment=adj, style='slider', xmaximum=200)
            ui.bar(adjustment=adj, style='scrollbar', xmaximum=200)
            ui.close()

            ui.close() # vbox

            ui.frame(style='menu_frame')
            ui.hbox(box_spacing=2)
            ui.bar(adjustment=adj, style='vbar', ymaximum=200)
            ui.bar(adjustment=adj, style='vslider', ymaximum=200)
            ui.bar(adjustment=adj, style='vscrollbar', ymaximum=200)
            ui.close()

            ui.frame(style='menu_frame', xmaximum=0.95)
            ui.vbox(box_spacing=20)
            layout.prompt("This is a prompt. We've made this text long enough to wrap around so it fills multiple lines.", None)
            ui.close()

            ui.close() # hbox

            ui.frame(style='menu_frame', xalign=.01, yalign=.99)
            ui.textbutton(_("Return to the developer menu"), clicked=ui.returns("return"))

            rv = ui.interact()
            if rv == "return":
                break

            elif rv == "set":
                toggle_var = True
            elif rv == "unset":
                toggle_var = False
            elif rv == "toggle":
                toggle_var = not toggle_var

    return


# Not used any more, but may be in save files.
init -1050 python:
    config.missing_background = "black"


# Not used any more, but may be in save files.
screen _missing_images:
    pass

init 1050 python:

    if config.developer:

        def __missing_show_callback(name, what, layer):
            if layer != 'master':
                return False

            if not renpy.count_displayables_in_layer(layer):
                p = Placeholder("bg")
            else:
                p = Placeholder()

            return p._duplicate(p._args.copy(args=what))

        def __missing_hide_callback(name, layer):
            if layer != 'master':
                return False

            return True

        def __missing_scene_callback(layer):
            if layer != 'master':
                return False

            return True

        if config.missing_scene is None:
            config.missing_scene = __missing_scene_callback

        if config.missing_show is None:
            config.missing_show = __missing_show_callback

        if config.missing_hide is None:
            config.missing_hide = __missing_hide_callback


init -1050 python:

    class __FPSMeter(object):

        def __init__(self):
            self.last_frames = None
            self.last_time = None

        def __call__(self, st, at):

            if self.last_time is not None:
                frames = config.frames - self.last_frames
                time = st - self.last_time

                text = "FPS: %.1f" % (frames / time)

            else:
                text = "FPS: --.-"

            self.last_frames = config.frames
            self.last_time = st

            return Text(text, xalign=1.0), .5

label _fps_meter:

    python hide:
        def fps_overlay():
            ui.add(DynamicDisplayable(__FPSMeter()))

        # We normally don't want to change this at runtime... but here
        # it's okay, because we don't want to save the FPS meter anyway.
        #
        # Do as I say, not as I do.
        config.overlay_functions.append(fps_overlay)

    return


init python:

    # This is a displayable that can keep track of the mouse coordinates,
    # and show them to the user.
    class __ImageLocationPicker(renpy.Displayable):

        def __init__(self, fn, **kwargs):
            super(__ImageLocationPicker, self).__init__(**kwargs)

            self.child = Image(fn)

            self.mouse = None
            self.point1 = None
            self.point2 = None

            self.size = (0, 0)

            self.clipboard = None

        def rectangle(self):
            x1, y1 = self.point1
            x2, y2 = self.point2

            width = self.size[0]
            height = self.size[1]

            x1 = min(x1, width)
            x2 = min(x2, width)
            y1 = min(y1, height)
            y2 = min(y2, height)

            minx = min(x1, x2)
            miny = min(y1, y2)
            maxx = max(x1, x2)
            maxy = max(y1, y2)

            w = int(maxx - minx)
            h = int(maxy - miny)

            minx = int(minx)
            miny = int(miny)


            return (minx, miny, w, h)

        def render(self, width, height, st, at):
            rv = renpy.Render(width, height)

            cr = renpy.render(self.child, width, height, st, at)
            rv.blit(cr, (0, 0))

            text = [ ]

            self.size = (cr.width, cr.height)

            if self.point1 and self.point2 and not self.point1 == self.point2:
                x, y, w, h = self.rectangle()

                if w and h:

                    sr = renpy.render(Solid("#0ff4"), w, h, st, at)
                    rv.blit(sr, (x, y))

                    # text.append("Imagemap rectangle: %r" % ((minx, miny, maxx, maxy),))
                    text.append(__("Rectangle: %r") % ((x, y, w, h),))

            if self.mouse:
                mx, my = self.mouse
                if mx < cr.width and my < cr.height:
                    text.append(__("Mouse position: %r") % (self.mouse,))

            if self.clipboard:
                text.append(self.clipboard)

            text.append(__("Right-click or escape to quit."))

            td = Text("\n".join(text), size=14, color="#fff", outlines=[ (1, "#000", 0, 0 ) ])
            tr = renpy.render(td, width, height, st, at)

            rv.blit(tr, (0, height - tr.height))

            return rv

        def event(self, ev, x, y, st):
            import pygame_sdl2 as pygame

            if (x < 0 or y < 0):
                return None

            self.mouse = (x, y)
            renpy.redraw(self, 0)

            if ev.type == pygame.MOUSEBUTTONDOWN and ev.button == 1:
                self.point1 = (x, y)
                self.point2 = (x, y)

            elif ev.type == pygame.MOUSEMOTION and ev.buttons[0]:
                self.point2 = (x, y)

            elif ev.type == pygame.MOUSEBUTTONUP and ev.button == 1:
                self.point2 = (x, y)

                x, y, w, h = self.rectangle()

                if w or h:
                    text = repr((x, y, w, h))
                    self.clipboard = __("Rectangle copied to clipboard.")
                else:
                    text = repr((x, y))
                    self.clipboard = __("Position copied to clipboard.")

                pygame.scrap.put(pygame.scrap.SCRAP_TEXT, text)

screen _image_location_picker(image_files):

    default filter = ""

    frame:
        style_prefix ""

        has side "t c b":
            spacing gui._scale(10)

        vbox:
            label _(u"Image Location Picker")

            hbox:
                text _("Type to filter: ")
                input value ScreenVariableInputValue("filter")

        viewport:
            xfill True
            yfill True
            scrollbars "both"
            mousewheel True

            has vbox

            for fn in [ i for i in image_files if filter.lower() in i.lower() ]:

                textbutton "[fn!q]":
                    action Return(fn)
                    style_suffix "small_button"

        hbox:
            spacing gui._scale(25)

            textbutton _(u"Return"):
                action Return(False)


label _image_location_picker:

    call _enter_game_menu

    scene black

    python hide:

        extensions = [ ".jpg", ".jpeg", ".png", ".webp" ]

        image_files = [ ]

        for fn in renpy.list_files():
            if fn[0] == "_":
                continue

            lfn = fn.lower()

            for i in extensions:
                if lfn.endswith(i):
                    image_files.append(fn)

        image_files.sort()

        xadjustment = ui.adjustment()
        yadjustment = ui.adjustment()

        while True:

            rv = renpy.call_screen("_image_location_picker", image_files=image_files)

            if rv is False:
                renpy.jump("_image_location_picker_done")

            # Now, allow the user to pick the image.

            ui.keymap(game_menu=ui.returns(True))
            ui.add(__ImageLocationPicker(rv))
            ui.interact()

        renpy.jump("_image_location_picker")

label _image_location_picker_done:
    return


label _filename_list:

    python hide:
        import os

        FILES_TXT = os.path.join(renpy.config.basedir, "files.txt")

        with file(FILES_TXT, "w") as f:
            for dirname, dirs, files in os.walk(config.gamedir):

                dirs.sort()
                files.sort()

                for fn in files:
                    fn = os.path.join(dirname, fn)
                    fn = fn[len(config.gamedir) + 1:]
                    print(fn.encode("utf-8", "replace"), file=f)
                    print(fn.encode("utf-8", "replace"))

        renpy.launch_editor([ FILES_TXT ], transient=1)

    return


screen _image_load_log():
    zorder 1500

    default show_help = True

    style_prefix ""

    python:
        load_log = list(renpy.get_image_load_log(5))
        tex_size, tex_count = renpy.get_texture_size()
        tex_size_mb = tex_size / 1024.0 / 1024.0

        cache_size = renpy.display.im.cache.get_current_size(2)
        cache_size_mb = cache_size * 4.0 / 1024 / 1024
        cache_pct = 100.0 * cache_size / renpy.display.im.cache.cache_limit

    drag:
        draggable True
        focus_mask None
        xpos 0
        ypos 0

        frame:
            style "empty"
            background "#0004"
            xpadding 5
            ypadding 5
            xminimum 200

            has vbox

            text _("Textures: [tex_count] ([tex_size_mb:.1f] MB)"):
                size 14
                color "#fff"

            text _("Image cache: [cache_pct:.1f]% ([cache_size_mb:.1f] MB)"):
                size 14
                color "#fff"

            if load_log:
                text "\n" size 14

            for when, filename, preload in load_log:
                if preload:
                    $ color="#ccffcc"
                    $ prefix=__("✔ ")
                else:
                    $ color="#ffcccc"
                    $ prefix=__("✘ ")

                text "[prefix][filename!q]" color color size 14

            if show_help:
                text _("\n{color=#cfc}✔ predicted image (good){/color}\n{color=#fcc}✘ unpredicted image (bad){/color}\n{color=#fff}Drag to move.{/color}"):
                    size 14

    timer 10.0 action SetScreenVariable("show_help", False)

init 1000 python hide:
    if config.transparent_tile:
        tile = im.Tile("_transparent_tile.png", (config.screen_width, config.screen_height))
        config.underlay.append(tile)
        renpy.start_predict(tile)

    config.per_frame_screens.append("_image_load_log")

    config.underlay.append(renpy.Keymap(
        image_load_log = ToggleScreen("_image_load_log")
    ))
