"""
:Author: P Hargrave <resixianatgmaildotcom>

:What it does:
Allows control of an mpd server from within weechat.

Also provides the 'now playing' (np) command to publish
currently playing track information into the current buffer.

:Usage:
    /mpc

    * When called with no command 'np' is assumed

    /mpc <command> <arg1 arg2 ...>

    *Arguments may be comma or space separated.

:Configuration:
==============|==========================================
Variable name | Description [Default Value]
==============|==========================================

format.........This is a string that will be used to show
               the 'now playing' banner.
               Use `$` to precede any variable name.

               Several keys are already avaialable just
               from the mpd.MPDClient() `currentsong`
               command. (So set verbose=1 then do a
               `mpc currentsong` to see all the keys)

               As a convenience the following are also
               available:
                $title_or_file,
                $length_min, $length_sec, $pct,
                $pos_min, $pos_sec,
                $bitrate

host...........The host where your mpd runs ["localhost"]
password.......The password used to connect to mpd [""]
port...........The port to connect to mpd (usually 6600)
verbose........If set then lots more will be printed
ch_pause.......These are just inserted in the short_status
ch_play        template displayed whenever an action is
ch_stop        performed.
autoswitch.....If set automatically switch to created
               buffers (e.g. playlist)

To restore the default value of any config variable just
/unset <the_variable> and reload the plugin.

:Requires:
 python-mpd: Python MPD client library
             http://mpd.wikia.com/wiki/ClientLib:python-mpd

:Etc...:
This script was originally devised while hacking mpdnp.py
script by Henning Hasemann. Since there is basically nothing
left of the original and all the controls are new I decided
'mpc' was more appropriate now.

All the commands are those available from the mpd.py MPDClient
object, mostly I have done little to wrap these for weechat,
but a few common ones can show some basic information in the
current buffer (e.g. when you pause/play/stop). Unfortunately
these functions are not documented afaik and so I can't even
be sure of the arguments or types, so the user may need to
do some experimenting ;)

Released under GPL license.
"""

import weechat as wc
from mpd import MPDClient, CommandError
from string import Template
from os.path import basename, splitext
from operator import itemgetter

DEFAULT_FMT = \
    "/me is listening to: $artist - $title_or_file ($length_min:$length_sec)"

wc.register("mpc", "Perry Hargrave", "0.2", "GPL", "mpc for weechat", "", "")

DEFAULT_CONFIG = {
    "host"      : "localhost",
    "port"      : "6600",
    "format"    : DEFAULT_FMT,
    "password"  : "",
    "verbose"   : "1",
    "debug"     : "0",
    "ch_pause"  : "||",
    "ch_play"   : ">>",
    "ch_stop"   : "--",
    "shortstats": "MPC : $state : $artist : $title",
    "playinfo"  : "$pos: $artist : $title : $album : $track : $time",
    "autoswitch": "0",
}

