"""
This is our MPD protocol implementation.

This is partly based upon the `MPD protocol documentation
<http://www.musicpd.org/doc/protocol/>`_, which is a useful resource, but it is
rather incomplete with regards to data formats, both for requests and
responses. Thus, we have had to talk a great deal with the the original `MPD
server <http://mpd.wikia.com/>`_ using telnet to get the details we need to
implement our own MPD server which is compatible with the numerous existing
`MPD clients <http://mpd.wikia.com/wiki/Clients>`_.
"""

import datetime as dt
import logging
import re

from mopidy.mpd import (MpdAckError, MpdArgError, MpdUnknownCommand,
    MpdNoExistError, MpdNotImplemented)
from mopidy.utils import flatten

logger = logging.getLogger('mopidy.mpd.frontend')

_commands = set()
_request_handlers = {}

def handle_pattern(pattern):
    """
    Decorator for connecting command handlers to command patterns.

    If you use named groups in the pattern, the decorated method will get the
    groups as keyword arguments. If the group is optional, remember to give the
    argument a default value.

    For example, if the command is ``do that thing`` the ``what`` argument will
    be ``this thing``::

        @handle_pattern('^do (?P<what>.+)$')
        def do(what):
            ...

    :param pattern: regexp pattern for matching commands
    :type pattern: string
    """
    def decorator(func):
        match = re.search('([a-z_]+)', pattern)
        if match is not None:
            _commands.add(match.group())
        if pattern in _request_handlers:
            raise ValueError(u'Tried to redefine handler for %s with %s' % (
                pattern, func))
        _request_handlers[pattern] = func
        func.__doc__ = '        - **Pattern:** ``%s``\n\n%s' % (
            pattern, func.__doc__ or '')
        return func
    return decorator

