# Copyright 2004-2014 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 for initializing and managing the display
# window.

import renpy.display
import renpy.audio
import renpy.text

import pygame #@UnusedImport

import sys
import os
import time
import cStringIO
import threading

try:
    import android #@UnresolvedImport @UnusedImport
    import android.sound #@UnresolvedImport
except:
    android = None

# Need to be +4, so we don't interfere with FFMPEG's events.
TIMEEVENT = pygame.USEREVENT
PERIODIC = pygame.USEREVENT + 1
JOYEVENT = pygame.USEREVENT + 2
REDRAW = pygame.USEREVENT + 3
EVENTNAME = pygame.USEREVENT + 4

# All events except for TIMEEVENT and REDRAW
ALL_EVENTS = [ i for i in range(0, REDRAW + 1) if i != TIMEEVENT and i != REDRAW ]

# The number of msec between periodic events.
PERIODIC_INTERVAL = 50

# Time management.
time_base = 0.0
time_mult = 1.0

def init_time():
    warp = os.environ.get("RENPY_TIMEWARP", "1.0")

    global time_base
    global time_mult

    time_base = time.time()
    time_mult = float(warp)

def get_time():
    t = time.time()
    return time_base + (t - time_base) * time_mult


def displayable_by_tag(layer, tag):
    """
    Get the displayable on the given layer with the given tag.
    """

    return renpy.game.context().scene_lists.get_displayable_by_tag(layer, tag)

class IgnoreEvent(Exception):
    """
    Exception that is raised when we want to ignore an event, but
    also don't want to return anything.
    """

    pass

class EndInteraction(Exception):
    """
    Exception that can be raised (for example, during the render method of
    a displayable) to end the current interaction immediately.
    """

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

class absolute(float):
    """
    This represents an absolute float coordinate.
    """
    __slots__ = [ ]


def place(width, height, sw, sh, placement):
    """
    Performs the Ren'Py placement algorithm.

    `width`, `height`
        The width and height of the area the image will be
        placed in.

    `size`
        The size of the image to be placed.

    `placement`
        The tuple returned by Displayable.get_placement().
    """

    xpos, ypos, xanchor, yanchor, xoffset, yoffset, _subpixel = placement

    if xpos is None:
        xpos = 0
    if ypos is None:
        ypos = 0
    if xanchor is None:
        xanchor = 0
    if yanchor is None:
        yanchor = 0
    if xoffset is None:
        xoffset = 0
    if yoffset is None:
        yoffset = 0

    # We need to use type, since isinstance(absolute(0), float).
    if xpos.__class__ is float:
        xpos *= width

    if xanchor.__class__ is float:
        xanchor *= sw

    x = xpos + xoffset - xanchor

    if ypos.__class__ is float:
        ypos *= height

    if yanchor.__class__ is float:
        yanchor *= sh

    y = ypos + yoffset - yanchor

    return x, y

class Displayable(renpy.object.Object):
    """
    The base class for every object in Ren'Py that can be
    displayed to the screen.

    Drawables will be serialized to a savegame file. Therefore, they
    shouldn't store non-serializable things (like pygame surfaces) in
    their fields.
    """

    # Some invariants about method call order:
    #
    # per_interact is called before render.
    # render is called before event.
    #
    # get_placement can be called at any time, so can't
    # assume anything.

    focusable = False
    full_focus_name = None
    role = ''

    # The event we'll pass on to our parent transform.
    transform_event = None

    # Can we change our look in response to transform_events?
    transform_event_responder = False

    # The main displayable, if this displayable is the root of a composite
    # displayable. (This is used by SL to figure out where to add children
    # to.) If None, it is itself.
    _main = None

    # A list of the children that make up this composite displayable.
    _composite_parts = [ ]

    # The location the displayable was created at, if known.
    _location = None

    def __init__(self, focus=None, default=False, style='default', **properties):
        self.style = renpy.style.Style(style, properties) # @UndefinedVariable
        self.focus_name = focus
        self.default = default

    def _equals(self, o):
        """
        This is a utility method that can be called by a Displayable's
        __eq__ method, to compare displayables for type and displayable
        component equality.
        """

        if type(self) is not type(o):
            return False

        if self.focus_name != o.focus_name:
            return False

        if self.style != o.style:
            return False

        if self.default != o.default:
            return False

        return True

    def __unicode__(self):
        return self.__class__.__name__

    def __repr__(self):
        return "<{} at {:x}>".format(unicode(self).encode("utf-8"), id(self))

    def find_focusable(self, callback, focus_name):

        focus_name = self.focus_name or focus_name

        if self.focusable:
            callback(self, focus_name)

        for i in self.visit():
            if i is None:
                continue

            i.find_focusable(callback, focus_name)


    def focus(self, default=False):
        """
        Called to indicate that this widget has the focus.
        """

        self.set_style_prefix(self.role + "hover_", True)

        if not default:
            if self.style.hover_sound:
                renpy.audio.music.play(self.style.hover_sound, channel="sound")

    def unfocus(self, default=False):
        """
        Called to indicate that this widget has become unfocused.
        """

        self.set_style_prefix(self.role + "idle_", True)

    def is_focused(self):

        if renpy.display.focus.grab and renpy.display.focus.grab is not self:
            return

        return renpy.game.context().scene_lists.focused is self

    def set_style_prefix(self, prefix, root):
        """
        Called to set the style prefix of this widget and its child
        widgets, if any.

        `root` - True if this is the root of a style tree, False if this
        has been passed on to a child.
        """

        if prefix == self.style.prefix:
            return

        self.style.set_prefix(prefix)
        renpy.display.render.redraw(self, 0)

    def parameterize(self, name, parameters):
        """
        Called to parameterize this. By default, we don't take any
        parameters.
        """

        if parameters:
            raise Exception("Image '%s' can't take parameters '%s'. (Perhaps you got the name wrong?)" %
                            (' '.join(name), ' '.join(parameters)))

        return self

    def render(self, width, height, st, at):
        """
        Called to display this displayable. This is called with width
        and height parameters, which give the largest width and height
        that this drawable can be drawn to without overflowing some
        bounding box. It's also given two times. It returns a Surface
        that is the current image of this drawable.

        @param st: The time since this widget was first shown, in seconds.
        @param at: The time since a similarly named widget was first shown,
        in seconds.
        """

        raise Exception("Render not implemented.")

    def event(self, ev, x, y, st):
        """
        Called to report than an event has occured. Ev is the raw
        pygame event object representing that event. If the event
        involves the mouse, x and y are the translation of the event
        into the coordinates of this displayable. st is the time this
        widget has been shown for.

        @returns A value that should be returned from Interact, or None if
        no value is appropriate.
        """

        return None

    def get_placement(self):
        """
        Returns a style object containing placement information for
        this Displayable. Children are expected to overload this
        to return something more sensible.
        """

        return self.style.get_placement()

    def visit_all(self, callback):
        """
        Calls the callback on this displayable and all children of this
        displayable.
        """

        for d in self.visit():
            if not d:
                continue
            d.visit_all(callback)

        callback(self)

    def visit(self):
        """
        Called to ask the displayable to return a list of its children
        (including children taken from styles). For convenience, this
        list may also include None values.
        """

        return [ ]

    def per_interact(self):
        """
        Called once per widget per interaction.
        """

        return None

    def predict_one(self):
        """
        Called to ask this displayable to call the callback with all
        the images it may want to load.
        """

        return

    def predict_one_action(self):
        """
        Called to ask this displayable to cause image prediction
        to occur for images that may be loaded by its actions.
        """

        return

    def place(self, dest, x, y, width, height, surf, main=True):
        """
        This places a render (which must be of this displayable)
        within a bounding area. Returns an (x, y) tuple giving the location
        the displayable was placed at.

        `dest`
            If not None, the `surf` will be blitted to `dest` at the
            computed coordinates.

        `x`, `y`, `width`, `height`
            The bounding area.

        `surf`
            The render to place.

        `main`
            This is passed to Render.blit().
        """

        placement = self.get_placement()
        subpixel = placement[6]

        xpos, ypos = place(width, height, surf.width, surf.height, placement)

        xpos += x
        ypos += y

        pos = (xpos, ypos)

        if dest is not None:
            if subpixel:
                dest.subpixel_blit(surf, pos, main, main, None)
            else:
                dest.blit(surf, pos, main, main, None)

        return pos

    def set_transform_event(self, event):
        """
        Sets the transform event of this displayable to event.
        """

        if event == self.transform_event:
            return

        self.transform_event = event

        if self.transform_event_responder:
            renpy.display.render.redraw(self, 0)

    def _hide(self, st, at, kind):
        """
        Returns None if this displayable is ready to be hidden, or
        a replacement displayable if it doesn't want to be hidden
        quite yet. Kind is either "hide" or "replaced".
        """

        return None

    def _show(self):
        """
        Called when the displayable is added to a scene list.
        """

    def _get_parameterized(self):
        """
        If this is a ImageReference to a parameterized image, return
        the get_parameterized() of the parameterized image. Otherwise,
        return this displayable.
        """

        return self

    def _change_transform_child(self, child):
        """
        If this is a transform, makes a copy of the transform and sets
        the child of the innermost transform to this. Otherwise,
        simply returns child.
        """

        return child

    def _clear(self):
        """
        Clears out the children of this displayable, if any.
        """

        return

    def _in_old_scene(self):
        """
        Returns a version of this displayable that will not change as it is
        rendered.
        """

        return self

    def _tts_common(self, default_alt=None):

        rv = [ ]

        for i in self.visit():
            if i is not None:
                rv.append(i._tts())

        rv = " ".join(rv)

        alt = self.style.alt

        if alt is None:
            alt = default_alt

        if alt is not None:
            rv = renpy.substitutions.substitute(alt, scope={ "text" : rv })[0]

        return rv

    def _tts(self):
        """
        Returns the self-voicing text of this displayable and all of its
        children that cannot take focus. If the displayable can take focus,
        retuns the empty string.
        """

        return self._tts_common()


    def _tts_all(self):
        """
        Returns the self-voicing text of this displayable and all of its
        children that cannot take focus.
        """
        return self._tts_common()


