"""This module contains functions, classes and exceptions that can be
included in a Python program for Reeborg's World.
"""

# Important: Multiline docstrings should have their text start
# on the second line. This results in nicer formatting when
# using help().

# When generating documentation using sphinx, these modules are both
# unavailable and not needed
try:
    from browser import window
    RUR = window.RUR
except ImportError:
    from collections import defaultdict
    window = defaultdict(str)
    print("\n --> Skipping importing from browser for sphinx.\n")

# All functions from Javascript used below should have names of the form
# RUR._xyz_ and be defined in commands.js and methods should have names of
# the form RUR._UR.xyz_;  functions and methods should appear
# alphabetically in this English version, with the exception of Python-specific
# functions or classes that should appear near the end.


def ist_am_ziel():  #py:at_goal
    """
    Zeigt an, ob Reeborg am Zielfeld ist.

    Returns:
        True wenn Reeborg das Ziel erreicht hat, False sonst.
    """
    return RUR._at_goal_()

def baue_wand():  #py:build_wall
    """Instructs Reeborg to build a wall at the location in front of itself."""
    RUR._build_wall_()


def hat_objekt(obj=None):  #py:carries_object
    """
    Zeigt ab, ob Reeborg ein Objekt trägt oder nicht.

    Args:
        obj: optionaler Parameter mit dem Objektnamen als String.

    Returns:
        Wenn kein Argument angegeben wird, ein dict das die Objekte
        (Name und Nummer) enthält;
        Wenn kein Argument angegeben wird, die Anzahl der getragenen Objekte.
        a dict of the type of objects carried by Reeborg.
        If Reeborg carries no object, or not the specified one,
        the result is zero.

    Examples:

        >>> carries_object()
        {"token": 2, "apple": 1}
        >>> carries_object("token")
        2
        >>> carries_object("banana")
        0
    """
    if obj is not None:
        return RUR._carries_object_(obj)
    else:
        ans = RUR._carries_object_()
        if ans:
            return dict(ans)
        else:
            return 0

def loesche_ausgabe():  #py:clear_print
    """Erase all the text previously written using a call to print()."""
    RUR._clear_print_()

def gib_farbe():  #py:color_here
    '''Returns the value of the color found at Reeborg's location'''
    return RUR._color_here_()

def default_roboter():  #py:default_robot
    """Returns a recreated version of the default robot."""
    class Robot(UsedRobot):
        def __init__(self):
            self.body = RUR._default_robot_body_()
    return Robot()

def gib_roboter_mit_id(serial_number):  #py:default_robot
    """
    Not intended for normal use.

    If a robot with the given serial_number
    exists, this function returns a recreated version of a UsedRobot
    corresponding to that robot; otherwise, it returns None.
    """
    r = RUR.get_robot_body_by_id(serial_number)
    if r is None:
        return r
    class Robot(UsedRobot):
        def __init__(self):
            self.body = r
    return Robot()


def beenden():  #py:done
    """
    Causes a program's execution to end.

    When used in REPL mode, this results in the world's goal being checked.
    """
    RUR._done_()


def ist_vorne_frei():  #py:front_is_clear
    """
    Indicates if an obstacle (wall, fence, water, etc.) blocks the path.

    Returns:
       True if the path is clear (not blocked), False otherwise.
    """
    return RUR._front_is_clear_()


def ist_norden():  #py:is_facing_north
    """Indicates if Reeborg is facing North (top of the screen) or not."""
    return RUR._is_facing_north_()


def schritt():  #py:move
    """Move forward, by one grid position."""
    RUR._move_()


