# -*- 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: Olivier Tilloy <olivier@fluendo.com>
#          Philippe Normand <philippe@fluendo.com>

from elisa.core.common import application
from elisa.core.media_uri import MediaUri
from elisa.core.components.model import Model
from elisa.core.utils import defer, caching
from elisa.core.utils.cancellable_defer import CancelledError
from elisa.core.utils.i18n import install_translation

from elisa.plugins.poblesec.base.hierarchy import HierarchyController
from elisa.plugins.poblesec.base.list import GenericListViewMode
from elisa.plugins.poblesec.base.preview_list import MenuItemPreviewListController
from elisa.plugins.poblesec.base.coverflow import ImageWithReflectionCoverflowController
from elisa.plugins.poblesec.base.grid import GridItemGridController
from elisa.plugins.poblesec.base.list_switcher import ListSwitcherController
from elisa.plugins.poblesec.actions import Action

from elisa.plugins.base.models.image import ImageModel

from elisa.plugins.flickr.models import FlickrResponseModel, \
                                        FlickrPhotoModel, FlickrTagModel, \
                                        FlickrPhotoSetModel, FlickrContactModel
from elisa.plugins.flickr.actions import LoginAction, LogoutAction, MyAccountAction
from elisa.plugins.flickr import flickr_api as flickr
from elisa.plugins.flickr.utils import get_flickr_provider

import os


_ = install_translation('flickr')
_p = install_translation('poblesec')


class FlickrEntryPointModel(Model):
    """ Model representing one of the main Flickr menus.

    @ivar title:     menu title
    @type title:     unicode
    @ivar uri:       entry point URI
    @type uri:       L{elisa.core.media_uri.MediaUri}
    @ivar need_grid: should the entry point be browsed using the grid or not
    @type need_grid: bool
    """

    def __init__(self):
        super(Model, self).__init__()
        self.title = None
        self.uri = None
        self.need_grid = False


def flickr_decorator(controller):
    controller.append_plugin('flickr', _('Flickr'), '/poblesec/flickr/home')
    return defer.succeed(None)


class ControllerMixin(object):

    empty_label = _('There are no pictures in this section')
    
    search_uri = MediaUri('flickr://search')
    account_uri = MediaUri('flickr://account')

    def node_clicked(self, widget, item):
        if isinstance(item, Action):
            item.run()
        elif isinstance(item, FlickrEntryPointModel):
            controllers = self.frontend.retrieve_controllers('/poblesec/browser')
            browser = controllers[0]
            if item.uri == self.search_uri:
                path = '/poblesec/flickr/search'
            elif item.uri == self.account_uri:
                path = '/poblesec/flickr/account'
            elif item.need_grid:
                path = '/poblesec/flickr'
            else:
                path = '/poblesec/flickr/folder'
            dfr = browser.history.append_controller(path,
                                                    item.title, uri=item.uri)

        elif isinstance(item, FlickrTagModel):
            args = {'tags': item.label}
            method = 'flickr.photos.search'
            authenticated = False
            if item.private:
                # only my photos
                args['user_id'] = 'me'
                authenticated = True
            item.uri = MediaUri(flickr.generate_call_uri(method=method,
                                                         arguments=args,
                                                         authenticated=authenticated))
            controllers = self.frontend.retrieve_controllers('/poblesec/browser')
            browser = controllers[0]
            dfr = browser.history.append_controller('/poblesec/flickr', item.label,
                                                    uri=item.uri)

        elif isinstance(item, FlickrPhotoSetModel):
            provider = get_flickr_provider()
            authenticated = provider.auth_token is not None
            item.uri = MediaUri(flickr.generate_call_uri(method='flickr.photosets.getPhotos',
                                                         arguments={'photoset_id': item.flickr_id},
                                                         authenticated=authenticated))
            controllers = self.frontend.retrieve_controllers('/poblesec/browser')
            browser = controllers[0]
            dfr = browser.history.append_controller('/poblesec/flickr', item.title, uri=item.uri)

        elif isinstance(item, FlickrContactModel):
            item.uri = MediaUri(flickr.generate_call_uri(method='flickr.photosets.getList',
                                                         arguments={'user_id': item.nsid},
                                                         authenticated=True))
            controllers = self.frontend.retrieve_controllers('/poblesec/browser')
            browser = controllers[0]
            name = item.realname or item.username
            dfr = browser.history.append_controller('/poblesec/flickr/folder', name,
                                                    uri=item.uri)

        elif isinstance(item, FlickrPhotoModel):
            # We want a Deferred here, too
            self.play_image(item)
            dfr = defer.Deferred()
            dfr.callback(True)

    def play_image(self, item):
        controllers = self.frontend.retrieve_controllers('/poblesec/slideshow_player')
        slideshow_controller = controllers[0]
        playlist = []

        # Cancel all currently queued requests
        for widget in self.nodes._widgets:
            self.cancel_deferreds(widget)

        def _failure(failure):
            # Swallow errbacks only when the deferred has been cancelled
            failure.trap(CancelledError)

        for flickr_photo in self.model:
            if isinstance(flickr_photo, FlickrPhotoModel):
                if flickr_photo == item:
                    # save the index of the image to be displayed first
                    index = len(playlist)
                if not hasattr(flickr_photo, 'images'):
                    dfr, photo = _get_image_available_sizes(flickr_photo)
                    self.register_deferred(photo, dfr)
                    dfr.addErrback(_failure)
                else:
                    photo = flickr_photo.images
                    if not photo.references:
                        dfr, photo = _get_image_available_sizes(flickr_photo)
                        self.register_deferred(photo, dfr)
                        dfr.addErrback(_failure)
                photo.title = flickr_photo.title or '<%s>' % flickr_photo.flickr_id
                # we want Flickr's photos to be branded as such in the slideshow
                photo.logo = "elisa.plugins.flickr.logo"
                playlist.append(photo)

        slideshow_controller.player.set_playlist(playlist, index)

        controllers = self.frontend.retrieve_controllers('/poblesec')
        main = controllers[0]
        main.show_slideshow_player()

    def _resource_loaded(self, resource):
        if isinstance(resource, FlickrResponseModel):
            if hasattr(resource, 'photos'):
                self.model.extend(resource.photos)
            elif hasattr(resource, 'tags'):
                self.empty_label = _('There are no tags for this account')
                self.model.extend(resource.tags)
            elif hasattr(resource, 'photosets'):
                self.empty_label = _('There are no sets for this account')
                self.model.extend(resource.photosets)
            elif hasattr(resource, 'contacts'):
                self.empty_label = _('There are no contacts for this account')
                self.model.extend(resource.contacts)
        return self

    def _load_resource(self, result):
        resource, get_deferred = application.resource_manager.get(self.uri)
        return get_deferred