class SceneListEntry(renpy.object.Object):
    """
    Represents a scene list entry. Since this was replacing a tuple,
    it should be treated as immutable after its initial creation.
    """

    def __init__(self, tag, zorder, show_time, animation_time, displayable, name):
        self.tag = tag
        self.zorder = zorder
        self.show_time = show_time
        self.animation_time = animation_time
        self.displayable = displayable
        self.name = name

    def __iter__(self):
        return iter((self.tag, self.zorder, self.show_time, self.animation_time, self.displayable))

    def __getitem__(self, index):
        return (self.tag, self.zorder, self.show_time, self.animation_time, self.displayable)[index]

    def __repr__(self):
        return "<SLE: %r %r %r>" % (self.tag, self.name, self.displayable)

    def copy(self):
        return SceneListEntry(
            self.tag,
            self.zorder,
            self.show_time,
            self.animation_time,
            self.displayable,
            self.name)

    def update_time(self, time):

        rv = self

        if self.show_time is None or self.animation_time is None:
            rv = self.copy()
            rv.show_time = rv.show_time or time
            rv.animation_time = rv.animation_time or time

        return rv


class SceneLists(renpy.object.Object):
    """
    This stores the current scene lists that are being used to display
    things to the user.
    """

    __version__ = 6

    def after_setstate(self):
        for i in renpy.config.layers + renpy.config.top_layers:
            if i not in self.layers:
                self.layers[i] = [ ]
                self.at_list[i] = { }
                self.layer_at_list[i] = (None, [ ])

    def after_upgrade(self, version):

        if version < 1:

            self.at_list = { }
            self.layer_at_list = { }

            for i in renpy.config.layers + renpy.config.top_layers:
                self.at_list[i] = { }
                self.layer_at_list[i] = (None, [ ])

        if version < 3:
            self.shown_window = False

        if version < 4:
            for k in self.layers:
                self.layers[k] = [ SceneListEntry(*(i + (None,)) ) for i in self.layers[k] ]

            self.additional_transient = [ ]

        if version < 5:
            self.drag_group = None

        if version < 6:
            self.shown = self.image_predict_info

    def __init__(self, oldsl, shown):

        super(SceneLists, self).__init__()

        # Has a window been shown as part of these scene lists?
        self.shown_window = False

        # A map from layer name -> list(SceneListEntry)
        self.layers = { }

        # A map from layer name -> tag -> at_list associated with that tag.
        self.at_list = { }

        # A map from layer to (star time, at_list), where the at list has
        # been applied to the layer as a whole.
        self.layer_at_list = { }

        # The current shown images,
        self.shown = shown

        # A list of (layer, tag) pairs that are considered to be
        # transient.
        self.additional_transient = [ ]

        # Either None, or a DragGroup that's used as the default for
        # drags with names.
        self.drag_group = None

        if oldsl:

            for i in renpy.config.layers + renpy.config.top_layers:

                try:
                    self.layers[i] = oldsl.layers[i][:]
                except KeyError:
                    self.layers[i] = [ ]

                if i in oldsl.at_list:
                    self.at_list[i] = oldsl.at_list[i].copy()
                    self.layer_at_list[i] = oldsl.layer_at_list[i]
                else:
                    self.at_list[i] = { }
                    self.layer_at_list[i] = (None, [ ])

            for i in renpy.config.overlay_layers:
                self.clear(i)

            self.replace_transient()

            self.focused = None

            self.drag_group = oldsl.drag_group

        else:
            for i in renpy.config.layers + renpy.config.top_layers:
                self.layers[i] = [ ]
                self.at_list[i] = { }
                self.layer_at_list[i] = (None, [ ])

            self.music = None
            self.focused = None

    def replace_transient(self):
        """
        Replaces the contents of the transient display list with
        a copy of the master display list. This is used after a
        scene is displayed to get rid of transitions and interface
        elements.
        """

        for i in renpy.config.transient_layers:
            self.clear(i, True)

        for layer, tag in self.additional_transient:
            self.remove(layer, tag)

        self.additional_transient = [ ]

    def transient_is_empty(self):
        """
        This returns True if all transient layers are empty. This is
        used by the rollback code, as we can't start a new rollback
        if there is something in a transient layer (as things in the
        transient layer may contain objects that cannot be pickled,
        like lambdas.)
        """

        for i in renpy.config.transient_layers:
            if self.layers[i]:
                return False

        return True

    def transform_state(self, old_thing, new_thing):
        """
        If the old thing is a transform, then move the state of that transform
        to the new thing.
        """

        if old_thing is None:
            return new_thing

        # Don't bother wrapping screens, as they can't be transformed.
        if isinstance(new_thing, renpy.display.screen.ScreenDisplayable):
            return new_thing

        old_transform = old_thing._get_parameterized()
        if not isinstance(old_transform, renpy.display.motion.Transform):
            return new_thing

        new_transform = new_thing._get_parameterized()
        if not isinstance(new_transform, renpy.display.motion.Transform):
            new_thing = new_transform = renpy.display.motion.Transform(child=new_thing)

        new_transform.take_state(old_transform)
        return new_thing


    def find_index(self, layer, tag, zorder, behind):
        """
        This finds the spot in the named layer where we should insert the
        displayable. It returns two things: an index at which the new thing
        should be added, and an index at which the old thing should be hidden.
        (Note that the indexes are relative to the current state of the list,
        which may change on an add or remove.)
        """

        add_index = None
        remove_index = None


        for i, sle in enumerate(self.layers[layer]):

            if add_index is None:

                if sle.zorder == zorder:
                    if sle.tag and (sle.tag == tag or sle.tag in behind):
                        add_index = i

                elif sle.zorder > zorder:
                    add_index = i


            if remove_index is None:
                if (sle.tag and sle.tag == tag) or sle.displayable == tag:
                    remove_index = i


        if add_index is None:
            add_index = len(self.layers[layer])

        return add_index, remove_index


    def add(self,
            layer,
            thing,
            key=None,
            zorder=0,
            behind=[ ],
            at_list=[ ],
            name=None,
            atl=None,
            default_transform=None,
            transient=False):
        """
        Adds something to this scene list. Some of these names are quite a bit
        out of date.

        `thing` - The displayable to add.

        `key` - A string giving the tag associated with this thing.

        `zorder` - Where to place this thing in the zorder, an integer
        A greater value means closer to the user.

        `behind` - A list of tags to place the thing behind.

        `at_list` - The at_list associated with this
        displayable. Counterintunitively, this is not actually
        applied, but merely stored for future use.

        `name` - The full name of the image being displayed. This is used for
        image lookup.

        `atl` - If not None, an atl block applied to the thing. (This actually is
        applied here.)

        `default_transform` - The default transform that is used to initialized
        the values in the other transforms.
        """

        if not isinstance(thing, Displayable):
            raise Exception("Attempting to show something that isn't a displayable:" + repr(thing))

        if layer not in self.layers:
            raise Exception("Trying to add something to non-existent layer '%s'." % layer)

        if key:
            self.remove_hide_replaced(layer, key)
            self.at_list[layer][key] = at_list

        if key and name:
            self.shown.predict_show(layer, name)

        if transient:
            self.additional_transient.append((layer, key))

        l = self.layers[layer]

        if atl:
            thing = renpy.display.motion.ATLTransform(atl, child=thing)

        add_index, remove_index = self.find_index(layer, key, zorder, behind)

        at = None
        st = None

        if remove_index is not None:
            sle = l[remove_index]
            at = sle.animation_time
            old = sle.displayable

            if (not atl and
                not at_list and
                renpy.config.keep_running_transform and
                isinstance(old, renpy.display.motion.Transform)):

                thing = sle.displayable._change_transform_child(thing)
            else:
                thing = self.transform_state(l[remove_index].displayable, thing)

            thing.set_transform_event("replace")
            thing._show()

        else:

            if not isinstance(thing, renpy.display.motion.Transform):
                thing = self.transform_state(default_transform, thing)

            thing.set_transform_event("show")
            thing._show()

        sle = SceneListEntry(key, zorder, st, at, thing, name)
        l.insert(add_index, sle)

        if remove_index is not None:
            if add_index <= remove_index:
                remove_index += 1

            self.hide_or_replace(layer, remove_index, "replaced")

    def hide_or_replace(self, layer, index, prefix):
        """
        Hides or replaces the scene list entry at the given
        index. `prefix` is a prefix that is used if the entry
        decides it doesn't want to be hidden quite yet.
        """

        if index is None:
            return

        l = self.layers[layer]
        oldsle = l[index]

        now = get_time()

        st = oldsle.show_time or now
        at = oldsle.animation_time or now

        if oldsle.tag:

            d = oldsle.displayable._hide(now - st, now - at, prefix)

            # _hide can mutate the layers, so we need to recompute
            # index.
            index = l.index(oldsle)

            if d is not None:

                sle = SceneListEntry(
                    prefix + "$" + oldsle.tag,
                    oldsle.zorder,
                    st,
                    at,
                    d,
                    None)

                l[index] = sle

                return

        l.pop(index)

    def get_all_displayables(self):
        """
        Gets all displayables reachable from this scene list.
        """

        rv = [ ]
        for l in self.layers.itervalues():
            for sle in l:
                rv.append(sle.displayable)

        return rv

    def remove_above(self, layer, thing):
        """
        Removes everything on the layer that is closer to the user
        than thing, which may be either a tag or a displayable. Thing must
        be displayed, or everything will be removed.
        """

        for i in reversed(xrange(len(self.layers[layer]))):

            sle = self.layers[layer][i]

            if thing:
                if sle.tag == thing or sle.displayable == thing:
                    break

            if sle.tag and "$" in sle.tag:
                continue

            self.hide_or_replace(layer, i, "hide")

    def remove(self, layer, thing):
        """
        Thing is either a key or a displayable. This iterates through the
        named layer, searching for entries matching the thing.
        When they are found, they are removed from the displaylist.

        It's not an error to remove something that isn't in the layer in
        the first place.
        """

        if layer not in self.layers:
            raise Exception("Trying to remove something from non-existent layer '%s'." % layer)

        _add_index, remove_index = self.find_index(layer, thing, 0, [ ])

        if remove_index is not None:
            tag = self.layers[layer][remove_index].tag

            if tag:
                self.shown.predict_hide(layer, (tag,))
                self.at_list[layer].pop(tag, None)

            self.hide_or_replace(layer, remove_index, "hide")

    def clear(self, layer, hide=False):
        """
        Clears the named layer, making it empty.

        If hide is True, then objects are hidden. Otherwise, they are
        totally wiped out.
        """

        if not layer in self.layers:
            return

        if not hide:
            self.layers[layer] = [ ]

        else:

            # Have to iterate in reverse order, since otherwise
            # the indexes might change.
            for i in reversed(xrange(len(self.layers[layer]))):
                self.hide_or_replace(layer, i, hide)

        self.at_list[layer].clear()
        self.shown.predict_scene(layer)
        self.layer_at_list[layer] = (None, [ ])

    def set_layer_at_list(self, layer, at_list):
        self.layer_at_list[layer] = (None, list(at_list))

    def set_times(self, time):
        """
        This finds entries with a time of None, and replaces that
        time with the given time.
        """

        for l, (t, list) in self.layer_at_list.items(): #@ReservedAssignment
            self.layer_at_list[l] = (t or time, list)

        for l, ll in self.layers.iteritems():
            self.layers[l] = [ i.update_time(time) for i in ll ]

    def showing(self, layer, name):
        """
        Returns true if something with the prefix of the given name
        is found in the scene list.
        """

        return self.shown.showing(layer, name)

    def make_layer(self, layer, properties):
        """
        Creates a Fixed with the given layer name and scene_list.
        """

        rv = renpy.display.layout.MultiBox(layout='fixed', focus=layer, **properties)
        rv.append_scene_list(self.layers[layer])
        rv.layer_name = layer

        time, at_list = self.layer_at_list[layer]

        if at_list:

            for a in at_list:

                if isinstance(a, renpy.display.motion.Transform):
                    rv = a(child=rv)
                else:
                    rv = a(rv)

            f = renpy.display.layout.MultiBox(layout='fixed')
            f.add(rv, time, time)
            f.layer_name = layer

            rv = f


        return rv

    def remove_hide_replaced(self, layer, tag):
        """
        Removes things that are hiding or replaced, that have the given
        tag.
        """

        hide_tag = "hide$" + tag
        replaced_tag = "replaced$" + tag

        l = self.layers[layer]
        self.layers[layer] = [ i for i in l if i.tag != hide_tag and i.tag != replaced_tag ]

    def remove_hidden(self):
        """
        Goes through all of the layers, and removes things that are
        hidden and are no longer being kept alive by their hide
        methods.
        """

        now = get_time()

        for l in self.layers:
            newl = [ ]

            for sle in self.layers[l]:

                if sle.tag:

                    if sle.tag.startswith("hide$"):
                        d = sle.displayable._hide(now - sle.show_time, now - sle.animation_time, "hide")
                        if not d:
                            continue

                    elif sle.tag.startswith("replaced$"):
                        d = sle.displayable._hide(now - sle.show_time, now - sle.animation_time, "replaced")
                        if not d:
                            continue

                newl.append(sle)

            self.layers[l] = newl

    def get_displayable_by_tag(self, layer, tag):
        """
        Returns the displayable on the layer with the given tag, or None
        if no such displayable exists. Note that this will usually return
        a Transform.
        """

        if layer not in self.layers:
            raise Exception("Unknown layer %r." % layer)

        for sle in self.layers[layer]:
            if sle.tag == tag:
                return sle.displayable

        return None

    def get_displayable_by_name(self, layer, name):
        """
        Returns the displayable on the layer with the given name, or None
        if no such displayable exists. Note that this will usually return
        a Transform.
        """

        if layer not in self.layers:
            raise Exception("Unknown layer %r." % layer)

        for sle in self.layers[layer]:
            if sle.name == name:
                return sle.displayable

        return None

    def get_image_bounds(self, layer, tag, width, height):
        """
        Implements renpy.get_image_bounds().
        """

        if layer not in self.layers:
            raise Exception("Unknown layer %r." % layer)

        for sle in self.layers[layer]:
            if sle.tag == tag:
                break
        else:
            return None

        now = get_time()

        if sle.show_time is not None:
            st = now - sle.show_time
        else:
            st = 0

        if sle.animation_time is not None:
            at = now - sle.animation_time
        else:
            at = 0

        surf = renpy.display.render.render(sle.displayable, width, height, st, at)

        sw = surf.width
        sh = surf.height

        x, y = place(width, height, sw, sh, sle.displayable.get_placement())

        surf.kill()

        return (x, y, sw, sh)