def neues_roboter_bild(images):  #py:new_robot_images
    """
    Allow to replace the images used for the robot.

    Instead of using this function, it is preferable to use
    RUR.new_robot_images in the Onload editor, so that the images can be
    fetched as quickly as possible.

        Args:
            images: a Python dict
            images[model]: The model name for the robot; if no value is
                specified, the name will be set to "anonymous".

            images["east"]  A url for the source of the image to be used
            for the robot in the East orientation. If it is not specified, the
            default "classic" image will be used.

            images["north"]  Similar to images["east"]

            images["west"]   Similar to images["east"]

            images["south"]  Similar to images["east"]
    """
    if "Ost" in images:
        images["east"] = images["Ost"]
    if "West" in images:
        images["west"] = images["West"]
    if "Nord" in images:
        images["north"] = images["Nord"]
    if "Süd" in images:
        images["south"] = images["Süd"]
    RUR._new_robot_images_(images)


def hervorheben_aus():  #py:no_highlight
    """
    Prevents code highlighting from occurring.

    This function has a similar effect to clicking the corresponding
    button in Reeborg's World.

    Code highlighting occurs thanks to some extra code inserted in a
    user's program prior to execution.  If this function is called
    with highlighting active, it will be disabled and the program will stop.
    If highlighting is not active, calls to this function will have no
    effect.
    """
    RUR._no_highlight_()


def ist_objekt_hier(obj=None):  #py:object_here
    """
    Indicates whether any type of objects are present at Reeborg's location.

    Args:
        obj: optional parameter which is the name of an object as a string.

    Returns:
        a list of the type of objects found.  If no object is present,
        or if the specified object is not found, the result is an empty list.

    Note: When using Javascript, instead of returning an empty list is no
    object if found, 'false' is returned.

    Examples:

        >>> object_here()
        ["token", "apple"]
        >>> object_here("token")
        ["token"]
        >>> object_here("banana")
        []
    """
    if obj is not None:
        ans = RUR._object_here_(obj)
    else:
        ans = RUR._object_here_()
    if ans:
        return list(ans)  # convert from JS list-like object to proper Python list
    else:
        return []


def male_zelle(color):  #py:paint_square
    """Fills the grid square where Reeborg is located with the specified color"""
    RUR._paint_square_(color)


def pause(ms=None):  #py:pause
    """
    Pauses a program's execution (playback).

    If an argument (time in milliseconds) is given, the execution
    automatically resumes after this time has elapsed.
    """
    if ms is None:
        RUR._pause_()
    else:
        RUR._pause_(ms)


def gib_koordinaten():
    '''
    Returns a tuple (x, y) giving the coordinates of the robot.

    The corresponding JavaScript function returns an array [x, y].
    '''
    body = RUR._default_robot_body_()
    return (body.x, body.y)


def gib_koordinaten_vorne():
    '''
    Returns a tuple (x, y) giving the coordinates of position immediately
    in front of the robot if the position is within the world boundaries,
    otherwise returns an empty tuple.

    The corresponding JavaScript function returns an array [x, y] or the value
    undefined if the position is not within the world boundaries.
    '''
    body = RUR._default_robot_body_()
    pos = RUR.get_position_in_front(body)
    if RUR.is_valid_position(pos["x"], pos["y"]):
        return (pos["x"], pos["y"])
    else:
        return tuple()


def drucke_html(html, replace=False):  #py:print_html
    """
    Intended primarily for world creators, this function is similar to
    print() except it can make use of html input.

    Args:
        html: the content (in html format) to be displayed.
        replace: if True, the html content will replace whatever was there
            already; otherwise, it is appended.

    """
    RUR._print_html_(html, replace)


def hinlegen(obj=None):  #py:put
    """
    Puts down an object.  If Reeborg carries more than one type of objects,
    the type must be specified as an argument, otherwise an exception
    will be raised.
    """
    if obj is None:
        RUR._put_()
    else:
        RUR._put_(obj)


def hinwerfen(obj=None):
    """
    Reeborg throws an object on the square in front of its current position.
    If Reeborg carries more than one type of objects,
    the type must be specified as an argument, otherwise an exception
    will be raised.
    """
    if obj is None:
        RUR._toss_()
    else:
        RUR._toss_(obj)

def setze_aufnahme(boolean):  #py:recording
    """
    Stops or starts recording changes occuring in the world.

    Args:
        bool: True if recording is desired, False otherwise.

    Returns: the previous recording state value.
    """
    return RUR._recording_(boolean)