class __MPC(object):
    _mpdc   = None
    _host   = DEFAULT_CONFIG['host']
    _port   = DEFAULT_CONFIG['port']
    verbose = bool(int(DEFAULT_CONFIG['verbose']))
    debug   = bool(int(DEFAULT_CONFIG['debug']))

    # Switch automatically to created buffers
    autoswitch = bool(int(DEFAULT_CONFIG['autoswitch']))

    # State symbols
    state_chars = {
            'pause':DEFAULT_CONFIG['ch_pause'],
            'play':DEFAULT_CONFIG['ch_play'],
            'stop':DEFAULT_CONFIG['ch_stop'],
            }

    def __init__(self, wc_buffer=None):
        object.__init__(self)
        self._mpdc = MPDClient()
        self._commands = self._mpdc._commands
        self.wcb = wc_buffer

        self.verbose = bool(int((wc.config_get_plugin("verbose"))))
        self.debug = bool(int((wc.config_get_plugin("debug"))))
        self.autoswitch = bool(int((wc.config_get_plugin("autoswitch"))))

        for k, v in self.state_chars.iteritems():
            self.state_chars[k] = wc.config_get_plugin('ch_' + k)

        self._commands['np'] = None

    @property
    def shortstats(self): return wc.config_get_plugin("shortstats")

    def __getattribute__(self, attr):
        try:
            return object.__getattribute__(self, attr)
        except AttributeError:
            return self._mpdc.__getattr__(attr)

    def _get_status(self, key):
        return self._mpdc.status()[key]

    def _print_current(f):
        """Do `f`, then prints the short_status in the buffer.
        """
        def pf(self, *args, **kwargs):
            robj = f(self, *args, **kwargs)
            if not self.verbose: return robj

            # Show the status in the current buffer
            csong = self._mpdc.currentsong()
            csong.update({'state':self.state_chars[self._get_status('state')]})
            wc.prnt(self.wcb or wc.current_buffer(),
                    Template(self.shortstats).safe_substitute(csong))
            return robj

        return pf

    def connect(self):
        self._host = wc.config_get_plugin("host")
        self._port = int(wc.config_get_plugin("port"))
        self._mpdc.connect(host=self._host, port=self._port)
        pw = wc.config_get_plugin("password")
        if len(pw) > 0: self._mpdc.password(pw)

        if self.debug: wc.prnt(self.wcb or wc.current_buffer(),
                               'mpc debug: Connected')

    def currentsong(self):
        ds = self._mpdc.currentsong()
        itime = int(ds['time'])
        ipos  = int(ds['pos'])
        pct   = int(100 * (ipos / itime))

        ds.update({
            "title_or_file" : ds['title'] or splitext(basename(ds['file']))[0],
            "pos_sec"       : "%02d" % (ipos / 60),
            "pos_min"       : str(ipos / 60),
            "length_sec"    : "%02d" % (itime % 60),
            "length_min"    : str(itime / 60),
            "pct"           : "%2.0f" % pct,
            "bitrate"       : self._get_status('bitrate') + "kbps",
            })

        return ds

    def np(self):
        """Pushes result of np template substitution to current buffer.
        """
        ds  = self.currentsong()
        if len(ds) == 0:
            wc.prnt(wc.current_buffer(), "MPC: ERROR: mpd is stopped")
            return
        wc.command(wc.current_buffer(),
                   Template(wc.config_get_plugin("format")).safe_substitute(ds))

    @_print_current
    def next(self):
        self._mpdc.next()

    @_print_current
    def pause(self):
        self._mpdc.pause()

    @_print_current
    def play(self, *args):
        self._mpdc.play()

    def playlist(self, *args):
        def ifn( b, s, d): wc.prnt(b, Template(s).safe_substitute(d))
        def cfn(): wc.prnt(None, "mpc closing playlist buffer")
        new_buf = wc.buffer_new('mpc: playlist', "ifn", "", "cfn", "")
        wc.buffer_set(new_buf, "localvar_set_no_log", "1")

        pl = self._mpdc.playlist()
        for line in pl:
            wc.prnt(new_buf, line)

        wc.buffer_set(new_buf, "display", "1")
        return pl

    def playlistinfo(self, sortkey='pos'):
        """Shows playlist information sorted by key
        """
        new_buf = wc.buffer_search("", "mpc: playlist")
        if len(new_buf) == 0:
            new_buf = wc.buffer_new('mpc: playlist', "", "", "", "")

        pl = self._mpdc.playlistinfo()
        try:
            # Numerical sort
            spl = sorted(pl,
                         cmp=lambda x,y: cmp(int(x), int(y)),
                         key=itemgetter(sortkey))
        except ValueError:
            # Alpha sort
            lcmp = lambda x,y: cmp(x.lower(), y.lower())
            spl = sorted(pl,
                         cmp=lambda x,y: cmp(x.lower(), y.lower()),
                         key=itemgetter(sortkey))

        t = Template(wc.config_get_plugin("playinfo"))
        for line in spl:
            wc.prnt(new_buf, t.safe_substitute(line))

        return pl

    @_print_current
    def previous(self):
        self._mpdc.previous()

    def random(self, *args):
        """Toggles randomness if no argument is given.
        """
        if len(args) == 0:
            args = [int(not int(self._get_status('random'))),]

        self._mpdc.random(*args)

    @_print_current
    def stop(self, *args):
        self._mpdc.stop()


def set_config():
    for k, v in DEFAULT_CONFIG.iteritems():
        if not wc.config_get_plugin(k):
            wc.config_set_plugin(k, v)


def control(data, wc_buf, cmd):
    """MPC-like controls from weechat.
    """
    debug = bool(int((wc.config_get_plugin("debug"))))

    if len(cmd) < 1: cmd = 'np'
    mpc = __MPC(wc_buffer=wc_buf)
    mpc.connect()
    cmdlist = cmd.split(' ')
    if len(cmdlist) < 2:
        cmdlist = cmd.split(',')

    try:
        f = mpc.__getattribute__(cmdlist[0])
    except AttributeError:
        wc.prnt(wc_buf, 'MPC: ERROR: Invalid command: %s' %cmdlist[0])
        return wc.WEECHAT_RC_ERROR

    if mpc.debug: wc.prnt(wc_buf, 'mpc debug: f=%s' %f)
    if mpc.debug: wc.prnt(wc_buf, 'mpc debug: cmdlist=%s' %cmdlist[1:])

    # FIXME: catch errors here?
    robj = f(*cmdlist[1:])

    if debug: wc.prnt(wc_buf, 'mpc debug: got: %s' %robj)

    return wc.WEECHAT_RC_OK


set_config()
mpc = __MPC()
CMD_LIST = mpc._commands.keys()
del mpc


wc.hook_command("mpc",
                __doc__,
                control.__doc__,
                '| '.join(CMD_LIST[0:]),
                '|| '.join(CMD_LIST[0:]),
                "control", "")
