#!/usr/bin/env python2

'''
'''

__docformat__ = 'restructuredtext'
__version__ = '$Id$'

import time

from pyglet.media import AbstractAudioPlayer, AbstractAudioDriver, \
                         MediaThread, MediaEvent

import pyglet
_debug = pyglet.options['debug_media']

class SilentAudioPacket(object):
    def __init__(self, timestamp, duration):
        self.timestamp = timestamp
        self.duration = duration

    def consume(self, dt):
        self.timestamp += dt
        self.duration -= dt

class SilentAudioPlayerPacketConsumer(AbstractAudioPlayer):
    # When playing video, length of audio (in secs) to buffer ahead.
    _buffer_time = 0.4

    # Minimum number of bytes to request from source
    _min_update_bytes = 1024

    # Maximum sleep time
    _sleep_time = 0.2

    def __init__(self, source_group, player):
        super(SilentAudioPlayerPacketConsumer, self).__init__(source_group, player)

        # System time of first timestamp
        self._timestamp_time = None

        # List of buffered SilentAudioPacket
        self._packets = []
        self._packets_duration = 0
        self._events = []

        # Actual play state.
        self._playing = False

        # TODO Be nice to avoid creating this thread if user doesn't care
        #      about EOS events and there's no video format.
        # NOTE Use thread.condition as lock for all instance vars used by worker
        self._thread = MediaThread(target=self._worker_func)
        if source_group.audio_format:
            self._thread.start()

    def delete(self):
        if _debug:
            print 'SilentAudioPlayer.delete'
        self._thread.stop()

    def play(self):
        if _debug: 
            print 'SilentAudioPlayer.play'

        with self._thread.condition:
            if not self._playing:
                self._playing = True
                self._timestamp_time = time.time()
                self._thread.condition.notify()

    def stop(self):
        if _debug:
            print 'SilentAudioPlayer.stop'

        with self._thread.condition:
            if self._playing:
                timestamp = self.get_time()
                if self._packets:
                    packet = self._packets[0]
                    self._packets_duration -= timestamp - packet.timestamp
                    packet.consume(timestamp - packet.timestamp)
                self._playing = False

    def clear(self):
        if _debug:
            print 'SilentAudioPlayer.clear'

        with self._thread.condition:
            del self._packets[:]
            self._packets_duration = 0
            del self._events[:]

    def get_time(self):
        if _debug:
            print 'SilentAudioPlayer.get_time()'

        with self._thread.condition:

            packets = self._packets

            if self._playing:
                # Consume timestamps
                result = None
                offset = time.time() - self._timestamp_time
                while packets:
                    packet = packets[0]
                    if offset > packet.duration:
                        del packets[0]
                        self._timestamp_time += packet.duration
                        offset -= packet.duration
                        self._packets_duration -= packet.duration
                    else:
                        packet.consume(offset)
                        self._packets_duration -= offset
                        self._timestamp_time += offset
                        result = packet.timestamp
                        break
            else:
                # Paused
                if packets:
                    result = packets[0].timestamp
                else:
                    result = None

        if _debug:
            print 'SilentAudioPlayer.get_time() -> ', result
        return result

    # Worker func that consumes audio data and dispatches events
    def _worker_func(self):
        thread = self._thread
        #buffered_time = 0
        eos = False
        events = self._events

        while True:
            with thread.condition:
                if thread.stopped or (eos and not events):
                    break

                # Use up "buffered" audio based on amount of time passed.
                timestamp = self.get_time()
                if _debug:
                    print 'timestamp: %r' % timestamp

                # Dispatch events
                while events and timestamp is not None:
                    if (events[0].timestamp is None or
                        events[0].timestamp <= timestamp):
                        events[0]._sync_dispatch_to_player(self.player)
                        del events[0]
                    else:
                        break

                # Calculate how much data to request from source
                secs = self._buffer_time - self._packets_duration
                bytes = secs * self.source_group.audio_format.bytes_per_second
                if _debug:
                    print 'Trying to buffer %d bytes (%r secs)' % (bytes, secs)

                while bytes > self._min_update_bytes and not eos:
                    # Pull audio data from source
                    audio_data = self.source_group.get_audio_data(int(bytes))
                    if not audio_data and not eos:
                        events.append(MediaEvent(timestamp, 'on_eos'))
                        events.append(MediaEvent(timestamp, 'on_source_group_eos'))
                        eos = True
                        break

                    # Pretend to buffer audio data, collect events.
                    if self._playing and not self._packets:
                        self._timestamp_time = time.time()
                    self._packets.append(SilentAudioPacket(audio_data.timestamp,
                                                        audio_data.duration))
                    self._packets_duration += audio_data.duration
                    for event in audio_data.events:
                        event.timestamp += audio_data.timestamp
                        events.append(event)
                    events.extend(audio_data.events)
                    bytes -= audio_data.length

                sleep_time = self._sleep_time
                if not self._playing:
                    sleep_time = None
                elif events and events[0].timestamp and timestamp:
                    sleep_time = min(sleep_time, events[0].timestamp - timestamp)

                if _debug:
                    print 'SilentAudioPlayer(Worker).sleep', sleep_time
                thread.sleep(sleep_time)

class SilentTimeAudioPlayer(AbstractAudioPlayer):
    # Note that when using this player (automatic if playing back video with
    # unsupported audio codec) no events are dispatched (because they are
    # normally encoded in the audio packet -- so no EOS events are delivered.
    # This is a design flaw.
    #
    # Also, seeking is broken because the timestamps aren't synchronized with
    # the source group.

    _time = 0.0
    _systime = None

    def play(self):
        self._systime = time.time()

    def stop(self):
        self._time = self.get_time()
        self._systime = None

    def delete(self):
        pass

    def clear(self):
        pass

    def get_time(self):
        if self._systime is None:
            return self._time
        else:
            return time.time() - self._systime + self._time

class SilentAudioDriver(AbstractAudioDriver):
    def create_audio_player(self, source_group, player):
        if source_group.audio_format:
            return SilentAudioPlayerPacketConsumer(source_group, player)
        else:
            return SilentTimeAudioPlayer(source_group, player)

    def delete(self):
        pass

def create_audio_driver():
    return SilentAudioDriver()

