# -*- coding: utf-8 -*-
# Elisa - Home multimedia server
# Copyright (C) 2006,2007 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 Elisa with Fluendo's plugins.
#
# The GPL part of Elisa is also available under a commercial licensing
# agreement from Fluendo.
# See "LICENSE.Elisa" in the root directory of this distribution package
# for details on that license.

__maintainer__ = 'Florian Boucault <florian@fluendo.com>'

import os, os.path, md5

from twisted.internet import defer

from elisa.core import common

import pgm
from long_loading_image import LongLoadingImage

plugin_registry = common.application.plugin_registry
BaseListView = plugin_registry.get_component_class('raval:list_view')

class SlideshowView(BaseListView):

    supported_controllers = ('raval:list_controller',)
    bindings = (("current_index", "selected_item"),
                ("hidden", "hidden"),
                ("playing", "playing"),
                ("focused", "focused"))

    def __init__(self):
        super(SlideshowView, self).__init__()
        self._cache_dir = os.path.join(os.path.expanduser("~"), ".elisa",
                                       "pictures_cache")
        try:
            os.makedirs(self._cache_dir)
        except OSError:
            # cache directory already existing
            pass

    def create_widgets(self):
        super(SlideshowView, self).create_widgets()

        canvas = self.frontend.context.viewport_handle.get_canvas()
        self.size = canvas.size
        # FIXME: z=-1.0 in order to be behind the menus
        self.position = (0.0, 0.0, -1.0)
        self.visible_range_size = 1.0
        self.preloaded = 1

        # OSD creation
        # FIXME: the code is not clean; separate views should be created
        self._create_back_button(canvas)
        self._create_osd_status_widget(canvas)

        # delay in seconds before the osd disappears automatically
        self._osd_duration = 3.0

        # FIXME: should be a connection to a drawable motion notify event
        # instead
        viewport = self.frontend.context.viewport_handle
        viewport.connect("motion-notify-event", self.mouse_moved)

    def create_item(self, model):
        widget = LongLoadingImage()
        widget.time_before_quick = 0.0
        widget.time_before_loading = 0.0

        return widget

    def load_item(self, index):

        model = self.controller.model[index]
        widget = self[index]

        self.load_from_theme('image_file_icon', widget.quick_image)

        uri = model.thumbnail_source
        if uri != None:
            self._model_thumbnail_to_image(model, widget.quick_image)
        else:
            self._create_image_stack(model, widget.quick_image)

        def load_full_size(path):
            # downscale the image on loading to a size never bigger than the
            # viewport size nor 1024
            viewport = self.frontend.context.viewport_handle
            size = max(viewport.width, viewport.height)
            size = min(size, 1024)
            widget.image.set_from_file(path, size)

        def got_error(failure, uri):
            path = common.application.log_failure(failure)
            self.warning("Could not open %s. Details at %s" %
                (uri, path))

        # FIXME: _retrieve_full_resolution is called without checking if it
        # was already called for the same uri. It can lead to parallel
        # dowloading of the same pictures.
        dfr = self._retrieve_full_resolution(model.uri)
        dfr.addCallback(load_full_size)
        dfr.addErrback(got_error, model.uri)

    def unload_item(self, widget):
        # FIXME: should cancel the call to self._thumbnail_source_to_image and
        # the call to _retrieve_full_resolution
        widget.quick_image.clear()
        widget.image.clear()

    def do_selected_item_changed(self, index):
        self.controller.current_index = index

    def hidden__set(self, value):
        self.visible = not value

    def _retrieve_full_resolution(self, uri):
        media_manager = common.application.media_manager
        uri = media_manager.get_real_uri(uri)

        if uri.scheme == 'file':
            # local files do not need to be cached
            dfr_open = defer.Deferred()
            path = uri.path
            dfr_open.callback(path)
        else:
            # remote files
            # FIXME: locally cache the picture if it is not a local file; that
            # should be the job of the media_manager

            def compute_cache(uri):
                filename = md5.new(str(uri)).hexdigest() + '.' + uri.extension
                path = os.path.join(self._cache_dir, filename)
                return path

            path = compute_cache(uri)

            if os.path.exists(path):
                # already cached
                dfr_open = defer.Deferred()
                dfr_open.callback(path)
                return dfr_open

            def read(data, handle):
                self.debug("caching full resolution image to %s", path)
                fd = open(path, 'w')
                fd.write(data)
                fd.close()
                handle.close()
                return path

            def opened(handle):
                if handle != None:
                    dfr_read = handle.read()
                    dfr_read.addCallback(read, handle)

                return dfr_read
            self.debug("downloading full resolution image from %s" % uri)
            try:
                dfr_open = media_manager.open(uri)
            except Exception, e:
                # We should better check in the first place, that it is not
                # directory!
                dfr_open = defer.fail(e)
            else:
                dfr_open.addCallback(opened)

        return dfr_open

    # FIXME: the following methods should go in separate views.

    def osd_show(self, time_visible=-1):
        self._osd_back_button.show(time_visible)
        self._osd_play_button.show(time_visible)
        self._osd_status.show(time_visible)

    def osd_hide(self):
        self._osd_back_button.hide()
        self._osd_play_button.hide()
        self._osd_status.hide()

    def mouse_moved(self, viewport, event):
        if not self.controller.focused:
            return False

        self.osd_show(self._osd_duration)
        return True

    def _create_back_button(self, canvas):
        back_size = canvas.height*0.15
        offset = canvas.height * 0.02

        from dock import Dock
        self._osd_back_button = Dock()
        self.context_handle.add(self._osd_back_button)
        self.load_from_theme("back_button", self._osd_back_button.background)
        self._osd_back_button.position = (canvas.width - back_size - offset, offset, 0.1)
        self._osd_back_button.size = (back_size, back_size)
        self._osd_back_button.opacity = 0
        self._osd_back_button.visible = True
        self._osd_back_button.connect("clicked", self._osd_back_button_clicked)

    def _osd_back_button_clicked(self, drawable, x, y, z, button, time):
        if not self.controller.focused:
            return False

        self.controller.parent.focus()
        return True


    def _create_osd_status_widget(self, canvas):
        # size relative to the parent (in percentage of the parent's size)
        relative_width = 0.7
        relative_height = 0.15
        # position of the middle of the status widget relative its parent
        relative_x = 0.55
        relative_y = 0.88

        # conversion in absolute size and position
        absolute_size = (canvas.width*relative_width,
                         canvas.height*relative_height)
        absolute_position = (canvas.width*relative_x-absolute_size[0]/2.0,
                             canvas.height*relative_y-absolute_size[1]/2.0,
                             0.1)

        from slideshow_osd import SlideshowOsd
        self._osd_status = SlideshowOsd()
        self.context_handle.add(self._osd_status)
        self._osd_status.size = absolute_size
        self._osd_status.opacity = 255
        self._osd_status.position = absolute_position
        self._osd_status.visible = True
        self._osd_status.index = 0
        self._osd_status.length = 0

        self.load_from_theme("dock_background_mouse",
                             self._osd_status.background)
        self.load_from_theme("dock_bar_fg",
                             self._osd_status.progressbar.foreground)
        self.load_from_theme("dock_bar_bg",
                             self._osd_status.progressbar.background)

        self._create_playpause_button(absolute_position, absolute_size)

    def _create_playpause_button(self, status_position, status_size):
        # play/pause button has the same height as the status box and is
        # square
        playpause_size = status_size[1]

        from dock import Dock
        self._osd_play_button = Dock()
        self.context_handle.add(self._osd_play_button)
        self._osd_play_button.size = (playpause_size, playpause_size)
        self._osd_play_button.opacity = 0
        self._osd_play_button.visible = True

        x = status_position[0] - playpause_size/1.2
        self._osd_play_button.position = (x, status_position[1],
                                          status_position[2])

        self._osd_play_button.connect("clicked", self._osd_play_button_clicked)

        self.load_from_theme("play_button_mouse",
                             self._osd_play_button.background)

    def _osd_play_button_clicked(self, drawable, x, y, z, button, time):
        if not self.controller.focused:
            return False

        self.controller.playing = not self.controller.playing
        return True

    def playing__set(self, playing):
        if playing:
            self.load_from_theme("pause_button_mouse",
                                 self._osd_play_button.background)
        else:
            self.load_from_theme("play_button_mouse",
                                 self._osd_play_button.background)

    def focused__set(self, focused):
        if not focused:
            self.osd_hide()

    def selected_item__set(self, index):
        super(SlideshowView, self).selected_item__set(index)

        # FIXME: this code does not belong here
        if self.controller.model != None:
            self._osd_status.length = len(self.controller.model)

        self._osd_status.index = self.controller.current_index + 1
