# 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 gio
import glib
import gobject
import gtk
import threading

import xl.radio, xl.playlist
from xl import (
    event,
    common,
    settings,
    trax,
    xdg
)
from xl.nls import gettext as _
import xlgui.panel.playlists as playlistpanel
from xlgui import (
    guiutil,
    icons,
    menu,
    panel,
    playlist as guiplaylist
)
from xlgui.widgets import dialogs

class RadioException(Exception): pass
class ConnectionException(RadioException): pass

class RadioPanel(panel.Panel, playlistpanel.BasePlaylistPanelMixin):
    """
        The Radio Panel
    """
    __gsignals__ = {
        'playlist-selected': (gobject.SIGNAL_RUN_LAST, None, (object,)),
        'append-items': (gobject.SIGNAL_RUN_LAST, None, (object,)),
        'replace-items': (gobject.SIGNAL_RUN_LAST, None, (object,)),
        'queue-items': (gobject.SIGNAL_RUN_LAST, None, (object,)),
    }
    __gsignals__.update(playlistpanel.BasePlaylistPanelMixin._gsignals_)
    
    ui_info = ('radio_panel.ui', 'RadioPanelWindow')
    _radiopanel = None

    def __init__(self, parent, collection,
        radio_manager, station_manager):
        """
            Initializes the radio panel
        """
        panel.Panel.__init__(self, parent)
        playlistpanel.BasePlaylistPanelMixin.__init__(self)

        self.collection = collection
        self.manager = radio_manager
        self.playlist_manager = station_manager
        self.nodes = {}
        self.load_nodes = {}
        self.complete_reload = {}
        self.loaded_nodes = []

        self._setup_tree()
        self._setup_widgets()
        self.playlist_image = gtk.gdk.pixbuf_new_from_file(
            xdg.get_data_path('images/playlist.png'))

        # menus
        self.playlist_menu = menu.RadioPanelPlaylistMenu()
        self.track_menu = menu.PlaylistsPanelTrackMenu()
        self._connect_events()

        self.load_streams()
        RadioPanel._radiopanel = self

    def load_streams(self):
        """
            Loads radio streams from plugins
        """
        for name in self.playlist_manager.playlists:
            pl = self.playlist_manager.get_playlist(name)
            if pl is not None:
                self.playlist_nodes[pl] = self.model.append(self.custom,
                    [self.playlist_image,
                    pl.name, pl])
                self._load_playlist_nodes(pl)
        self.tree.expand_row(self.model.get_path(self.custom), False)

        for name, value in self.manager.stations.iteritems():
            self.add_driver(value)

    def _add_driver_cb(self, type, object, driver):
        self.add_driver(driver)

    def add_driver(self, driver):
        """
            Adds a driver to the radio panel
        """
        node = self.model.append(self.radio_root, [self.folder, str(driver), driver])
        self.nodes[driver] = node
        self.load_nodes[driver] = self.model.append(node, [self.refresh_image,
            _('Loading streams...'), None])
        self.tree.expand_row(self.model.get_path(self.radio_root), False)

        if settings.get_option('gui/radio/%s_station_expanded' %
                driver.name, False):
            self.tree.expand_row(self.model.get_path(node), False)

    def _remove_driver_cb(self, type, object, driver):
        self.remove_driver(driver)

    def remove_driver(self, driver):
        """
            Removes a driver from the radio panel
        """
        if driver in self.nodes:
            self.model.remove(self.nodes[driver])
            del self.nodes[driver]

    def _setup_widgets(self):
        """
            Sets up the various widgets required for this panel
        """
        self.status = self.builder.get_object('status_label')

    @guiutil.idle_add()
    def _set_status(self, message, timeout=0):
        self.status.set_text(message)

        if timeout:
            glib.timeout_add_seconds(timeout, self._set_status, '', 0)

    def _connect_events(self):
        """
            Connects events used in this panel
        """
        self.track_menu.connect('remove-track', lambda *e:
            self.remove_selected_track())

        menu = self.playlist_menu
        menu.connect('add-playlist', self._on_add_button_clicked)

        menu.connect('append-items', lambda *e:
            self.emit('append-items', self.tree.get_selected_tracks()))
        menu.connect('replace-items', lambda *e:
            self.emit('replace-items', self.tree.get_selected_tracks()))
        menu.connect('queue-items', lambda *e:
            self.emit('queue-items', self.tree.get_selected_tracks()))
        menu.connect('open-playlist', lambda *e:
            self.open_selected_playlist())
        menu.connect('export-playlist', lambda widget, path:
            self.export_selected_playlist(path))
        menu.connect('rename-playlist', lambda widget, name:
            self.rename_selected_playlist(name))
        menu.connect('remove-playlist', lambda *e:
            self.remove_selected_playlist())


        self.builder.connect_signals({
            'on_add_button_clicked': self._on_add_button_clicked,
        })
        self.tree.connect('row-expanded', self.on_row_expand)
        self.tree.connect('row-collapsed', self.on_collapsed)
        self.tree.connect('row-activated', self.on_row_activated)
        self.tree.connect('key-release-event', self.on_key_released)

        event.add_callback(self._add_driver_cb, 'station_added',
                self.manager)
        event.add_callback(self._remove_driver_cb, 'station_removed',
                self.manager)

    def _on_add_button_clicked(self, *e):
        dialog = dialogs.MultiTextEntryDialog(self.parent,
            _("Add Radio Station"))

        dialog.add_field(_("Name:"))
        url_field = dialog.add_field(_("URL:"))

        clipboard = gtk.clipboard_get()
        text = clipboard.wait_for_text()

        if text is not None:
            location = gio.File(uri=text)

            if location.get_uri_scheme() is not None:
                url_field.set_text(text)

        result = dialog.run()
        dialog.hide()

        if result == gtk.RESPONSE_OK:
            (name, uri) = dialog.get_values()
            self._do_add_playlist(name, uri)

    @common.threaded
    def _do_add_playlist(self, name, uri):
        from xl import playlist, trax
        if playlist.is_valid_playlist(uri):
            pl = playlist.import_playlist(uri)
            pl.name = name
        else:
            pl = playlist.Playlist(name)
            tracks = trax.get_tracks_from_uri(uri)
            try:
                pl.add_tracks(tracks)
            # Catch empty directories
            except IndexError:
                pass

        self.playlist_manager.save_playlist(pl)
        self._add_to_tree(pl)

    @guiutil.idle_add()
    def _add_to_tree(self, pl):
        self.playlist_nodes[pl] = self.model.append(self.custom,
            [self.playlist_image, pl.name, pl])
        self._load_playlist_nodes(pl)

    def _setup_tree(self):
        """
            Sets up the tree that displays the radio panel
        """
        box = self.builder.get_object('RadioPanel')
        self.tree = playlistpanel.PlaylistDragTreeView(self, True, True)
        self.tree.set_headers_visible(False)

        self.targets = [('text/uri-list', 0, 0)]

        # columns
        text = gtk.CellRendererText()
        if settings.get_option('gui/ellipsize_text_in_panels', False):
            import pango
            text.set_property( 'ellipsize-set', True)
            text.set_property( 'ellipsize', pango.ELLIPSIZE_END)
        icon = gtk.CellRendererPixbuf()
        col = gtk.TreeViewColumn('radio')
        col.pack_start(icon, False)
        col.pack_start(text, True)
        col.set_attributes(icon, pixbuf=0)
        col.set_cell_data_func(text, self.cell_data_func)
        self.tree.append_column(col)

        self.model = gtk.TreeStore(gtk.gdk.Pixbuf, str, object)
        self.tree.set_model(self.model)

        self.track = gtk.gdk.pixbuf_new_from_file(
            xdg.get_data_path('images/track.png'))
        self.folder = self.tree.render_icon(
            gtk.STOCK_DIRECTORY, gtk.ICON_SIZE_SMALL_TOOLBAR)
        self.refresh_image = icons.MANAGER.pixbuf_from_stock(
            gtk.STOCK_REFRESH)

        self.custom = self.model.append(None, [self.folder, _("Saved Stations"), None])
        self.radio_root = self.model.append(None, [self.folder, _("Radio "
            "Streams"), None])

        scroll = gtk.ScrolledWindow()
        scroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
        scroll.add(self.tree)
        scroll.set_shadow_type(gtk.SHADOW_IN)

        box.pack_start(scroll, True, True)

    def on_row_activated(self, tree, path, column):
        item = self.model[path][2]
        if isinstance(item, xl.radio.RadioItem):
            self.emit('playlist-selected', item.get_playlist())
        elif isinstance(item, playlistpanel.TrackWrapper):
            self.emit('playlist-selected', item.playlist)
        elif isinstance(item, xl.playlist.Playlist):
            self.open_station(item)

    def open_station(self, playlist):
        """
            Opens a saved station
        """
        self.emit('playlist-selected', playlist)

    def get_menu(self):
        """
            Returns the menu that all radio stations use
        """
        menu = guiutil.Menu()
        menu.append(_("Refresh"), self.on_reload, gtk.STOCK_REFRESH)
        return menu

    def on_key_released(self, widget, event):
        """
            Called when a key is released in the tree
        """
        if event.keyval == gtk.keysyms.Menu:
            (mods,paths) = self.tree.get_selection().get_selected_rows()
            if paths and paths[0]:
                iter = self.model.get_iter(paths[0])
                item = self.model.get_value(iter, 2)
                if isinstance(item, (xl.radio.RadioStation, xl.radio.RadioList,
                    xl.radio.RadioItem)):
                    if isinstance(item, xl.radio.RadioStation):
                        station = item
                    else:
                        station = item.station

                    if station and hasattr(station, 'get_menu'):
                        menu = station.get_menu(self)
                        gtk.Menu.popup(menu, None, None, None, 0, event.time)
                elif isinstance(item, xl.playlist.Playlist):
                    gtk.Menu.popup(self.playlist_menu, None, None, None, 0, event.time)
                elif isinstance(item, playlistpanel.TrackWrapper):
                    gtk.Menu.popup(self.track_menu, None, None, None, 0, event.time)
            return True

        if event.keyval == gtk.keysyms.Left:
            (mods,paths) = self.tree.get_selection().get_selected_rows()
            if paths and paths[0]:
                self.tree.collapse_row(paths[0])
            return True

        if event.keyval == gtk.keysyms.Right:
            (mods,paths) = self.tree.get_selection().get_selected_rows()
            if paths and paths[0]:
                self.tree.expand_row(paths[0], False)
            return True

        return False

    def button_press(self, widget, event):
        """
            Called when someone clicks on the tree
        """
        if event.button == 3:
            (x, y) = map(int, event.get_coords())
            path = self.tree.get_path_at_pos(x, y)
            if path:
                iter = self.model.get_iter(path[0])
                item = self.model.get_value(iter, 2)

                if isinstance(item, (xl.radio.RadioStation, xl.radio.RadioList,
                    xl.radio.RadioItem)):
                    if isinstance(item, xl.radio.RadioStation):
                        station = item
                    else:
                        station = item.station

                    if station and hasattr(station, 'get_menu'):
                        menu = station.get_menu(self)
                        menu.popup(None, None, None, event.button, event.time)
                elif isinstance(item, xl.playlist.Playlist):
                    self.playlist_menu.popup(event)
                elif isinstance(item, playlistpanel.TrackWrapper):
                    self.track_menu.popup(event)

    def cell_data_func(self, column, cell, model, iter):
        """
            Called when the tree needs a value for column 1
        """
        object = model.get_value(iter, 1)
        cell.set_property('text', str(object))

    def drag_data_received(self, tv, context, x, y, selection, info, etime):
        """
            Called when someone drags some thing onto the playlist panel
        """
        #if the drag originated from radio view deny it
        #TODO this might change if we are allowed to change the order of radio
        if tv == context.get_source_widget():
            context.drop_finish(False, etime)
            return

        locs = list(selection.get_uris())

        path = self.tree.get_path_at_pos(x, y)
        if path:
            # Add whatever we received to the playlist at path
            iter = self.model.get_iter(path[0])
            current_playlist = self.model.get_value(iter, 2)

            # if it's a track that we've dragged to, get the parent
            if isinstance(current_playlist, playlistpanel.TrackWrapper):
                current_playlist = current_playlist.playlist

            elif not isinstance(current_playlist, xl.playlist.Playlist):
                self._add_new_station(locs)
                return
            (tracks, playlists) = self.tree.get_drag_data(locs)
            current_playlist.add_tracks(tracks)
            # Do we save in the case when a user drags a file onto a playlist in the playlist panel?
            # note that the playlist does not have to be open for this to happen
            self.playlist_manager.save_playlist(current_playlist, overwrite=True)
            self._load_playlist_nodes(current_playlist)
        else:
            self._add_new_station(locs)

    def _add_new_station(self, locs):
        """
            Add a new station
        """
        # If the user dragged files prompt for a new playlist name
        # else if they dragged a playlist add the playlist

        #We don't want the tracks in the playlists to be added to the
        # master tracks list so we pass in False
        (tracks, playlists) = self.tree.get_drag_data(locs, False)
        #First see if they dragged any playlist files
        for new_playlist in playlists:
            self.model.append(self.custom, [self.playlist_image,
                new_playlist.name, new_playlist])
            # We are adding a completely new playlist with tracks so we save it
            self.playlist_manager.save_playlist(new_playlist, overwrite=True)

        #After processing playlist proceed to ask the user for the
        #name of the new playlist to add and add the tracks to it
        if len(tracks) > 0:
            dialog = dialogs.TextEntryDialog(
            _("Enter the name you want for your new playlist"),
            _("New Playlist"))
            result = dialog.run()
            if result == gtk.RESPONSE_OK:
                name = dialog.get_value()
                if not name == "":
                    #Create the playlist from all of the tracks
                    new_playlist = xl.playlist.Playlist(name)
                    new_playlist.add_tracks(tracks)
                    self.playlist_nodes[new_playlist] = self.model.append(self.custom,
                        [self.playlist_image,
                        new_playlist.name, new_playlist])
                    self.tree.expand_row(self.model.get_path(self.custom), False)
                    # We are adding a completely new playlist with tracks so we save it
                    self.playlist_manager.save_playlist(new_playlist)
                    self._load_playlist_nodes(new_playlist)

    def drag_get_data(self, tv, context, selection_data, info, time):
        """
            Called when the user drags a playlist from the radio panel
        """
        tracks = self.tree.get_selected_tracks()

        if not tracks:
            return

        for track in tracks:
            guiutil.DragTreeView.dragged_data[track.get_loc_for_io()] = track

        uris = trax.util.get_uris_from_tracks(tracks)
        selection_data.set_uris(uris)

    def drag_data_delete(self, *e):
        """
            stub
        """
        pass

    def on_reload(self, *e):
        """
            Called when the refresh button is clicked
        """
        selection = self.tree.get_selection()
        info = selection.get_selected_rows()
        if not info: return
        (model, paths) = info
        iter = self.model.get_iter(paths[0])
        object = self.model.get_value(iter, 2)

        try:
            self.loaded_nodes.remove(self.nodes[object])
        except ValueError: pass

        if isinstance(object, (xl.radio.RadioList, xl.radio.RadioStation)):
            self._clear_node(iter)
            self.load_nodes[object] = self.model.append(iter,
                [self.refresh_image, _("Loading streams..."), None])

            self.complete_reload[object] = True
            self.tree.expand_row(self.model.get_path(iter), False)

    @staticmethod
    def set_station_expanded_value(station, value):
        settings.set_option(
                'gui/radio/%s_station_expanded' % station,
                True,
                )

    def on_row_expand(self, tree, iter, path):
        """
            Called when a user expands a row in the tree
        """
        driver = self.model.get_value(iter, 2)

        if not isinstance(driver, xl.playlist.Playlist):
            self.model.set_value(iter, 0, self.folder)

        if isinstance(driver, xl.radio.RadioStation) or \
            isinstance(driver, xl.radio.RadioList):
            if not self.nodes[driver] in self.loaded_nodes:
                self._load_station(iter, driver)

        if isinstance(driver, xl.radio.RadioStation):
            self.set_station_expanded_value(driver.name, True)

    def on_collapsed(self, tree, iter, path):
        """
            Called when someone collapses a tree item
        """
        driver = self.model.get_value(iter, 2)

        if not isinstance(driver, xl.playlist.Playlist):
            self.model.set_value(iter, 0, self.folder)

        if isinstance(driver, xl.radio.RadioStation):
            self.set_station_expanded_value(driver.name, False)

    @common.threaded
    def _load_station(self, iter, driver):
        """
            Loads a radio station
        """
        lists = None
        no_cache = False
        if driver in self.complete_reload:
            no_cache = True
            del self.complete_reload[driver]

        if isinstance(driver, xl.radio.RadioStation):
            try:
                lists = driver.get_lists(no_cache=no_cache)
            except RadioException, e:
                self._set_status(str(e), 2)
        else:
            try:
                lists = driver.get_items(no_cache=no_cache)
            except RadioException, e:
                self._set_status(str(e), 2)

        if not lists: return
        glib.idle_add(self._done_loading, iter, driver, lists)

    def _done_loading(self, iter, object, items):
        """
            Called when an item is done loading.  Adds items to the tree
        """
        self.loaded_nodes.append(self.nodes[object])
        for item in items:
            if isinstance(item, xl.radio.RadioList):
                node = self.model.append(self.nodes[object], [self.folder, item.name, item])
                self.nodes[item] = node
                self.load_nodes[item] = self.model.append(node, [self.refresh_image,
                    _("Loading streams..."), None])
            else:
                self.model.append(self.nodes[object], [self.track, item.name,
                    item])

        try:
            self.model.remove(self.load_nodes[object])
            del self.load_nodes[object]
        except KeyError: pass

    def _clear_node(self, node):
        """
            Clears a node of all children
        """
        remove = []
        iter = self.model.iter_children(node)
        while iter:
            remove.append(iter)
            iter = self.model.iter_next(iter)
        for row in remove:
            self.model.remove(row)

def set_status(message, timeout=0):
    RadioPanel._radiopanel._set_status(message, timeout)
