# -*- 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.
#
# Author: Gernot Klimscha <gernot@fluendo.com>

"""
ResourceProvider for browsing iPod databases
"""

from elisa.core.media_uri import MediaUri, _unicode
import os, platform

from twisted.internet import defer, threads, task

import gpod

# import all the models
from elisa.plugins.base.models.audio import AlbumModel, TrackModel, ArtistModel
from elisa.plugins.ipod.models import IpodTrackListModel, IpodSongListModel, \
                                      IpodAlbumListModel, IpodArtistListModel

from elisa.core.components.resource_provider import ResourceProvider

class IpodResource(ResourceProvider):
    """
    This class implements iPod support
    """

    supported_uri = "^ipod://"

    def __init__(self):
        super(IpodResource, self).__init__()
        self.dbs = {}

    def get(self, uri, model):
        """
        the action parameter of the uri tells what informations are
        requested, but at the moment only tracks can be requested

        the search parameter tells which tracks should be requested

        possible search values are:
        albums, songs and artists (which is the default search)

        the search can also be narrowed by setting artist and/or album
        parameter to search only for example for all albums of a given
        artist.

        for each different request, a different model is returned, which is
        always a list model that contains a list of artist, album or song models

        examples (all for tracks at the moment):
        get songs from artist a:
        ipod://mnt_point/?action=tracks&search=songs&artist=a

        get albums from artist a:
        ipod://mnt_point/?action=tracks&search=albums&artist=a

        get all artists:
        ipod://mnt_point/?action=tracks&search=artist
        or (because it is the default search):
        ipod://mnt_point/?action=tracks

        contextual models are not supported!
        """
        dfr = None
        action = uri.get_param('action')

        # first time the request is made for this path, parse the ipod db
        path = str(uri.path)
        if path not in self.dbs:
            kw = {}
            if os.path.isdir(path):
                kw['mountpoint'] = path
            elif os.path.isfile(path):
                # it's a iTunes database
                kw['localdb'] = path

            dfr = threads.deferToThread(gpod.Database, **kw)

            # different actions like parsing all tracks, playlists ...
            if action.lower() == "tracks":
                # parse tracks
                dfr.addCallback(self._parse_tracks, path)
                dfr.addCallback(self._get_tracks, uri, model)
            else:
                # TODO: other actions like pictures, podcasts ...
                return None, defer.fail(NotImplementedError())
        else:
            if action.lower() == "tracks":
                return model, self._get_tracks(None, uri, model)
        return model, dfr

    def _get_tracks(self, db, uri, model):
        search = uri.get_param('search').lower()

        track_list = self.dbs[str(uri.path)][1]

        # filtering methods:
        def filter_albums_for_artist(track, model, already_stored, artist):
            if (track.album not in already_stored and track.artist == artist):
                already_stored.append(track.album)
                album = AlbumModel()
                album.album = track.album
                album.artist = track.artist
                model.albums.append(album)

        def filter_all_albums(track, model, already_stored):
            if track.album not in already_stored:
                already_stored.append(track.album)
                album = AlbumModel()
                album.album = track.album
                model.albums.append(album)

        def filter_all_songs(track, model, already_stored):
            if track.title not in already_stored:
                already_stored.append(track.title)
                model.songs.append(track)

        def filter_songs_for_album(track, model, already_stored, album):
            if (track.title not in already_stored and track.album == album):
                already_stored.append(track.title)
                model.songs.append(track)

        def filter_songs_for_artist(track, model, already_stored, artist):
            if (track.title not in already_stored and track.artist == artist):
                already_stored.append(track.title)
                model.songs.append(track)

        def filter_all_artists(track, model, already_stored):
            if track.artist not in already_stored:
                already_stored.append(track.artist)
                artist = ArtistModel()
                artist.name = track.artist
                model.artists.append(artist)

        # init filter_args
        filter_args = ()
        # already stored list
        already = []
        # filtering check
        if search == 'albums':
            # get albums
            model = IpodAlbumListModel()
            artist = uri.get_param('artist')
            if artist:
                # only albums from given artist
                filter_callback = filter_albums_for_artist
                filter_args = (artist,)
            else:
                # get all albums
                filter_callback = filter_all_albums

        elif search == 'songs':
            # get songs
            model = IpodSongListModel()
            artist = uri.get_param('artist')
            album = uri.get_param('album')
            if album:
                # only songs from given album
                filter_callback = filter_songs_for_album
                filter_args = (album,)
            elif artist:
                # only songs from given artist
                filter_callback = filter_songs_for_artist
                filter_args = (artist,)
            else:
                # get all songs
                filter_callback = filter_all_songs
        else:
            # get all artists
            model = IpodArtistListModel()
            filter_callback = filter_all_artists

        # filter method
        def async_filter(tracks, model, callback, already_stored, *args):
            for track in tracks:
                yield callback(track, model, already_stored, *args)

        dfr = task.coiterate(async_filter(track_list.tracks, model,
                filter_callback, already, *filter_args))
        return dfr.addCallback(lambda x: model)

    def _parse_tracks(self, db, path):
        """
        parse all tracks on the ipod and store them in a TrackListModel
        """
        def iterate(path, db, track_list):
            for track in db:
                artist_name = _unicode(track['artist'])
                album_name = _unicode(track['album'])

                # workaround some gpod oddities with recent iPods (tested with
                # 6th gen Nano)
                if artist_name in ('', None):
                    continue
                if album_name in ('', None):
                    continue

                track_id = track['id']
                title = _unicode(track['title'])
                track_nb = str(track['track_nr']).zfill(2)
                track_type = track['filetype']

                # win32 hack
                if platform.system() == 'Windows':
                    file_uri = "file:///"
                else:
                    file_uri = "file://"

                song_path = MediaUri("%s%s%s" % (file_uri, path,
                                           track['ipod_path'].replace(':','/')))
                track_model = TrackModel()
                track_model.title = title
                track_model.artist = artist_name
                track_model.track_number = track_nb
                track_model.playable_uri = song_path

                # not in the base TrackModel:
                track_model.album = album_name

                track_list.tracks.append(track_model)
                yield track_model

        def add_to_store(result, db, path, track_list):
            self.dbs[path] = (db, track_list)

        track_list = IpodTrackListModel()
        dfr = task.coiterate(iterate(path, db, track_list))
        dfr.addCallback(add_to_store, db, path, track_list)
        return dfr