class FlickrController(ControllerMixin, HierarchyController):

    start_uri = MediaUri('http://%s/services/rest/' % flickr.API_SERVER)

    def initialize(self, uri=start_uri):
        deferred = super(FlickrController, self).initialize()
        self.uri = uri

        def add_entries(result):
            entries = [(_('Last 7 days interesting'), True,
                        flickr.generate_call_uri(method='flickr.interestingness.getList')),
                       (_('Popular tags'), False,
                        flickr.generate_call_uri(method='flickr.tags.getHotList')),
                       (_('Most recent uploads'), True,
                        flickr.generate_call_uri(method='flickr.photos.getRecent')),
                       ]

            entry_models = []
            for title, need_grid, url in entries:
                entry_model = FlickrEntryPointModel()
                entry_model.title = title
                entry_model.uri = MediaUri(url)
                entry_model.need_grid = need_grid
                entry_models.append(entry_model)

            self.model.extend(entry_models)
            self.model.append(MyAccountAction(self))
            return self

        def check_for_login_dfr(result):
            provider = get_flickr_provider()
            if provider.login_dfr is not None:
                return provider.login_dfr

        deferred.addCallback(check_for_login_dfr)

        if uri == self.start_uri:
            # List of the entry points
            deferred.addCallback(add_entries)
        else:
            # One particular entry point
            deferred.addCallback(self._load_resource)
            deferred.addCallback(self._resource_loaded)

        return deferred


class FlickrAccountController(ControllerMixin, HierarchyController):

    def initialize(self, uri=ControllerMixin.account_uri):
        deferred = super(FlickrAccountController, self).initialize()
        self.uri = uri

        provider = get_flickr_provider()

        def build_account_menu(self):
            entries = []
            if provider.auth_token:
                entries = [(_('Sets'), False,
                            flickr.generate_call_uri(method='flickr.photosets.getList',
                                                     authenticated=True)),
                           (_('Tags'), False,
                            flickr.generate_call_uri(method='flickr.tags.getListUser',
                                                     authenticated=True)),
                           (_('Unorganized photos'), True,
                            flickr.generate_call_uri(method='flickr.photos.getNotInSet',
                                                     authenticated=True)),
                           (_('Contacts'), False,
                            flickr.generate_call_uri(method='flickr.contacts.getList',
                                                     authenticated=True)),
                           ]

            entry_models = []
            for title, need_grid, url in entries:
                entry_model = FlickrEntryPointModel()
                entry_model.title = title
                entry_model.uri = MediaUri(url)
                entry_model.need_grid = need_grid
                entry_models.append(entry_model)

            # login/logout actions
            if not provider.auth_token:
                entry_models.append(LoginAction(self))
            else:
                entry_models.append(LogoutAction(self))

            self.model.extend(entry_models)
            return self

        if uri == self.account_uri:
            # List of the entry points
            deferred.addCallback(build_account_menu)
        else:
            # One particular entry point
            deferred.addCallback(self._load_resource)
            deferred.addCallback(self._resource_loaded)

        return deferred


def _get_photo_thumbnail(item):
    # Load the photo thumbnail
    thumbnail_uri = item.images.references[0]
    try:
        thumbnail_file = item.thumbnail_file
    except AttributeError:
        item.thumbnail_file = caching.get_cached_image_path(thumbnail_uri)
        thumbnail_file = item.thumbnail_file

    if os.path.exists(thumbnail_file):
        return defer.succeed(thumbnail_file)
    else:
        data_model, dfr = application.resource_manager.get(thumbnail_uri)
        dfr.addCallback(lambda data_model: \
                        caching.cache_to_file(data_model.data, thumbnail_file))
        return dfr


