# Copyright (C) 2008-2010 Adam Olsen
#
# This program 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 2, or (at your option)
# any later version.
#
# This program 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 this program; if not, write to the Free Software
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#
#
# The developers of the Exaile media player hereby grant permission
# for non-GPL compatible GStreamer and Exaile plugins to be used and
# distributed together with GStreamer and Exaile. This permission is
# above and beyond the permissions granted by the GPL license by which
# Exaile is covered. If you modify this code, you may extend this
# exception to your version of the code, but you are not obligated to
# do so. If you do not wish to do so, delete this exception statement
# from your version.

import logging
from optparse import OptionParser
import os
import sys
import traceback

import dbus
import dbus.service
import gio
import gobject

# Be VERY careful what you import here! This module gets loaded even if
# we are just issuing a dbus command to a running instance, so we need
# to keep imports as light as possible.
from xl import event
from xl.nls import gettext as _

logger = logging.getLogger(__name__)

def check_dbus(bus, interface):
    obj = bus.get_object('org.freedesktop.DBus', '/org/freedesktop/DBus')
    dbus_iface = dbus.Interface(obj, 'org.freedesktop.DBus')
    avail = dbus_iface.ListNames()
    return interface in avail

def check_exit(options, args):
    """
        Check to see if dbus is running, and if it is, call the appropriate
        methods
    """
    iface = None
    exaile = None
    if not options.NewInstance:
        # TODO: handle dbus stuff
        bus = dbus.SessionBus()
        if check_dbus(bus, 'org.exaile.Exaile'):
            remote_object = bus.get_object('org.exaile.Exaile',
                '/org/exaile/Exaile')
            iface = dbus.Interface(remote_object, 'org.exaile.Exaile')
            iface.TestService('testing dbus service')
            exaile = remote_object.exaile

            # Assume that args are files to be added to the current playlist.
            # This enables:    exaile PATH/*.mp3
            if args:
                # if '-' is the first argument then we look for a newline
                # separated list of filenames from stdin.
                # This enables:    find PATH -name *.mp3 | exaile -
                if args[0] == '-':
                    args = sys.stdin.read().split('\n')
                args = [ os.path.abspath(arg) for arg in args ]
                iface.Enqueue(args)

    if not iface:
        for command in ['GetArtist', 'GetTitle', 'GetAlbum', 'GetLength',
                'GetRating', 'SetRating', 'IncreaseVolume', 'DecreaseVolume',
                'Play', 'Stop', 'Next', 'Prev', 'PlayPause',
                'StopAfterCurrent', 'GuiToggleVisible', 'CurrentPosition',
                'CurrentProgress', 'GetVolume', 'Query']:
            if getattr(options, command):
                return "command"
        return "continue"

    run_commands(options, iface)
    return "exit"

def run_commands(options, iface):
    """
        Actually invoke any commands passed in.
    """
    comm = False
    info_commands = {
        'GetArtist': 'artist',
        'GetTitle': 'title',
        'GetAlbum': 'album',
        'GetLength': '__length',
    }

    for command, attr in info_commands.iteritems():
        if getattr(options, command):
            value = iface.GetTrackAttr(attr)
            if value is None:
                print _('Not playing.')
            else:
                print value
            comm = True

    modify_commands = [
        'SetRating',
        'Add'
    ]

    for command in modify_commands:
        argument = getattr(options, command)
        if argument is not None:
            getattr(iface, command)(argument)
            comm = True

    volume_commands = (
            'IncreaseVolume',
            'DecreaseVolume',
            )

    for command in volume_commands:
        value = getattr(options, command)
        if value:
            if command == 'DecreaseVolume': value = -value
            iface.ChangeVolume(value)

    run_commands = (
            'Play',
            'Stop',
            'Next',
            'Prev',
            'PlayPause',
            'StopAfterCurrent',
            'GuiToggleVisible',
            'ToggleMute'
            )
    for command in run_commands:
        if getattr(options, command):
            getattr(iface, command)()
            comm = True

    query_commands = (
            'CurrentPosition',
            'CurrentProgress',
            'GetRating',
            'GetVolume',
            'Query',
            )

    for command in query_commands:
        if getattr(options, command):
            print getattr(iface, command)()
            comm = True

    to_implement = (
            'GuiQuery',
            )
    for command in to_implement:
        if getattr(options, command):
            logger.warning("FIXME: command not implemented")
            comm = True

    if not comm:
        iface.GuiToggleVisible()

