# -*- coding: utf-8 -*-
# Elisa - Home multimedia server
# Copyright (C) 2006-2008 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>'


from elisa.core.observers.list import ListObserver
from elisa.core.media_manager import MediaProviderNotFound
from elisa.core import common

from twisted.internet import reactor, defer

plugin_registry = common.application.plugin_registry
PigmentView = plugin_registry.get_component_class('pigment:pigment_view')

# for image stack support
import os, cairo, math, array, pgm

class ListView(PigmentView, ListObserver):

    supported_controllers = ('raval:list_controller',)
    bindings = (("current_index", "selected_item"),
                ("focused", "selector_visible"),
                ("action_called", "action_called"))
    
    # duration between two updates of the preview
    update_delay = 0.500
    update_delayed_call = None
    update_needed = True
    images_per_stack = 3

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

        if self.controller == None or self.controller.model == None:
            return

        # if it's been more than self.update_delay since the
        # last update, update now and schedule a potential update
        # in the future
        if self.update_delayed_call == None or \
           not self.update_delayed_call.active():
            self.update_details()
            self.update_needed = False

            def update_if_needed():
                if self.update_needed:
                    self.update_details()
                    self.update_needed = False
                    call = reactor.callLater(self.update_delay,
                                             update_if_needed)
                    self.update_delayed_call = call

            call = reactor.callLater(self.update_delay, update_if_needed)
            self.update_delayed_call = call
        else:
            self.update_needed = True
            
    def selected_item__get(self):
        return super(ListView, self).selected_item__get()

    def update_details(self):
        pass

    def create_widgets(self):
        self.debug("creating widgets")

        # NOTE: this is a bit evil: since ListView is a PigmentView, it
        # already has a context_handle which is a Group.
        # self.context_handle.add(self) would be more logical but less
        # efficient.
        self.context_handle = self
        self.context_handle.visible = True

    def create_item(self, model):
        raise NotImplementedError

    def inserted(self, elements, position):
        self.debug("inserted %r elements at %r: %r", len(elements), position,
                   elements)
        index = position
        for model in elements:
            drawable = self.create_item(model)
            # FIXME: need to catch IndexError, list_ng is not threadsafe
            try:
                self.insert(index, drawable)
            except IndexError:
                self.append(drawable)
            else:
                index += 1
 
    def removed(self, elements, position):
        self.debug("removed %r elements from %r: %r", len(elements), position,
                   elements)
        for i in xrange(len(elements)):
            try:
                self.pop(position)
            except IndexError:
                break

    def modified(self, position, value):
        self.debug("element at %r modified: %r", position, value)

    def dirtied(self):
        self.debug("all the elements changed")

    def element_attribute_set(self, position, key, old_value, new_value):
        self.debug("attribute %r of element at %r set to %r", key, position,
                   new_value)

    def controller_changed(self, old_controller, new_controller):
        self.animated = False
        super(ListView, self).controller_changed(old_controller, new_controller)

        if new_controller == None:
            if self.update_delayed_call != None and \
                self.update_delayed_call.active():
                self.update_delayed_call.cancel()
        if new_controller != None:
            self.empty()

            # save the visible state
            old_visible = self.visible
            self.visible = False

            if new_controller.model:
                self.inserted(new_controller.model, 0)

            # reset the visible state as it was before
            self.visible = old_visible

            # update the selected item of the list widget after all the elements
            # got inserted; it was already done by the bindings but too early
            self.selected_item = self.controller.current_index

        self.animated = True

    def _model_thumbnail_to_image(self, model, image):
        # TODO: move that to PigmentView
        uri = model.thumbnail_source

        def display(result):
            image_path = result[0]
            if image_path:
                image.set_from_file(image_path)
                image.set_name(image_path)
            return image_path

        dfr = common.application.thumbnailer.get_thumbnail(uri, 256,
                                                           model.file_type)
        dfr.addCallback(display)
        return dfr

    def _get_path_from_thumbnailer(self, uri):
        return common.application.thumbnailer.get_thumbnail(uri, 256, None)

    # image stack support
    def _create_image_stack(self, model, image, reflection = None,
                                gradient = 0.5):

        if hasattr(model, 'uri') and model.uri is not None:
            media_manager = common.application.media_manager
            real_uri = media_manager.get_real_uri(model.uri)

            if real_uri is None or real_uri.scheme != 'file':
                return

            dfr = media_manager.is_directory(real_uri)
            dfr.addCallback(self._model_is_directory,
                                real_uri,
                                image,
                                reflection,
                                gradient)

    def _model_is_directory(self, it_is, uri, image, reflection, stop_gradient):

        if not it_is:
            # No directory, nothing to do :(
            return

        smalling = 0.7
        canvas = self.frontend.context.viewport_handle.get_canvas()
        media_manager = common.application.media_manager
        list_of_images = []

        def create_stack():
            if len(list_of_images) == 0:
                return

            size = 450
            data = array.array('c', chr(0) * size * size * 4)
            dest_surface = cairo.ImageSurface.create_for_data(data, cairo.FORMAT_ARGB32,
                                                              size,
                                                              size,
                                                              size * 4)

            ctx = cairo.Context(dest_surface)

            degrees = math.pi / 180.0 * 15.0
            
            list_of_images.reverse()
            
            # turn it back the amount, we need
            ctx.translate(size/2, size/2)
            ctx.scale(1.3, 1.3)
            ctx.rotate(-degrees * len(list_of_images))
            ctx.translate(-size/2, -size/2)

            for path in list_of_images:
                path = path[0]

                image_surface = cairo.ImageSurface.create_from_png(path)
                width = image_surface.get_width()
                height = image_surface.get_height()
                
                # move to the correct offset
                left_offset = ((size - width) / 2)
                top_offset = ((size - height) / 2)
                ctx.translate(left_offset, top_offset)

                # turn the image
                ctx.translate(width / 2, height / 2)
                ctx.rotate(degrees)
                ctx.translate(-width / 2, -height / 2)
                ctx.set_source_surface(image_surface)
                ctx.paint()

                # and the same for the borders
                ctx.rectangle(0, 0, width, height)
                ctx.set_line_width(5.0)
                ctx.set_source_rgb(255, 255, 255)
                ctx.stroke()
                
                # move back the offset
                ctx.translate(-left_offset, -top_offset)

            ctx.save()

            # and set the new image
            image.set_from_buffer(pgm.IMAGE_BGRA, size, size, size*4,
                                  size*size*4, data.tostring())

            image.opacity = 255
            image.bg_color = (0, 0, 0, 0)
            image.visible = True

            if reflection is not None:
                # We should also create the reflection
                ref_data = array.array('c', chr(0) * size * size * 4)
                dest_surface = cairo.ImageSurface.create_for_data(ref_data,
                                                              cairo.FORMAT_ARGB32,
                                                              size,
                                                              size,
                                                              size * 4) 
                source = cairo.ImageSurface.create_for_data(data,
                                                              cairo.FORMAT_ARGB32,
                                                              size,
                                                              size,
                                                              size * 4) 


                context = cairo.Context(dest_surface)
                context.set_source_surface(source)

                context.scale(size, size)

                gradient = cairo.LinearGradient(0.0, 0.0, 0.0, 1.0)
                gradient.add_color_stop_rgba(stop_gradient, 0, 0, 0, 0)
                gradient.add_color_stop_rgba(1, 0, 0, 0, 1)

                context.mask(gradient)

                reflection.set_from_buffer(pgm.IMAGE_BGRA, size, size, size*4,
                                      size*size*4, ref_data.tostring())


        def image_created(image_path, list_of_children):
            if image_path is None:
                process_next_child(list_of_children)
                return

            if not os.path.isfile(image_path[0]):
                process_next_child(list_of_children)
                return

            list_of_images.append(image_path)
            if len(list_of_images) == self.images_per_stack \
                            or len(list_of_children) == 0:
                self.debug("creating stack")
                create_stack()
            else:
                process_next_child(list_of_children)

        def error(failure, pending_children):
            self.info("Got an error:%s" % failure)
            process_next_child(pending_children)

        def process_next_child(pending_children):
            if len(pending_children) == 0:
                return

            uri, metadata = pending_children.pop(0)
            self.info("Looking for thumbnail for %s" % uri)

            real_uri = media_manager.get_real_uri(uri)
            dfr = self._get_path_from_thumbnailer(real_uri)
            dfr.addCallback(image_created, pending_children)
            dfr.addErrback(error, pending_children)

        lst = []
        dfr = media_manager.get_direct_children(uri, lst)
        dfr.addCallback(process_next_child)

    def do_scrolled(self, x, y, z, direction, time):
        if self.controller == None or not self.controller.focused:
            return False

        if direction == pgm.SCROLL_UP:
            self.selected_item -= 1
        else:
            self.selected_item += 1
        return True
