# -*- coding: utf-8 -*-
# Moovida - Home multimedia server
# Copyright (C) 2006-2009 Fluendo Embedded S.L. (www.fluendo.com).
# All rights reserved.
#
# This file is available under one of two license agreements.
#
# This file is licensed under the GPL version 3.
# See "LICENSE.GPL" in the root of this distribution including a special
# exception to use Moovida with Fluendo's plugins.
#
# The GPL part of Moovida is also available under a commercial licensing
# agreement from Fluendo.
# See "LICENSE.Moovida" in the root directory of this distribution package
# for details on that license.
#
# Author: Benjamin Kampmann <benjamin@fluendo.com>

import dbus
import dbus.service
from twisted.internet import defer

from elisa.plugins.database.models import MusicTrack, Video, Artist,\
        MusicAlbum, File
from elisa.plugins.base.models.audio import TrackModel, AlbumModel
from elisa.plugins.base.models.media import PlayableModel

from elisa.core import common
from elisa.core.media_uri import MediaUri

from elisa.plugins.database.dbus_service import dbus_errback, result_set_one, \
        result_set_all, ArtistNotFoundError, MusicAlbumNotFoundError

def no_data_dbus_callback(result, callback):
    callback()

class InformationNotAvailableError(Exception):
    pass

class PlayerImpl(object):

    def __init__(self, poblesec_controller):
        self.player = poblesec_controller.music_player.player
        self._poblesec_controller = poblesec_controller

    def _show_player(self):
        self._poblesec_controller.show_music_player()

    def _hide_player(self):
        self._poblesec_controller.hide_current_player()

    def play_music_album(self, artist_name, album_name):
        def play(result):
            # as seen in the actions
            self.player.play_model(result.pop(0))
            self.player.playlist.extend(result)
            self._show_player()
        
        dfr = self.get_album_tracks(artist_name, album_name)
        dfr.addCallback(play)
        return dfr

    # copy-paste and slightly changed. IMHO this shows exactly that this methods
    # don't belong in here but into the music thingy ...
    def get_album_tracks(self, artist_name, album_name):
        def build_track_list(tracks):
            if not tracks:
                raise MusicAlbumNotFoundError()
            return tracks

        def find_tracks(artist):
            if artist is None:
                raise ArtistNotFoundError()

            dfr = artist.tracks.find(MusicTrack.album_name == album_name,
                                     MusicTrack.file_path == File.path,
                                     File.hidden == False)
            dfr.addCallback(result_set_all)
            dfr.addCallback(build_track_list)

            return dfr

        dfr = common.application.store.find(Artist, Artist.name == artist_name)
        dfr.addCallback(result_set_one)
        dfr.addCallback(find_tracks)

        return dfr

    def play_file(self, file_path):
        store = common.application.store

        def found(result, path):
            if result is not None:
                self.player.play_model(result)
            else:
                model = PlayableModel()
                model.uri = MediaUri('file://%s' % path)
                self.player.play_model(model)
                self._show_player()

        dfr = store.get(MusicTrack, file_path)
        dfr.addCallback(found, file_path)
        return dfr

    def toggle_pause(self):
        if self.player.status == self.player.PLAYING:
            self.player.pause()
        else:
            self.player.play()

    def stop(self):
        self.player.stop()
        self._hide_player()

    def next(self):
        self.player.play_next()

    def previous(self):
        self.player.play_previous()

    def set_volume(self, volume_level):
        if volume_level < 0 or volume_level > 100:
            return

        volume = self.player.volume_max / 100. * volume_level
        self.player.set_volume(volume)

    def get_volume(self):
        return self.player.get_volume() * 100 / self.player.volume_max

    def get_position(self):
        position = self.player.get_position()
        if position < 0:
            raise InformationNotAvailableError()

        return position

    def get_duration(self):
        duration = self.player.get_duration()
        if duration < 0 :
            raise InformationNotAvailableError()

        return duration

    def get_metadata(self):
        model = self.player.playlist[self.player.current_index]
        if isinstance(model, PlayableModel):
            return defer.succeed( (model.uri.path, '', '', '', '') )
        elif isinstance(model, MusicTrack):
            return self._get_music_track_metadata(model)
        elif isinstance(model, TrackModel):
            return self._get_track_metadata(model)

        raise NotImplementedError()

    def _get_music_track_metadata(self, model):

        track = ''
        if model.track_number is not None:
            track = str(model.track_number)

        def got_artists(artists):
            return (model.file_path, model.title, track, \
                    ', '.join(artists), model.album_name)

        dfr = model.get_artists()
        dfr.addCallback(got_artists)
        return dfr

    def _get_track_metadata(self, model):

        result = ['',  '', '', '', '']

        if model.track_number is not None:
            result[2] = str(model.track_number)
        if model.title is not None:
            result[1] = model.title

        def got_playable(playable):
            result[0] = playable.uri.path

        def got_artists(artists):
            if artists:
                result[3] = ', '.join(artists)

        def got_album(album):
            result[4] = album.album

        def getter(old_result, method):
            return method()

        def return_result(old_result):
            return result

        # first get the playable model
        dfr = model.get_playable_model()
        dfr.addCallback(got_playable)
        # get the artists
        dfr.addCallback(getter, model.get_artists)
        dfr.addCallback(got_artists)
        # get the album
        dfr.addCallback(getter, model.get_album)
        dfr.addCallback(got_album)
        # return
        dfr.addCallback(return_result)
        return dfr
    
