# Copyright 2004-2015 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 the code for in-game Ren'Py error handling. It's a
# module (as opposed to a .rpy file) because that allows us to ensure
# that it is fully loaded or run before any other Ren'Py code runs.


init python:
    style._default = Style(None)

style _default:

    # Text properties
    font "DejaVuSans.ttf"
    language "unicode"
    antialias True
    size 14
    color "#111"
    black_color (0, 0, 0, 255)
    bold False
    italic False
    underline False
    strikethrough False
    kerning 0.0
    drop_shadow None
    drop_shadow_color (0, 0, 0, 255)
    outlines [ ]
    minwidth 0
    text_align 0
    justify False
    text_y_fudge 0
    first_indent 0
    rest_indent 0
    line_spacing 0
    line_leading 0
    line_overlap_split 0
    layout "tex"
    subtitle_width 0.9
    slow_cps None
    slow_cps_multiplier 1.0
    slow_abortable False
    hinting "auto"
    adjust_spacing True

    # Window properties
    background None
    xpadding 0
    ypadding 0
    xmargin 0
    ymargin 0
    xfill False
    yfill False
    xminimum 0
    yminimum 0

    # Placement properties
    xpos None
    ypos None
    xanchor None
    yanchor None
    xmaximum None
    ymaximum None
    xoffset 0
    yoffset 0
    subpixel False

    # Sound properties
    activate_sound None
    hover_sound None

    # Box properties
    spacing 0
    first_spacing None
    box_layout None
    box_wrap False
    box_reverse False
    order_reverse False

    # Button properties
    focus_mask None
    focus_rect None
    keyboard_focus True

    # Bar properties
    fore_bar Null()
    aft_bar Null()
    thumb None
    thumb_shadow None
    left_gutter 0
    right_gutter 0
    thumb_offset 0
    unscrollable None
    bar_invert False
    bar_vertical False

    # Viewport properties
    clipping False

init python:
    # Temporarily, until the real styles can be defined.
    style.default = style._default
    style.image = style._default
    style.fixed = style._default

    ##########################################################################

style _frame is _default:
    background Frame("_theme_amie2/frame.png", 40, 40)
    xmargin 0
    ymargin 0
    xpadding 15
    ypadding 10

style _text is _default
style _fixed is _default

style _hbox is _default:
    box_layout 'horizontal'

style _vbox is _default:
    box_layout 'vertical'

style _grid is _default
style _side is _default

style _viewport is _default:
    clipping True
    xfill True
    yfill True

style _button is _default:
    xpadding 8
    ypadding 6
    xoffset -5
    background Frame("_theme_amie2/button.png", 10, 10)
    hover_background Frame("_theme_amie2/button_hover.png", 10, 10)

style _button_text is _default:
    size 16
    color "#111"
    selected_color "#11f"
    insensitive_color "#ccc"
    xalign 0.5
    yoffset 0

style _label is _default:
    top_margin 10
    bottom_margin 5

style _label_text is _default:
    color "#000"
    bold True

style _bar is _default:
    left_bar Frame("_theme_amie2/hover_frame.png", 10, 10)
    right_bar Frame("_theme_amie2/bar.png", 10, 10)
    thumb None
    hover_thumb None
    ymaximum 20

style _vbar is _default:
    bottom_bar Frame("_theme_amie2/hover_frame.png", 10, 10)
    top_bar Frame("_theme_amie2/bar.png", 10, 10)
    thumb None
    hover_thumb None
    xmaximum 20
    bar_vertical True

style _scrollbar is _default:
    left_bar Frame("_theme_amie2/bar.png", 10, 10)
    right_bar Frame("_theme_amie2/bar.png", 10, 10)
    thumb Frame("_theme_amie2/frame.png", 10, 10)
    hover_thumb Frame("_theme_amie2/hover_frame.png", 10, 10)
    thumb_offset 10
    left_gutter 5
    right_gutter 5
    unscrollable "hide"
    ymaximum 20

