# -*- 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.
#
# Authors: Guido Amoruso <guidonte@fluendo.com>
#          Benjamin Kampmann <benjamin@fluendo.com>
#          Olivier Tilloy <olivier@fluendo.com>

from elisa.core import common
from elisa.core.media_uri import MediaUri
from elisa.core.utils.i18n import install_translation
from elisa.core.utils.cancellable_defer import CancelledError
from elisa.core.utils.text import name_to_shortcut

from elisa.plugins.base.utils import get_and_cache_thumbnail
from elisa.plugins.base.models.media import PlayableModel

from elisa.plugins.database.models import *

from elisa.plugins.pigment.graph.text import Text

from elisa.plugins.poblesec.link import Link

from elisa.plugins.poblesec.section import SectionMenuViewMode
from elisa.plugins.poblesec.base.list import BaseListController
from elisa.plugins.poblesec.base.list_switcher import ListSwitcherController
from elisa.plugins.poblesec.base.info_list import InfoListController, \
                                                  ListInfoScreen
from elisa.plugins.poblesec.music_library import ArtistsViewMode, \
                                                 AlbumsViewMode, \
                                                 TracksViewMode

from elisa.plugins.poblesec.base.preview_list import \
    MenuItemPreviewListController, DoubleLineMenuItemPreviewListController
from elisa.plugins.poblesec.base.coverflow import \
    ImageWithReflectionCoverflowController
from elisa.plugins.poblesec.base.grid import GridItemGridController
from elisa.plugins.poblesec.base.info_list import InfoListController, \
                                                  ListInfoScreen

from elisa.plugins.poblesec.actions import Action

from elisa.plugins.database.actions import \
    PlayArtistAction, ListArtistAlbumsAction, ArtistInformationAction, ArtistMoreOptionsAction, \
    PlayAlbumAction, ListAlbumTracksAction, AlbumInformationAction, AlbumMoreOptionsAction, \
    PlayTrackAction, PlaySeveralTracksAction

from elisa.plugins.database.actions import ArtistTracksShuffleAction, \
     AlbumTracksShuffleAction

from twisted.internet import defer, task

from storm.expr import Not

import datetime

_ = install_translation('database')

# this is totally searcher controller based
from elisa.plugins.poblesec.search_controller import SearcherEntry


def music_search_result_decorator(controller):
    searcher = 'DBSearcher'
    title = _('Music On Your Computer')
    searcher_entry = SearcherEntry(searcher, title, None)
    controller.searchers.insert(0, searcher_entry)
    return defer.succeed(None)


class GenericArtistsController(BaseListController):

    empty_label = _('There are no artists in this section')
    albums_controller_path = '/poblesec/database/music/albums_of_artist'

    def create_actions(self):
        # Default action: list all albums of the artist.
        default = ListArtistAlbumsAction(self, self.albums_controller_path)
        # Contextual actions: more options.
        more = ArtistMoreOptionsAction(self)
        return default, [more]


class ArtistsDbController(GenericArtistsController):

    def initialize(self, artists=None):
        self.artists = artists
        return super(ArtistsDbController, self).initialize()

    def populate_model(self):
        if self.artists is not None:
            return defer.succeed(self.artists)

        def sort_artists(result_set):
            result_set.order_by(Artist.name)
            return result_set.all()

        dfr = common.application.store.find(Artist)
        dfr.addCallback(sort_artists)
        return dfr

    def clean(self):
        self.artists = None
        return super(ArtistsDbController, self).clean()


class ArtistsDbPreviewListController(ArtistsDbController, MenuItemPreviewListController):
    view_mode = ArtistsViewMode
    fastscroller_enabled = True

    def get_shortcut_for_item(self, item):
        return name_to_shortcut(item.name)

class ArtistsDbCoverflowController(ArtistsDbController, ImageWithReflectionCoverflowController):
    view_mode = ArtistsViewMode

class ArtistsDbGridController(ArtistsDbController, GridItemGridController):
    view_mode = ArtistsViewMode

