# -*- 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: Jesus Corrius <jcorrius@fluendo.com>
#          Olivier Tilloy <olivier@fluendo.com>

import cgi

from elisa.core import common
from elisa.core.media_uri import MediaUri
from elisa.core.metadata_manager import IncompleteMetadataResponse
from elisa.core.utils import defer, locale_helper
from elisa.core.utils.i18n import install_translation
from elisa.core.utils.text import name_to_shortcut

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

from elisa.plugins.base.utils import get_and_cache_image, \
                                     get_and_cache_thumbnail

from elisa.plugins.poblesec.base.list import BaseListController, \
                                             GenericListViewMode
from elisa.plugins.poblesec.base.list_switcher import ListSwitcherController
from elisa.plugins.poblesec.base.preview_list import \
    DoubleLineMenuItemPreviewListController, MenuItemPreviewListController
from elisa.plugins.poblesec.base.coverflow import \
    ImageWithReflectionCoverflowController
from elisa.plugins.poblesec.base.grid import GridItemGridController
from elisa.plugins.poblesec.widgets.info_screen import TextInfoScreen
from elisa.plugins.poblesec.widgets.button import PanelButton
from elisa.plugins.poblesec.actions import OpenControllerAction

from elisa.plugins.database.models import Movie, Video, File
from elisa.plugins.database.actions import PlayVideoAction, \
                                           OpenMovieSynopsisAction

import os, time

import gobject


_ = install_translation('database')


class ThumbnailMixin(object):
    thumbnail_type = 'video'

    def __init__(self):
        # FIXME: we need the frontend to get a reference to the gst_metadata
        # instance. This a cheap - UGLY - way to get the frontend without
        # changing a lot of client code. It is really ugly as we assume there
        # is only one frontend, which might not be the case in the future...
        frontend = common.application.interface_controller.frontends.values()[0]
        # Retrieve and store a reference to gst_metadata
        controllers = frontend.retrieve_controllers('/poblesec')
        try:
            self.gst_metadata = controllers[0].gst_metadata
        except AttributeError:
            msg = 'GstMetadata missing: thumbnails will not be generated.'
            self.warning(msg)
            self.gst_metadata = None

    def _updated_thumbnail(self, thumbnail, item):
        if thumbnail != None:
            # got a byte string path from gst_metadata, let's convert
            # it to unicode
            item.thumbnail_uri = thumbnail.decode(locale_helper.system_encoding())
        return thumbnail

    def _request_thumbnail(self, path):
        if self.gst_metadata is None:
            msg = 'No gst_metadata.'
            return defer.fail(IncompleteMetadataResponse(msg))

        def got_metadata(metadata):
            return metadata['thumbnail']

        uri = MediaUri({'scheme': 'file', 'path': path})
        metadata = {'uri': uri, 'thumbnail': None,
                    'mime_type': None, 'file_type': self.thumbnail_type}
        dfr = self.gst_metadata.get_metadata(metadata)
        dfr.addCallback(got_metadata)
        return dfr


class VideoViewMode(GenericListViewMode, ThumbnailMixin):

    def get_label(self, item):
        return defer.succeed(os.path.basename(item.file_path))

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

    def get_image(self, item, theme):
        def have_file(thumbnail_path):
            if os.path.exists(thumbnail_path):
                stat = os.stat(thumbnail_path)
                return stat.st_size != 0
            return False

        if getattr(item, 'thumbnail_uri', None) not in (None, "None") and \
                                                have_file(item.thumbnail_uri):
            return defer.succeed(item.thumbnail_uri)

        dfr = self._request_thumbnail(item.file_path)
        dfr.addCallback(self._updated_thumbnail, item)
        return dfr

    def get_preview_image(self, item, theme):
        if hasattr(item, 'thumbnail_uri') and item.thumbnail_uri != None and \
            item.thumbnail_uri != "None":
            return item.thumbnail_uri
        else:
            return None