style _vscrollbar is _default:
    top_bar Frame("_theme_amie2/bar.png", 10, 10)
    bottom_bar Frame("_theme_amie2/bar.png", 10, 10)
    thumb Frame("_theme_amie2/frame.png", 10, 10)
    hover_thumb Frame("_theme_amie2/hover_frame.png", 10, 10)
    thumb_offset 10
    top_gutter 5
    bottom_gutter 5
    unscrollable "hide"
    xmaximum 20
    bar_vertical True
    bar_invert True

style _hyperlink is _default:
    color "#008"
    hover_underline True

init python:

    # The keymap we use before the real keymap is defined.
    config.keymap = dict(

        # Bindings present almost everywhere, unless explicitly
        # disabled.
        toggle_fullscreen = [ 'f', 'alt_K_RETURN', 'alt_K_KP_ENTER', 'K_F11' ],
        reload_game = [ 'R' ],
        quit = [ 'meta_q', 'alt_K_F4', 'alt_q' ],
        iconify = [ 'meta_m', 'alt_m' ],
        choose_renderer = [ 'G' ],

        # Focus.
        focus_left = [ 'K_LEFT' ],
        focus_right = [ 'K_RIGHT' ],
        focus_up = [ 'K_UP' ],
        focus_down = [ 'K_DOWN' ],

        # Button.
        button_ignore = [ 'mousedown_1' ],
        button_select = [ 'mouseup_1', 'K_RETURN', 'K_KP_ENTER', 'K_SELECT' ],

        # Viewport.
        viewport_up = [ 'mousedown_4' ],
        viewport_down = [ 'mousedown_5' ],
        viewport_drag_start = [ 'mousedown_1' ],
        viewport_drag_end = [ 'mouseup_1' ],

        # These control the bar.
        bar_activate = [ 'mousedown_1', 'K_RETURN', 'K_KP_ENTER', 'K_SELECT' ],
        bar_deactivate = [ 'mouseup_1', 'K_RETURN', 'K_KP_ENTER', 'K_SELECT' ],
        bar_decrease = [ 'K_LEFT' ],
        bar_increase = [ 'K_RIGHT' ],
        )

    config.pad_bindings = {
        "pad_leftshoulder_press" : [ "rollback", ],
        "pad_lefttrigger_pos" : [ "rollback", ],
        "pad_back_press" : [ "rollback", ],

        "pad_guide_press" : [ "game_menu", ],
        "pad_start_press" : [ "game_menu", ],

        "pad_y_press" : [ "hide_windows", ],

        "pad_rightshoulder_press" : [ "rollforward", ],

        "pad_righttrigger_pos" : [ "dismiss", "button_select" ],
        "pad_a_press" : [ "dismiss", "button_select" ],
        "pad_b_press" : [ "button_alternate" ],

        "pad_dpleft_press" : [ "focus_left", "bar_left" ],
        "pad_leftx_neg" : [ "focus_left", "bar_left" ],
        "pad_rightx_neg" : [ "focus_left", "bar_left" ],

        "pad_dpright_press" : [ "focus_right", "bar_right" ],
        "pad_leftx_pos" : [ "focus_right", "bar_right" ],
        "pad_rightx_pos" : [ "focus_right", "bar_right" ],

        "pad_dpup_press" : [ "focus_up", "bar_up" ],
        "pad_lefty_neg" :  [ "focus_up", "bar_up" ],
        "pad_righty_neg" : [ "focus_up", "bar_up" ],

        "pad_dpdown_press" : [ "focus_down", "bar_down" ],
        "pad_lefty_pos" : [ "focus_down", "bar_down" ],
        "pad_righty_pos" : [ "focus_down", "bar_down" ],
    }