class ArtistsDbListSwitcherController(ListSwitcherController):
    modes = [ArtistsDbPreviewListController,
             ArtistsDbCoverflowController,
             ArtistsDbGridController]
    default_mode = ArtistsDbPreviewListController



class ArtistInfoScreen(ListInfoScreen):
    """
    Specialized ListInfoScreen widget to display information or more options for
    a Music artist.
    """

    def pack_captions(self, caption):
        caption.artist = Text()
        caption.artist.visible = True
        caption.foreground.pack_start(caption.artist, expand=True)


class ArtistMoreOptionsController(InfoListController):
    """
    The "More Options" controller for a given artist.
    """

    view_mode = ArtistsViewMode
    info_screen_cls = ArtistInfoScreen

    def initialize(self, artist):
        self.artist = artist
        return super(ArtistMoreOptionsController, self).initialize()

    def populate_info(self):
        self.info_screen.right_panel.title.foreground.label = _('MORE OPTIONS')
        self.info_screen.left_panel.caption.artist.label = self.artist.name

        artwork = self.info_screen.left_panel.artwork
        default_resource = 'elisa.plugins.poblesec.glyphs.large.artist'

        def _set_poster(thumbnail_path):
            return artwork.foreground.set_from_file_deferred(thumbnail_path)

        poster_uri = self.artist.image_uri
        if poster_uri is not None:
            dfr = get_and_cache_thumbnail(self.artist, MediaUri(poster_uri))
            dfr.addCallback(_set_poster)
            return dfr
        else:
            return self.frontend.load_from_theme(default_resource, 
                                                 artwork.foreground)            

    def populate_model(self):
        shuffle = ArtistTracksShuffleAction(self)
        model = [shuffle,]
        return defer.succeed(model)

    def node_renderer(self, item, widget):
        widget.item_widget.label.label = item.label

    def item_activated(self, item):
        return item.execute(self.artist)



class GenericAlbumsDbController(BaseListController):

    empty_label = _('There are no albums in this section')

    def initialize(self, artist=None):
        self.artist = artist
        return super(GenericAlbumsDbController, self).initialize()

    def create_actions(self):
        # Default action: play all tracks in the album.
        default = PlayAlbumAction(self)
        # Contextual actions: list tracks, more options.
        list_tracks = ListAlbumTracksAction(self)
        more = AlbumMoreOptionsAction(self)
        return default, [list_tracks, more]

    def clean(self):
        self.artist = None
        return super(GenericAlbumsDbController, self).clean()


class AlbumsOfArtistDbController(GenericAlbumsDbController):

    def populate_model(self):
        if getattr(self.artist, 'uri', None) is not None:
            # Albums for non-database artists
            return defer.succeed(self.artist.albums)

        def order_tracks(result_set):
            return result_set.values(MusicTrack.album_name)

        def get_albums(album_names):
            return common.application.store.find(MusicAlbum,
                MusicAlbum.name.is_in(album_names))

        def sort_albums(result_set):
            result_set.order_by(MusicAlbum.name)
            return result_set.all()

        # Albums for database artists
        dfr = self.artist.tracks.find(MusicTrack.file_path == File.path,
                                      File.hidden == False)
        dfr.addCallback(order_tracks)
        dfr.addCallback(get_albums)
        dfr.addCallback(sort_albums)
        return dfr


class AlbumsDbController(GenericAlbumsDbController):

    def initialize(self, albums=None):
        self.albums = albums
        return super(AlbumsDbController, self).initialize()

    def populate_model(self):
        def sort_albums(result_set):
            result_set.order_by(MusicAlbum.name)
            return result_set.all()

        def add_albums(albums):
            self.model.extend(albums)
            return self

        # Show given albums
        if self.albums is not None:
            return defer.succeed(self.albums)

        # All albums in the collection
        dfr = common.application.store.find(MusicAlbum)
        dfr.addCallback(sort_albums)
        return dfr

    def clean(self):
        self.albums = None
        return super(AlbumsDbController, self).clean()