def _get_image_available_sizes(item):
    # Retrieve the list of available sizes (URLs) for an image
    item.images = ImageModel()
    # TODO: implement image rotation
    item.images.can_rotate = False

    # Check if we have this list in the cache first, will avoid a costly call
    # to the Flickr API
    cache_dir = caching.get_cache_path(u'flickr_cache')
    cache_file = os.path.join(cache_dir, item.flickr_id)
    if os.path.exists(cache_file):
        item.images.references = caching.deserialize(cache_file)
        return (defer.succeed(item.images), item.images)

    provider = get_flickr_provider()
    authenticated = provider.auth_token is not None
    request = flickr.generate_call_uri(method='flickr.photos.getSizes',
                                       arguments={'photo_id': item.flickr_id},
                                       authenticated=authenticated)

    def got_available_sizes(result_model):
        if not item.images.references:
            item.images.references.extend(result_model.sizes.references)
            caching.serialize(item.images.references, cache_file)
        return item.images

    result_model, dfr = application.resource_manager.get(request)
    dfr.addCallback(got_available_sizes)
    return (dfr, item.images)


class FlickrViewMode(GenericListViewMode):

    """
    Implementation of the common view modes API.
    """

    def get_label(self, item):
        if isinstance(item, FlickrEntryPointModel):
            title = item.title
        elif isinstance(item, FlickrTagModel):
            title = item.label
        elif isinstance(item, FlickrPhotoModel):
            title = item.title or '<%s>' % item.flickr_id
        elif isinstance(item, FlickrPhotoSetModel):
            title = item.title
        elif isinstance(item, FlickrContactModel):
            title = item.realname or item.username
        elif isinstance(item, Action):
            title = item.title
        return defer.succeed(title)

    def get_default_image(self, item):
        return 'elisa.plugins.poblesec.glyphs.small.pictures'

    def get_image(self, item, theme):
        if isinstance(item, FlickrPhotoModel):
            # Load the photo thumbnail
            def get_thumbnail(images):
                return _get_photo_thumbnail(item)

            if not hasattr(item, 'images') or not item.images.references:
                images_deferred, images = _get_image_available_sizes(item)
            else:
                images_deferred = defer.succeed(item)
            images_deferred.addCallback(get_thumbnail)
            return images_deferred
        else:
            return None

    def get_preview_image(self, item, theme):
        if isinstance(item, FlickrPhotoModel):
            try:
                return item.thumbnail_file
            except AttributeError:
                return None
        else:
            return None


class FlickrPreviewListController(FlickrController, MenuItemPreviewListController):
    view_mode = FlickrViewMode
    item_widget_kwargs = {'with_artwork_box': False}

class FlickrCoverflowController(FlickrController, ImageWithReflectionCoverflowController):
    view_mode = FlickrViewMode


class FlickrGridController(FlickrController, GridItemGridController):
    view_mode = FlickrViewMode


class FlickrListSwitcherController(ListSwitcherController):
    modes = [FlickrPreviewListController,
             FlickrCoverflowController,
             FlickrGridController]
    crumbs_logo = "elisa.plugins.flickr.crumbs_logo"
    default_mode = FlickrGridController

class FlickrListSwitcherPreviewController(FlickrListSwitcherController):
    default_mode = FlickrPreviewListController


# Account

class FlickrAccountPreviewListController(FlickrAccountController,
                                         MenuItemPreviewListController):
    view_mode = FlickrViewMode
    item_widget_kwargs = {'with_artwork_box': False}

class FlickrAccountCoverflowController(FlickrAccountController,
                                       ImageWithReflectionCoverflowController):
    view_mode = FlickrViewMode


class FlickrAccountGridController(FlickrAccountController, GridItemGridController):
    view_mode = FlickrViewMode


class FlickrAccountListSwitcherController(ListSwitcherController):
    modes = [FlickrAccountPreviewListController,
             FlickrAccountCoverflowController,
             FlickrAccountGridController]
    crumbs_logo = "elisa.plugins.flickr.crumbs_logo"
    default_mode = FlickrAccountPreviewListController


def use_me_hook(frontend):
    """
    'Use me' hook that takes the user to the Pictures/Internet/Flickr section.

    @param frontend: the current frontend (poblesec)
    @type frontend:  L{elisa.plugins.pigment.pigment_frontend.PigmentFrontend}

    @return:         a deferred fired when the action is complete
    @rtype:          L{elisa.core.utils.defer.Deferred}
    """
    browser = frontend.retrieve_controllers('/poblesec/browser')[0]
    paths = [('/poblesec/internet_menu', _p('INTERNET MEDIA'), {}),
             ('/poblesec/pictures/internet', _p('Images'), {}),
             ('/poblesec/flickr/home', _('Flickr'), {})]
    return browser.navigate(paths)
