# -*- 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.

import sys
import re

from elisa.core import common
from elisa.core.media_uri import MediaUri
from elisa.core.utils import typefinding
from elisa.core.utils.notifying_dict import NotifyingDictionary
from elisa.core.metadata_manager import IncompleteMetadataResponse
from elisa.core.utils.i18n import install_translation
from elisa.core.utils.text import name_to_shortcut
from elisa.core.utils import defer

from elisa.plugins.base.models.file import FileModel, DirectoryModel
from elisa.plugins.base.models.media import PlayableModel, PlaylistModel
from elisa.plugins.base.models.image import ImageModel
from elisa.plugins.base.messages.device import DeviceRemoved
from elisa.plugins.base.models.device import VolumeModel

from elisa.plugins.poblesec.base.list import BaseListController, \
                                             GenericListViewMode
from elisa.plugins.poblesec.base.preview_list import \
    ListItemWidgetWithActions, 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 OpenControllerAction

from twisted.internet import task


_ = install_translation('poblesec')


class OpenFolderOrPlayFileAction(OpenControllerAction):

    """
    All-in-one contextual action for filesystem browsing: open folders and play
    media files.
    """

    def execute(self, item):
        if isinstance(item, DirectoryModel):
            # Cancel all pending deferred calls (thumbnails requests)
            self.controller.cancel_all_deferreds()

            return self.open_controller(self.path, item.name, uri=item.uri,
                                        media_type=self.controller.media_type)

        elif isinstance(item, FileModel):
            # The actual enqueuing/playback is implemented in the controller.
            if item.media_type == "image":
                return self.controller.play_image(item)
            elif item.media_type == "audio":
                return self.controller.play_music_or_video(item, 'music')
            elif item.media_type == "video":
                return self.controller.play_music_or_video(item, 'video')

        # This should never happen, but is here for the sake of completeness.
        return defer.fail(NotImplementedError())


class FilesystemController(BaseListController):

    """
    A basic filesystem controller that allows browsing and playing media.
    It doesn't provide any contextual action by default.

    @cvar model_parsers: List of functions that take the controller and a
                         model in parameter and return a model, this is a way
                         for plugins to modify the appearance and/or actions
                         on some models.
    @type model_parsers: C{list} of callables
    """

    model_parsers = []
    hide_files = False
    node_widget = ListItemWidgetWithActions

    def __init__(self):
        super(FilesystemController, self).__init__()
        self._load_error = None

    def initialize(self, uri=None, media_type=None):
        self.uri = uri
        self.media_type = media_type
        return super(FilesystemController, self).initialize()

    @property
    def empty_label(self):
        media_type = self.media_type
        # FIXME: please clean the media_type values mess.
        if self._load_error is not None:
            msg = _("There was an error loading the folder.")
        elif media_type == 'video':
            msg = _("This folder does not contain any video files.")
        elif media_type in ('music', 'audio'):
            msg = _("This folder does not contain any audio files.")
        elif media_type in ('pictures', 'image'):
            msg = _("This folder does not contain any image files.")
        else:
            msg = _("This folder does not contain any multimedia files")
        return msg

    def populate_model(self):
        if self.uri is None:
            return defer.succeed([])

        media_directories = common.application.media_directories
        media_directories.refresh_directory_list(self.media_type)

        # self.uri points to a directory: add a trailing slash to get the actual
        # contents of the directory
        uri = self.uri.join('')
        resource, dfr = common.application.resource_manager.get(uri)

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

        def got_list(contents):
            model = []

            def iterate_contents(contents, model):
                for index, item in enumerate(contents.files):
                    for transformation in self.model_parsers:
                        item = transformation(self, item)
                    contents.files[index] = item
                    
                # contents.files actually contains files and sub-folders. Nasty!
                folder_and_files = sorted(contents.files,
                                          key=lambda elem: elem.name.lower())
                folders = []
                files = []
                for folder_or_file in folder_and_files:
                    if folder_or_file.name.startswith('.'):
                        # Ignore hidden files
                        yield None
                        continue
    
                    elif isinstance(folder_or_file, DirectoryModel):
                        # Is the folder listed in the media directories?
                        # Note: this media_directory attribute is not documented
                        # in the DirectoryModel class.
                        folder_or_file.is_a_media_source = \
                            media_directories.is_a_media_source(folder_or_file.uri)
                        folders.append(folder_or_file)

                    elif not self.hide_files and \
                        isinstance(folder_or_file, FileModel):
                        if not folder_or_file.media_type:
                            # FIXME: a lot of that code is obsolete now that
                            # we don't have section-specific file browsers any
                            # more
                            try:
                                media_types = typefinding.typefind(MediaUri(folder_or_file.uri))
                            except typefinding.UnknownFileExtension:
                                media_types = []

                            if self.media_type is not None and \
                                self.media_type not in media_types:
                                # Unknown media type, ignore the file
                                yield None
                                continue

                            if self.media_type is not None:
                                folder_or_file.media_type = self.media_type
                            elif media_types:
                                folder_or_file.media_type = media_types[0]
                            else:
                                # No media type recognized for the file, ignore it
                                yield None
                                continue

                        files.append(folder_or_file)

                    yield None

                # list folders first and then files
                model.extend(folders)
                model.extend(files)

            dfr = task.coiterate(iterate_contents(contents, model))
            dfr.addCallbacks(lambda result: model, error_getting_list)
            return dfr

        dfr.addCallbacks(got_list, error_getting_list)
        return dfr

    def create_actions(self):
        default = OpenFolderOrPlayFileAction(self, '/poblesec/filesystem')
        return default, []

    def item_to_uri(self, item):
        if isinstance(item, DirectoryModel):
            return item.uri
        elif isinstance(item, VolumeModel):
            return MediaUri({'scheme': 'file', 'path': item.mount_point})

    def play_image(self, item):
        playlist = []
        index = [0] # twisted use of a list to save the index

        def iterate_files(playlist, index):
            for folder_or_file in self.model:
                if isinstance(folder_or_file, FileModel) and \
                    folder_or_file.media_type == item.media_type:
                    if folder_or_file == item:
                        # save the index of the image to be displayed first
                        index[0] = len(playlist)
                    image = ImageModel()
                    if folder_or_file.thumbnail is not None:
                        thumbnail_uri = \
                            MediaUri("file://" + folder_or_file.thumbnail)
                        image.references.append(thumbnail_uri)
                    image.references.append(folder_or_file.uri)
                    image.title = folder_or_file.name
                    playlist.append(image)
                yield None

        def enqueue_and_play(result, playlist, index):
            path = '/poblesec/slideshow_player'
            slideshow = self.frontend.retrieve_controllers(path)[0]
            slideshow.player.set_playlist(playlist)
            main = self.frontend.retrieve_controllers('/poblesec')[0]
            main.show_slideshow_player()
            slideshow.player.start_slideshow(index[0])

        dfr = task.coiterate(iterate_files(playlist, index))
        dfr.addCallback(enqueue_and_play, playlist, index)
        return dfr

    def play_music_or_video(self, item, player):
        # player should be one of ('music', 'video')
        playlist = PlaylistModel()
        index = [0] # twisted use of a list to save the index

        def iterate_files(playlist, index):
            for folder_or_file in self.model:
                if isinstance(folder_or_file, FileModel) and \
                    folder_or_file.media_type == item.media_type:
                    if folder_or_file == item:
                        # save the index of the file to be played first
                        index[0] = len(playlist)
                    playable_model = PlayableModel()
                    playable_model.uri = folder_or_file.uri
                    playlist.append(playable_model)
                yield None

        def enqueue_and_play(result, playlist, index):
            path = '/poblesec/%s_player' % player
            player_controller = self.frontend.retrieve_controllers(path)[0]
            player_controller.player.set_playlist(playlist)
            main = self.frontend.retrieve_controllers('/poblesec')[0]
            eval('main.show_%s_player()' % player)
            return player_controller.player.play_at_index(index[0])

        dfr = task.coiterate(iterate_files(playlist, index))
        dfr.addCallback(enqueue_and_play, playlist, index)
        return dfr


