# -*- mode: python; coding: utf-8 -*-
#
# Pigment Python tools
#
# Copyright © 2006-2008 Fluendo Embedded S.L.
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the
# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
# Boston, MA 02111-1307, USA.

import math

import pgm
from pgm.graph.group import Group
from pgm.graph.image import Image
from pgm.timing import implicit

from twisted.internet import reactor

class LongLoadingImage(Group):
    """
    Widget providing facilities for displaying a temporary quickly loaded
    image while a bigger one is being loaded.

    Display L{quick_image} and then L{image} when it is finished loading.

    Also supports a rotating image on top to notify loadings.

    @ivar image:               image that can take time to load
    @type image:               L{pgm.graph.image.Image}
    @ivar quick_image:         image quick to load
    @type quick_image:         L{pgm.graph.image.Image}
    @ivar loading:             DOCME
    @type loading:             bool
    @ivar loading_image_path:  DOCME
    @type loading_image_path:  str

    @cvar time_before_quick:   DOCME
    @type time_before_quick:   float
    @cvar time_before_loading: DOCME
    @type time_before_loading: float
    """

    time_before_quick = 0.250
    time_before_loading = 0.250

    def __init__(self):
        Group.__init__(self)

        # image creation
        self._image = Image()
        self.add(self._image)
        self._image.bg_color = (0, 0, 0, 0)
        self._image.opacity = 0
        self._image.layout = pgm.IMAGE_SCALED
        self._image.visible = False
        self._image.connect("pixbuf-loaded", self._image_loaded)

        settings = {'duration': 300,
                    'transformation': implicit.SMOOTH,
                    'end_callback' : lambda c: self._hide_quick_image()
                   }

        attributes = ('opacity',)
        self._animated_image = implicit.AnimatedObject(self._image, attributes)
        self._animated_image.setup_next_animations(**settings)

        # quick image initialisation
        self._quick_image = None
        self._quick_image_call = None

        # loading image initialisation
        self._loading = False
        self._loading_image = None
        self._loading_image_path = None
        self._loading_call = None
        self._rotation_matrix = None
        self._rotation_call = None

    def image__get(self):
        return self._image

    def _image_loaded(self, image):
        # cancel the delayed loading of quick image
        if self._quick_image_call != None and self._quick_image_call.active():
            self._quick_image_call.cancel()

        # fade in the image
        self._animated_image.stop_animations()
        self._image.opacity = 0
        self._image.visible = True
        self._animated_image.opacity = 255

    def _create_quick_image(self):
        # Quick image is created on demand
        if self._quick_image == None:
            self._quick_image = Image()
            self._quick_image.size = self.size
            self.add(self._quick_image)
            self._quick_image.z -= 0.001
            self._quick_image.bg_color = (0, 0, 0, 0)
            self._quick_image.layout = pgm.IMAGE_SCALED
            self._quick_image.visible = False
            self._quick_image.connect("pixbuf-loaded", self._quick_image_loaded)

    def quick_image__get(self):
        # FIXME : fake the emission of the signal 'pixbuf-loaded' because there
        # is no notification when a set_from_image is done and, in our use case,
        # quick_image__get is used to set a clone. Pigment needs a signal to
        # notify changes to the master image.
        self._create_quick_image()
        self._quick_image_loaded(self._quick_image)
        return self._quick_image

    def _quick_image_loaded(self, image):
        # cancel previous delayed loading of quick image
        if self._quick_image_call != None and self._quick_image_call.active():
            self._quick_image_call.cancel()

        self._quick_image_call = reactor.callLater(self.time_before_quick,
                                                   self._show_quick_image)

    def _show_quick_image(self):
        if self._quick_image != None:
            self._quick_image.opacity = 255
            self._quick_image.visible = True

    def _hide_quick_image(self):
        if self._quick_image != None:
            self._quick_image.visible = False
            self.remove(self._quick_image)
            self._quick_image = None

    def loading_image_path__set(self, path):
        self._loading_image_path = path
        if self._loading_image != None:
            self._loading_image.set_from_file(path)

    def _create_loading_image(self):
        # Loading image is created on demand
        if self._loading_image == None:
            self._loading_image = Image()
            self._loading_image.size = self.size
            self.add(self._loading_image)
            self._loading_image.bg_color = (0, 0, 0, 0)
            self._loading_image.layout = pgm.IMAGE_SCALED
            self._loading_image.visible = False

            self._rotation_matrix = pgm.mat4x4_new_identity()
            self._rotation_matrix.translate(0.5, 0.5, 0.0)
            self._rotation_matrix.rotate_z(math.pi / 30.0)
            self._rotation_matrix.translate(-0.5, -0.5, 0.0)
            if self._loading_image_path:
                self._loading_image.set_from_file(self._loading_image_path)

    def loading__set(self, loading):
        self._loading = loading

        # cancel the delayed starting of loading animation
        if self._loading_call != None and self._loading_call.active():
            self._loading_call.cancel()

        if loading == True:
            self._loading_call = reactor.callLater(self.time_before_loading, 
                                                   self._start_loading_animation)
        else:
            # cancel the rotating animation if any
            if self._loading_call != None and self._loading_call.active():
                self._rotation_call.cancel()

            if self._loading_image != None:
                self.remove(self._loading_image)
                self._loading_image = None

    def loading__get(self):
        return self._loading

    def _start_loading_animation(self):
        if self._loading_image_path != None:
            if self._loading_image == None:
                self._create_loading_image()
            self._loading_image.visible = self.loading

            # start rotating animation if needed
            if self._loading_call == None or not self._loading_call.active():
                self._rotation_call = reactor.callLater(0.017,
                                                        self._rotate_loading)

    def _rotate_loading(self):
        if self._loading_image:
            self._loading_image.mapping_matrix *= self._rotation_matrix
        if self._loading:
            self._rotation_call = reactor.callLater(0.017,
                                                    self._rotate_loading)