class AlbumsDbPreviewListController(AlbumsDbController, DoubleLineMenuItemPreviewListController):
    view_mode = AlbumsViewMode
    fastscroller_enabled = True

    def get_shortcut_for_item(self, item):
        return name_to_shortcut(item.name)

class AlbumsDbCoverflowController(AlbumsDbController, ImageWithReflectionCoverflowController):
    view_mode = AlbumsViewMode

class AlbumsDbGridController(AlbumsDbController, GridItemGridController):
    view_mode = AlbumsViewMode

class AlbumsDbListSwitcherController(ListSwitcherController):
    modes = [AlbumsDbPreviewListController,
             AlbumsDbCoverflowController,
             AlbumsDbGridController]
    default_mode = AlbumsDbPreviewListController

class AlbumsOfArtistDbPreviewListController(AlbumsOfArtistDbController, MenuItemPreviewListController):
    view_mode = AlbumsViewMode
    fastscroller_enabled = True

    def get_shortcut_for_item(self, item):
        return name_to_shortcut(item.name)

class AlbumsOfArtistDbCoverflowController(AlbumsOfArtistDbController, ImageWithReflectionCoverflowController):
    view_mode = AlbumsViewMode

class AlbumsOfArtistDbGridController(AlbumsOfArtistDbController, GridItemGridController):
    view_mode = AlbumsViewMode

class AlbumsOfArtistDbListSwitcherController(ListSwitcherController):
    modes = [AlbumsOfArtistDbPreviewListController,
             AlbumsOfArtistDbCoverflowController,
             AlbumsOfArtistDbGridController]
    default_mode = AlbumsOfArtistDbPreviewListController


class AlbumInfoScreen(ListInfoScreen):
    """
    Specialized ListInfoScreen widget to display information or more options for
    a Music artist.
    """

    def pack_captions(self, caption):
        caption.album = Text()
        caption.album.visible = True
        caption.foreground.pack_start(caption.album, expand=True)
        caption.artist = Text()
        caption.artist.visible = True
        caption.foreground.pack_start(caption.artist, expand=True)


class AlbumInfoListController(InfoListController):
    
    view_mode = TracksViewMode
    info_screen_cls = AlbumInfoScreen

    def initialize(self, album):
        self.album = album
        return super(AlbumInfoListController, self).initialize()

    def populate_info(self):
        artwork = self.info_screen.left_panel.artwork
        default_resource = 'elisa.plugins.poblesec.glyphs.large.music'
        self.info_screen.left_panel.caption.album.label = self.album.name

        def _set_poster(thumbnail_path):
            return artwork.foreground.set_from_file_deferred(thumbnail_path)

        def got_artist_name(artist_name):
            self.info_screen.left_panel.caption.artist.label = artist_name
            poster_uri = self.album.cover_uri
            if poster_uri is not None:
                dfr = get_and_cache_thumbnail(self.album, MediaUri(poster_uri))
                dfr.addCallback(_set_poster)
                return dfr
            else: 
                return self.frontend.load_from_theme(default_resource, 
                                                 artwork.foreground)

        dfr = self.album.get_artist_name()
        dfr.addCallback(got_artist_name)
        return dfr


class AlbumMoreOptionsController(AlbumInfoListController):
    """
    The "More Options" controller for a given album.
    """
    
    def populate_info(self):
        self.info_screen.right_panel.title.foreground.label = _('MORE OPTIONS')
        super(AlbumMoreOptionsController, self).populate_info()

    def populate_model(self):
        shuffle = AlbumTracksShuffleAction(self)
        model = [shuffle,]
        return defer.succeed(model)

    def node_renderer(self, item, widget):
        widget.item_widget.label.label = item.label

    def item_activated(self, item):
        return item.execute(self.album)