def scene_lists(index=-1):
    """
    Returns either the current scenelists object, or the one for the
    context at the given index.
    """

    return renpy.game.context(index).scene_lists


class MouseMove(object):
    """
    This contains information about the current mouse move.
    """

    def __init__(self, x, y, duration):
        self.start = get_time()

        if duration is not None:
            self.duration = duration
        else:
            self.duration = 0

        self.start_x, self.start_y = renpy.display.draw.get_mouse_pos()

        self.end_x = x
        self.end_y = y

    def perform(self):
        """
        Performs the mouse move. Returns True if this should be called
        again, or False if the move has finished.
        """

        elapsed = get_time() - self.start

        if elapsed >= self.duration:
            renpy.display.draw.set_mouse_pos(self.end_x, self.end_y)
            return False

        done = 1.0 * elapsed / self.duration

        x = int(self.start_x + done * (self.end_x - self.start_x))
        y = int(self.start_y + done * (self.end_y - self.start_y))

        renpy.display.draw.set_mouse_pos(x, y)
        return True

def get_safe_mode():
    """
    Returns true if we should go into safe mode.
    """

    if not renpy.first_utter_start:
        return False


    try:
        if renpy.linux:
            if (pygame.key.get_mods() & pygame.KMOD_SHIFT):
                return True
            else:
                return False

        if renpy.windows:
            import ctypes

            VK_SHIFT      = 0x10

            ctypes.windll.user32.GetKeyState.restype = ctypes.c_ushort
            if ctypes.windll.user32.GetKeyState(VK_SHIFT) & 0x8000:
                return True
            else:
                return False

        # We don't need safe mode on mac or android, as those platforms
        # should always have OpenGL 2 or OpenGL ES 2.
        return False

    except:
        return False