class DbusManager(dbus.service.Object):
    """
        The dbus interface object for Exaile
    """
    def __init__(self, exaile):
        """
            Initilializes the interface
        """
        self.exaile = exaile
        self.bus = dbus.SessionBus()
        self.bus_name = dbus.service.BusName('org.exaile.Exaile',
            bus=self.bus)
        dbus.service.Object.__init__(self, self.bus_name, '/org/exaile/Exaile')
        self.cached_track = ""
        self.cached_state = ""
        self.cached_volume = -1

    def _connect_signals(self):
        # connect events
        from xl import player
        event.add_callback(self.emit_state_changed,
            'playback_player_end', player.PLAYER)
        event.add_callback(self.emit_state_changed,
            'playback_track_start', player.PLAYER)
        event.add_callback(self.emit_state_changed,
            'playback_toggle_pause', player.PLAYER)
        event.add_callback(self.emit_track_changed,
            'tags_parsed', player.PLAYER)
        event.add_callback(self.emit_state_changed,
            'playback_buffering', player.PLAYER)
        event.add_callback(self.emit_state_changed,
            'playback_error', player.PLAYER)


    @dbus.service.method('org.exaile.Exaile', 's')
    def TestService(self, arg):
        """
            Just test the dbus object
        """
        logger.debug(arg)

    @dbus.service.method('org.exaile.Exaile', None, 'b')
    def IsPlaying(self):
        """
            Returns True if Exaile is playing (or paused), False if it's not
        """
        return bool(self.exaile.player.current)

    @dbus.service.method('org.exaile.Exaile', 's', 's')
    def GetTrackAttr(self, attr):
        """
            Returns an attribute of a track
        """
        from xl import player
        try:
            value = player.PLAYER.current.get_tag_raw(attr) or ''
        except (ValueError, TypeError, AttributeError):
            value = ''

        if type(value) == list:
            return u"\n".join(value)
        return unicode(value)

    @dbus.service.method('org.exaile.Exaile', 'sv')
    def SetTrackAttr(self, attr, value):
        """
            Sets rating of a track
        """
        from xl import player
        try:
            player.PLAYER.current.set_tag_raw(attr, value)
        except (AttributeError, TypeError):
            pass

    @dbus.service.method('org.exaile.Exaile', None, 'i')
    def GetRating(self):
        """
            Returns the current track's rating
        """
        try:
            rating = int(float(self.GetTrackAttr('__rating')))
        except ValueError:
            rating = 0

        return rating

    @dbus.service.method('org.exaile.Exaile', 'i')
    def SetRating(self, value):
        """
            Sets the current track's rating
        """
        self.SetTrackAttr('__rating', value)
        event.log_event('rating_changed', self, value)

    @dbus.service.method('org.exaile.Exaile', 'i')
    def ChangeVolume(self, value):
        """
            Changes volume by the specified amount (in percent, can be negative)
        """
        from xl import player
        player.PLAYER.set_volume(player.PLAYER.get_volume() + value)
        self.cached_volume = -1

    @dbus.service.method('org.exaile.Exaile')
    def ToggleMute(self):
        """
            Mutes or unmutes the volume
        """
        from xl import player
        if self.cached_volume == -1:
            self.cached_volume = player.PLAYER.get_volume()
            player.PLAYER.set_volume(0)
        else:
            player.PLAYER.set_volume(self.cached_volume)
            self.cached_volume = -1

    @dbus.service.method('org.exaile.Exaile', 'd')
    def Seek(self, value):
        """
            Seeks to the given position in seconds
        """
        from xl import player
        player.PLAYER.seek(value)

    @dbus.service.method('org.exaile.Exaile')
    def Prev(self):
        """
            Jumps to the previous track
        """
        from xl import player
        player.QUEUE.prev()

    @dbus.service.method('org.exaile.Exaile')
    def Stop(self):
        """
            Stops playback
        """
        from xl import player
        player.PLAYER.stop()

    @dbus.service.method('org.exaile.Exaile')
    def Next(self):
        """
            Jumps to the next track
        """
        from xl import player
        player.QUEUE.next()

    @dbus.service.method('org.exaile.Exaile')
    def Play(self):
        """
            Starts playback
        """
        from xl import player
        player.QUEUE.play()

    @dbus.service.method('org.exaile.Exaile')
    def PlayPause(self):
        """
            Toggle Play or Pause
        """
        from xl import player
        if player.PLAYER.get_state() == 'stopped':
            player.PLAYER.play(player.QUEUE.get_current())
        else:
            player.PLAYER.toggle_pause()

    @dbus.service.method('org.exaile.Exaile')
    def StopAfterCurrent(self):
        """
            Toggle stopping after current track
        """
        from xl import player
        player.QUEUE.stop_track = player.QUEUE.get_current()

    @dbus.service.method('org.exaile.Exaile', None, 's')
    def CurrentProgress(self):
        """
            Returns the progress into the current track (in percent)
        """
        from xl import player
        progress = player.PLAYER.get_progress()
        if progress == -1:
            return ""
        return str(int(progress * 100))

    @dbus.service.method('org.exaile.Exaile', None, 's')
    def CurrentPosition(self):
        """
            Returns the position inside the current track (as time)
        """
        from xl import player
        progress = player.PLAYER.get_time()
        return '%d:%02d' % (progress // 60, progress % 60)

    @dbus.service.method('org.exaile.Exaile', None, 's')
    def GetVolume(self):
        """
            Returns the current volume level (in percent)
        """
        from xl import player
        return str(player.PLAYER.get_volume())

    @dbus.service.method('org.exaile.Exaile', None, 's')
    def Query(self):
        """
            Returns information about the currently playing track
        """
        from xl import player
        current_track = player.QUEUE.get_current()
        if current_track is None or player.PLAYER.is_stopped():
            return _('Not playing.')

        length = float(self.GetTrackAttr('__length') or 0)
        length = '%d:%02d' % (length // 60, length % 60)

        result = _('status: %(status)s, title: %(title)s, artist: %(artist)s,'
                   ' album: %(album)s, length: %(length)s,'
                   ' position: %(progress)s%% [%(position)s]') % {
                         'status': player.PLAYER.get_state(),
                         'title': self.GetTrackAttr('title'),
                         'artist': self.GetTrackAttr('artist'),
                         'album': self.GetTrackAttr('album'),
                         'length': length,
                         'progress': self.CurrentProgress(),
                         'position': self.CurrentPosition(),
                     }

        return result

    @dbus.service.method('org.exaile.Exaile', None, 's')
    def GetVersion(self):
        """
            Returns the version of Exaile
        """
        return self.exaile.get_version()

    @dbus.service.method('org.exaile.Exaile', 's')
    def PlayFile(self, filename):
        """
            Plays the specified file
        """
        self.exaile.gui.open_uri(filename)

    @dbus.service.method('org.exaile.Exaile', 'as')
    def Enqueue(self, locations):
        """
            Adds the tracks at the specified locations
            to the current playlist
        """
        import xl.playlist
        from xl import player
        from xl import trax   # do this here to avoid loading
                              # settings when issuing dbus commands
        # FIXME: Get rid of dependency on xlgui
        #        by moving sorting column somewhere else
        pl = self.exaile.gui.main.get_selected_playlist()
        column, descending = pl.get_sort_by()
        tracks = []
        play_track = None
        playlists = []

        for location in locations:
            tracks = []

            if xl.playlist.is_valid_playlist(location):
                try:
                    pl = xl.playlist.import_playlist(location)
                    tracks = pl.get_tracks()
                    continue
                except xl.playlist.InvalidPlaylistTypeException:
                    pass
                except:
                    traceback.print_exc()
            else:
                tracks = trax.get_tracks_from_uri(location)

            if tracks:
                tracks = trax.sort_tracks(['album', column], tracks, reverse=descending)
                player.QUEUE.current_playlist.add_tracks(tracks)

                if play_track is None:
                    play_track = tracks[0]

        if player.PLAYER.is_stopped() and play_track is not None:
            pos = player.QUEUE.current_playlist.index(play_track)
            player.QUEUE.current_playlist.set_current_pos(pos)
            player.QUEUE.play()

    @dbus.service.method('org.exaile.Exaile', 's')
    def Add(self, location):
        """
            Adds the tracks at the specified
            location to the collection
        """
        from xl import trax

        tracks = trax.get_tracks_from_uri(location)
        self.exaile.collection.add_tracks(tracks)

    @dbus.service.method('org.exaile.Exaile')
    def GuiToggleVisible(self):
        """
            Toggles visibility of the GUI, if possible
        """
        self.exaile.gui.main.toggle_visible(bringtofront=True)

    @dbus.service.method('org.exaile.Exaile', None, 'ay')
    def GetCoverData(self):
        """
            Returns the data of the cover image of the playing track, or
            an empty string if there is no cover available.
        """
        from xl import covers
        cover = covers.MANAGER.get_cover(player.PLAYER.current)
        if not cover:
            cover = ''
        return cover

    @dbus.service.method('org.exaile.Exaile', None, 's')
    def GetState(self):
        """
            Returns the surrent verbatim state (unlocalized)
        """
        from xl import player
        return player.PLAYER.get_state()

    @dbus.service.signal('org.exaile.Exaile')
    def StateChanged(self):
        """
            Emitted when state change occurs: 'playing' 'paused' 'stopped'
        """
        pass

    @dbus.service.signal('org.exaile.Exaile')
    def TrackChanged(self):
        """
            Emitted when track change occurs.
        """
        pass

    def emit_state_changed(self, type, player, object):
        """
            Called from main to emit signal
        """
        new_state = player.get_state()
        if self.cached_state != new_state:
            self.cached_state = new_state
            self.StateChanged()

    def emit_track_changed(self, type, player, object):
        """
            Called from main to emit signal
        """
        new_track = self.GetTrackAttr('__loc')
        if self.cached_track != new_track:
            self.cached_track = new_track
            self.TrackChanged()

# vim: et sts=4 sw=4