class AlbumTracksListController(AlbumInfoListController):
    """
    A generic info list controller for a given album. 
    Contains list of all tracks.
    """

    def populate_info(self):
        self.info_screen.right_panel.title.foreground.label = _('TRACKS')
        super(AlbumTracksListController, self).populate_info()

    def populate_model(self):
        return self.album.get_tracks()

    def node_renderer(self, item, widget):
        trash_letters = " .,-_"
        title = item.title.strip(trash_letters)
        if item.track_number:
            title = '%s. %s' % (str(item.track_number).zfill(2), title)
        widget.item_widget.label.label = title

    def create_actions(self):
        # Default action: play the track.
        return PlaySeveralTracksAction(self), []


class GenericTracksDbController(BaseListController):

    track_controller_path = '/poblesec/database/music/tracks'

    empty_label = _('There are no tracks in this section')

    def initialize(self, tracks=None, artist=None):
        self.tracks = tracks
        self.artist = artist

        return super(GenericTracksDbController, self).initialize()

    def get_shortcut_for_item(self, item):
        return name_to_shortcut(item.title)

    def create_actions(self):
        # Default action: play the track.
        return PlayTrackAction(self), []

    def clean(self):
        self.artist = None
        self.tracks = None
        return super(GenericTracksDbController, self).clean()


class TracksDbController(GenericTracksDbController):

    def populate_model(self):
        if self.tracks is not None:
            return defer.succeed(self.tracks)

        if self.artist is not None:
            return self.artist.get_tracks()

        # All tracks in the collection
        store = common.application.store
        dfr = store.find(MusicTrack, MusicTrack.title != None,
                         MusicTrack.file_path == File.path,
                         File.hidden == False)
        # Add client side ordering - can this be done "server" side? Is
        # there a better way to trash spourious characters?
        dfr.addCallback(lambda result_set: result_set.all())

        def sort_tracks_title_client_side(tracks):
            trash_letters = " .,-_"
            tracks.sort(key=lambda t: t.title.strip(trash_letters).lower())
            return tracks

        dfr.addCallback(sort_tracks_title_client_side)
        return dfr


class DBTracksViewMode(TracksViewMode):

    def get_sublabel(self, item):
        def set_values(result_list):
            result_list_len = len(result_list)
            if result_list_len == 0:
                # nothing found
                sublabel = ''
            elif result_list_len == 1:
                sublabel = result_list[0]
            elif result_list_len > 1:
                sublabel = ', '.join(result_list)
            return sublabel

        dfr = item.get_artists()
        dfr.addCallback(set_values)
        return dfr

class TracksPreviewListController(TracksDbController, DoubleLineMenuItemPreviewListController):
    view_mode = DBTracksViewMode
    fastscroller_enabled = True


class TracksDbCoverflowController(TracksDbController, ImageWithReflectionCoverflowController):
    view_mode = DBTracksViewMode

class TracksDbGridController(TracksDbController, GridItemGridController):
    view_mode = DBTracksViewMode

class TracksDbListSwitcherController(ListSwitcherController):
    modes = [TracksPreviewListController,
             TracksDbCoverflowController,
             TracksDbGridController]
    default_mode = TracksPreviewListController



# A list of important genres
GENRES = [u"Acid Jazz", u"Alternative", u"Alternative & Punk",
          u"Alternative Pop/Rock", u"Alternative Rock", u"AlternRock",
          u"Ambient", u"Blues", u"Blues/R&B", u"Books & Spoken",
          u"Children's Music", u"Classic Rock", u"Classical", u"Comedy",
          u"Dance", u"Easy Listening", u"Electronic", u"Electronica & Dance",
          u"Electronice/Dance", u"Entertainment", u"Folk", u"Funk",
          u"Games", u"Garage/Punk Rock Revival", u"General Alternative",
          u"Hard Rock", u"Hip Hop", u"Hip Hop/Rap", u"Hip-Hop",
          u"Hip-Hop/Rap", u"Holiday", u"House", u"Indi", u"Industrial",
          u"Jazz", u"Latin", u"Metal", u"Music Videos", u"New Age",
          u"Other", u"Pop", u"Punk", u"R&B", u"R&B/Soul", u"Rap", u"Raggae",
          u"Religious", u"Rock", u"Rock & Roll", u"Soundtrack", u"Techno",
          u"Trance", u"World"]