class Interface(object):
    """
    This represents the user interface that interacts with the user.
    It manages the Display objects that display things to the user, and
    also handles accepting and responding to user input.

    @ivar display: The display that we used to display the screen.

    @ivar profile_time: The time of the last profiling.

    @ivar screenshot: A screenshot, or None if no screenshot has been
    taken.

    @ivar old_scene: The last thing that was displayed to the screen.

    @ivar transition: A map from layer name to the transition that will
    be applied the next time interact restarts.

    @ivar transition_time: A map from layer name to the time the transition
    involving that layer started.

    @ivar transition_from: A map from layer name to the scene that we're
    transitioning from on that layer.

    @ivar suppress_transition: If True, then the next transition will not
    happen.

    @ivar force_redraw: If True, a redraw is forced.

    @ivar restart_interaction: If True, the current interaction will
    be restarted.

    @ivar pushed_event: If not None, an event that was pushed back
    onto the stack.

    @ivar mouse: The name of the mouse cursor to use during the current
    interaction.

    @ivar ticks: The number of 20hz ticks.

    @ivar frame_time: The time at which we began drawing this frame.

    @ivar interact_time: The time of the start of the first frame of the current interact_core.

    @ivar time_event: A singleton ignored event.

    @ivar event_time: The time of the current event.

    @ivar timeout_time: The time at which the timeout will occur.
    """

    def __init__(self):

        # PNG data and the surface for the current file screenshot.
        self.screenshot = None
        self.screenshot_surface = None

        self.old_scene = { }
        self.transition = { }
        self.ongoing_transition = { }
        self.transition_time = { }
        self.transition_from = { }
        self.suppress_transition = False
        self.quick_quit = False
        self.force_redraw = False
        self.restart_interaction = False
        self.pushed_event = None
        self.ticks = 0
        self.mouse = 'default'
        self.timeout_time = None
        self.last_event = None
        self.current_context = None
        self.roll_forward = None

        # Things to be preloaded.
        self.preloads = [ ]

        # The time at which this draw occurs.
        self.frame_time = 0

        # The time when this interaction occured.
        self.interact_time = None

        # The time we last tried to quit.
        self.quit_time = 0

        # Are we currently processing the quit event?
        self.in_quit_event = False

        self.time_event = pygame.event.Event(TIMEEVENT)
        self.redraw_event = pygame.event.Event(REDRAW)

        # Are we focused?
        self.mouse_focused = True
        self.keyboard_focused = True

        # Properties for each layer.
        self.layer_properties = { }

        # Have we shown the window this interaction?
        self.shown_window = False

        # Are we in fullscren mode?
        self.fullscreen = False

        # Should we ignore the rest of the current touch? Used to ignore the
        # rest of a mousepress after a longpress occurs.
        self.ignore_touch = False

        for layer in renpy.config.layers + renpy.config.top_layers:
            if layer in renpy.config.layer_clipping:
                x, y, w, h = renpy.config.layer_clipping[layer]
                self.layer_properties[layer] = dict(
                    xpos = x,
                    xanchor = 0,
                    ypos = y,
                    yanchor = 0,
                    xmaximum = w,
                    ymaximum = h,
                    xminimum = w,
                    yminimum = h,
                    clipping = True,
                    )

            else:
                self.layer_properties[layer] = dict()


        # A stack giving the values of self.transition and self.transition_time
        # for contexts outside the current one. This is used to restore those
        # in the case where nothing has changed in the new context.
        self.transition_info_stack = [ ]

        # The time when the event was dispatched.
        self.event_time = 0

        # The time we saw the last mouse event.
        self.mouse_event_time = None

        # Should we show the mouse?
        self.show_mouse = True

        # Should we reset the display?
        self.display_reset = False

        # The last size we were resized to. This lets us debounce the
        # VIDEORESIZE event.
        self.last_resize = None

        # The thread that can do display operations.
        self.thread = threading.current_thread()

        # Ensure that we kill off the presplash.
        renpy.display.presplash.end()

        # Initialize pygame.
        if pygame.version.vernum < (1, 8, 1):
            raise Exception("Ren'Py requires pygame 1.8.1 to run.")

        try:
            import pygame.macosx as macosx
            macosx.init() #@UndefinedVariable
        except:
            pass

        try:
            macosx.Video_AutoInit() #@UndefinedVariable
        except:
            pass

        # pygame.font.init()
        renpy.audio.audio.init()
        renpy.display.joystick.init()
        pygame.display.init()

        # Init timing.
        init_time()
        self.profile_time = get_time()
        self.mouse_event_time = get_time()

        # The current window caption.
        self.window_caption = None

        renpy.game.interface = self
        renpy.display.interface = self

        # Are we in safe mode, from holding down shift at start?
        self.safe_mode = get_safe_mode()

        # Setup the video mode.
        self.set_mode()

        # Double check, since at least on Linux, we can't set safe_mode until
        # the window maps.
        self.safe_mode = get_safe_mode()

        # Load the image fonts.
        renpy.text.font.load_image_fonts()

        # Setup the android keymap.
        if android is not None:
            android.map_key(android.KEYCODE_BACK, pygame.K_PAGEUP)
            android.map_key(android.KEYCODE_MENU, pygame.K_ESCAPE)

        # Setup periodic event.
        pygame.time.set_timer(PERIODIC, PERIODIC_INTERVAL)

        # Don't grab the screen.
        pygame.event.set_grab(False)

        # Do we need a background screenshot?
        self.bgscreenshot_needed = False

        # Event used to signal background screenshot taken.
        self.bgscreenshot_event = threading.Event()

        # The background screenshot surface.
        self.bgscreenshot_surface = None

        # Mouse move. If not None, information about the current mouse
        # move.
        self.mouse_move = None

        renpy.display.emulator.init_emulator()


    def post_init(self):
        # Setup.

        # Needed for Unity.
        wmclass = renpy.config.save_directory or os.path.basename(sys.argv[0])
        os.environ['SDL_VIDEO_X11_WMCLASS'] = wmclass

        self.set_window_caption(force=True)
        self.set_icon()

        if renpy.config.key_repeat is not None:
            delay, repeat_delay = renpy.config.key_repeat
            pygame.key.set_repeat(int(1000 * delay), int(1000 * repeat_delay))

    def set_icon(self):
        """
        This is called to set up the window icon.
        """

        # Window icon.
        icon = renpy.config.window_icon

        if renpy.windows and renpy.config.windows_icon:
            icon = renpy.config.windows_icon

        if icon:

            im = renpy.display.scale.image_load_unscaled(
                renpy.loader.load(icon),
                icon,
                convert=False,
                )

            # Convert the aspect ratio to be square.
            iw, ih = im.get_size()
            imax = max(iw, ih)
            square_im = renpy.display.pgrender.surface_unscaled((imax, imax), True)
            square_im.blit(im, ( (imax-iw)/2, (imax-ih)/2 ))
            im = square_im

            if renpy.windows and im.get_size() != (32, 32):
                im = renpy.display.scale.real_smoothscale(im, (32, 32))

            pygame.display.set_icon(im)


    def set_window_caption(self, force=False):
        caption = renpy.config.window_title + renpy.store._window_subtitle

        if renpy.exports.get_autoreload():
            caption += " - autoreload"

        if not force and caption == self.window_caption:
            return

        self.window_caption = caption
        pygame.display.set_caption(caption.encode("utf-8"))

    def iconify(self):
        pygame.display.iconify()

    def get_draw_constructors(self):
        """
        Figures out the list of draw constructors to try.
        """

        renderer = renpy.game.preferences.renderer
        renderer = os.environ.get("RENPY_RENDERER", renderer)

        if self.safe_mode:
            renderer = "sw"

        renpy.config.renderer = renderer

        if renderer == "auto":
            if renpy.windows:
                renderers = [ "gl", "angle", "sw" ]
            else:
                renderers = [ "gl", "sw" ]
        else:
            renderers = [ renderer, "sw" ]

        draw_objects = { }

        def make_draw(name, mod, cls, *args):
            if name not in renderers:
                return False

            try:
                __import__(mod)
                module = sys.modules[mod]
                draw_class = getattr(module, cls)
                draw_objects[name] = draw_class(*args)
                return True

            except:
                renpy.display.log.write("Couldn't import {0} renderer:".format(name))
                renpy.display.log.exception()

                return False

        if renpy.windows:
            has_angle = make_draw("angle", "renpy.angle.gldraw", "GLDraw")
        else:
            has_angle = False

        make_draw("gl", "renpy.gl.gldraw", "GLDraw", not has_angle)
        make_draw("sw", "renpy.display.swdraw", "SWDraw")

        rv = [ ]

        def append_draw(name):
            if name in draw_objects:
                rv.append(draw_objects[name])
            else:
                renpy.display.log.write("Unknown renderer: {0}".format(name))

        for i in renderers:
            append_draw(i)

        return rv


    def kill_textures(self):
        renpy.display.render.free_memory()
        renpy.text.text.layout_cache_clear()

    def kill_textures_and_surfaces(self):
        """
        Kill all textures and surfaces that are loaded.
        """

        self.kill_textures()

        renpy.display.im.cache.clear()
        renpy.display.module.bo_cache = None

    def set_mode(self, physical_size=None):
        """
        This sets the video mode. It also picks the draw object.
        """

        # Ensure that we kill off the movie when changing screen res.
        if renpy.display.draw and renpy.display.draw.info["renderer"] == "sw":
            renpy.display.video.movie_stop(clear=False)

        if self.display_reset:
            renpy.display.draw.deinit()

            if renpy.display.draw.info["renderer"] == "angle":
                renpy.display.draw.quit()

                # This is necessary to fix a bug with restoring a window from
                # minimized state on windows.
                pygame.display.quit()

            renpy.display.render.free_memory()
            renpy.display.im.cache.clear()
            renpy.text.text.layout_cache_clear()

            renpy.display.module.bo_cache = None

            self.kill_textures_and_surfaces()

        self.display_reset = False

        virtual_size = (renpy.config.screen_width, renpy.config.screen_height)

        if physical_size is None:
            if renpy.android or renpy.game.preferences.physical_size is None: #@UndefinedVariable
                physical_size = (renpy.config.screen_width, renpy.config.screen_height)
            else:
                physical_size = renpy.game.preferences.physical_size

        # Setup screen.
        fullscreen = renpy.game.preferences.fullscreen

        # If we're in fullscreen mode, and changing to another mode, go to
        # windowed mode first.
        s = pygame.display.get_surface()
        if s and (s.get_flags() & pygame.FULLSCREEN):
            fullscreen = False

        old_fullscreen = self.fullscreen
        self.fullscreen = fullscreen

        if os.environ.get('RENPY_DISABLE_FULLSCREEN', False):
            fullscreen = False
            self.fullscreen = renpy.game.preferences.fullscreen

        if renpy.display.draw:
            draws = [ renpy.display.draw ]
        else:
            draws = self.get_draw_constructors()

        for draw in draws:
            if draw.set_mode(virtual_size, physical_size, fullscreen):
                renpy.display.draw = draw
                break
            else:
                # pygame.display.quit()
                pass
        else:
            # Ensure we don't get stuck in fullscreen.
            renpy.game.preferences.fullscreen = False
            raise Exception("Could not set video mode.")

        # Save the video size.
        if renpy.config.save_physical_size and not fullscreen and not old_fullscreen:
            renpy.game.preferences.physical_size = renpy.display.draw.get_physical_size()

        if android:
            android.init()

        # We need to redraw the (now blank) screen.
        self.force_redraw = True

        # Assume we have focus until told otherwise.
        self.mouse_focused = True
        self.keyboard_focused = True

        # Assume we're not minimized.
        self.minimized = False

        # Force an interaction restart.
        self.restart_interaction = True

        # True if we're doing a one-time profile.
        self.profile_once = False


    def draw_screen(self, root_widget, fullscreen_video, draw):

        surftree = renpy.display.render.render_screen(
            root_widget,
            renpy.config.screen_width,
            renpy.config.screen_height,
            )

        if draw:
            renpy.display.draw.draw_screen(surftree, fullscreen_video)

        renpy.display.render.mark_sweep()
        renpy.display.focus.take_focuses()

        self.surftree = surftree
        self.fullscreen_video = fullscreen_video


    def take_screenshot(self, scale, background=False):
        """
        This takes a screenshot of the current screen, and stores it so
        that it can gotten using get_screenshot()

        `background`
           If true, we're in a background thread. So queue the request
           until it can be handled by the main thread.
        """

        if background:
            self.bgscreenshot_event.clear()
            self.bgscreenshot_needed = True

            if not self.bgscreenshot_event.wait(1.0):
                raise Exception("Screenshot timed out.")

            surf = self.bgscreenshot_surface
            self.bgscreenshot_surface = None

        else:

            surf = renpy.display.draw.screenshot(self.surftree, self.fullscreen_video)

        surf = renpy.display.scale.smoothscale(surf, scale)

        renpy.display.render.mutated_surface(surf)

        self.screenshot_surface = surf

        sio = cStringIO.StringIO()
        renpy.display.module.save_png(surf, sio, 0)
        self.screenshot = sio.getvalue()
        sio.close()


    def check_background_screenshot(self):
        """
        Handles requests for a background screenshot.
        """

        if self.bgscreenshot_needed:
            self.bgscreenshot_needed = False
            self.bgscreenshot_surface = renpy.display.draw.screenshot(self.surftree, self.fullscreen_video)
            self.bgscreenshot_event.set()

    def get_screenshot(self):
        """
        Gets the current screenshot, as a string. Returns None if there isn't
        a current screenshot.
        """

        rv = self.screenshot

        if not rv:
            self.take_screenshot(
                (renpy.config.thumbnail_width, renpy.config.thumbnail_height),
                background=(threading.current_thread() is not self.thread),
                )
            rv = self.screenshot
            self.lose_screenshot()

        return rv


    def lose_screenshot(self):
        """
        This deallocates the saved screenshot.
        """

        self.screenshot = None
        self.screenshot_surface = None


    def save_screenshot(self, filename):
        """
        Saves a full-size screenshot in the given filename.
        """

        window = renpy.display.draw.screenshot(self.surftree, self.fullscreen_video)

        if renpy.config.screenshot_crop:
            window = window.subsurface(renpy.config.screenshot_crop)

        try:
            renpy.display.scale.image_save_unscaled(window, filename)
        except:
            if renpy.config.debug:
                raise
            pass



    def show_window(self):

        if not renpy.store._window:
            return

        if not renpy.game.preferences.show_empty_window:
            return

        if renpy.game.context().scene_lists.shown_window:
            return

        if renpy.config.empty_window:
            renpy.config.empty_window()

    def do_with(self, trans, paired, clear=False):

        if renpy.config.with_callback:
            trans = renpy.config.with_callback(trans, paired)

        if (not trans) or self.suppress_transition:
            self.with_none()
            return False
        else:
            self.set_transition(trans)
            return self.interact(trans_pause=True,
                                 suppress_overlay=not renpy.config.overlay_during_with,
                                 mouse='with',
                                 clear=clear)

    def with_none(self):
        """
        Implements the with None command, which sets the scene we will
        be transitioning from.
        """

        renpy.game.context().say_attributes = None

        # Show the window, if that's necessary.
        self.show_window()

        # Compute the overlay.
        self.compute_overlay()

        scene_lists = renpy.game.context().scene_lists

        # Compute the scene.
        self.old_scene = self.compute_scene(scene_lists)

        # Get rid of transient things.

        for i in renpy.config.overlay_layers:
            scene_lists.clear(i)

        scene_lists.replace_transient()
        scene_lists.shown_window = False


    def set_transition(self, transition, layer=None, force=False):
        """
        Sets the transition that will be performed as part of the next
        interaction.
        """

        if self.suppress_transition and not force:
            return

        if transition is None:
            self.transition.pop(layer, None)
        else:
            self.transition[layer] = transition


    def event_peek(self):
        """
        This peeks the next event. It returns None if no event exists.
        """

        if self.pushed_event:
            return self.pushed_event

        ev = pygame.event.poll()

        if ev.type == pygame.NOEVENT:
            # Seems to prevent the CPU from speeding up.
            renpy.display.draw.event_peek_sleep()
            return None

        self.pushed_event = ev

        return ev

    def event_poll(self):
        """
        Called to busy-wait for an event while we're waiting to
        redraw a frame.
        """

        if self.pushed_event:
            rv = self.pushed_event
            self.pushed_event = None
        else:
            rv = pygame.event.poll()

        self.last_event = rv

        return rv


    def event_wait(self):
        """
        This is in its own function so that we can track in the
        profiler how much time is spent in interact.
        """

        if self.pushed_event:
            rv = self.pushed_event
            self.pushed_event = None
            self.last_event = rv
            return rv

        self.check_background_screenshot()

        ev = pygame.event.wait()
        self.last_event = ev

        return ev

    def compute_overlay(self):

        if renpy.store.suppress_overlay:
            return

        # Figure out what the overlay layer should look like.
        renpy.ui.layer("overlay")

        for i in renpy.config.overlay_functions:
            i()

        if renpy.game.context().scene_lists.shown_window:
            for i in renpy.config.window_overlay_functions:
                i()

        renpy.ui.close()


    def compute_scene(self, scene_lists):
        """
        This converts scene lists into a dictionary mapping layer
        name to a Fixed containing that layer.
        """

        rv = { }

        for layer in renpy.config.layers + renpy.config.top_layers:
            rv[layer] = scene_lists.make_layer(layer, self.layer_properties[layer])

        root = renpy.display.layout.MultiBox(layout='fixed')
        root.layers = { }

        for layer in renpy.config.layers:
            root.layers[layer] = rv[layer]
            root.add(rv[layer])
        rv[None] = root

        return rv


    def quit_event(self):
        """
        This is called to handle the user invoking a quit.
        """

        if self.quit_time > (time.time() - .75):
            raise renpy.game.QuitException()

        if self.in_quit_event:
            raise renpy.game.QuitException()

        if renpy.config.quit_action is not None:
            self.quit_time = time.time()

            # Make the screen more suitable for interactions.
            renpy.exports.movie_stop(only_fullscreen=True)
            renpy.store.mouse_visible = True

            try:
                self.in_quit_event = True
                renpy.display.behavior.run(renpy.config.quit_action)
            finally:
                self.in_quit_event = False

        else:
            raise renpy.game.QuitException()


    def get_mouse_info(self):
        # Figure out if the mouse visibility algorithm is hiding the mouse.
        if self.mouse_event_time + renpy.config.mouse_hide_time < renpy.display.core.get_time():
            visible = False
        else:
            visible = renpy.store.mouse_visible and (not renpy.game.less_mouse)

        visible = visible and self.show_mouse

        # If not visible, hide the mouse.
        if not visible:
            return False, 0, 0, None

        # Deal with a hardware mouse, the easy way.
        if not renpy.config.mouse:
            return True, 0, 0, None

        # Deal with the mouse going offscreen.
        if not self.mouse_focused:
            return False, 0, 0, None

        mouse_kind = renpy.display.focus.get_mouse() or self.mouse

        # Figure out the mouse animation.
        if mouse_kind in renpy.config.mouse:
            anim = renpy.config.mouse[mouse_kind]
        else:
            anim = renpy.config.mouse[getattr(renpy.store, 'default_mouse', 'default')]

        img, x, y = anim[self.ticks % len(anim)]
        tex = renpy.display.im.load_image(img)

        return False, x, y, tex

    def set_mouse_pos(self, x, y, duration):
        """
        Sets the mouse position. Duration can be a number of seconds or
        None.
        """

        self.mouse_move = MouseMove(x, y, duration)
        self.force_redraw = True

    def drawn_since(self, seconds_ago):
        """
        Returns true if the screen has been drawn in the last `seconds_ago`,
        and false otherwise.
        """

        return (get_time() - self.frame_time) <= seconds_ago

    def android_check_suspend(self):

        if android.check_pause():

            android.sound.pause_all()

            pygame.time.set_timer(PERIODIC, 0)
            pygame.time.set_timer(REDRAW, 0)
            pygame.time.set_timer(TIMEEVENT, 0)

            # The game has to be saved.
            renpy.loadsave.save("_reload-1")

            # So does the persistent data.
            renpy.persistent.update(True)

            android.wait_for_resume()

            # Since we came back to life, we can get rid of the
            # auto-reload.
            renpy.loadsave.unlink_save("_reload-1")

            pygame.time.set_timer(PERIODIC, PERIODIC_INTERVAL)

            android.sound.unpause_all()

    def iconified(self):
        """
        Called when we become an icon.
        """

        if self.minimized:
            return

        self.minimized = True

        renpy.display.log.write("The window was minimized.")


    def restored(self):
        """
        Called when we are restored from being an icon.
        """

        # This is necessary on Windows/DirectX/Angle, as otherwise we get
        # a blank screen.

        if not self.minimized:
            return

        self.minimized = False

        renpy.display.log.write("The window was restored.")

        if renpy.windows:
            self.display_reset = True
            self.set_mode(self.last_resize)

    def enter_context(self):
        """
        Called when we enter a new context.
        """

        # Stop ongoing transitions.
        self.ongoing_transition.clear()
        self.transition_from.clear()
        self.transition_time.clear()

    def post_time_event(self):
        """
        Posts a time_event object to the queue.
        """

        try:
            pygame.event.post(self.time_event)
        except:
            pass

    def after_longpress(self):
        """
        Called after a longpress, to ignore the mouse button release.
        """

        self.ignore_touch = True
        renpy.display.focus.mouse_handler(None, -1, -1, default=False)


    def interact(self, clear=True, suppress_window=False, **kwargs):
        """
        This handles an interaction, restarting it if necessary. All of the
        keyword arguments are passed off to interact_core.
        """

        # Cancel magic error reporting.
        renpy.bootstrap.report_error = None

        context = renpy.game.context()

        if context.interacting:
            raise Exception("Cannot start an interaction in the middle of an interaction, without creating a new context.")

        context.interacting = True


        # Show a missing window.
        if not suppress_window:
            self.show_window()

        # These things can be done once per interaction.

        preloads = self.preloads
        self.preloads = [ ]

        try:
            for i in renpy.config.start_interact_callbacks:
                i()

            repeat = True

            while repeat:
                repeat, rv = self.interact_core(preloads=preloads, **kwargs)

            return rv

        finally:

            context.interacting = False

            # Clean out transient stuff at the end of an interaction.
            if clear:
                scene_lists = renpy.game.context().scene_lists
                scene_lists.replace_transient()

            self.ongoing_transition = { }
            self.transition_time = { }
            self.transition_from = { }

            self.restart_interaction = True

            renpy.game.context().scene_lists.shown_window = False

    def interact_core(self,
                      show_mouse=True,
                      trans_pause=False,
                      suppress_overlay=False,
                      suppress_underlay=False,
                      mouse='default',
                      preloads=[],
                      roll_forward=None,
                      ):

        """
        This handles one cycle of displaying an image to the user,
        and then responding to user input.

        @param show_mouse: Should the mouse be shown during this
        interaction? Only advisory, and usually doesn't work.

        @param trans_pause: If given, we must have a transition. Should we
        add a pause behavior during the transition?

        @param suppress_overlay: This suppresses the display of the overlay.
        @param suppress_underlay: This suppresses the display of the underlay.
        """

        self.roll_forward = roll_forward
        self.show_mouse = show_mouse

        suppress_transition = renpy.config.skipping or renpy.game.less_updates

        # The global one.
        self.suppress_transition = False

        # Figure out transitions.
        for k in self.transition:
            if k not in self.old_scene:
                continue

            self.ongoing_transition[k] = self.transition[k]
            self.transition_from[k] = self.old_scene[k]._in_old_scene()
            self.transition_time[k] = None

        self.transition.clear()

        if suppress_transition:
            self.ongoing_transition.clear()
            self.transition_from.clear()
            self.transition_time.clear()

        ## Safety condition, prevents deadlocks.
        if trans_pause:
            if not self.ongoing_transition:
                return False, None
            if None not in self.ongoing_transition:
                return False, None
            if suppress_transition:
                return False, None
            if not self.old_scene:
                return False, None

        # Check to see if the language has changed.
        renpy.translation.check_language()

        # We just restarted.
        self.restart_interaction = False

        # Setup the mouse.
        self.mouse = mouse

        # The start and end times of this interaction.
        start_time = get_time()
        end_time = start_time

        # frames = 0

        for i in renpy.config.interact_callbacks:
            i()

        # Set the window caption.
        self.set_window_caption()

        # Tick time forward.
        renpy.display.im.cache.tick()
        renpy.text.text.layout_cache_tick()
        renpy.display.predict.reset()

        # Clear the size groups.
        renpy.display.layout.size_groups.clear()

        # Clear the set of updated screens.
        renpy.display.screen.updated_screens.clear()

        # Clear some events.
        pygame.event.clear((pygame.MOUSEMOTION,
                            PERIODIC,
                            TIMEEVENT,
                            REDRAW))

        # Add a single TIMEEVENT to the queue.
        self.post_time_event()

        # Figure out the scene list we want to show.
        scene_lists = renpy.game.context().scene_lists

        # Remove the now-hidden things.
        scene_lists.remove_hidden()

        # Compute the overlay.
        if not suppress_overlay:
            self.compute_overlay()

        # The root widget of everything that is displayed on the screen.
        root_widget = renpy.display.layout.MultiBox(layout='fixed')
        root_widget.layers = { }

        # A list of widgets that are roots of trees of widgets that are
        # considered for focusing.
        focus_roots = [ ]

        # Add the underlay to the root widget.
        if not suppress_underlay:
            for i in renpy.config.underlay:
                root_widget.add(i)
                focus_roots.append(i)

            if roll_forward is not None:
                rfw = renpy.display.behavior.RollForward(roll_forward)
                root_widget.add(rfw)
                focus_roots.append(rfw)

        # Figure out the scene. (All of the layers, and the root.)
        scene = self.compute_scene(scene_lists)
        renpy.display.tts.set_root(scene[None])

        # If necessary, load all images here.
        for w in scene.itervalues():
            try:
                renpy.display.predict.displayable(w)
            except:
                pass

        # The root widget of all of the layers.
        layers_root = renpy.display.layout.MultiBox(layout='fixed')
        layers_root.layers = { }

        def add_layer(where, layer):

            scene_layer = scene[layer]
            focus_roots.append(scene_layer)

            if (self.ongoing_transition.get(layer, None) and
                not suppress_transition):

                trans = self.ongoing_transition[layer](
                    old_widget=self.transition_from[layer],
                    new_widget=scene_layer)

                if not isinstance(trans, Displayable):
                    raise Exception("Expected transition to be a displayable, not a %r" % trans)

                transition_time = self.transition_time.get(layer, None)

                where.add(trans, transition_time, transition_time)
                where.layers[layer] = trans

            else:
                where.layers[layer] = scene_layer
                where.add(scene_layer)

        # Add layers (perhaps with transitions) to the layers root.
        for layer in renpy.config.layers:
            add_layer(layers_root, layer)

        # Add layers_root to root_widget, perhaps through a transition.
        if (self.ongoing_transition.get(None, None) and
            not suppress_transition):

            old_root = renpy.display.layout.MultiBox(layout='fixed')
            old_root.layers = { }

            for layer in renpy.config.layers:
                d = self.transition_from[None].layers[layer]
                old_root.layers[layer] = d
                old_root.add(d)

            trans = self.ongoing_transition[None](
                old_widget=old_root,
                new_widget=layers_root)

            if not isinstance(trans, Displayable):
                raise Exception("Expected transition to be a displayable, not a %r" % trans)

            trans._show()

            transition_time = self.transition_time.get(None, None)
            root_widget.add(trans, transition_time, transition_time)

            if trans_pause:
                sb = renpy.display.behavior.SayBehavior()
                root_widget.add(sb)
                focus_roots.append(sb)

                pb = renpy.display.behavior.PauseBehavior(trans.delay)
                root_widget.add(pb, transition_time, transition_time)
                focus_roots.append(pb)

        else:
            root_widget.add(layers_root)

        # Add top_layers to the root_widget.
        for layer in renpy.config.top_layers:
            add_layer(root_widget, layer)

        for i in renpy.display.emulator.overlay:
            root_widget.add(i)

        del add_layer

        prediction_coroutine = renpy.display.predict.prediction_coroutine(root_widget)
        prediction_coroutine.send(None)

        # Clean out the registered adjustments.
        renpy.display.behavior.adj_registered.clear()

        # Clean up some movie-related things.
        renpy.display.video.early_interact()

        # Call per-interaction code for all widgets.
        root_widget.visit_all(lambda i : i.per_interact())

        # Now, update various things regarding scenes and transitions,
        # so we are ready for a new interaction or a restart.
        self.old_scene = scene

        # Okay, from here on we now have a single root widget (root_widget),
        # which we will try to show to the user.

        # Figure out what should be focused.
        renpy.display.focus.before_interact(focus_roots)

        # Redraw the screen.
        renpy.display.render.process_redraws()
        needs_redraw = True

        # First pass through the while loop?
        first_pass = True

        # We don't yet know when the interaction began.
        self.interact_time = None

        # We only want to do autosave once.
        did_autosave = False

        old_timeout_time = None
        old_redraw_time = None

        rv = None

        # Start sound.
        renpy.audio.audio.interact()

        # How long until we redraw.
        _redraw_in = 3600

        # Have we drawn a frame yet?
        video_frame_drawn = False

        # We're no longer after rollback.
        renpy.game.after_rollback = False

        # This try block is used to force cleanup even on termination
        # caused by an exception propagating through this function.
        try:

            while rv is None:

                # Check for a change in fullscreen preference.
                if self.fullscreen != renpy.game.preferences.fullscreen or self.display_reset:
                    self.set_mode()
                    needs_redraw = True

                # Check for suspend.
                if android:
                    self.android_check_suspend()

                # Check for autoreload.
                if renpy.loader.needs_autoreload:
                    renpy.loader.needs_autoreload = False
                    renpy.exports.reload_script()

                # Redraw the screen.
                if (self.force_redraw or
                    ((first_pass or not pygame.event.peek(ALL_EVENTS)) and
                     renpy.display.draw.should_redraw(needs_redraw, first_pass))):

                    self.force_redraw = False

                    # If we have a movie, start showing it.
                    fullscreen_video = renpy.display.video.interact()

                    # Clean out the redraws, if we have to.
                    # renpy.display.render.kill_redraws()

                    # Draw the screen.
                    self.frame_time = get_time()

                    if not self.interact_time:
                        self.interact_time = self.frame_time

                    self.draw_screen(root_widget, fullscreen_video, (not fullscreen_video) or video_frame_drawn)

                    if first_pass:
                        scene_lists.set_times(self.interact_time)
                        for k, v in self.transition_time.iteritems():
                            if v is None:
                                self.transition_time[k] = self.interact_time

                    renpy.config.frames += 1

                    # If profiling is enabled, report the profile time.
                    if renpy.config.profile or self.profile_once:
                        new_time = get_time()

                        if self.profile_once or (new_time - self.profile_time > .015):
                            print "Profile: Redraw took %.3f ms." % (1000 * (new_time - self.frame_time))
                            print "Profile: %.3f ms to complete event." % (1000 * (new_time - self.profile_time))

                        self.profile_once = False

                    if first_pass and self.last_event and self.last_event.type in [ pygame.MOUSEBUTTONDOWN, pygame.MOUSEBUTTONUP, pygame.MOUSEMOTION ]:

                            x, y = renpy.display.draw.get_mouse_pos()
                            ev, x, y = renpy.display.emulator.emulator(self.last_event, x, y)

                            if self.ignore_touch:
                                x = -1
                                y = -1

                            if renpy.android and self.last_event.type == pygame.MOUSEBUTTONUP:
                                x = -1
                                y = -1

                            renpy.display.focus.mouse_handler(None, x, y, default=False)

                    needs_redraw = False
                    first_pass = False

                    pygame.time.set_timer(REDRAW, 0)
                    pygame.event.clear([REDRAW])
                    old_redraw_time = None

                # Move the mouse, if necessary.
                if self.mouse_move is not None:
                    if not self.mouse_move.perform():
                        self.mouse_move = None

                # Draw the mouse, if it needs drawing.
                renpy.display.draw.update_mouse()

                # See if we want to restart the interaction entirely.
                if self.restart_interaction:
                    return True, None

                # Determine if we need a redraw. (We want to run these
                # functions, so we put them first to prevent short-circuiting.)

                if renpy.display.video.frequent():
                    needs_redraw = True
                    video_frame_drawn = True

                if renpy.display.render.process_redraws():
                    needs_redraw = True

                # How many seconds until we timeout.
                _timeout_in = 3600

                # Handle the redraw timer.
                redraw_time = renpy.display.render.redraw_time()

                if (redraw_time is not None) and not needs_redraw:
                    if redraw_time != old_redraw_time:
                        time_left = redraw_time - get_time()
                        time_left = min(time_left, 3600)
                        _redraw_in = time_left

                        if time_left <= 0:
                            try:
                                pygame.event.post(self.redraw_event)
                            except:
                                pass
                            pygame.time.set_timer(REDRAW, 0)
                        else:
                            pygame.time.set_timer(REDRAW, max(int(time_left * 1000), 1))

                        old_redraw_time = redraw_time
                else:
                    _redraw_in = 3600
                    pygame.time.set_timer(REDRAW, 0)

                # Handle the timeout timer.
                if not self.timeout_time:
                    pygame.time.set_timer(TIMEEVENT, 0)
                else:
                    time_left = self.timeout_time - get_time()
                    time_left = min(time_left, 3600)
                    _timeout_in = time_left

                    if time_left <= 0:
                        self.timeout_time = None
                        pygame.time.set_timer(TIMEEVENT, 0)
                        self.post_time_event()
                    elif self.timeout_time != old_timeout_time:
                        # Always set to at least 1ms.
                        pygame.time.set_timer(TIMEEVENT, int(time_left * 1000 + 1))
                        old_timeout_time = self.timeout_time

                # Predict images, if we haven't done so already.
                while prediction_coroutine is not None:

                    # Can we do expensive prediction?
                    expensive_predict = not (needs_redraw or self.event_peek() or renpy.audio.music.is_playing("movie"))

                    result = prediction_coroutine.send(expensive_predict)

                    if not result:
                        prediction_coroutine = None
                        break

                    if not expensive_predict:
                        break

                # If we need to redraw again, do it if we don't have an
                # event going on.
                if (prediction_coroutine or needs_redraw) and not self.event_peek():
                    self.profile_time = get_time()
                    continue

                # Handle autosaving and persistent checking, as necessary.
                if not did_autosave:
                    renpy.loadsave.autosave()
                    did_autosave = True

                renpy.persistent.check_update()

                if needs_redraw or self.mouse_move or renpy.display.video.playing():
                    ev = self.event_poll()
                else:
                    ev = self.event_wait()

                if ev.type == pygame.NOEVENT:
                    continue

                self.profile_time = get_time()

                # Try to merge an TIMEEVENT with other timeevents.
                if ev.type == TIMEEVENT:
                    old_timeout_time = None
                    pygame.event.clear([TIMEEVENT])

                # On Android, where we have multiple mouse buttons, we can
                # merge a mouse down and mouse up event with its successor. This
                # prevents us from getting overwhelmed with too many events on
                # a multitouch screen.
                if android and (ev.type == pygame.MOUSEBUTTONDOWN or ev.type == pygame.MOUSEBUTTONUP):
                    pygame.event.clear(ev.type)

                # Handle redraw timeouts.
                if ev.type == REDRAW:
                    pygame.event.clear([REDRAW])
                    old_redraw_time = None
                    continue

                # Handle periodic events. This includes updating the mouse timers (and through the loop,
                # the mouse itself), and the audio system periodic calls.
                if ev.type == PERIODIC:
                    events = 1 + len(pygame.event.get([PERIODIC]))
                    self.ticks += events

                    if renpy.config.periodic_callback:
                        renpy.config.periodic_callback()

                    renpy.audio.audio.periodic()
                    renpy.display.tts.periodic()
                    continue


                # Handle quit specially for now.
                if ev.type == pygame.QUIT:
                    self.quit_event()
                    continue

                # Handle videoresize.
                if ev.type == pygame.VIDEORESIZE:
                    evs = pygame.event.get([pygame.VIDEORESIZE])

                    if len(evs):
                        ev = evs[-1]

                    if self.last_resize is None and renpy.windows:
                        self.last_resize = ev.size

                    if self.last_resize != ev.size:
                        self.last_resize = ev.size
                        self.set_mode((ev.w, ev.h))

                    continue

                # If we're ignoring touch events, and get a mouse up, stop
                # ignoring those events.
                if self.ignore_touch and \
                    ev.type == pygame.MOUSEBUTTONUP and \
                    ev.button == 1:

                    self.ignore_touch = False
                    continue


                # Merge mousemotion events.
                if ev.type == pygame.MOUSEMOTION:
                    evs = pygame.event.get([pygame.MOUSEMOTION])
                    if len(evs):
                        ev = evs[-1]

                    if renpy.windows:
                        self.mouse_focused = True

                # Handle mouse event time, and ignoring touch.
                if ev.type == pygame.MOUSEMOTION or \
                        ev.type == pygame.MOUSEBUTTONDOWN or \
                        ev.type == pygame.MOUSEBUTTONUP:

                    self.mouse_event_time = renpy.display.core.get_time()

                    if self.ignore_touch:
                        renpy.display.focus.mouse_handler(None, -1, -1, default=False)

                # Handle focus notifications.
                if ev.type == pygame.ACTIVEEVENT:

                    if ev.state & 1:
                        self.mouse_focused = ev.gain

                    if ev.state & 2:
                        self.keyboard_focused = ev.gain

                    if ev.state & 4:
                        if ev.gain:
                            self.restored()
                        else:
                            self.iconified()

                    pygame.key.set_mods(0)

                # This returns the event location. It also updates the
                # mouse state as necessary.
                x, y = renpy.display.draw.mouse_event(ev)

                ev, x, y = renpy.display.emulator.emulator(ev, x, y)
                if ev is None:
                    continue

                if not self.mouse_focused or self.ignore_touch:
                    x = -1
                    y = -1

                # This can set the event to None, to ignore it.
                ev = renpy.display.joystick.event(ev)
                if not ev:
                    continue

                # Handle skipping.
                renpy.display.behavior.skipping(ev)

                self.event_time = end_time = get_time()

                try:

                    # Handle the event normally.
                    rv = renpy.display.focus.mouse_handler(ev, x, y)

                    if rv is None:
                        rv = root_widget.event(ev, x, y, 0)

                    if rv is None:
                        rv = renpy.display.focus.key_handler(ev)

                    if rv is not None:
                        break

                    # Handle displayable inspector.
                    if renpy.config.inspector and renpy.display.behavior.inspector(ev):
                        l = self.surftree.main_displayables_at_point(x, y, renpy.config.transient_layers + renpy.config.context_clear_layers + renpy.config.overlay_layers)
                        renpy.game.invoke_in_new_context(renpy.config.inspector, l)

                except IgnoreEvent:
                    # An ignored event can change the timeout. So we want to
                    # process an TIMEEVENT to ensure that the timeout is
                    # set correctly.
                    self.post_time_event()


                # Check again after handling the event.
                needs_redraw |= renpy.display.render.process_redraws()

                if self.restart_interaction:
                    return True, None

            # If we were trans-paused and rv is true, suppress
            # transitions up to the next interaction.
            if trans_pause and rv:
                self.suppress_transition = True

            # But wait, there's more! The finally block runs some cleanup
            # after this.
            return False, rv

        except EndInteraction as e:
            return False, e.value

        finally:

            renpy.game.context().say_attributes = None

            # Clean out the overlay layers.
            for i in renpy.config.overlay_layers:
                scene_lists.clear(i)

            # Stop ongoing preloading.
            renpy.display.im.cache.end_tick()

            # We no longer disable periodic between interactions.
            # pygame.time.set_timer(PERIODIC, 0)

            pygame.time.set_timer(TIMEEVENT, 0)
            pygame.time.set_timer(REDRAW, 0)

            renpy.game.context().runtime += end_time - start_time

            # Restart the old interaction, which also causes a
            # redraw if needed.
            self.restart_interaction = True

            # print "It took", frames, "frames."

    def timeout(self, offset):
        if offset < 0:
            return

        if self.timeout_time:
            self.timeout_time = min(self.event_time + offset, self.timeout_time)
        else:
            self.timeout_time = self.event_time + offset

    def finish_pending(self):
        """
        Called before a quit or restart to finish any pending work that might
        block other threads.
        """

        self.check_background_screenshot()