class MpdFrontend(object):
    def __init__(self, backend=None):
        self.backend = backend
        self.command_list = False
        self.command_list_ok = False

    def handle_request(self, request, command_list_index=None):
        if self.command_list is not False and request != u'command_list_end':
            self.command_list.append(request)
            return None
        try:
            (handler, kwargs) = self.find_handler(request)
            result = handler(self, **kwargs)
        except MpdAckError as e:
            if command_list_index is not None:
                e.index = command_list_index
            return self.handle_response(e.get_mpd_ack(), add_ok=False)
        if request in (u'command_list_begin', u'command_list_ok_begin'):
            return None
        if command_list_index is not None:
            return self.handle_response(result, add_ok=False)
        return self.handle_response(result)

    def find_handler(self, request):
        for pattern in _request_handlers:
            matches = re.match(pattern, request)
            if matches is not None:
                return (_request_handlers[pattern], matches.groupdict())
        command = request.split(' ')[0]
        if command in _commands:
            raise MpdArgError(u'incorrect arguments', command=command)
        raise MpdUnknownCommand(command=command)

    def handle_response(self, result, add_ok=True):
        response = []
        if result is None:
            result = []
        elif not isinstance(result, list):
            result = [result]
        for line in flatten(result):
            if isinstance(line, dict):
                for (key, value) in line.items():
                    response.append(u'%s: %s' % (key, value))
            elif isinstance(line, tuple):
                (key, value) = line
                response.append(u'%s: %s' % (key, value))
            else:
                response.append(line)
        if add_ok and (not response or not response[-1].startswith(u'ACK')):
            response.append(u'OK')
        return response

    @handle_pattern(r'^disableoutput "(?P<outputid>\d+)"$')
    def _audio_output_disableoutput(self, outputid):
        """
        *musicpd.org, audio output section:*

            ``disableoutput``

            Turns an output off.
        """
        raise MpdNotImplemented # TODO

    @handle_pattern(r'^enableoutput "(?P<outputid>\d+)"$')
    def _audio_output_enableoutput(self, outputid):
        """
        *musicpd.org, audio output section:*

            ``enableoutput``

            Turns an output on.
        """
        raise MpdNotImplemented # TODO

    @handle_pattern(r'^outputs$')
    def _audio_output_outputs(self):
        """
        *musicpd.org, audio output section:*

            ``outputs``

            Shows information about all outputs.
        """
        return [
            ('outputid', 0),
            ('outputname', self.backend.__class__.__name__),
            ('outputenabled', 1),
        ]

    @handle_pattern(r'^command_list_begin$')
    def _command_list_begin(self):
        """
        *musicpd.org, command list section:*

            To facilitate faster adding of files etc. you can pass a list of
            commands all at once using a command list. The command list begins
            with ``command_list_begin`` or ``command_list_ok_begin`` and ends
            with ``command_list_end``.

            It does not execute any commands until the list has ended. The
            return value is whatever the return for a list of commands is. On
            success for all commands, ``OK`` is returned. If a command fails,
            no more commands are executed and the appropriate ``ACK`` error is
            returned. If ``command_list_ok_begin`` is used, ``list_OK`` is
            returned for each successful command executed in the command list.
        """
        self.command_list = []
        self.command_list_ok = False

    @handle_pattern(r'^command_list_end$')
    def _command_list_end(self):
        """See :meth:`_command_list_begin`."""
        if self.command_list is False:
            # Test for False exactly, and not e.g. empty list
            raise MpdUnknownCommand(command='command_list_end')
        (command_list, self.command_list) = (self.command_list, False)
        (command_list_ok, self.command_list_ok) = (self.command_list_ok, False)
        result = []
        for i, command in enumerate(command_list):
            response = self.handle_request(command, command_list_index=i)
            if response is not None:
                result.append(response)
            if response and response[-1].startswith(u'ACK'):
                return result
            if command_list_ok:
                response.append(u'list_OK')
        return result

    @handle_pattern(r'^command_list_ok_begin$')
    def _command_list_ok_begin(self):
        """See :meth:`_command_list_begin`."""
        self.command_list = []
        self.command_list_ok = True

    @handle_pattern(r'^close$')
    def _connection_close(self):
        """
        *musicpd.org, connection section:*

            ``close``

            Closes the connection to MPD.
        """
        # TODO Does not work after multiprocessing branch merge
        #self.session.do_close()

    @handle_pattern(r'^kill$')
    def _connection_kill(self):
        """
        *musicpd.org, connection section:*

            ``kill``

            Kills MPD.
        """
        # TODO Does not work after multiprocessing branch merge
        #self.session.do_kill()

    @handle_pattern(r'^password "(?P<password>[^"]+)"$')
    def _connection_password(self, password):
        """
        *musicpd.org, connection section:*

            ``password {PASSWORD}``

            This is used for authentication with the server. ``PASSWORD`` is
            simply the plaintext password.
        """
        raise MpdNotImplemented # TODO

    @handle_pattern(r'^ping$')
    def _connection_ping(self):
        """
        *musicpd.org, connection section:*

            ``ping``

            Does nothing but return ``OK``.
        """
        pass

    @handle_pattern(r'^add "(?P<uri>[^"]*)"$')
    def _current_playlist_add(self, uri):
        """
        *musicpd.org, current playlist section:*

            ``add {URI}``

            Adds the file ``URI`` to the playlist (directories add recursively).
            ``URI`` can also be a single file.
        """
        track = self.backend.library.lookup(uri)
        if track is None:
            raise MpdNoExistError(
                u'directory or file not found', command=u'add')
        self.backend.current_playlist.add(track)

    @handle_pattern(r'^addid "(?P<uri>[^"]*)"( "(?P<songpos>\d+)")*$')
    def _current_playlist_addid(self, uri, songpos=None):
        """
        *musicpd.org, current playlist section:*

            ``addid {URI} [POSITION]``

            Adds a song to the playlist (non-recursive) and returns the song id.

            ``URI`` is always a single file or URL. For example::

                addid "foo.mp3"
                Id: 999
                OK
        """
        if songpos is not None:
            songpos = int(songpos)
        track = self.backend.library.lookup(uri)
        if track is None:
            raise MpdNoExistError(u'No such song', command=u'addid')
        if songpos and songpos > len(self.backend.current_playlist.tracks):
            raise MpdArgError(u'Bad song index', command=u'addid')
        cp_track = self.backend.current_playlist.add(track, at_position=songpos)
        return ('Id', cp_track[0])

    @handle_pattern(r'^delete "(?P<start>\d+):(?P<end>\d+)*"$')
    def _current_playlist_delete_range(self, start, end=None):
        """
        *musicpd.org, current playlist section:*

            ``delete [{POS} | {START:END}]``

            Deletes a song from the playlist.
        """
        start = int(start)
        if end is not None:
            end = int(end)
        else:
            end = len(self.backend.current_playlist.tracks)
        cp_tracks = self.backend.current_playlist.cp_tracks[start:end]
        if not cp_tracks:
            raise MpdArgError(u'Bad song index', command=u'delete')
        for (cpid, track) in cp_tracks:
            self.backend.current_playlist.remove(cpid=cpid)

    @handle_pattern(r'^delete "(?P<songpos>\d+)"$')
    def _current_playlist_delete_songpos(self, songpos):
        """See :meth:`_current_playlist_delete_range`"""
        try:
            songpos = int(songpos)
            (cpid, track) = self.backend.current_playlist.cp_tracks[songpos]
            self.backend.current_playlist.remove(cpid=cpid)
        except IndexError:
            raise MpdArgError(u'Bad song index', command=u'delete')

    @handle_pattern(r'^deleteid "(?P<cpid>\d+)"$')
    def _current_playlist_deleteid(self, cpid):
        """
        *musicpd.org, current playlist section:*

            ``deleteid {SONGID}``

            Deletes the song ``SONGID`` from the playlist
        """
        try:
            cpid = int(cpid)
            return self.backend.current_playlist.remove(cpid=cpid)
        except LookupError:
            raise MpdNoExistError(u'No such song', command=u'deleteid')

    @handle_pattern(r'^clear$')
    def _current_playlist_clear(self):
        """
        *musicpd.org, current playlist section:*

            ``clear``

            Clears the current playlist.
        """
        self.backend.current_playlist.clear()

    @handle_pattern(r'^move "(?P<start>\d+):(?P<end>\d+)*" "(?P<to>\d+)"$')
    def _current_playlist_move_range(self, start, to, end=None):
        """
        *musicpd.org, current playlist section:*

            ``move [{FROM} | {START:END}] {TO}``

            Moves the song at ``FROM`` or range of songs at ``START:END`` to
            ``TO`` in the playlist.
        """
        if end is None:
            end = len(self.backend.current_playlist.tracks)
        start = int(start)
        end = int(end)
        to = int(to)
        self.backend.current_playlist.move(start, end, to)

    @handle_pattern(r'^move "(?P<songpos>\d+)" "(?P<to>\d+)"$')
    def _current_playlist_move_songpos(self, songpos, to):
        """See :meth:`_current_playlist_move_range`."""
        songpos = int(songpos)
        to = int(to)
        self.backend.current_playlist.move(songpos, songpos + 1, to)

    @handle_pattern(r'^moveid "(?P<cpid>\d+)" "(?P<to>\d+)"$')
    def _current_playlist_moveid(self, cpid, to):
        """
        *musicpd.org, current playlist section:*

            ``moveid {FROM} {TO}``

            Moves the song with ``FROM`` (songid) to ``TO`` (playlist index) in
            the playlist. If ``TO`` is negative, it is relative to the current
            song in the playlist (if there is one).
        """
        cpid = int(cpid)
        to = int(to)
        cp_track = self.backend.current_playlist.get(cpid=cpid)
        position = self.backend.current_playlist.cp_tracks.index(cp_track)
        self.backend.current_playlist.move(position, position + 1, to)

    @handle_pattern(r'^playlist$')
    def _current_playlist_playlist(self):
        """
        *musicpd.org, current playlist section:*

            ``playlist``

            Displays the current playlist.

            .. note::

                Do not use this, instead use ``playlistinfo``.
        """
        return self._current_playlist_playlistinfo()

    @handle_pattern(r'^playlistfind (?P<tag>[^"]+) "(?P<needle>[^"]+)"$')
    @handle_pattern(r'^playlistfind "(?P<tag>[^"]+)" "(?P<needle>[^"]+)"$')
    def _current_playlist_playlistfind(self, tag, needle):
        """
        *musicpd.org, current playlist section:*

            ``playlistfind {TAG} {NEEDLE}``

            Finds songs in the current playlist with strict matching.

        *GMPC:*

        - does not add quotes around the tag.
        """
        if tag == 'filename':
            try:
                cp_track = self.backend.current_playlist.get(uri=needle)
                return cp_track[1].mpd_format()
            except LookupError:
                return None
        raise MpdNotImplemented # TODO

    @handle_pattern(r'^playlistid( "(?P<cpid>\d+)")*$')
    def _current_playlist_playlistid(self, cpid=None):
        """
        *musicpd.org, current playlist section:*

            ``playlistid {SONGID}``

            Displays a list of songs in the playlist. ``SONGID`` is optional
            and specifies a single song to display info for.
        """
        if cpid is not None:
            try:
                cpid = int(cpid)
                cp_track = self.backend.current_playlist.get(cpid=cpid)
                position = self.backend.current_playlist.cp_tracks.index(
                    cp_track)
                return cp_track[1].mpd_format(position=position, cpid=cpid)
            except LookupError:
                raise MpdNoExistError(u'No such song', command=u'playlistid')
        else:
            return self.backend.current_playlist.mpd_format()

    @handle_pattern(r'^playlistinfo$')
    @handle_pattern(r'^playlistinfo "(?P<songpos>-?\d+)"$')
    @handle_pattern(r'^playlistinfo "(?P<start>\d+):(?P<end>\d+)*"$')
    def _current_playlist_playlistinfo(self, songpos=None,
            start=None, end=None):
        """
        *musicpd.org, current playlist section:*

            ``playlistinfo [[SONGPOS] | [START:END]]``

            Displays a list of all songs in the playlist, or if the optional
            argument is given, displays information only for the song
            ``SONGPOS`` or the range of songs ``START:END``.

        *ncmpc and mpc:*

        - uses negative indexes, like ``playlistinfo "-1"``, to request
          the entire playlist
        """
        if songpos == "-1":
            songpos = None

        if songpos is not None:
            songpos = int(songpos)
            start = songpos
            end = songpos + 1
            if start == -1:
                end = None
            return self.backend.current_playlist.mpd_format(start, end)
        else:
            if start is None:
                start = 0
            start = int(start)
            if not (0 <= start <= len(self.backend.current_playlist.tracks)):
                raise MpdArgError(u'Bad song index', command=u'playlistinfo')
            if end is not None:
                end = int(end)
                if end > len(self.backend.current_playlist.tracks):
                    end = None
            return self.backend.current_playlist.mpd_format(start, end)

    @handle_pattern(r'^playlistsearch "(?P<tag>[^"]+)" "(?P<needle>[^"]+)"$')
    @handle_pattern(r'^playlistsearch (?P<tag>\S+) "(?P<needle>[^"]+)"$')
    def _current_playlist_playlistsearch(self, tag, needle):
        """
        *musicpd.org, current playlist section:*

            ``playlistsearch {TAG} {NEEDLE}``

            Searches case-sensitively for partial matches in the current
            playlist.

        *GMPC:*

        - does not add quotes around the tag
        - uses ``filename`` and ``any`` as tags
        """
        raise MpdNotImplemented # TODO

    @handle_pattern(r'^plchanges "(?P<version>\d+)"$')
    def _current_playlist_plchanges(self, version):
        """
        *musicpd.org, current playlist section:*

            ``plchanges {VERSION}``

            Displays changed songs currently in the playlist since ``VERSION``.

            To detect songs that were deleted at the end of the playlist, use
            ``playlistlength`` returned by status command.
        """
        # XXX Naive implementation that returns all tracks as changed
        if int(version) < self.backend.current_playlist.version:
            return self.backend.current_playlist.mpd_format()

    @handle_pattern(r'^plchangesposid "(?P<version>\d+)"$')
    def _current_playlist_plchangesposid(self, version):
        """
        *musicpd.org, current playlist section:*

            ``plchangesposid {VERSION}``

            Displays changed songs currently in the playlist since ``VERSION``.
            This function only returns the position and the id of the changed
            song, not the complete metadata. This is more bandwidth efficient.

            To detect songs that were deleted at the end of the playlist, use
            ``playlistlength`` returned by status command.
        """
        # XXX Naive implementation that returns all tracks as changed
        if int(version) != self.backend.current_playlist.version:
            result = []
            for (position, (cpid, track)) in enumerate(
                    self.backend.current_playlist.cp_tracks):
                result.append((u'cpos', position))
                result.append((u'Id', cpid))
            return result

    @handle_pattern(r'^shuffle$')
    @handle_pattern(r'^shuffle "(?P<start>\d+):(?P<end>\d+)*"$')
    def _current_playlist_shuffle(self, start=None, end=None):
        """
        *musicpd.org, current playlist section:*

            ``shuffle [START:END]``

            Shuffles the current playlist. ``START:END`` is optional and
            specifies a range of songs.
        """
        if start is not None:
            start = int(start)
        if end is not None:
            end = int(end)
        self.backend.current_playlist.shuffle(start, end)

    @handle_pattern(r'^swap "(?P<songpos1>\d+)" "(?P<songpos2>\d+)"$')
    def _current_playlist_swap(self, songpos1, songpos2):
        """
        *musicpd.org, current playlist section:*

            ``swap {SONG1} {SONG2}``

            Swaps the positions of ``SONG1`` and ``SONG2``.
        """
        songpos1 = int(songpos1)
        songpos2 = int(songpos2)
        tracks = self.backend.current_playlist.tracks
        song1 = tracks[songpos1]
        song2 = tracks[songpos2]
        del tracks[songpos1]
        tracks.insert(songpos1, song2)
        del tracks[songpos2]
        tracks.insert(songpos2, song1)
        self.backend.current_playlist.load(tracks)

    @handle_pattern(r'^swapid "(?P<cpid1>\d+)" "(?P<cpid2>\d+)"$')
    def _current_playlist_swapid(self, cpid1, cpid2):
        """
        *musicpd.org, current playlist section:*

            ``swapid {SONG1} {SONG2}``

            Swaps the positions of ``SONG1`` and ``SONG2`` (both song ids).
        """
        cpid1 = int(cpid1)
        cpid2 = int(cpid2)
        cp_track1 = self.backend.current_playlist.get(cpid=cpid1)
        cp_track2 = self.backend.current_playlist.get(cpid=cpid2)
        position1 = self.backend.current_playlist.cp_tracks.index(cp_track1)
        position2 = self.backend.current_playlist.cp_tracks.index(cp_track2)
        self._current_playlist_swap(position1, position2)

    @handle_pattern(r'^$')
    def _empty(self):
        """The original MPD server returns ``OK`` on an empty request.``"""
        pass

    @handle_pattern(r'^count "(?P<tag>[^"]+)" "(?P<needle>[^"]*)"$')
    def _music_db_count(self, tag, needle):
        """
        *musicpd.org, music database section:*

            ``count {TAG} {NEEDLE}``

            Counts the number of songs and their total playtime in the db
            matching ``TAG`` exactly.
        """
        return [('songs', 0), ('playtime', 0)] # TODO

    @handle_pattern(r'^find (?P<field>([Aa]lbum|[Aa]rtist|[Tt]itle)) '
        r'"(?P<what>[^"]+)"$')
    @handle_pattern(r'^find "(?P<field>(album|artist|title))" '
        r'"(?P<what>[^"]+)"$')
    @handle_pattern(r'^find (?P<field>(album)) '
        r'"(?P<what>[^"]+)" artist "([^"]+)"$')
    def _music_db_find(self, field, what):
        """
        *musicpd.org, music database section:*

            ``find {TYPE} {WHAT}``

            Finds songs in the db that are exactly ``WHAT``. ``TYPE`` should be
            ``album``, ``artist``, or ``title``. ``WHAT`` is what to find.

        *GMPC:*

        - does not add quotes around the field argument.
        - also uses ``find album "[ALBUM]" artist "[ARTIST]"`` to list album
          tracks.

        *ncmpc:*

        - does not add quotes around the field argument.
        - capitalizes the type argument.
        """
        field = field.lower()
        if field == u'title':
            field = u'track'
        return self.backend.library.find_exact(field, what).mpd_format()

    @handle_pattern(r'^findadd "(?P<field>(album|artist|title))" '
        r'"(?P<what>[^"]+)"$')
    def _music_db_findadd(self, field, what):
        """
        *musicpd.org, music database section:*

            ``findadd {TYPE} {WHAT}``

            Finds songs in the db that are exactly ``WHAT`` and adds them to
            current playlist. ``TYPE`` can be any tag supported by MPD.
            ``WHAT`` is what to find.
        """
        result = self._music_db_find(field, what)
        # TODO Add result to current playlist
        #return result

    @handle_pattern(r'^list (?P<field>[Aa]rtist)$')
    @handle_pattern(r'^list "(?P<field>[Aa]rtist)"$')
    @handle_pattern(r'^list (?P<field>album( artist)?)( "(?P<artist>[^"]+)")*$')
    @handle_pattern(r'^list "(?P<field>album(" "artist)?)"( "(?P<artist>[^"]+)")*$')
    def _music_db_list(self, field, artist=None):
        """
        *musicpd.org, music database section:*

            ``list {TYPE} [ARTIST]``

            Lists all tags of the specified type. ``TYPE`` should be ``album``
            or ``artist``.

            ``ARTIST`` is an optional parameter when type is ``album``, this
            specifies to list albums by an artist.

        *GMPC:*

        - does not add quotes around the field argument.
        - asks for multiple fields, i.e.::

            list album artist "an artist name"

          returns the albums available for the asked artist::

            list album artist "Tiesto"
            Album: Radio Trance Vol 4-Promo-CD
            Album: Ur  A Tear in the Open CDR
            Album: Simple Trance 2004 Step One
            Album: In Concert 05-10-2003

        *ncmpc:*

        - does not add quotes around the field argument.
        - capitalizes the field argument.
        """
        field = field.lower()
        pass # TODO

    @handle_pattern(r'^listall "(?P<uri>[^"]+)"')
    def _music_db_listall(self, uri):
        """
        *musicpd.org, music database section:*

            ``listall [URI]``

            Lists all songs and directories in ``URI``.
        """
        raise MpdNotImplemented # TODO

    @handle_pattern(r'^listallinfo "(?P<uri>[^"]+)"')
    def _music_db_listallinfo(self, uri):
        """
        *musicpd.org, music database section:*

            ``listallinfo [URI]``

            Same as ``listall``, except it also returns metadata info in the
            same format as ``lsinfo``.
        """
        raise MpdNotImplemented # TODO

    @handle_pattern(r'^lsinfo$')
    @handle_pattern(r'^lsinfo "(?P<uri>[^"]*)"$')
    def _music_db_lsinfo(self, uri=None):
        """
        *musicpd.org, music database section:*

            ``lsinfo [URI]``

            Lists the contents of the directory ``URI``.

            When listing the root directory, this currently returns the list of
            stored playlists. This behavior is deprecated; use
            ``listplaylists`` instead.

        MPD returns the same result, including both playlists and the files and
        directories located at the root level, for both ``lsinfo``, ``lsinfo
        ""``, and ``lsinfo "/"``.
        """
        if uri is None or uri == u'/' or uri == u'':
            return self._stored_playlists_listplaylists()
        raise MpdNotImplemented # TODO

    @handle_pattern(r'^rescan( "(?P<uri>[^"]+)")*$')
    def _music_db_rescan(self, uri=None):
        """
        *musicpd.org, music database section:*

            ``rescan [URI]``

            Same as ``update``, but also rescans unmodified files.
        """
        return self._music_db_update(uri, rescan_unmodified_files=True)

    @handle_pattern(r'^search '
        r'(?P<field>([Aa]lbum|[Aa]rtist|[Ff]ilename|[Tt]itle|[Aa]ny)) '
        r'"(?P<what>[^"]+)"$')
    @handle_pattern(r'^search "(?P<field>(album|artist|filename|title|any))" '
        r'"(?P<what>[^"]+)"$')
    def _music_db_search(self, field, what):
        """
        *musicpd.org, music database section:*

            ``search {TYPE} {WHAT}``

            Searches for any song that contains ``WHAT``. ``TYPE`` can be
            ``title``, ``artist``, ``album`` or ``filename``. Search is not
            case sensitive.

        *GMPC:*

        - does not add quotes around the field argument.
        - uses the undocumented field ``any``.
        - searches for multiple words like this::

            search any "foo" any "bar" any "baz"

        *ncmpc:*

        - does not add quotes around the field argument.
        - capitalizes the field argument.
        """
        # TODO Support GMPC multi-word search
        field = field.lower()
        if field == u'title':
            field = u'track'
        return self.backend.library.search(field, what).mpd_format()

    @handle_pattern(r'^update( "(?P<uri>[^"]+)")*$')
    def _music_db_update(self, uri=None, rescan_unmodified_files=False):
        """
        *musicpd.org, music database section:*

            ``update [URI]``

            Updates the music database: find new files, remove deleted files,
            update modified files.

            ``URI`` is a particular directory or song/file to update. If you do
            not specify it, everything is updated.

            Prints ``updating_db: JOBID`` where ``JOBID`` is a positive number
            identifying the update job. You can read the current job id in the
            ``status`` response.
        """
        return {'updating_db': 0} # TODO

    @handle_pattern(r'^consume "(?P<state>[01])"$')
    def _playback_consume(self, state):
        """
        *musicpd.org, playback section:*

            ``consume {STATE}``

            Sets consume state to ``STATE``, ``STATE`` should be 0 or
            1. When consume is activated, each song played is removed from
            playlist.
        """
        if int(state):
            self.backend.playback.consume = True
        else:
            self.backend.playback.consume = False

    @handle_pattern(r'^crossfade "(?P<seconds>\d+)"$')
    def _playback_crossfade(self, seconds):
        """
        *musicpd.org, playback section:*

            ``crossfade {SECONDS}``

            Sets crossfading between songs.
        """
        seconds = int(seconds)
        raise MpdNotImplemented # TODO

    @handle_pattern(r'^next$')
    def _playback_next(self):
        """
        *musicpd.org, playback section:*

            ``next``

            Plays next song in the playlist.

        *MPD's behaviour when affected by repeat/random/single/consume:*

            Given a playlist of three tracks numbered 1, 2, 3, and a currently
            playing track ``c``. ``next_track`` is defined at the track that
            will be played upon calls to ``next``.

            Tests performed on MPD 0.15.4-1ubuntu3.

            ======  ======  ======  =======  =====  =====  =====  =====
                        Inputs                    next_track
            -------------------------------  -------------------  -----
            repeat  random  single  consume  c = 1  c = 2  c = 3  Notes
            ======  ======  ======  =======  =====  =====  =====  =====
            T       T       T       T        2      3      EOPL
            T       T       T       .        Rand   Rand   Rand   [1]
            T       T       .       T        Rand   Rand   Rand   [4]
            T       T       .       .        Rand   Rand   Rand   [4]
            T       .       T       T        2      3      EOPL
            T       .       T       .        2      3      1
            T       .       .       T        3      3      EOPL
            T       .       .       .        2      3      1
            .       T       T       T        Rand   Rand   Rand   [3]
            .       T       T       .        Rand   Rand   Rand   [3]
            .       T       .       T        Rand   Rand   Rand   [2]
            .       T       .       .        Rand   Rand   Rand   [2]
            .       .       T       T        2      3      EOPL
            .       .       T       .        2      3      EOPL
            .       .       .       T        2      3      EOPL
            .       .       .       .        2      3      EOPL
            ======  ======  ======  =======  =====  =====  =====  =====

            - When end of playlist (EOPL) is reached, the current track is
              unset.
            - [1] When *random* and *single* is combined, ``next`` selects
              a track randomly at each invocation, and not just the next track
              in an internal prerandomized playlist.
            - [2] When *random* is active, ``next`` will skip through
              all tracks in the playlist in random order, and finally EOPL is
              reached.
            - [3] *single* has no effect in combination with *random*
              alone, or *random* and *consume*.
            - [4] When *random* and *repeat* is active, EOPL is never
              reached, but the playlist is played again, in the same random
              order as the first time.

        """
        return self.backend.playback.next()

    @handle_pattern(r'^pause "(?P<state>[01])"$')
    def _playback_pause(self, state):
        """
        *musicpd.org, playback section:*

            ``pause {PAUSE}``

            Toggles pause/resumes playing, ``PAUSE`` is 0 or 1.
        """
        if int(state):
            self.backend.playback.pause()
        else:
            self.backend.playback.resume()

    @handle_pattern(r'^play$')
    def _playback_play(self):
        """
        The original MPD server resumes from the paused state on ``play``
        without arguments.
        """
        return self.backend.playback.play()

    @handle_pattern(r'^playid "(?P<cpid>\d+)"$')
    @handle_pattern(r'^playid "(?P<cpid>-1)"$')
    def _playback_playid(self, cpid):
        """
        *musicpd.org, playback section:*

            ``playid [SONGID]``

            Begins playing the playlist at song ``SONGID``.

        *GMPC:*

        - issues ``playid "-1"`` after playlist replacement to start playback
          at the first track.
        """
        cpid = int(cpid)
        try:
            if cpid == -1:
                cp_track = self.backend.current_playlist.cp_tracks[0]
            else:
                cp_track = self.backend.current_playlist.get(cpid=cpid)
            return self.backend.playback.play(cp_track)
        except LookupError:
            raise MpdNoExistError(u'No such song', command=u'playid')

    @handle_pattern(r'^play "(?P<songpos>\d+)"$')
    @handle_pattern(r'^play "(?P<songpos>-1)"$')
    def _playback_playpos(self, songpos):
        """
        *musicpd.org, playback section:*

            ``play [SONGPOS]``

            Begins playing the playlist at song number ``SONGPOS``.

        *MPoD:*

        - issues ``play "-1"`` after playlist replacement to start playback at
          the first track.
        """
        songpos = int(songpos)
        try:
            if songpos == -1:
                cp_track = self.backend.current_playlist.cp_tracks[0]
            else:
                cp_track = self.backend.current_playlist.cp_tracks[songpos]
            return self.backend.playback.play(cp_track)
        except IndexError:
            raise MpdArgError(u'Bad song index', command=u'play')

    @handle_pattern(r'^previous$')
    def _playback_previous(self):
        """
        *musicpd.org, playback section:*

            ``previous``

            Plays previous song in the playlist.

        *MPD's behaviour when affected by repeat/random/single/consume:*

            Given a playlist of three tracks numbered 1, 2, 3, and a currently
            playing track ``c``. ``previous_track`` is defined at the track
            that will be played upon ``previous`` calls.

            Tests performed on MPD 0.15.4-1ubuntu3.

            ======  ======  ======  =======  =====  =====  =====
                        Inputs                  previous_track
            -------------------------------  -------------------
            repeat  random  single  consume  c = 1  c = 2  c = 3
            ======  ======  ======  =======  =====  =====  =====
            T       T       T       T        Rand?  Rand?  Rand?
            T       T       T       .        3      1      2
            T       T       .       T        Rand?  Rand?  Rand?
            T       T       .       .        3      1      2
            T       .       T       T        3      1      2
            T       .       T       .        3      1      2
            T       .       .       T        3      1      2
            T       .       .       .        3      1      2
            .       T       T       T        c      c      c
            .       T       T       .        c      c      c
            .       T       .       T        c      c      c
            .       T       .       .        c      c      c
            .       .       T       T        1      1      2
            .       .       T       .        1      1      2
            .       .       .       T        1      1      2
            .       .       .       .        1      1      2
            ======  ======  ======  =======  =====  =====  =====

            - If :attr:`time_position` of the current track is 15s or more,
              ``previous`` should do a seek to time position 0.

        """
        return self.backend.playback.previous()

    @handle_pattern(r'^random "(?P<state>[01])"$')
    def _playback_random(self, state):
        """
        *musicpd.org, playback section:*

            ``random {STATE}``

            Sets random state to ``STATE``, ``STATE`` should be 0 or 1.
        """
        if int(state):
            self.backend.playback.random = True
        else:
            self.backend.playback.random = False

    @handle_pattern(r'^repeat "(?P<state>[01])"$')
    def _playback_repeat(self, state):
        """
        *musicpd.org, playback section:*

            ``repeat {STATE}``

            Sets repeat state to ``STATE``, ``STATE`` should be 0 or 1.
        """
        if int(state):
            self.backend.playback.repeat = True
        else:
            self.backend.playback.repeat = False

    @handle_pattern(r'^replay_gain_mode "(?P<mode>(off|track|album))"$')
    def _playback_replay_gain_mode(self, mode):
        """
        *musicpd.org, playback section:*

            ``replay_gain_mode {MODE}``

            Sets the replay gain mode. One of ``off``, ``track``, ``album``.

            Changing the mode during playback may take several seconds, because
            the new settings does not affect the buffered data.

            This command triggers the options idle event.
        """
        raise MpdNotImplemented # TODO

    @handle_pattern(r'^replay_gain_status$')
    def _playback_replay_gain_status(self):
        """
        *musicpd.org, playback section:*

            ``replay_gain_status``

            Prints replay gain options. Currently, only the variable
            ``replay_gain_mode`` is returned.
        """
        return u'off' # TODO

    @handle_pattern(r'^seek "(?P<songpos>\d+)" "(?P<seconds>\d+)"$')
    def _playback_seek(self, songpos, seconds):
        """
        *musicpd.org, playback section:*

            ``seek {SONGPOS} {TIME}``

            Seeks to the position ``TIME`` (in seconds) of entry ``SONGPOS`` in
            the playlist.
        """
        raise MpdNotImplemented # TODO

    @handle_pattern(r'^seekid "(?P<cpid>\d+)" "(?P<seconds>\d+)"$')
    def _playback_seekid(self, cpid, seconds):
        """
        *musicpd.org, playback section:*

            ``seekid {SONGID} {TIME}``

            Seeks to the position ``TIME`` (in seconds) of song ``SONGID``.
        """
        raise MpdNotImplemented # TODO

    @handle_pattern(r'^setvol "(?P<volume>[-+]*\d+)"$')
    def _playback_setvol(self, volume):
        """
        *musicpd.org, playback section:*

            ``setvol {VOL}``

            Sets volume to ``VOL``, the range of volume is 0-100.
        """
        volume = int(volume)
        if volume < 0:
            volume = 0
        if volume > 100:
            volume = 100
        self.backend.mixer.volume = volume

    @handle_pattern(r'^single "(?P<state>[01])"$')
    def _playback_single(self, state):
        """
        *musicpd.org, playback section:*

            ``single {STATE}``

            Sets single state to ``STATE``, ``STATE`` should be 0 or 1. When
            single is activated, playback is stopped after current song, or
            song is repeated if the ``repeat`` mode is enabled.
        """
        if int(state):
            self.backend.playback.single = True
        else:
            self.backend.playback.single = False

    @handle_pattern(r'^stop$')
    def _playback_stop(self):
        """
        *musicpd.org, playback section:*

            ``stop``

            Stops playing.
        """
        self.backend.playback.stop()

    @handle_pattern(r'^commands$')
    def _reflection_commands(self):
        """
        *musicpd.org, reflection section:*

            ``commands``

            Shows which commands the current user has access to.

        As permissions is not implemented, any user has access to all commands.
        """
        commands = sorted(list(_commands))

        # Not shown by MPD in its command list
        commands.remove('command_list_begin')
        commands.remove('command_list_ok_begin')
        commands.remove('command_list_end')
        commands.remove('idle')
        commands.remove('noidle')
        commands.remove('sticker')

        return [('command', c) for c in commands]

    @handle_pattern(r'^decoders$')
    def _reflection_decoders(self):
        """
        *musicpd.org, reflection section:*

            ``decoders``

            Print a list of decoder plugins, followed by their supported
            suffixes and MIME types. Example response::

                plugin: mad
                suffix: mp3
                suffix: mp2
                mime_type: audio/mpeg
                plugin: mpcdec
                suffix: mpc
        """
        raise MpdNotImplemented # TODO

    @handle_pattern(r'^notcommands$')
    def _reflection_notcommands(self):
        """
        *musicpd.org, reflection section:*

            ``notcommands``

            Shows which commands the current user does not have access to.

        As permissions is not implemented, any user has access to all commands.
        """
        pass

    @handle_pattern(r'^tagtypes$')
    def _reflection_tagtypes(self):
        """
        *musicpd.org, reflection section:*

            ``tagtypes``

            Shows a list of available song metadata.
        """
        pass # TODO

    @handle_pattern(r'^urlhandlers$')
    def _reflection_urlhandlers(self):
        """
        *musicpd.org, reflection section:*

            ``urlhandlers``

            Gets a list of available URL handlers.
        """
        return [(u'handler', uri) for uri in self.backend.uri_handlers]

    @handle_pattern(r'^clearerror$')
    def _status_clearerror(self):
        """
        *musicpd.org, status section:*

            ``clearerror``

            Clears the current error message in status (this is also
            accomplished by any command that starts playback).
        """
        raise MpdNotImplemented # TODO

    @handle_pattern(r'^currentsong$')
    def _status_currentsong(self):
        """
        *musicpd.org, status section:*

            ``currentsong``

            Displays the song info of the current song (same song that is
            identified in status).
        """
        if self.backend.playback.current_track is not None:
            return self.backend.playback.current_track.mpd_format(
                position=self.backend.playback.current_playlist_position,
                cpid=self.backend.playback.current_cpid)

    @handle_pattern(r'^idle$')
    @handle_pattern(r'^idle (?P<subsystems>.+)$')
    def _status_idle(self, subsystems=None):
        """
        *musicpd.org, status section:*

            ``idle [SUBSYSTEMS...]``

            Waits until there is a noteworthy change in one or more of MPD's
            subsystems. As soon as there is one, it lists all changed systems
            in a line in the format ``changed: SUBSYSTEM``, where ``SUBSYSTEM``
            is one of the following:

            - ``database``: the song database has been modified after update.
            - ``update``: a database update has started or finished. If the
              database was modified during the update, the database event is
              also emitted.
            - ``stored_playlist``: a stored playlist has been modified,
              renamed, created or deleted
            - ``playlist``: the current playlist has been modified
            - ``player``: the player has been started, stopped or seeked
            - ``mixer``: the volume has been changed
            - ``output``: an audio output has been enabled or disabled
            - ``options``: options like repeat, random, crossfade, replay gain

            While a client is waiting for idle results, the server disables
            timeouts, allowing a client to wait for events as long as MPD runs.
            The idle command can be canceled by sending the command ``noidle``
            (no other commands are allowed). MPD will then leave idle mode and
            print results immediately; might be empty at this time.

            If the optional ``SUBSYSTEMS`` argument is used, MPD will only send
            notifications when something changed in one of the specified
            subsystems.
        """
        pass # TODO

    @handle_pattern(r'^noidle$')
    def _status_noidle(self):
        """See :meth:`_status_idle`."""
        pass # TODO

    @handle_pattern(r'^stats$')
    def _status_stats(self):
        """
        *musicpd.org, status section:*

            ``stats``

            Displays statistics.

            - ``artists``: number of artists
            - ``songs``: number of albums
            - ``uptime``: daemon uptime in seconds
            - ``db_playtime``: sum of all song times in the db
            - ``db_update``: last db update in UNIX time
            - ``playtime``: time length of music played
        """
        return {
            'artists': 0, # TODO
            'albums': 0, # TODO
            'songs': 0, # TODO
            # TODO Does not work after multiprocessing branch merge
            'uptime': 0, # self.session.stats_uptime(),
            'db_playtime': 0, # TODO
            'db_update': 0, # TODO
            'playtime': 0, # TODO
        }

    @handle_pattern(r'^status$')
    def _status_status(self):
        """
        *musicpd.org, status section:*

            ``status``

            Reports the current status of the player and the volume level.

            - ``volume``: 0-100
            - ``repeat``: 0 or 1
            - ``single``: 0 or 1
            - ``consume``: 0 or 1
            - ``playlist``: 31-bit unsigned integer, the playlist version
              number
            - ``playlistlength``: integer, the length of the playlist
            - ``state``: play, stop, or pause
            - ``song``: playlist song number of the current song stopped on or
              playing
            - ``songid``: playlist songid of the current song stopped on or
              playing
            - ``nextsong``: playlist song number of the next song to be played
            - ``nextsongid``: playlist songid of the next song to be played
            - ``time``: total time elapsed (of current playing/paused song)
            - ``elapsed``: Total time elapsed within the current song, but with
              higher resolution.
            - ``bitrate``: instantaneous bitrate in kbps
            - ``xfade``: crossfade in seconds
            - ``audio``: sampleRate``:bits``:channels
            - ``updatings_db``: job id
            - ``error``: if there is an error, returns message here
        """
        result = [
            ('volume', self.__status_status_volume()),
            ('repeat', self.__status_status_repeat()),
            ('random', self.__status_status_random()),
            ('single', self.__status_status_single()),
            ('consume', self.__status_status_consume()),
            ('playlist', self.__status_status_playlist_version()),
            ('playlistlength', self.__status_status_playlist_length()),
            ('xfade', self.__status_status_xfade()),
            ('state', self.__status_status_state()),
        ]
        if self.backend.playback.current_track is not None:
            result.append(('song', self.__status_status_songpos()))
            result.append(('songid', self.__status_status_songid()))
        if self.backend.playback.state in (
                self.backend.playback.PLAYING, self.backend.playback.PAUSED):
            result.append(('time', self.__status_status_time()))
            result.append(('elapsed', self.__status_status_time_elapsed()))
            result.append(('bitrate', self.__status_status_bitrate()))
        return result

    def __status_status_bitrate(self):
        if self.backend.playback.current_track is not None:
            return self.backend.playback.current_track.bitrate

    def __status_status_consume(self):
        if self.backend.playback.consume:
            return 1
        else:
            return 0

    def __status_status_playlist_length(self):
        return len(self.backend.current_playlist.tracks)

    def __status_status_playlist_version(self):
        return self.backend.current_playlist.version

    def __status_status_random(self):
        return int(self.backend.playback.random)

    def __status_status_repeat(self):
        return int(self.backend.playback.repeat)

    def __status_status_single(self):
        return int(self.backend.playback.single)

    def __status_status_songid(self):
        if self.backend.playback.current_cpid is not None:
            return self.backend.playback.current_cpid
        else:
            return self.__status_status_songpos()

    def __status_status_songpos(self):
        return self.backend.playback.current_playlist_position

    def __status_status_state(self):
        if self.backend.playback.state == self.backend.playback.PLAYING:
            return u'play'
        elif self.backend.playback.state == self.backend.playback.STOPPED:
            return u'stop'
        elif self.backend.playback.state == self.backend.playback.PAUSED:
            return u'pause'

    def __status_status_time(self):
        return u'%s:%s' % (self.__status_status_time_elapsed() // 1000,
            self.__status_status_time_total() // 1000)

    def __status_status_time_elapsed(self):
        return self.backend.playback.time_position

    def __status_status_time_total(self):
        if self.backend.playback.current_track is None:
            return 0
        elif self.backend.playback.current_track.length is None:
            return 0
        else:
            return self.backend.playback.current_track.length

    def __status_status_volume(self):
        if self.backend.mixer.volume is not None:
            return self.backend.mixer.volume
        else:
            return 0

    def __status_status_xfade(self):
        return 0 # TODO

    @handle_pattern(r'^sticker delete "(?P<field>[^"]+)" '
        r'"(?P<uri>[^"]+)"( "(?P<name>[^"]+)")*$')
    def _sticker_delete(self, field, uri, name=None):
        """
        *musicpd.org, sticker section:*

            ``sticker delete {TYPE} {URI} [NAME]``

            Deletes a sticker value from the specified object. If you do not
            specify a sticker name, all sticker values are deleted.
        """
        raise MpdNotImplemented # TODO

    @handle_pattern(r'^sticker find "(?P<field>[^"]+)" "(?P<uri>[^"]+)" '
        r'"(?P<name>[^"]+)"$')
    def _sticker_find(self, field, uri, name):
        """
        *musicpd.org, sticker section:*

            ``sticker find {TYPE} {URI} {NAME}``

            Searches the sticker database for stickers with the specified name,
            below the specified directory (``URI``). For each matching song, it
            prints the ``URI`` and that one sticker's value.
        """
        raise MpdNotImplemented # TODO

    @handle_pattern(r'^sticker get "(?P<field>[^"]+)" "(?P<uri>[^"]+)" '
        r'"(?P<name>[^"]+)"$')
    def _sticker_get(self, field, uri, name):
        """
        *musicpd.org, sticker section:*

            ``sticker get {TYPE} {URI} {NAME}``

            Reads a sticker value for the specified object.
        """
        raise MpdNotImplemented # TODO

    @handle_pattern(r'^sticker list "(?P<field>[^"]+)" "(?P<uri>[^"]+)"$')
    def _sticker_list(self, field, uri):
        """
        *musicpd.org, sticker section:*

            ``sticker list {TYPE} {URI}``

            Lists the stickers for the specified object.
        """
        raise MpdNotImplemented # TODO

    @handle_pattern(r'^sticker set "(?P<field>[^"]+)" "(?P<uri>[^"]+)" '
        r'"(?P<name>[^"]+)" "(?P<value>[^"]+)"$')
    def _sticker_set(self, field, uri, name, value):
        """
        *musicpd.org, sticker section:*

            ``sticker set {TYPE} {URI} {NAME} {VALUE}``

            Adds a sticker value to the specified object. If a sticker item
            with that name already exists, it is replaced.
        """
        raise MpdNotImplemented # TODO

    @handle_pattern(r'^listplaylist "(?P<name>[^"]+)"$')
    def _stored_playlists_listplaylist(self, name):
        """
        *musicpd.org, stored playlists section:*

            ``listplaylist {NAME}``

            Lists the files in the playlist ``NAME.m3u``.

        Output format::

            file: relative/path/to/file1.flac
            file: relative/path/to/file2.ogg
            file: relative/path/to/file3.mp3
        """
        try:
            return ['file: %s' % t.uri
                for t in self.backend.stored_playlists.get(name=name).tracks]
        except LookupError:
            raise MpdNoExistError(u'No such playlist', command=u'listplaylist')

    @handle_pattern(r'^listplaylistinfo "(?P<name>[^"]+)"$')
    def _stored_playlists_listplaylistinfo(self, name):
        """
        *musicpd.org, stored playlists section:*

            ``listplaylistinfo {NAME}``

            Lists songs in the playlist ``NAME.m3u``.

        Output format:

            Standard track listing, with fields: file, Time, Title, Date,
            Album, Artist, Track
        """
        try:
            return self.backend.stored_playlists.get(name=name).mpd_format()
        except LookupError:
            raise MpdNoExistError(
                u'No such playlist', command=u'listplaylistinfo')

    @handle_pattern(r'^listplaylists$')
    def _stored_playlists_listplaylists(self):
        """
        *musicpd.org, stored playlists section:*

            ``listplaylists``

            Prints a list of the playlist directory.

            After each playlist name the server sends its last modification
            time as attribute ``Last-Modified`` in ISO 8601 format. To avoid
            problems due to clock differences between clients and the server,
            clients should not compare this value with their local clock.

        Output format::

            playlist: a
            Last-Modified: 2010-02-06T02:10:25Z
            playlist: b
            Last-Modified: 2010-02-06T02:11:08Z
        """
        result = []
        for playlist in self.backend.stored_playlists.playlists:
            result.append((u'playlist', playlist.name))
            last_modified = (playlist.last_modified or
                dt.datetime.now()).isoformat()
            # Remove microseconds
            last_modified = last_modified.split('.')[0]
            # Add time zone information
            # TODO Convert to UTC before adding Z
            last_modified = last_modified + 'Z'
            result.append((u'Last-Modified', last_modified))
        return result

    @handle_pattern(r'^load "(?P<name>[^"]+)"$')
    def _stored_playlists_load(self, name):
        """
        *musicpd.org, stored playlists section:*

            ``load {NAME}``

            Loads the playlist ``NAME.m3u`` from the playlist directory.
        """
        matches = self.backend.stored_playlists.search(name)
        if matches:
            self.backend.current_playlist.load(matches[0].tracks)

    @handle_pattern(r'^playlistadd "(?P<name>[^"]+)" "(?P<uri>[^"]+)"$')
    def _stored_playlist_playlistadd(self, name, uri):
        """
        *musicpd.org, stored playlists section:*

            ``playlistadd {NAME} {URI}``

            Adds ``URI`` to the playlist ``NAME.m3u``.

            ``NAME.m3u`` will be created if it does not exist.
        """
        raise MpdNotImplemented # TODO

    @handle_pattern(r'^playlistclear "(?P<name>[^"]+)"$')
    def _stored_playlist_playlistclear(self, name):
        """
        *musicpd.org, stored playlists section:*

            ``playlistclear {NAME}``

            Clears the playlist ``NAME.m3u``.
        """
        raise MpdNotImplemented # TODO

    @handle_pattern(r'^playlistdelete "(?P<name>[^"]+)" "(?P<songpos>\d+)"$')
    def _stored_playlist_playlistdelete(self, name, songpos):
        """
        *musicpd.org, stored playlists section:*

            ``playlistdelete {NAME} {SONGPOS}``

            Deletes ``SONGPOS`` from the playlist ``NAME.m3u``.
        """
        raise MpdNotImplemented # TODO

    @handle_pattern(r'^playlistmove "(?P<name>[^"]+)" '
        r'"(?P<from_pos>\d+)" "(?P<to_pos>\d+)"$')
    def _stored_playlist_playlistmove(self, name, from_pos, to_pos):
        """
        *musicpd.org, stored playlists section:*

            ``playlistmove {NAME} {SONGID} {SONGPOS}``

            Moves ``SONGID`` in the playlist ``NAME.m3u`` to the position
            ``SONGPOS``.

        *Clarifications:*

            - The second argument is not a ``SONGID`` as used elsewhere in the
              protocol documentation, but just the ``SONGPOS`` to move *from*,
              i.e. ``playlistmove {NAME} {FROM_SONGPOS} {TO_SONGPOS}``.
        """
        raise MpdNotImplemented # TODO

    @handle_pattern(r'^rename "(?P<old_name>[^"]+)" "(?P<new_name>[^"]+)"$')
    def _stored_playlists_rename(self, old_name, new_name):
        """
        *musicpd.org, stored playlists section:*

            ``rename {NAME} {NEW_NAME}``

            Renames the playlist ``NAME.m3u`` to ``NEW_NAME.m3u``.
        """
        raise MpdNotImplemented # TODO

    @handle_pattern(r'^rm "(?P<name>[^"]+)"$')
    def _stored_playlists_rm(self, name):
        """
        *musicpd.org, stored playlists section:*

            ``rm {NAME}``

            Removes the playlist ``NAME.m3u`` from the playlist directory.
        """
        raise MpdNotImplemented # TODO

    @handle_pattern(r'^save "(?P<name>[^"]+)"$')
    def _stored_playlists_save(self, name):
        """
        *musicpd.org, stored playlists section:*

            ``save {NAME}``

            Saves the current playlist to ``NAME.m3u`` in the playlist
            directory.
        """
        raise MpdNotImplemented # TODO