def entferne_roboter():  #py:remove_robots
    """Remove all robots found in the world."""
    RUR._remove_robots_()


def ist_rechts_frei():  #py:right_is_clear
    """
    Indicates if an obstacle (wall, fence, water, etc.) is on the
    immediate right of Reeborg.

    Returns:
       True if an obstacle is on Reeborg's right, False otherwise.
    """
    return RUR._right_is_clear_()


def setze_max_anzahl_instruktionen(nb):  #py:set_max_nb_steps
    """
    Intended primarily for world creators, this function allows
    to change the default maximum number of instructions executed in a
    program (1000) by a different value.
    """
    RUR._set_max_nb_steps_(nb)


def setze_spur_farbe(color):  #py:set_trace_color
    """
    Changes the color of the trace (oil leak).

       Args:
            color (string): four formats are possible: named color,
                   rgb and rgba, and hexadecimal notation.

       Examples::

            >>> set_trace_color("red")
            >>> set_trace_color("rgb(125, 0, 0)")
            >>> set_trace_color("rgba(125, 0, 0, 0.5)")
            >>> set_trace_color("#FF00FF")
    """
    RUR._set_trace_color_(color)


def setze_spur_stil(style="default"):  #py:set_trace_style
    """
    Changes the trace style of the robot.

       Args:
            style: "thick", "invisible" and "default" are the three possible
                   arguments.  "invisible" is equivalent to
                   set_trace_color("rgba(0, 0, 0, 0)"), that is it sets
                   the colour to a completely transparent value.

                   The "thick" style is centered on the path followed,
                   so that it is impossible to distinguish between motion
                   to the left or to the right, and right handed turns
                   appear to be done all at once, if one only looks at the
                   trace.
    """
    if style not in ["thick", "default", "invisible"]:
        raise ReeborgError("Unrecognized style in set_trace_style().")
    RUR._set_trace_style_(style)


def ton_an(boolean):  #py:sound
    """Activate or deactivate sound effects."""
    RUR._sound_(boolean)


def nimm(obj=None):  #py:take
    """
    Takes an object.  If more than one type of objects is at Reeborg's location,
    the type must be specified as an argument, otherwise an exception
    will be raised.
    """
    if obj is None:
        RUR._take_()
    else:
        RUR._take_(obj)


def denke(ms):  #py:think
    """
    Set a time delay (in milliseconds) between Reeborg's actions
    played back.

    Returns the time delay value previously used.
    """
    return RUR._think_(ms)


def drehe_links():  #py:turn_left
    """Reeborg turns to its left."""
    RUR._turn_left_()


def ist_wand_vorne():  #py:wall_in_front
    """
    Indicates if a wall blocks the way.

    Returns:
       True if the path blocked by a wall, False otherwise.
    """
    return RUR._wall_in_front_()


def ist_wand_rechts():  #py:wall_on_right
    """
    Indicates if an wall is on the immediate right of Reeborg.

    Returns:
       True if a wall is on Reeborg's right, False otherwise.
    """
    return RUR._wall_on_right_()


def ErstelleEigenesMenu(content):  #py:MakeCustomMenu
    """
    Designed for use by educators.  Makes it possible to create custom world
    menus.  See the documentation for more details.
    """
    RUR._MakeCustomMenu_(content)


def Welt(url, shortname=None):  #py:World
    """
    Allows to select a specific world within a program.

    If the world currently shown is different than the one selected by
    using this function, the result of running the program will simply
    be to change the world - the rest of the program will be ignored.

    If the desired world is already selected, this command is ignored
    and the rest of the program is executed.

    If the world is not already present in the html selector,
    it will be added.

       Args:
            url: two possible choices: either a name appearing in the html
                 selector, or a URL ("link") to a world defined on some
                 website.

            shortname: Optional parameter; if specified, this will be the
                       name shown in the html selector.

       Examples:

           >>> World("Home 1")  # world included by default
           >>> World("http://reeborg.ca/my_world")   # fictitious example
           # the name http://reeborg.ca/my_world will be added to the selector
           >>> World("http://reeborg.ca/my_world", "Hello")
           # The name "Hello" will be shown in the selector instead
           # of the full url
    """
    if shortname is None:
        RUR._World_(url)
    else:
        RUR._World_(url, shortname)