# Early hyperlink support.
init python hide:
    def hyperlink_styler(target):
        return style._hyperlink

    def hyperlink_function(target):
        if target.startswith("http:"):
            try:
                import webbrowser
                webbrowser.open(target)
            except:
                pass

        if target.startswith("edit:"):
            prefix, line, filename = target.split(":", 2)
            line = int(line)

            renpy.launch_editor([ filename ], line)

    style._default.hyperlink_functions = (hyperlink_styler, hyperlink_function, None)

init python:

    # Null translation function. This gets redefined once things start
    # successfully.
    def _(s):
        return s

    # This function is responsible for taking a traceback, and converting
    # it to a string that can be shown with text.
    def __format_traceback(s):
        import re

        lines = [ i.replace("{", "{{") for i in s.split("\n") ]

        rv = [ "{b}" + lines[0] + "{/b}" ]

        for i in lines[1:]:
            i = re.sub(r'(File "(.*)", line (\d+))', r'{a=edit:\3:\2}\1{/a}', i)
            rv.append(i)

        return "\n".join(rv)


    def __format_parse_errors(s):
        import re

        rv = ""

        lines = s.split("\n")
        len_lines = len(lines)

        ln = 0

        while ln < len_lines:
            line = lines[ln]
            ln += 1

            if ln < len_lines and lines[ln].endswith("^"):
                highlight = len(lines[ln]) - 1
                ln += 1
            else:
                highlight = -1

            pos = 0

            for c in line:
                if pos == highlight:
                    rv += u"{color=#c00}\u2192{/color}"
                    highlight = -1

                pos += 1

                if c == "{":
                    rv += "{{"
                else:
                    rv += c

            if highlight > 0:
                rv += u"{color=#c00}\u2190{/color}"

            rv += "\n"


        rv = re.sub(r'(File "(.*)", line (\d+))', r'{a=edit:\3:\2}\1{/a}', rv)

        return rv

    class _EditFile(Action):
        def __init__(self, filename, line=1):
            self.filename = filename
            self.line = line

        def __call__(self):
            try:
                renpy.launch_editor([ self.filename ], self.line, transient=1)
            except:
                pass

    class _CopyFile(Action):
        def __init__(self, filename):
            self.filename = filename

        def __call__(self):
            import pygame.scrap

            with open(self.filename, "rb") as f:
                f.read(3) # skip the BOM.
                s = u"[code]\n{}[/code]\n".format(f.read().decode("utf-8"))

            s = s.replace("\n", "\r\n")
            s = s.replace("\r\r", "\r")

            pygame.scrap.put(pygame.SCRAP_TEXT, s.encode("utf-8"))

    def __can_open_traceback():
        return True

    # The transform used for errors. ATL isn't ready yet.
    def __transform_function(state, st, at):
        done = min(st / .1, 1.0)
        state.zoom = .5 + .5 * done
        state.alpha = done
        state.xalign = 0.5
        state.yalign = 0.5

        if done < 1.0:
            return 0
        else:
            return None

    __transform = Transform(function=__transform_function, style='_default')

    class __TooltipAction(object):

        def __init__(self, tooltip, value):
            self.tooltip = tooltip
            self.value = value

        def __call__(self):
            if self.tooltip.value != self.value:
                self.tooltip.value = self.value
                renpy.restart_interaction()

        def unhovered(self):
            if self.tooltip.value != self.tooltip.default:
                self.tooltip.value = self.tooltip.default
                renpy.restart_interaction()

    class __Tooltip(object):
        def __init__(self, default):
            self.value = default
            self.default = default

        def action(self, value):
            return __TooltipAction(self, value)

    class __XScrollValue(BarValue):
        def __init__(self, viewport):
            self.viewport = viewport

        def get_adjustment(self):
            w = renpy.get_widget(None, self.viewport)
            if not isinstance(w, Viewport):
                raise Exception("The displayable with id %r is not declared, or not a viewport." % self.viewport)

            return w.xadjustment

        def get_style(self):
            return "scrollbar", "vscrollbar"

    class __YScrollValue(BarValue):
        def __init__(self, viewport):
            self.viewport = viewport

        def get_adjustment(self):
            w = renpy.get_widget(None, self.viewport)
            if not isinstance(w, Viewport):
                raise Exception("The displayable with id %r is not declared, or not a viewport." % self.viewport)

            return w.yadjustment

        def get_style(self):
            return "scrollbar", "vscrollbar"

    class __ErrorQuit(Action):
        """
        An action that quits with an error status.
        """

        def __call__(self):
            renpy.quit(status=1)