class MovieViewMode(GenericListViewMode):

    def get_label(self, item):
        return defer.succeed(item.title)

    def get_sublabel(self, item):
        return defer.succeed(_("%(runtime)d min") % {'runtime': item.runtime})

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

    def get_image(self, item, theme):
        try:
            return defer.succeed(item.thumbnail_path)
        except AttributeError:
            if item.cover_uri:
                return get_and_cache_thumbnail(item, MediaUri(item.cover_uri))
            else:
                return None

    def get_preview_image(self, item, theme):
        try:
            return item.thumbnail_path
        except AttributeError:
            return None

    def get_contextual_background(self, item):
        if item.backdrop_uri:
            return get_and_cache_image(MediaUri(item.backdrop_uri))
        else:
            return defer.succeed(None)


class ListRefreshNotifier(gobject.GObject):
    """
    This object emits one signal to ask its controller to refresh its models.
    """
    __gsignals__ = {'model-refresh-needed': (gobject.SIGNAL_RUN_LAST,
                                             gobject.TYPE_BOOLEAN,
                                             ()),
                   }


gobject.type_register(ListRefreshNotifier)


class VideoController(BaseListController):

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

    def initialize(self, notifiers=None):
        dfr = super(VideoController, self).initialize()
        dfr.addCallback(self._set_notifiers, notifiers)
        dfr.addCallback(lambda result: self)
        return dfr

    def _set_notifiers(self, result, notifiers):
        if notifiers is None:
            handler_id, notifier = self._create_notifier()
            notifiers = [(handler_id, notifier),]
        self.notifiers = notifiers

    def _create_notifier(self):
        notifier = ListRefreshNotifier()
        handler_id = notifier.connect('model-refresh-needed',
                                      self.refresh)
        return (handler_id, notifier)

    def refresh(self, controller):
        self.model[:] = []

        def model_populated(model):
            self.model = notifying_list.List(model)

        def error_populating(failure):
            self.warning('Error populating model: %s' % \
                         failure.getErrorMessage())
            return failure

        dfr = self.populate_model()
        dfr.addCallback(model_populated)
        dfr.addErrback(error_populating)
        return dfr

    def clean(self):
        for handler_id, notifier in self.notifiers:
            notifier.disconnect(handler_id)
        self.notifiers = None
        return super(VideoController, self).clean()

    def create_actions(self):
        # Default action: play the video.
        default = PlayVideoAction(self)
        return default, []


class StartRecategorizationAction(OpenControllerAction):

    name = 'recategorize'

    def __init__(self, controller):
        path = '/poblesec/database/video/recategorize/choose_category'
        super(StartRecategorizationAction, self).__init__(controller, path)

    def execute(self, item):
        if isinstance(item, Movie):
            title = item.title
        else:
            title = item.name
        frontend = self.controller.frontend
        browser = frontend.retrieve_controllers('/poblesec/browser')[0]
        browser.history.set_mark('start_recategorization')
        browser.history.connect('pop_controller', self._controller_popped)
        return self.open_controller(self.path, title, video=item)

    # Localized hack to workaround the lack of a generic system to trigger a
    # refresh or update of a list controller's model.
    # This solution should be considered temporary. One of its drawbacks is that
    # the controller's model will be reloaded even if no actual recategorization
    # has been performed (e.g. if the process has been canceled).
    def _controller_popped(self, history, previous, current):
        try:
            if current.list_controller is self.controller:
                history.disconnect_by_func(self._controller_popped)
                self.controller.reload()
        except AttributeError:
            # Some intermediate controllers may not have a list_controller
            # attribute.
            pass


class AllVideosController(VideoController):
    empty_label = _('There are no videos in this section')

    def populate_model(self):

        def sort_by_name(result_set):
            result_set.config(distinct=True)
            result_set.order_by(Video.name, Video.file_path)
            return result_set.all()

        dfr = Video.unclassified_videos()
        dfr.addCallback(sort_by_name)
        return dfr

    def create_actions(self):
        # Default action: play the video.
        default = PlayVideoAction(self)
        # Contextual action: recategorize.
        recategorize = StartRecategorizationAction(self)
        return default, [recategorize]


class MovieController(VideoController):

    def create_actions(self):
        # Default action: play the movie.
        default = PlayVideoAction(self)
        # Contextual actions: synopsis, recategorize.
        # Note: this is a temporary solution until we set up a "more options"
        # screen, at which point the recategorize action will be moved there.
        synopsis = OpenMovieSynopsisAction(self)
        recategorize = StartRecategorizationAction(self)
        return default, [synopsis, recategorize]