class UsedRobot(object):  #py:UR
    '''The OOP version of Reeborg'''
    def __init__(self, x=1, y=1, orientation='e', tokens=None, **kwargs):  #py:UR.__init__
        """
        Creates a UsedRobot.

            Args:
               x: horizontal coordinate; an integer greater or equal to 1.
               y: vertical coordinate; an integer greater or equal to 1.
               orientation (string):
                            one of "e" or "east","w" or "west",
                            "n" or "north", "s" or "south", or "random".
               tokens: Initial number of tokens to give to the robot;
                       its value must be a positive integer, or the string
                       "infinite" to indicate an infinite quantity.

               other: any other keyword argument will be taken as a number of
                      objects to give to a robot.
        """
        if tokens is None:
            robot = RUR.robot.create_robot(x, y, orientation)
        else:
            robot = RUR.robot.create_robot(x, y, orientation, tokens)
        self.body = robot
        for key in kwargs:
            if key not in {'x', 'y', 'orientation', 'tokens'}:
                RUR.give_object_to_robot(key, kwargs[key], robot)
        RUR.add_robot(self.body)

    def __str__(self):  #py:UR.__str__
        location = "({}, {})".format(self.body.x, self.body.y)
        if self.body._orientation == RUR.EAST:
            facing = "facing East"
        elif self.body._orientation == RUR.WEST:
            facing = "facing West"
        elif self.body._orientation == RUR.NORTH:
            facing = "facing North"
        elif self.body._orientation == RUR.SOUTH:
            facing = "facing South"

        carries = ''
        for obj in self.body.objects:
            if self.body.objects[obj] == 'inf':
                carries += "\ncarries an infinite number of %s" % obj
            else:
                carries += '\ncarries %s %s' % (self.body.objects[obj], obj)
        if not carries:
            carries = 'carries no objects'
        return "UsedRobot at {} {} {}.".format(location, facing, carries)

    def ist_am_ziel(self):  #py:UR.at_goal
        """
        Indicates if Reeborg has reached the desired location.

        Returns:
            True if Reeborg has reached its goal, False otherwise.
        """
        return RUR._UR.at_goal_(self.body)

    def baue_wand(self):  #py:UR.build_wall
        """
        Instructs Reeborg to build a wall at the location in
        front of itself.
        """
        RUR._UR.build_wall_(self.body)

    def traegt_objekt(self, obj=None):  #py:UR.carries_object
        """
        Indicates whether Reeborg carries an object or not.

        Args:
            obj: optional parameter which is the name of an object as a string.

        Returns:
            If no argument is specified, a dict showing the objects carried
            (name and number); if an argument is specified, it returns the
            number of arguments carried.
            a dict of the type of objects carried by Reeborg.
            If Reeborg carries no object, or not the specified one,
            the result is zero.

        Examples:

            >>> reeborg = UsedRobot()
            >>> reeborg.carries_object()
            {"token": 2}
            >>> reeborg.carries_object("token")
            2
            >>> reeborg.carries_object("banana")
            0
        """

        if obj is not None:
            return RUR._UR.carries_object_(self.body, obj)
        else:
            ans = RUR._UR.carries_object_(self.body)
            if ans:
                return dict(ans)
            else:
                return 0

    def gib_farbe(self):  #py:color_here
        '''Returns the value of the color found at Reeborg's location'''
        return RUR._UR.color_here_(self.body)

    def farbe_hier(self):
        '''Returns the value of the color found at Reeborg's location'''
        return self.gib_farbe()


    def ist_vorne_frei(self):  #py:UR.front_is_clear
        """
        Indicates if an obstacle (wall, fence, water, etc.) blocks the path.

        Returns:
           True if the path is clear (not blocked), False otherwise.
        """
        return RUR._UR.front_is_clear_(self.body)

    def is_facing_north(self):  #py:UR.is_facing_north
        """Indicates if Reeborg is facing North (top of the screen) or not."""
        return RUR._UR.is_facing_north_(self.body)

    def move(self):  #py:UR.move
        """Move forward, by one grid position."""
        RUR._UR.move_(self.body)

    def object_here(self, obj=None):  #py:UR.object_here
        """
        Indicates whether any type of objects are present at Reeborg's location.

        Args:
            obj: optional parameter which is the name of an object as a string.

        Returns:
            a list of the type of objects found.  If no object is present,
            or if the specified object is not found, the result is an
            empty list.

        Note: When using Javascript, instead of returning an empty list is no
        object if found, 'false' is returned.

        Examples:

            >>> reeborg = UsedRobot()
            >>> reeborg.object_here()
            ["token", "apple"]
            >>> reeborg.object_here("token")
            ["token"]
            >>> reeborg.object_here("banana")
            []
        """
        if obj is not None:
            ans = RUR._UR.object_here_(self.body, obj)
        else:
            ans = RUR._UR.object_here_(self.body)
        if ans:
            return list(ans)
        else:
            return []


    def paint_square(self, color):
        """Fills the grid square where Reeborg is located with the specified color"""
        RUR._UR.paint_square_(color, self.body)


    def position_here(self):
        '''
        Returns a tuple (x, y) giving the coordinates of the robot.

        The corresponding JavaScript function returns an array [x, y].
        '''
        return (self.body.x, self.body.y)


    def position_in_front(self):
        '''
        Returns a tuple (x, y) giving the coordinates of position immediately
        in front of the robot if the position is within the world boundaries,
        otherwise returns an empty tuple.

        The corresponding JavaScript function returns an array [x, y] or the
        value undefined if the position is not within the world boundaries.
        '''
        pos = RUR.get_position_in_front(self.body)
        if RUR.is_valid_position(pos["x"], pos["y"]):
            return (pos["x"], pos["y"])
        else:
            return tuple()


    def put(self, obj=None):  #py:UR.put
        """
        Puts down an object.  If Reeborg carries more than one type of objects,
        the type must be specified as an argument, otherwise an exception
        will be raised.
        """
        if obj is None:
            RUR._UR.put_(self.body)
        else:
            RUR._UR.put_(self.body, obj)

    def toss(self, obj=None):
        """
        Reeborg throws an object on the square in front of its current position.
        If Reeborg carries more than one type of objects,
        the type must be specified as an argument, otherwise an exception
        will be raised.
        """
        if obj is None:
            RUR._UR.toss_(self.body)
        else:
            RUR._UR.toss_(self.body, obj)

    def right_is_clear(self):  #py:UR.right_is_clear
        """
        Indicates if an obstacle (wall, fence, water, etc.) is on the
        immediate right of Reeborg.

        Returns:
           True if an obstacle is on Reeborg's right, False otherwise.
        """
        return RUR._UR.right_is_clear_(self.body)

    def set_model(self, model):  #py:UR.set_model
        """
        Select the model (images) for the robot.
        """
        RUR._UR.set_model_(self.body, model)

    def set_trace_color(self, color):  #py:UR.set_trace_color
        """
        Changes the color of the trace (oil leak).

           Args:
                color (string): four formats are possible: named color,
                       rgb and rgba, and hexadecimal notation.

           Examples::

                >>> reeborg = UsedRobot()
                >>> reeborg.set_trace_color("red")
                >>> reeborg.set_trace_color("rgb(125, 0, 0)")
                >>> reeborg.set_trace_color("rgba(125, 0, 0, 0.5)")
                >>> reeborg.set_trace_color("#FF00FF")
        """
        RUR._UR.set_trace_color_(self.body, color)

    def set_trace_style(self, style):  #py:UR.set_trace_style
        """
        Changes the trace style of the robot.

           Args:
                style: "thick", "invisible" and "default" are the three
                       possible arguments.  "invisible" is equivalent to
                       set_trace_color("rgba(0, 0, 0, 0)"), that is it sets
                       the colour to a completely transparent value.


                       The "thick" style is centered on the path followed,
                       so that it is impossible to distinguish between motion
                       to the left or to the right, and right handed turns
                       appear to be done all at once, if one only looks at the
                       trace.
        """
        if style not in ["thick", "default", "invisible"]:
            raise ReeborgError("Unrecognized style in set_trace_style().")
        RUR._UR.set_trace_style_(self.body, style)

    def take(self, obj=None):  #py:UR.take
        """
        Instruct Reeborg to take an object.
        If more than one type of objects is at Reeborg's location,
        the type must be specified as an argument, otherwise an exception
        will be raised.
        """
        if obj is None:
            RUR._UR.take_(self.body)
        else:
            RUR._UR.take_(self.body, obj)

    def turn_left(self):  #py:UR.turn_left
        """Reeborg turns to its left."""
        RUR._UR.turn_left_(self.body)

    def wall_in_front(self):  #py:UR.wall_in_front
        """
        Indicates if a wall blocks the way.

        Returns:
           True if the path blocked by a wall, False otherwise.
        """
        return RUR._UR.wall_in_front_(self.body)

    def wall_on_right(self):  #py:UR.wall_on_right
        """
        Indicates if an wall is on the immediate right of Reeborg.

        Returns:
           True if a wall is on Reeborg's right, False otherwise.
        """
        return RUR._UR.wall_on_right_(self.body)