class GenresDbController(BaseListController):

    empty_label = _('There are no tracks in this section')

    def initialize(self, all_genres=False):
        self.all_genres = all_genres

        deferred = super(GenresDbController, self).initialize()

        def got_genres(genres, all_genres):
            def iterate(genres, models):
                for genre in genres:
                    link = Link()
                    link.controller_path = '/poblesec/database/music/genre'
                    link.label = genre
                    link.icon = 'elisa.plugins.poblesec.glyphs.small.music'
                    link.controller_args = {'genre': genre}
                    models.append(link)
                    yield link

            def iterate_done(result, models):
                self.model.extend(models)
                return self

            models = []

            if not all_genres:
                link = Link()
                link.controller_path = '/poblesec/database/music/genres'
                link.label = _("All genres")
                link.icon = 'elisa.plugins.poblesec.glyphs.small.music'
                link.controller_args = {'all_genres': True}
                models.append(link)

            dfr = task.coiterate(iterate(genres, models))
            dfr.addCallback(iterate_done, models)
            return dfr

        def sort_by_genre(result_set):
            result_set.order_by(MusicTrack.genre)
            return result_set

        def set_distinct(result_set):
            result_set.config(distinct=True)
            return result_set

        def get_genres(result_set):
            return result_set.values(MusicTrack.genre)

        def get_tracks(self):
            store = common.application.store
            if all_genres:
                dfr = store.find(MusicTrack, MusicTrack.genre != None)
            else:
                dfr = store.find(MusicTrack, MusicTrack.genre.is_in(GENRES))
            return dfr

        deferred.addCallback(get_tracks)
        deferred.addCallback(set_distinct)
        deferred.addCallback(sort_by_genre)
        deferred.addCallback(get_genres)
        deferred.addCallback(got_genres, all_genres)
        return deferred

    def item_activated(self, item):
        if isinstance(item, Action):
            return item.run()
        else:
            browser = self.frontend.retrieve_controllers('/poblesec/browser')[0]
            path = item.controller_path
            args = item.controller_args
            return browser.history.append_controller(path, item.label, **args)


class GenresDbPreviewListController(GenresDbController, MenuItemPreviewListController):
    view_mode = SectionMenuViewMode
    item_widget_kwargs = {'with_artwork_box': False}

class GenresDbCoverflowController(GenresDbController, ImageWithReflectionCoverflowController):
    view_mode = SectionMenuViewMode

class GenresDbGridController(GenresDbController, GridItemGridController):
    view_mode = SectionMenuViewMode

class GenresDbListSwitcherController(ListSwitcherController):
    modes = [GenresDbPreviewListController,
             GenresDbCoverflowController,
             GenresDbGridController]
    default_mode = GenresDbPreviewListController


class GenreDbController(GenericAlbumsDbController):

    def initialize(self, genre=None):
        self.genre = genre
        return super(GenreDbController, self).initialize()

    def populate_model(self):
        store = common.application.store

        def distinct_all(result_set):
            result_set.config(distinct=True)
            return result_set.all()

        def get_albums(results):
            return store.find(MusicAlbum, MusicAlbum.name.is_in(results))

        def sort_by(result_set):
            result_set.order_by(MusicAlbum.name)
            return result_set.all()

        dfr = store.find(MusicTrack.album_name,
                         MusicTrack.genre == unicode(self.genre),
                         MusicTrack.file_path == File.path,
                         File.hidden == False)
        dfr.addCallback(distinct_all)
        dfr.addCallback(get_albums)
        dfr.addCallback(sort_by)
        return dfr


class GenrePreviewListController(GenreDbController, DoubleLineMenuItemPreviewListController):
    view_mode = AlbumsViewMode
    fastscroller_enabled = True

    def get_shortcut_for_item(self, item):
        return name_to_shortcut(item.name)

