# Licensed under the MIT license
# http://opensource.org/licenses/mit-license.php

# Copyright 2006, Frank Scholz <coherence@beebits.net>

from twisted.internet import reactor
from twisted.internet.task import LoopingCall
from twisted.internet.defer import Deferred
from twisted.python import failure

from twisted.spread import pb

from coherence.upnp.core.soap_service import errorCode
from coherence.upnp.core import DIDLLite

import string

import coherence.extern.louie as louie

from coherence.extern.simple_plugin import Plugin

from coherence import log


class ElisaPlayer(log.Loggable, Plugin):

    """ a backend to the Elisa player

    """
    logCategory = 'elisa_player'

    implements = ['MediaRenderer']
    vendor_value_defaults = {'RenderingControl': {'A_ARG_TYPE_Channel': 'Master'}}
    vendor_range_defaults = {'RenderingControl': {'Volume': {'maximum': 100}}}

    def __init__(self, device, **kwargs):
        log.Loggable.__init__(self)
        self.name = kwargs.get('name', 'Elisa MediaRenderer')
        self.host = kwargs.get('host', '127.0.0.1')
        self.player = None
        self.metadata = None

        if self.host == 'internal':
            try:
                from elisa.core import common
                self.player = common.get_application().get_player()
                louie.send('Coherence.UPnP.Backend.init_completed', None, backend=self)
            except:
                self.warning("this works only from within Elisa")
                raise ImportError
        else:
            factory = pb.PBClientFactory()
            factory.noisy = False
            reactor.connectTCP(self.host, 8789, factory)
            d = factory.getRootObject()

            def result(player):
                self.player = player
                louie.send('Coherence.UPnP.Backend.init_completed', None, backend=self)

            def got_error(error):
                self.warning("connection to Elisa failed!")

            d.addCallback(lambda object: object.callRemote("get_player"))
            d.addCallback(result)
            d.addErrback(got_error)

        self.playing = False
        self.state = None
        self.duration = None
        self.view = []
        self.tags = {}
        self.server = device
        self.poll_LC = LoopingCall(self.poll_player)

    def call_player(self, method, callback, *args):
        self.debug('call player.%s%s', method, args)
        if self.host == 'internal':
            dfr = Deferred()
            dfr.addCallback(callback)
            result = getattr(self.player, method)(*args)
            dfr.callback(result)
        else:
            dfr = self.player.callRemote(method, *args)
            dfr.addCallback(callback)
        return dfr

    def __repr__(self):
        return str(self.__class__).split('.')[-1]

    def poll_player(self):

        def got_result(result):
            self.info("poll_player %r", result)
            if self.server != None:
                connection_id = self.server.connection_manager_server.lookup_avt_id(self.current_connection_id)
            if result in ('STOPPED', 'READY'):
                transport_state = 'STOPPED'
            if result == 'PLAYING':
                transport_state = 'PLAYING'
            if result == 'PAUSED':
                transport_state = 'PAUSED_PLAYBACK'

            if transport_state == 'PLAYING':
                self.query_position()

            if self.state != transport_state:
                self.state = transport_state
                if self.server != None:
                    self.server.av_transport_server.set_variable(connection_id,
                                                                 'TransportState',
                                                                 transport_state)
        self.call_player("get_readable_state", got_result)

    def query_position(self):
        def got_result(result):
            self.info("query_position %s", result)
            position, duration = result
            if self.server != None:
                connection_id = self.server.connection_manager_server.lookup_avt_id(self.current_connection_id)
                self.server.av_transport_server.set_variable(connection_id, 'CurrentTrack', 0)

            if position is not None:
                m, s = divmod(position / 1000000000, 60)
                h, m = divmod(m, 60)
                if self.server != None:
                    self.server.av_transport_server.set_variable(connection_id, 'RelativeTimePosition', '%02d:%02d:%02d' % (h, m, s))
                    self.server.av_transport_server.set_variable(connection_id, 'AbsoluteTimePosition', '%02d:%02d:%02d' % (h, m, s))

            if duration is not None:
                m, s = divmod(duration / 1000000000, 60)
                h, m = divmod(m, 60)

                if self.server != None:
                    self.server.av_transport_server.set_variable(connection_id, 'CurrentTrackDuration', '%02d:%02d:%02d' % (h, m, s))
                    self.server.av_transport_server.set_variable(connection_id, 'CurrentMediaDuration', '%02d:%02d:%02d' % (h, m, s))

                if self.duration is None:
                    if self.metadata is not None:
                        elt = DIDLLite.DIDLElement.fromString(self.metadata)
                        for item in elt:
                            for res in item.findall('res'):
                                res.attrib['duration'] = "%d:%02d:%02d" % (h, m, s)
                        self.metadata = elt.toString()

                        if self.server != None:
                            self.server.av_transport_server.set_variable(connection_id, 'AVTransportURIMetaData', self.metadata)
                            self.server.av_transport_server.set_variable(connection_id, 'CurrentTrackMetaData', self.metadata)

                    self.duration = duration

        self.call_player("get_status", got_result)

    def load(self, uri, metadata):

        def got_result(result):
            self.duration = None
            self.metadata = metadata
            self.tags = {}
            connection_id = self.server.connection_manager_server.lookup_avt_id(self.current_connection_id)
            self.server.av_transport_server.set_variable(connection_id, 'CurrentTransportActions', 'Play,Stop,Pause')
            self.server.av_transport_server.set_variable(connection_id, 'NumberOfTracks', 1)
            self.server.av_transport_server.set_variable(connection_id, 'CurrentTrackURI', uri)
            self.server.av_transport_server.set_variable(connection_id, 'AVTransportURI', uri)
            self.server.av_transport_server.set_variable(connection_id, 'AVTransportURIMetaData', metadata)
            self.server.av_transport_server.set_variable(connection_id, 'CurrentTrackURI', uri)
            self.server.av_transport_server.set_variable(connection_id, 'CurrentTrackMetaData', metadata)

        self.call_player("set_uri", got_result, uri)

    def start(self, uri):
        self.load(uri)
        self.play()

    def stop(self):
        def got_result(result):
            self.server.av_transport_server.set_variable( \
                self.server.connection_manager_server.lookup_avt_id(self.current_connection_id), \
                                 'TransportState', 'STOPPED')

        self.call_player("stop", got_result)

    def play(self):
        def got_result(result):
            self.server.av_transport_server.set_variable( \
                self.server.connection_manager_server.lookup_avt_id(self.current_connection_id), \
                                 'TransportState', 'PLAYING')

        self.call_player("play", got_result)

    def pause(self):
        def got_result(result):
            self.server.av_transport_server.set_variable( \
                self.server.connection_manager_server.lookup_avt_id(self.current_connection_id), \
                                 'TransportState', 'PAUSED_PLAYBACK')
        self.call_player("pause", got_result)

    def seek(self, location):
        """
        @param location:    simple number = time to seek to, in seconds
                            +nL = relative seek forward n seconds
                            -nL = relative seek backwards n seconds
        """

    def mute(self):
        def got_result(result):
            rcs_id = self.server.connection_manager_server.lookup_rcs_id(self.current_connection_id)
            #FIXME: use result, not True
            self.server.rendering_control_server.set_variable(rcs_id, 'Mute', 'True')

        self.call_player("mute", got_result)

    def unmute(self):
        def got_result(result):
            rcs_id = self.server.connection_manager_server.lookup_rcs_id(self.current_connection_id)
            #FIXME: use result, not False
            self.server.rendering_control_server.set_variable(rcs_id, 'Mute', 'False')

        self.call_player("un_mute", got_result)

    def get_mute(self):
        def got_infos(result):
            self.info("got_mute: %r", result)
            return result

        return self.call_player("get_mute", got_infos)

    def get_volume(self):
        """ playbin volume is a double from 0.0 - 10.0
        """

        def got_infos(result):
            self.info("got_volume: %r", result)
            return result

        return self.call_player('get_volume', got_infos)

    def set_volume(self, volume):
        volume = int(volume)
        if volume < 0:
            volume = 0
        if volume > 100:
            volume = 100

        def got_result(result):
            rcs_id = self.server.connection_manager_server.lookup_rcs_id(self.current_connection_id)
            #FIXME: use result, not volume
            self.server.rendering_control_server.set_variable(rcs_id, 'Volume', volume)

        self.call_player("set_volume", got_result, volume)

    def upnp_init(self):
        self.current_connection_id = None
        self.server.connection_manager_server.set_variable(0, 'SinkProtocolInfo',
                            ['internal:%s:*:*' % self.host,
                             'http-get:*:audio/mpeg:*'],
                            default=True)
        self.server.av_transport_server.set_variable(0, 'TransportState', 'NO_MEDIA_PRESENT', default=True)
        self.server.av_transport_server.set_variable(0, 'TransportStatus', 'OK', default=True)
        self.server.av_transport_server.set_variable(0, 'CurrentPlayMode', 'NORMAL', default=True)
        self.server.av_transport_server.set_variable(0, 'CurrentTransportActions', '', default=True)
        self.server.rendering_control_server.set_variable(0, 'Volume', self.get_volume())
        self.server.rendering_control_server.set_variable(0, 'Mute', self.get_mute())
        self.poll_LC.start(1.0, True)

    def upnp_Play(self, *args, **kwargs):
        InstanceID = int(kwargs['InstanceID'])
        Speed = int(kwargs['Speed'])
        self.play()
        return {}

    def upnp_Pause(self, *args, **kwargs):
        InstanceID = int(kwargs['InstanceID'])
        self.pause()
        return {}

    def upnp_Stop(self, *args, **kwargs):
        InstanceID = int(kwargs['InstanceID'])
        self.stop()
        return {}

    def upnp_SetAVTransportURI(self, *args, **kwargs):
        InstanceID = int(kwargs['InstanceID'])
        CurrentURI = kwargs['CurrentURI']
        CurrentURIMetaData = kwargs['CurrentURIMetaData']
        local_protocol_infos = self.server.connection_manager_server.get_variable('SinkProtocolInfo').value.split(',')
        #print '>>>', local_protocol_infos
        if len(CurrentURIMetaData) == 0:
            self.load(CurrentURI, CurrentURIMetaData)
        else:
            elt = DIDLLite.DIDLElement.fromString(CurrentURIMetaData)
            if elt.numItems() == 1:
                item = elt.getItems()[0]
                res = item.res.get_matching(local_protocol_infos, protocol_type='internal')
                if len(res) == 0:
                    res = item.res.get_matching(local_protocol_infos)
                if len(res) > 0:
                    res = res[0]
                    remote_protocol, remote_network, remote_content_format, _ = res.protocolInfo.split(':')
                    self.load(res.data, CurrentURIMetaData)
                    return {}
        return failure.Failure(errorCode(714))

    def upnp_SetMute(self, *args, **kwargs):
        InstanceID = int(kwargs['InstanceID'])
        Channel = kwargs['Channel']
        DesiredMute = kwargs['DesiredMute']
        if DesiredMute in ['TRUE', 'True', 'true', '1', 'Yes', 'yes']:
            self.mute()
        else:
            self.unmute()
        return {}

    def upnp_SetVolume(self, *args, **kwargs):
        InstanceID = int(kwargs['InstanceID'])
        Channel = kwargs['Channel']
        DesiredVolume = int(kwargs['DesiredVolume'])
        self.set_volume(DesiredVolume)
        return {}


def main():

    f = ElisaPlayer(None)

    def call_player():
        f.get_volume()
        f.get_mute()
        f.poll_player()

    reactor.callLater(2, call_player)

if __name__ == '__main__':

    from twisted.internet import reactor

    reactor.callWhenRunning(main)
    reactor.run()