# The screen that is used for error handling.
screen _exception:
    modal True
    zorder 1090

    default tt = __Tooltip("")
    default fmt_short = __format_traceback(short)
    default fmt_full = __format_traceback(full)

    add Solid("#000")

    frame:
        style_group ""
        xfill True

        at __transform

        has side "t c r b"

        vbox:
            label _("An exception has occurred.")

        viewport:
            id "viewport"
            child_size (4000, None)
            mousewheel True

            has vbox

            text fmt_short substitute False
            text fmt_full substitute False

        bar:
            style "_vscrollbar"
            value __YScrollValue("viewport")

        vbox:

            bar:
                style "_scrollbar"
                value __XScrollValue("viewport")

            hbox:
                box_wrap True

                if rollback_action:
                    textbutton _("Rollback"):
                        action rollback_action
                        hovered tt.action(_("Attempts a roll back to a prior time, allowing you to save or choose a different choice."))

                if ignore_action:
                    textbutton _("Ignore"):
                        action ignore_action
                        hovered tt.action(_("Ignores the exception, allowing you to continue. This often leads to additional errors."))

                if config.developer and not renpy.mobile:
                    textbutton _("Reload"):
                        action reload_action
                        hovered tt.action(_("Reloads the game from disk, saving and restoring game state if possible."))

                textbutton _("Open Traceback"):
                    action _EditFile(traceback_fn)
                    hovered tt.action(_("Opens the traceback.txt file in a text editor."))

                if renpy.windows or renpy.macintosh:
                    textbutton _("Copy to Clipboard"):
                        action _CopyFile(traceback_fn)
                        hovered tt.action(_("Copies the traceback.txt file to the clipboard."))

                null width 30

                textbutton _("Quit"):
                    action __ErrorQuit()
                    hovered tt.action(_("Quits the game."))

            # Tooltip.
            text tt.value size 12

    if config.developer and reload_action:
        key "R" action reload_action

# The screen that is used for error handling.
screen _parse_errors:
    modal True
    zorder 1090

    default tt = __Tooltip("")
    default fmt_errors = __format_parse_errors(errors)

    add Solid("#000")

    frame:
        style_group ""
        xfill True

        at __transform

        has side "t c r b"

        vbox:
            label _("Parsing the script failed.")

        viewport:
            id "viewport"
            child_size (None, None)
            mousewheel True

            has vbox
            text fmt_errors substitute False

        bar:
            style "_vscrollbar"
            value __YScrollValue("viewport")

        vbox:

            bar:
                style "_scrollbar"
                value __XScrollValue("viewport")

            hbox:
                box_wrap True

                textbutton _("Reload"):
                    action reload_action
                    hovered tt.action(_("Reloads the game from disk, saving and restoring game state if possible."))

                textbutton _("Open Parse Errors"):
                    action _EditFile(error_fn)
                    hovered tt.action(_("Opens the errors.txt file in a text editor."))

                if renpy.windows or renpy.macintosh:
                    textbutton _("Copy to Clipboard"):
                        action _CopyFile(error_fn)
                        hovered tt.action(_("Copies the errors.txt file to the clipboard."))

                null width 30

                textbutton _("Quit"):
                    action __ErrorQuit()
                    hovered tt.action(_("Quits the game."))

            # Tooltip.
            text tt.value size 12

    if config.developer and reload_action:
        key "R" action reload_action

