# -*- 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 pgm
import math
import gobject

from node import Node
from drawable import Drawable

class NodeNotInGroup(Exception):
    pass

class Group(Node):
    """
    DOCME

    Emits signals:
        - 'resized': when the size of the group changes; parameters are its
                     absolute width and height before the change.
        - 'mapped': when the group is about to be visible, that is when it is
                    in the canvas and is absolutely visible

    DOCME more signals.
    """

    # FIXME: add other Pigment signals: pressured, changed

    __gsignals__ = {
        'drag-begin': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_BOOLEAN,
                (gobject.TYPE_FLOAT, gobject.TYPE_FLOAT, gobject.TYPE_FLOAT,
                pgm.ButtonType, gobject.TYPE_UINT, gobject.TYPE_UINT)),
        'drag-motion': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_BOOLEAN,
                (gobject.TYPE_FLOAT, gobject.TYPE_FLOAT, gobject.TYPE_FLOAT,
                pgm.ButtonType, gobject.TYPE_UINT, gobject.TYPE_UINT)),
        'drag-end': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_BOOLEAN,
                (gobject.TYPE_FLOAT, gobject.TYPE_FLOAT, gobject.TYPE_FLOAT,
                pgm.ButtonType, gobject.TYPE_UINT)),
        'clicked': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_BOOLEAN,
                (gobject.TYPE_FLOAT, gobject.TYPE_FLOAT, gobject.TYPE_FLOAT,
                pgm.ButtonType, gobject.TYPE_UINT, gobject.TYPE_UINT)),
        'pressed': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_BOOLEAN,
                (gobject.TYPE_FLOAT, gobject.TYPE_FLOAT, gobject.TYPE_FLOAT,
                pgm.ButtonType, gobject.TYPE_UINT, gobject.TYPE_UINT)),
        'released': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_BOOLEAN,
                (gobject.TYPE_FLOAT, gobject.TYPE_FLOAT, gobject.TYPE_FLOAT,
                pgm.ButtonType, gobject.TYPE_UINT)),
        'double-clicked': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_BOOLEAN,
                (gobject.TYPE_FLOAT, gobject.TYPE_FLOAT, gobject.TYPE_FLOAT,
                pgm.ButtonType, gobject.TYPE_UINT)),
        'scrolled': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_BOOLEAN,
                (gobject.TYPE_FLOAT, gobject.TYPE_FLOAT, gobject.TYPE_FLOAT,
                pgm.ScrollDirection, gobject.TYPE_UINT)),
        'resized': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
                (gobject.TYPE_FLOAT, gobject.TYPE_FLOAT)),
        'mapped': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
    }

    def __init__(self):
        self._canvas = None
        self._layer = pgm.DRAWABLE_MIDDLE
        self._children = []

        # A dict instance->[<signal id>,...] to keep track of connected
        # signals and be able to disconnect from them.
        self._children_signal_id = {}
        
        super(Group, self).__init__()

    @property
    def is_mapped(self):
        """ True when the group is visible, that is when it is
            in a canvas and absolute_visible is True """
        return bool(self.absolute_visible and self._canvas)

    def canvas__set(self, canvas):
        old_canvas = self._canvas
        new_canvas = canvas

        self._canvas = canvas

        if old_canvas != None:
            for child in self._children:
                if isinstance(child, Drawable):
                    old_canvas.remove(child)
                elif isinstance(child, Group):
                    child.canvas = None

        if new_canvas != None:
            for child in self._children:
                if isinstance(child, Drawable):
                    new_canvas.add(self._layer, child)
                elif isinstance(child, Group):
                    child.canvas = new_canvas

        if self.absolute_visible and self._canvas:
            self.emit("mapped")

    def canvas__get(self):
        return self._canvas

    canvas = property(canvas__get, canvas__set)

    def layer__set(self, layer):
        if layer == self._layer:
            return

        self._layer = layer

        for child in self._children:
            if isinstance(child, Drawable) and self._canvas != None:
                self._canvas.remove(child)
                self._canvas.add(self._layer, child)
            elif isinstance(child, Group):
                child.layer = self._layer

    def layer__get(self):
        return self._layer

    layer = property(layer__get, layer__set)

    def add(self, child, forward_signals=True, layer=None):
        self._children.append(child)
        child.parent = self

        if forward_signals and type(child) is not Node:
            id_list = []
            for signal_name in ('double-clicked', 'drag-end', 'released'):
                signal_id = child.connect(signal_name,
                                          self._proxy_child_signal, signal_name)
                id_list.append(signal_id)

            for signal_name in ('clicked', 'pressed', 'drag-begin', 'drag-motion'):
                signal_id = child.connect(signal_name,
                                      self._proxy_child_signal_with_pressure,
                                      signal_name)
                id_list.append(signal_id)
            signal_id = child.connect('scrolled',
                                      self._proxy_child_signal_scrolled,
                                      'scrolled')
            id_list.append(signal_id)

            self._children_signal_id[child] = id_list

        if isinstance(child, Group):
            if layer != None:
                child.layer = layer
            else:
                child.layer = self._layer

        if self._canvas != None:
            if isinstance(child, Drawable):
                self._canvas.add(self._layer, child)
            elif isinstance(child, Group):
                child.canvas = self._canvas
                # update absolute visibility of the child
                # FIXME: this should not be necessary since child.parent has
                # been set just before and this has called update_absolute_visible
                # the case might be that the 'mapped' signal was emitted during
                # child.canvas = self._canvas and one of the handlers added
                # children to child
                child.update_absolute_visible(self)

    def _proxy_child_signal_scrolled(self, drawable, x, y, z, direction, time, signal_name):
        if self.absolute_visible and self.absolute_opacity != 0:
            return self.emit(signal_name, x, y, z, direction, time)

    def _proxy_child_signal(self, drawable, x, y, z, button, time, signal_name):
        if self.absolute_visible and self.absolute_opacity != 0:
            return self.emit(signal_name, x, y, z, button, time)

    def _proxy_child_signal_with_pressure(self, drawable, x, y, z, button, time, pressure, signal_name):
        if self.absolute_visible and self.absolute_opacity != 0:
            return self.emit(signal_name, x, y, z, button, time, pressure)

    def remove(self, child):
        try:
            self._children.remove(child)
        except:
            raise NodeNotInGroup

        if child in self._children_signal_id:
            for signal_id in self._children_signal_id[child]:
                child.disconnect(signal_id)
            del self._children_signal_id[child]

        child.parent = None

        if self._canvas != None:
            if isinstance(child, Drawable):
                self._canvas.remove(child)
            elif isinstance(child, Group):
                child.canvas = None

    def get_factors_relative(self, unit):
        """
        Compute the size in relative coordinates, that is in the group's
        coordinates system, of one 'unit' for the x-axis and for the y-axis.

        Example: to know how much one pixel is on the x-axis:
                 pixel_in_x, pixel_in_y = get_factors_relative("px")

        @param unit: unit for which to compute the factors
        @param unit: str

        @rtype: 2-tuple of float
        """
        if unit == "px":
            ox, oy = self._canvas.get_pixel_offsets()
            aw, ah = self.absolute_size
            return (ox/aw, oy/ah)
        else:
            return (1.0, 1.0)

    def get_factors_absolute(self, unit):
        """
        Compute the size in absolute coordinates, that is the canvas
        coordinates system, of one 'unit' for the x-axis and for the y-axis.

        Example: to know how much one pixel is on the x-axis:
                 pixel_in_x, pixel_in_y = get_factors_absolute("px")

        @param unit: unit for which to compute the factors
        @param unit: str

        @rtype: 2-tuple of float
        """
        if unit == "px":
            return self._canvas.get_pixel_offsets()
        else:
            return (1.0, 1.0)

    def empty(self):
        for child in self._children:
            child.parent = None
            if isinstance(child, Drawable):
                self._canvas.remove(child)
            elif isinstance(child, Group):
                child.canvas = None

        self._children = []

    def __len__(self):
        return len(self._children)

    def __iter__(self):
        return self._children.__iter__()

    # x, y, z and position

    def set_absolute_x(self, value):
        super(Group, self).set_absolute_x(value)
        [child.update_absolute_x(self) for child in self._children]

    def update_absolute_x(self, parent):
        super(Group, self).update_absolute_x(parent)
        [child.update_absolute_x(self) for child in self._children]


    def set_absolute_y(self, value):
        super(Group, self).set_absolute_y(value)
        [child.update_absolute_y(self) for child in self._children]

    def update_absolute_y(self, parent):
        super(Group, self).update_absolute_y(parent)
        [child.update_absolute_y(self) for child in self._children]


    def set_absolute_z(self, value):
        super(Group, self).set_absolute_z(value)
        [child.update_absolute_z(self) for child in self._children]

    def update_absolute_z(self, parent):
        super(Group, self).update_absolute_z(parent)
        [child.update_absolute_z(self) for child in self._children]


    def set_absolute_position(self, value):
        super(Group, self).set_absolute_position(value)
        [child.update_absolute_position(self) for child in self._children]

    def update_absolute_position(self, parent):
        super(Group, self).update_absolute_position(parent)
        [child.update_absolute_position(self) for child in self._children]


    # width, height and size

    def set_absolute_width(self, value):
        width, height = self.absolute_size
        super(Group, self).set_absolute_width(value)
        [child.update_absolute_width(self) for child in self._children]
        [child.update_absolute_x(self) for child in self._children]
        self.emit("resized", width, height)

    def update_absolute_width(self, parent):
        width, height = self.absolute_size
        super(Group, self).update_absolute_width(parent)
        [child.update_absolute_width(self) for child in self._children]
        [child.update_absolute_x(self) for child in self._children]
        self.emit("resized", width, height)


    def set_absolute_height(self, value):
        width, height = self.absolute_size
        super(Group, self).set_absolute_height(value)
        [child.update_absolute_height(self) for child in self._children]
        [child.update_absolute_y(self) for child in self._children]
        self.emit("resized", width, height)

    def update_absolute_height(self, parent):
        width, height = self.absolute_size
        super(Group, self).update_absolute_height(parent)
        [child.update_absolute_height(self) for child in self._children]
        [child.update_absolute_y(self) for child in self._children]
        self.emit("resized", width, height)


    def set_absolute_size(self, value):
        width, height = self.absolute_size
        super(Group, self).set_absolute_size(value)
        [child.update_absolute_size(self) for child in self._children]
        [child.update_absolute_position(self) for child in self._children]
        self.emit("resized", width, height)

    def update_absolute_size(self, parent):
        width, height = self.absolute_size
        super(Group, self).update_absolute_size(parent)
        [child.update_absolute_size(self) for child in self._children]
        [child.update_absolute_position(self) for child in self._children]
        self.emit("resized", width, height)


    # visibility and opacity

    def set_absolute_visible(self, value):
        old_value = self.absolute_visible
        super(Group, self).set_absolute_visible(value)
        [child.update_absolute_visible(self) for child in self._children]
        if not old_value and self.absolute_visible and self._canvas:
            self.emit("mapped")

    def update_absolute_visible(self, parent):
        old_value = self.absolute_visible
        super(Group, self).update_absolute_visible(parent)
        [child.update_absolute_visible(self) for child in self._children]
        if not old_value and self.absolute_visible and self._canvas:
            self.emit("mapped")

    def set_absolute_opacity(self, value):
        super(Group, self).set_absolute_opacity(value)
        [child.update_absolute_opacity(self) for child in self._children]

    def update_absolute_opacity(self, parent):
        super(Group, self).update_absolute_opacity(parent)
        [child.update_absolute_opacity(self) for child in self._children]

    # FIXME: write tests
    def regenerate(self):
        for child in self._children:
            child.regenerate()
    
    def clean(self):
        # We need to create a new list since  we modify it in the loop.
        for child in list(self._children):
            self.remove(child)
            child.clean()

        return super(Group, self).clean()