class GenreDbCoverflowController(GenreDbController, ImageWithReflectionCoverflowController):
    view_mode = AlbumsViewMode

class GenreDbGridController(GenreDbController, GridItemGridController):
    view_mode = AlbumsViewMode

class GenreDbListSwitcherController(ListSwitcherController):
    modes = [GenrePreviewListController,
             GenreDbCoverflowController,
             GenreDbGridController]
    default_mode = GenrePreviewListController


class DecadesDbController(BaseListController):

    def initialize(self, all_genres=False):
        self.all_genres = all_genres

        deferred = super(DecadesDbController, self).initialize()

        def iterate(models):
            # FIXME: What about other decades (10's, 20's, 30's, 40's, ...)?
            #        And what about other centuries?
            for decade in (50, 60, 70, 80, 90):
                link = Link()
                link.label = _("%(decade)d's") % {'decade': decade}
                link.controller_args = \
                    {'begin': datetime.datetime(1900 + decade, 1, 1),
                     'end': datetime.datetime(1909 + decade, 12, 31)}
                models.append(link)
                yield link

        def iterate_done(result, models):
            self.model.extend(models)
            return self

        models = []

        def populate(self):
            dfr = task.coiterate(iterate(models))
            dfr.addCallback(iterate_done, models)
            return dfr

        deferred.addCallback(populate)
        return deferred

    def node_clicked(self, widget, item):
        if isinstance(item, Action):
            item.run()
        else:
            browser = self.frontend.retrieve_controllers('/poblesec/browser')[0]
            path = '/poblesec/database/music/time'
            args = item.controller_args
            dfr = browser.history.append_controller(path, item.label, **args)


class DecadesDbPreviewListController(DecadesDbController, MenuItemPreviewListController):
    view_mode = SectionMenuViewMode

class DecadesDbCoverflowController(DecadesDbController, ImageWithReflectionCoverflowController):
    view_mode = SectionMenuViewMode

class DecadesDbGridController(DecadesDbController, GridItemGridController):
    view_mode = SectionMenuViewMode

class DecadesDbListSwitcherController(ListSwitcherController):
    modes = [DecadesDbPreviewListController,
             DecadesDbCoverflowController,
             DecadesDbGridController]
    default_mode = DecadesDbPreviewListController


class TimeDbController(GenericAlbumsDbController):

    def initialize(self, begin=None, end=None):
        self.begin = begin
        self.end = end

        deferred = super(TimeDbController, self).initialize()

        def got_albums(albums):
            if not albums:
                # FIXME: We should never raise generic exceptions, let's find
                #        a suitable specific exception for this case.
                raise Exception('Missing Data')
            self.model.extend(albums)
            return self

        def sort_by_name(result_set):
            result_set.order_by(MusicAlbum.name)
            return result_set.all()

        def get_albums(self):
            store = common.application.store
            dfr = store.find(MusicAlbum,
                             (MusicAlbum.release_date >= begin) & \
                             (MusicAlbum.release_date <= end))
            return dfr

        def missing_data(failure):
            self.warning("Missing Data for decade.")
            return self

        deferred.addCallback(get_albums)
        deferred.addCallback(sort_by_name)
        deferred.addCallback(got_albums)
        deferred.addErrback(missing_data)
        return deferred


class TimeDbPreviewListController(TimeDbController, DoubleLineMenuItemPreviewListController):
    view_mode = AlbumsViewMode

class TimeDbCoverflowController(TimeDbController, ImageWithReflectionCoverflowController):
    view_mode = AlbumsViewMode

class TimeDbGridController(TimeDbController, GridItemGridController):
    view_mode = AlbumsViewMode

class TimeDbListSwitcherController(ListSwitcherController):
    modes = [TimeDbPreviewListController,
             TimeDbCoverflowController,
             TimeDbGridController]
    default_mode = TimeDbPreviewListController
