#!/usr/bin/python

# =============================================================================
#
#    Remuco - A remote control system for media players.
#    Copyright (C) 2006-2010 by the Remuco team, see AUTHORS.
#
#    This file is part of Remuco.
#
#    Remuco is free software: you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation, either version 3 of the License, or
#    (at your option) any later version.
#
#    Remuco is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with Remuco.  If not, see <http://www.gnu.org/licenses/>.
#
# =============================================================================

"""Amarok1.4 adapter for Remuco, implemented as an executable script."""

import commands
import os
import os.path
import sqlite3

import eyeD3
import gobject

import remuco
from remuco import log

# =============================================================================
# constants
# =============================================================================

IA_JUMP = remuco.ItemAction("Play Now")
IA_REMOVE = remuco.ItemAction("Remove", multiple=True)
PLAYLIST_ACTIONS = (IA_JUMP, IA_REMOVE)

IA_ADD = remuco.ItemAction("Add to playlist", multiple=True)
MLIB_ITEM_ACTIONS = (IA_ADD,)

SEARCH_MASK = [ "Artist", "Album", "Title" ]

AMAROK_DB = os.path.expanduser('~/.kde/share/apps/amarok/collection.db')

# =============================================================================
# player adapter
# =============================================================================

class Amarok14Adapter(remuco.PlayerAdapter):

    lastpath = ""
    search_list = []
    db = None

    def __init__(self):

        remuco.PlayerAdapter.__init__(self, "Amarok 1.4",
                                      playback_known=True,
                                      volume_known=True,
                                      progress_known=True,
                                      shuffle_known=True,
                                      repeat_known=True,
                                      mime_types=remuco.MIMETYPES_AUDIO,
                                      max_rating=10,
                                      poll=10,
                                      search_mask=SEARCH_MASK
        )

    def start(self):

        remuco.PlayerAdapter.start(self)

        self.db = sqlite3.connect(AMAROK_DB).cursor()

    def stop(self):

        remuco.PlayerAdapter.stop(self)

        self.db = None

    def poll(self):

        if not self._check_running():
            return # manager will stop us

        import random

        current_state =  self._amarok("player randomModeStatus")
        if(current_state == "true"):
            self.update_shuffle(True)
        else:
            self.update_shuffle(False)

        current_state = self._amarok("player repeatPlaylistStatus")
        if(current_state == "true"):
            self.update_repeat(True)
        else:
            self.update_repeat(False)

        volume = self._amarok("player getVolume", 50)
        self.update_volume(volume)

        playing = self._amarok("player isPlaying")
        if playing == "true":
            self.update_playback(remuco.PLAYBACK_PLAY)
        else:
            self.update_playback(remuco.PLAYBACK_PAUSE)

        #~ Get and calculate total time and progress
        current = self._amarok("player currentTime")
        if(len(current.split(":")) == 2):
            cmin, csec = current.split(":")
        else:
            cmin, csec = 0,0

        total = self._amarok("player totalTime")
        if(len(total.split(":")) == 2):
            tmin, tsec = total.split(":")
        else:
            tmin, tsec = 0,0

        cmin, csec, tmin, tsec = int(cmin), int(csec), int(tmin), int(tsec)
        current = (cmin*60)+csec
        total = (tmin*60)+tsec
        self.update_progress(progress=current, length=total)

        curpath = self._amarok("player path")
        if(self.lastpath != curpath and len(curpath) != 0):

            info = {}
            info[remuco.INFO_ARTIST] = self._amarok("player artist")
            info[remuco.INFO_ALBUM] = self._amarok("player album")
            info[remuco.INFO_TITLE] = self._amarok("player title")
            info[remuco.INFO_GENRE] = self._amarok("player genre")
            info[remuco.INFO_YEAR] = self._amarok("player year")
            info[remuco.INFO_BITRATE] = int(self._amarok("player bitrate"))
            info[remuco.INFO_RATING] = self._amarok("player rating")

            self.update_item("", info, self.find_image(curpath, True))
            self.lastpath = curpath


    # =========================================================================
    # control interface
    # =========================================================================

    def ctrl_rate(self, rate):
        self._amarok("player setRating %s" % (rate))

    def ctrl_volume(self, direction):
        current = int(self._amarok("player getVolume", 50))
        if(direction == 1 ):
            self._amarok("player setVolume %s" % (current+10))
        elif(direction == -1):
            self._amarok("player setVolume %s" % (current-10))
        elif(direction == 0):
            self._amarok("player mute")

    def ctrl_toggle_shuffle(self):
        current_state =  self._amarok("player randomModeStatus")
        if(current_state == "true"):
            self._amarok("player enableRandomMode false")
            self.update_shuffle(False)
        else:
            self._amarok("player enableRandomMode true")
            self.update_shuffle(True)

    def ctrl_toggle_repeat(self):
        current_state = self._amarok("player repeatPlaylistStatus")
        if(current_state == "true"):
            self._amarok("player enableRepeatPlaylist false")
            self.update_repeat(False)
        else:
            self._amarok("player enableRepeatPlaylist true")
            self.update_repeat(True)


    def ctrl_toggle_playing(self):
        self._amarok("player playPause")
        gobject.idle_add(self.poll)

    def ctrl_next(self):
        self._amarok("player next")
        gobject.timeout_add(150, self.poll)

    def ctrl_previous(self):
        self._amarok("player prev")
        gobject.timeout_add(150, self.poll)

    # =========================================================================
    # request interface
    # =========================================================================

    def request_playlist(self, reply):
        path = os.path.expanduser("~/.cache/current.m3u")
        self._amarok('playlist saveM3u "%s" false' % path)
        f = open(path)
        tag = eyeD3.Tag()
        while 1:
            line = f.readline()
            if not line: break

            if line.find("#") == -1:
                tag.link(line.rstrip())
                reply.ids.append(len(reply.names))
                reply.names.append("%s - %s" % (tag.getArtist(), tag.getTitle()))

        f.close
        reply.item_actions = PLAYLIST_ACTIONS
        reply.send()

    def request_search(self, reply, query):
        search = []
        fields = ["artist.name", "album.name", "tags.title"]
        for q, f in zip(query, fields):
            if q:
                search.append('lower(%s) like "%%%s%%"' % (f, q))

        if not search:
            reply.send()
            return

        sql = ("select tags.title as title, artist.name as artist, album.name "
               "as album, tags.url as url from tags join artist on "
               "(tags.artist = artist.id) join album on "
               "(tags.album = album.id) where %s" % " and ".join(search))

        res = self.db.execute(sql)
        id = 0
        self.search_list = []
        for r in res:
            self.search_list.append("")
            self.search_list[len(reply.names)] = r[3]
            reply.ids.append(len(reply.names))
            reply.names.append(" ".join(r[0:3]))

        reply.item_actions = MLIB_ITEM_ACTIONS
        reply.send()

    def request_mlib(self, reply, path):
        reply.nested, reply.names, reply.ids = [], [], []
        if not path:
            sql = "select name from artist order by name asc"
            res = self.db.execute(sql)
            for r in res:
                reply.nested.append(r[0])
        elif(len(path) == 1):
            sql = ('select distinct(album.name) from album '
                   'join tags on (tags.album = album.id) '
                   'join artist on (tags.artist = artist.id) '
                   'where artist.name = "%s"' % path[0])
            res = self.db.execute(sql)
            for r in res:
                if(len(r[0]) == 0):
                    reply.nested.append("_no_album_")
                else:
                    reply.nested.append(r[0])

        elif(len(path) == 2):
            album = (path[1] != "_no_album_" and
                     ' and album.name = "%s"' % path[1] or "")
            sql = ('select tags.title from tags '
                   'join album on (tags.album = album.id) '
                   'join artist on (tags.artist = artist.id) '
                   'where artist.name = "%s"%s '
                   'order by tags.url' % (path[0], album))
            res = self.db.execute(sql)
            for r in res:
                reply.ids.append(len(reply.names))
                reply.names.append(r[0])

        reply.item_actions = MLIB_ITEM_ACTIONS
        reply.send()

    # =========================================================================
    # Actions
    # =========================================================================

    def action_playlist_item(self, action_id, positions, ids):
        if(action_id == IA_JUMP.id):
            self._amarok("playlist playByIndex %s" % (positions[0]))
        elif(action_id == IA_REMOVE.id):
            for i, v in enumerate(positions):
                self._amarok("playlist removeByIndex %s" % (v))
        else:
            print "Unknown action %s" %s (action_id)

    def action_search_item(self, action_id, positions, ids):
        path = []
        if action_id == IA_ADD.id: #Add to playlist
            for pos in positions:
                # Ugly hack but it works. Amarok prefixes all paths with .
                # (period). Don't know why but this is a quickfix
                path.append('"%s"' %
                            (os.path.abspath("/%s" %(self.search_list[pos]))))

            self._amarok("playlist addMediaList [ %s ]" % " ".join(path))

    def action_mlib_item(self, action_id, path, positions, ids):
        if action_id == IA_ADD.id:
            search = []
            if(len(path[0]) > 0):
                search.append('artist.name = "%s"' % path[0])
            if(len(path[1]) > 0 and path[1] != "_no_album_"):
                search.append('album.name = "%s"' % path[1])

            sql = ('select tags.url from tags '
                   'join album on (album.id = tags.album) '
                   'join artist on (artist.id = tags.artist) '
                   'where %s '
                   'order by tags.url' % " AND ".join(search))
            res = self.db.execute(sql)
            list = []
            for r in res:
                list.append(r[0])

            path = []
            if(len(list) > 0):
                for pos in positions:
                     # Ugly hack but it works. Amarok prefixes all paths with .
                     # (period). Don't know why but this is a quickfix
                    path.append(' "%s"' % os.path.abspath("/%s" % list[pos]))
                self._amarok("playlist addMediaList [ %s ]" % " ".join(path))

    # =========================================================================
    # internal methods
    # =========================================================================

    def _amarok(self, cmd, default=""):
        """Shortcut for running command 'dcop amarok ...'."""

        ret, out = commands.getstatusoutput("dcop amarok %s" % cmd)
        if ret != os.EX_OK:
            log.warning("'dcop amarok %s' failed (%s)" % (cmd, out))
            return default
        else:
            return out

# =============================================================================
# main
# =============================================================================

def run_check():
    """Check if Amarok is running."""
    return commands.getstatusoutput("dcop amarok")[0] == 0

if __name__ == '__main__':

    pa = Amarok14Adapter()
    mg = remuco.Manager(pa, poll_fn=run_check)
    mg.run()
