# This file is part of beets.
# Copyright 2013, Adrian Sampson.
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.

"""A wrapper for the GStreamer Python bindings that exposes a simple
music player.
"""
from __future__ import print_function

import sys
import time
import gobject
import thread
import os
import copy
import urllib

import pygst
pygst.require('0.10')
import gst


class GstPlayer(object):
    """A music player abstracting GStreamer's Playbin element.

    Create a player object, then call run() to start a thread with a
    runloop. Then call play_file to play music. Use player.playing
    to check whether music is currently playing.

    A basic play queue is also implemented (just a Python list,
    player.queue, whose last element is next to play). To use it,
    just call enqueue() and then play(). When a track finishes and
    another is available on the queue, it is played automatically.
    """

    def __init__(self, finished_callback=None):
        """Initialize a player.

        If a finished_callback is provided, it is called every time a
        track started with play_file finishes.

        Once the player has been created, call run() to begin the main
        runloop in a separate thread.
        """

        # Set up the Gstreamer player. From the pygst tutorial:
        # http://pygstdocs.berlios.de/pygst-tutorial/playbin.html
        self.player = gst.element_factory_make("playbin2", "player")
        fakesink = gst.element_factory_make("fakesink", "fakesink")
        self.player.set_property("video-sink", fakesink)
        bus = self.player.get_bus()
        bus.add_signal_watch()
        bus.connect("message", self._handle_message)

        # Set up our own stuff.
        self.playing = False
        self.finished_callback = finished_callback
        self.cached_time = None
        self._volume = 1.0

    def _get_state(self):
        """Returns the current state flag of the playbin."""
        # gst's get_state function returns a 3-tuple; we just want the
        # status flag in position 1.
        return self.player.get_state()[1]

    def _handle_message(self, bus, message):
        """Callback for status updates from GStreamer."""
        if message.type == gst.MESSAGE_EOS:
            # file finished playing
            self.player.set_state(gst.STATE_NULL)
            self.playing = False
            self.cached_time = None
            if self.finished_callback:
                self.finished_callback()

        elif message.type == gst.MESSAGE_ERROR:
            # error
            self.player.set_state(gst.STATE_NULL)
            err, debug = message.parse_error()
            print("Error: " + str(err))
            self.playing = False

    def _set_volume(self, volume):
        """Set the volume level to a value in the range [0, 1.5]."""
        # And the volume for the playbin.
        self._volume = volume
        self.player.set_property("volume", volume)

    def _get_volume(self):
        """Get the volume as a float in the range [0, 1.5]."""
        return self._volume

    volume = property(_get_volume, _set_volume)

    def play_file(self, path):
        """Immediately begin playing the audio file at the given
        path.
        """
        self.player.set_state(gst.STATE_NULL)
        if isinstance(path, unicode):
            path = path.encode('utf8')
        uri = 'file://' + urllib.quote(path)
        self.player.set_property("uri", uri)
        self.player.set_state(gst.STATE_PLAYING)
        self.playing = True

    def play(self):
        """If paused, resume playback."""
        if self._get_state() == gst.STATE_PAUSED:
            self.player.set_state(gst.STATE_PLAYING)
            self.playing = True

    def pause(self):
        """Pause playback."""
        self.player.set_state(gst.STATE_PAUSED)

    def stop(self):
        """Halt playback."""
        self.player.set_state(gst.STATE_NULL)
        self.playing = False
        self.cached_time = None

    def run(self):
        """Start a new thread for the player.

        Call this function before trying to play any music with
        play_file() or play().
        """
        # If we don't use the MainLoop, messages are never sent.
        gobject.threads_init()

        def start():
            loop = gobject.MainLoop()
            loop.run()
        thread.start_new_thread(start, ())

    def time(self):
        """Returns a tuple containing (position, length) where both
        values are integers in seconds. If no stream is available,
        returns (0, 0).
        """
        fmt = gst.Format(gst.FORMAT_TIME)
        try:
            pos = self.player.query_position(fmt, None)[0] / (10 ** 9)
            length = self.player.query_duration(fmt, None)[0] / (10 ** 9)
            self.cached_time = (pos, length)
            return (pos, length)

        except gst.QueryError:
            # Stream not ready. For small gaps of time, for instance
            # after seeking, the time values are unavailable. For this
            # reason, we cache recent.
            if self.playing and self.cached_time:
                return self.cached_time
            else:
                return (0, 0)

    def seek(self, position):
        """Seeks to position (in seconds)."""
        cur_pos, cur_len = self.time()
        if position > cur_len:
            self.stop()
            return

        fmt = gst.Format(gst.FORMAT_TIME)
        ns = position * 10 ** 9  # convert to nanoseconds
        self.player.seek_simple(fmt, gst.SEEK_FLAG_FLUSH, ns)

        # save new cached time
        self.cached_time = (position, cur_len)

    def block(self):
        """Block until playing finishes."""
        while self.playing:
            time.sleep(1)


def play_simple(paths):
    """Play the files in paths in a straightforward way, without
    using the player's callback function.
    """
    p = GstPlayer()
    p.run()
    for path in paths:
        p.play_file(path)
        p.block()


def play_complicated(paths):
    """Play the files in the path one after the other by using the
    callback function to advance to the next song.
    """
    my_paths = copy.copy(paths)

    def next_song():
        my_paths.pop(0)
        p.play_file(my_paths[0])
    p = GstPlayer(next_song)
    p.run()
    p.play_file(my_paths[0])
    while my_paths:
        time.sleep(1)

if __name__ == '__main__':
    # A very simple command-line player. Just give it names of audio
    # files on the command line; these are all played in sequence.
    paths = [os.path.abspath(os.path.expanduser(p))
             for p in sys.argv[1:]]
    # play_simple(paths)
    play_complicated(paths)