#py:python_specific

def add_watch(expr):  #py:add_watch
    """
    Adds a valid Python expression (given as a string) to
    the watch list.
    """
    RUR.add_watch(expr)


class ReeborgOK(Exception):  #py:RE
    """
    Exception specific to Reeborg's World. Used to indicate that a
    program ended with the correct result using a custom message.

        Args:
            message
    """

    def __init__(self, message):  #py:RE.__init__
        self.reeborg_success = message
        self.message = message
        super().__init__(message)

    def __str__(self):  #py:RE.__str__
        return self.reeborg_success
try:
    window['ReeborgOK_de'] = ReeborgOK
    window['ReeborgOk_de'] = ReeborgOK # preventing an annoying typo
except:
    pass
ReeborgOk = ReeborgOK  # preventing an annoying typo

class ReeborgError(Exception):  #py:RE
    """
    Exception specific to Reeborg's World.

       Examples:

            def done():  #py:
                message = "You can not use done() for this task."
                raise ReeborgError(message)

            #---- or ------

            try:
                move()
            except ReeborgError:   # ignore a collision
                turn_left()
    """

    def __init__(self, message):  #py:RE.__init__
        self.reeborg_failure = message
        self.message = message
        super().__init__(message)

    def __str__(self):  #py:RE.__str__
        return self.reeborg_failure
try:
    window['ReeborgError_de'] = ReeborgError
except:
    pass


class WallCollisionError(ReeborgError):  #py:WCE
    """
    Exception specific to Reeborg's World.

    This exception is raised when Reeborg hits a wall.
    """
    pass
try:
    window['WallCollisionError_de'] = WallCollisionError
except:
    pass

class MissingObjectError(ReeborgError):
    """
    Exception specific to Reeborg's World.

    Can occur when Reeborg attempts to take or put down an object.
    """
    pass
try:
    window['MissingObjectError_de'] = MissingObjectError
except:
    pass


class SatelliteInfo():  #py:SI
    '''Used to obtain a map of the world'''
    @property
    def world_map(self):  #py:SI.world_map
        '''Returns a dict containing information about world.
        '''
        import json
        return json.loads(RUR._world_map())

    def print_world_map(self):  #py:SI.print_world_map
        '''Prints a formatted copy of the world'''
        print(RUR._world_map())