class FilesystemViewMode(GenericListViewMode):

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

    filetype_to_resource = {
        'video': 'elisa.plugins.poblesec.glyphs.small.video',
        'audio': 'elisa.plugins.poblesec.glyphs.small.music',
        'image': 'elisa.plugins.poblesec.glyphs.small.pictures',
        'directory': 'elisa.plugins.poblesec.glyphs.small.devices_shares',
        'disc': 'elisa.plugins.poblesec.glyphs.small.disc',
    }

    volume_label_re = re.compile(r'.* \([A-Z]:\)')

    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 = 'No gst_metadata: all gstreamer metadata requests will fail.'
            self.warning(msg)
            self.gst_metadata = None

    def get_label(self, item):
        label = item.name
        if isinstance(item, VolumeModel) and 'linux' in sys.platform:
            label = '%s (%s)' % (label, item.mount_point)
        if isinstance(item, VolumeModel) and sys.platform == 'win32' and \
            self.volume_label_re.match(label) is None:
            # For devices that have a label, add the drive letter between
            # parentheses. Others that don't have a label already have this
            # information in their name (e.g. "Volume (D:)").
            label = '%s (%s)' % (label, item.device)
        return defer.succeed(label)

    def get_default_image(self, item):
        if isinstance(item, VolumeModel):
            # FIXME: should depend on the type of device
            return 'elisa.plugins.poblesec.glyphs.small.devices_shares'

        fallback = None
        resource = self.filetype_to_resource.get(item.media_type, fallback)
        return resource

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

        metadata = {'uri': item.uri, 'thumbnail': None}

        def _got_metadata(metadata):
            return metadata.get('thumbnail', None)

        if self.gst_metadata.able_to_handle(metadata):
            deferred = self.gst_metadata.get_metadata(metadata)
            deferred.addCallback(_got_metadata)
            return deferred
        else:
            return None

    def get_image(self, item, theme):
        if isinstance(item, VolumeModel):
            return None

        if item.thumbnail is not None:
            return defer.succeed(item.thumbnail)

        return self._request_thumbnail(item)

    def get_preview_image(self, item, theme):
        if isinstance(item, VolumeModel):
            return None
        return item.thumbnail

    def get_contextual_background(self, item):
        return defer.succeed(self.get_preview_image(item, None))


class FilesystemPreviewListController(FilesystemController,
                                      MenuItemPreviewListController):
    view_mode = FilesystemViewMode

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


class FilesystemCoverflowController(FilesystemController,
                                    ImageWithReflectionCoverflowController):
    view_mode = FilesystemViewMode


class FilesystemGridController(FilesystemController,
                                GridItemGridController):
    view_mode = FilesystemViewMode


class FilesystemListSwitcherController(ListSwitcherController):
    modes = [FilesystemPreviewListController,
             FilesystemCoverflowController,
             FilesystemGridController]
    default_mode = FilesystemPreviewListController

class FilesystemListSwitcherGridController(FilesystemListSwitcherController):
    default_mode = FilesystemGridController