class FilePlayer(dbus.service.Object):
    interface = 'com.fluendo.Elisa.Plugins.Poblesec.FilePlayer'
    
    def __init__(self, main_controller, *args, **kw):
        dbus.service.Object.__init__(self, *args, **kw)
        self.main = main_controller

    @dbus.service.method(dbus_interface=interface,
            in_signature='as', out_signature='',)
    def play_file(self, files):
        self.main.play_files(files)

class Player(dbus.service.Object):
    interface = 'com.fluendo.Elisa.Plugins.Poblesec.AudioPlayer'
    
    # FIXME: what is this weird 'PLAY' ?
    status = ['stopped', 'playing', 'paused', 'buffering']

    def __init__(self, poblesec_controller, *args, **kw):
        dbus.service.Object.__init__(self, *args, **kw)
        self.impl = PlayerImpl(poblesec_controller)

        self.impl.player.connect('status-changed', self._status_changed)
        self.impl.player.connect('volume-changed', self._volume_changed)

    def _status_changed(self, player, status):
        self.status_changed(self.status[status])

    def _volume_changed(self, player, volume):
        # the maximal volume is dynamic but the API wants a clear percent value
        volume_in_percent = volume * 100 / player.volume_max
        self.volume_changed(volume_in_percent)

    # signals
    @dbus.service.signal(dbus_interface=interface, signature='s')
    def status_changed(self, new_status):
        # just emit it
        pass

    @dbus.service.signal(dbus_interface=interface, signature='i')
    def volume_changed(self, volume):
        # just emit it
        pass

    @dbus.service.method(dbus_interface=interface,
            in_signature='ss', out_signature='',
            async_callbacks=('callback', 'errback'))
    def play_music_album(self, artist_name, album_name, callback, errback):
        dfr = self.impl.play_music_album(artist_name, album_name)
        dfr.addCallback(no_data_dbus_callback, callback)
        dfr.addErrback(dbus_errback, errback)

    @dbus.service.method(dbus_interface=interface,
            in_signature='s', out_signature='',
            async_callbacks=('callback', 'errback'))
    def play_file(self, file_path, callback, errback):
        dfr = self.impl.play_file(file_path)
        dfr.addCallback(no_data_dbus_callback, callback)
        dfr.addErrback(dbus_errback, errback)

    @dbus.service.method(dbus_interface=interface,
            in_signature='', out_signature='')
    def toggle_pause(self):
        self.impl.toggle_pause()

    @dbus.service.method(dbus_interface=interface,
            in_signature='', out_signature='')
    def stop(self):
        self.impl.stop()

    @dbus.service.method(dbus_interface=interface,
            in_signature='', out_signature='')
    def next(self):
        self.impl.next()

    @dbus.service.method(dbus_interface=interface,
            in_signature='', out_signature='')
    def previous(self):
        self.impl.previous()

    @dbus.service.method(dbus_interface=interface,
            in_signature='i', out_signature='')
    def set_volume(self, volume_level):
        self.impl.set_volume(volume_level)

    @dbus.service.method(dbus_interface=interface,
            in_signature='', out_signature='i')
    def get_volume(self):
        return self.impl.get_volume()

    @dbus.service.method(dbus_interface=interface,
            in_signature='', out_signature='x')
    def get_position(self):
        return self.impl.get_position()

    @dbus.service.method(dbus_interface=interface,
            in_signature='', out_signature='x')
    def get_duration(self):
        return self.impl.get_duration()

    @dbus.service.method(dbus_interface=interface,
            in_signature='', out_signature='as',
            async_callbacks=('callback', 'errback'))
    def get_metadata(self, callback, errback):
        dfr = self.impl.get_metadata()
        dfr.addCallbacks(callback, errback)