class AllMoviesController(MovieController):

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

        def sort_by_title(result_set):
            result_set.order_by(Movie.title, Movie.file_path)
            return result_set.all()

        dfr = store.find(Movie, Movie.file_path == File.path)
        dfr.addCallback(sort_by_title)
        return dfr


class MovieSynopsis(TextInfoScreen):

    """
    Specialized information screen widget to display the synopsis of a movie.
    """

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

        caption.runtime = Text()
        caption.foreground.pack_start(caption.runtime, expand=True)
        caption.runtime.visible = True

    def pack_buttons(self, footer):
        # Play movie button
        footer.play_button = PanelButton()
        footer.play_button.text.label = _('Play Movie')
        footer.play_button.visible = True
        footer.pack_start(footer.play_button, expand=True)


class MovieSynopsisController(PigmentController):

    def initialize(self, movie, movies):
        self.movie = movie
        self.movies = movies
        return super(MovieSynopsisController, self).initialize()

    def set_frontend(self, frontend):
        super(MovieSynopsisController, self).set_frontend(frontend)
        info_screen = MovieSynopsis()
        info_screen.visible = True
        self.widget.add(info_screen)
        self.widget.set_focus_proxy(info_screen)
        info_screen.right_panel.set_focus_proxy(info_screen.right_panel.footer)
        self.info_screen = info_screen
        right_panel = info_screen.right_panel
        right_panel.title.foreground.label = _('INFORMATION')
        right_panel.footer.play_button.connect('activated',
                                               self._play_button_cb)
        self._populate()

    def _populate(self):
        left_panel = self.info_screen.left_panel
        caption = left_panel.caption
        caption.movie.label = self.movie.title
        caption.runtime.label = _('%(runtime)d min') % {'runtime': self.movie.runtime}

        contents = self.info_screen.right_panel.contents
        overview = cgi.escape(self.movie.short_overview)
        if not overview:
            overview = _('No synopsis available.')
        else:
            overview = _('<b>Synopsis</b>\n%(overview)s') % {'overview': overview}
        contents.summary.foreground.markup = overview

        self._set_poster()

    def _set_poster(self):
        def _set_poster(poster_path):
            artwork = self.info_screen.left_panel.artwork
            return artwork.foreground.set_from_file_deferred(poster_path)

        cover_uri = self.movie.cover_uri
        if cover_uri is None:
            theme = self.frontend.get_theme()
            resource = 'elisa.plugins.poblesec.glyphs.small.video'
            cover_uri = 'file://%s' % theme.get_resource(resource)
        dfr = get_and_cache_image(MediaUri(cover_uri))
        dfr.addCallback(_set_poster)
        return dfr

    def _play_button_cb(self, button, *args):
        action = PlayVideoAction(self)
        action.execute(self.movie)


# All Movies
class AllMoviesVerticalWithPreview(AllMoviesController, DoubleLineMenuItemPreviewListController):
    fastscroller_enabled = True
    view_mode = MovieViewMode

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

class AllMoviesCoverflow(AllMoviesController, ImageWithReflectionCoverflowController):
    view_mode = MovieViewMode

class AllMoviesGrid(AllMoviesController, GridItemGridController):
    view_mode = MovieViewMode

class AllMoviesListSwitcherController(ListSwitcherController):
    modes = [AllMoviesVerticalWithPreview,
             AllMoviesCoverflow,
             AllMoviesGrid]
    default_mode = AllMoviesVerticalWithPreview


# All Videos
class AllVideosVerticalWithPreview(AllVideosController, MenuItemPreviewListController):
    fastscroller_enabled = True
    view_mode = VideoViewMode

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

class AllVideosCoverflow(AllVideosController, ImageWithReflectionCoverflowController):
    view_mode = VideoViewMode

class AllVideosGrid(AllVideosController, GridItemGridController):
    view_mode = VideoViewMode

class AllVideosListSwitcherController(ListSwitcherController):
    modes = [AllVideosVerticalWithPreview,
             AllVideosCoverflow,
             AllVideosGrid]
    default_mode = AllVideosVerticalWithPreview
