# GNU Enterprise Common Library - Trigger Enabled Base Classes
#
# Copyright 2000-2007 Free Software Foundation
#
# This file is part of GNU Enterprise.
#
# GNU Enterprise is free software; you can redistribute it
# and/or modify it under the terms of the GNU General Public
# License as published by the Free Software Foundation; either
# version 2, or (at your option) any later version.
#
# GNU Enterprise 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public
# License along with program; see the file COPYING. If not,
# write to the Free Software Foundation, Inc., 59 Temple Place
# - Suite 330, Boston, MA 02111-1307, USA.
#
# $Id: GTriggerCore.py 9329 2007-01-26 06:47:06Z jcater $

"""
Base Classes to derive from to make use of the action/trigger system.
"""

import types

from gnue.common.apps import errors

__all__ = ['GTriggerCore']


# =============================================================================
# Base class for all objects that are exported to trigger namespace
# =============================================================================

class GTriggerCore:
    """
    Base class for all objects that can be exported to action/trigger
    namespace.

    Descendants can define how they want to be seen in action/trigger code by
    setting the following properties:

    @ivar _triggerGlobal: If set to True, this object is added to the global
        namespace. Otherwise, the object is only available as a property of its
        parent object.
    @ivar _triggerGet: Can be set to a method that returns the string value of
        the object. In action/trigger code, "str(object)" will implicitly call
        this function.
    @ivar _triggerSet: Can be set to a method that sets the value of this
        object. In action/trigger code, "parent.object = 'foo' will implicitly
        call this function.
    @ivar _triggerFunctions: Dictionary defining the functions this object
        should present in action/trigger code. Keys in this dictionary are the
        function names how they should be visible in the action/trigger code.
        Values are dictionaries, where the key 'function' contains the method
        to call, and the key 'global' can be set to True to make this a global
        function.
    @ivar _triggerProperties: Dictionary defining the properties this object
        should present in action/trigger code. Keys in this dictionary are the
        property names how they should be visible in action/trigger code.
        Values are dictionaries, where the key 'get' contains the method used
        to read the property, and the key 'set' optionally contains the method
        used to set the property. Properties with no 'set' key are read only
        properties.
    """

    # -------------------------------------------------------------------------
    # Constructor
    # -------------------------------------------------------------------------

    def __init__(self):
        """
        Initialize a GTriggerCore instance.
        """

        # TODO: It would be nice if _triggerFunctions and _triggerGlobals would
        # also define a 'description' key where one could place text for an
        # autogenerated documentation.

        self._triggerGlobal = 0
        self._triggerGet = None
        self._triggerSet = None
        self._triggerFunctions = {}
        self._triggerProperties = {}

        self.__namespace_name = '<uninitialized object>'
        self.__namespace_object = None


    # -------------------------------------------------------------------------
    # Nice string representation
    # -------------------------------------------------------------------------

    def __repr__(self):

        return self.__namespace_name


    # -------------------------------------------------------------------------
    # Get the namespace object belonging to this XML object
    # -------------------------------------------------------------------------

    def get_namespace_object(self):
        """
        Return the namespace object that mirrors this object in the
        action/trigger namespace.
        """
        return self.__namespace_object


    # -------------------------------------------------------------------------
    # Construct a namespace object tree for an XML object tree
    # -------------------------------------------------------------------------

    def create_namespace_object(self, global_namespace, namespace_name):
        """
        Construct a namespace object tree from an XML
        (L{definitions.GObjects.GObj}) object tree.

        This function creates a L{NamespaceElement} object for each
        L{definitions.GObjects.GObj} in the object.
        """

        # Create dictionary with methods as defined
        methods = {}
        for (name, definition) in self._triggerFunctions.items():
            function = definition.get('function')
            assert isinstance(function, types.MethodType)
            methods[name] = function
            # Add this function to global namespace if the GObj requests it
            if definition.get('global', False):
                global_namespace[name] = function

        # Create dictionary with properties as defined
        properties = {}
        for (name, definition) in self._triggerProperties.items():
            get_function = definition.get('get')
            set_function = definition.get('set')
            assert isinstance(get_function, types.MethodType)
            if set_function is not None:
                assert isinstance(set_function, types.MethodType)
            properties[name] = (get_function, set_function)

        # Create the namespace object
        namespace_object = NamespaceElement(
                name       = namespace_name,
                xml_object = self,
                get_method = self._triggerGet,
                set_method = self._triggerSet,
                methods    = methods,
                properties = properties)

        # Process the children of this xml object
        if len(self._children):
            self.__add_children(self._children, namespace_object,
                    global_namespace, namespace_name)

        # Remember the namespace name and the namespace object
        self.__namespace_name = namespace_name
        self.__namespace_object = namespace_object

        # Add the namespace object to global namespace if the xml object
        # requests it and the object has a name
        if self._triggerGlobal and hasattr(self, 'name') and self.name:
            global_namespace[self.name] = namespace_object

        return namespace_object


    # -------------------------------------------------------------------------
    # Add children to a Namespace object matching the children of the GObj
    # -------------------------------------------------------------------------

    def __add_children(self, children, namespace_object, global_namespace,
            namespace_prefix):
        """
        Create child namespace objects according to child XML objects
        """
        for child in children:
            # Skip children that are not namespace enabled
            if not isinstance(child, GTriggerCore):
                continue

            # Skip GRPassThrough objects
            if hasattr(child, 'name') and child.name \
                    and child.name.startswith('__'):
                if len(child._children):
                    self.__add_children(child._children, namespace_object,
                            global_namespace, namespace_prefix)
                continue

            # Decide which name this child should present on repr()
            if hasattr(child, 'name') and child.name:
                namespace_name = namespace_prefix + '.' + child.name
            else:
                namespace_name = namespace_prefix + '.<unnamed %s>' \
                        % child.getXmlTag()

            # Create namespace objects for all children, even for those without
            # a name - one of the children might have triggers attached, so we
            # would need a "self" namespace object
            child_object = child.create_namespace_object (global_namespace,
                    namespace_name)

            # Add this objects children to its namespace by their name
            if hasattr(child, 'name') and child.name:
                namespace_object.__dict__[child.name] = child_object


# =============================================================================
# Exceptions used by namespace objects
# =============================================================================

class NoSetValueError(errors.ApplicationError):
    """
    Cannot set value of this object.
    """
    def __init__(self, name):
        errors.ApplicationError.__init__(self,
                u_("Cannot set value of object '%s'") % name)

# -----------------------------------------------------------------------------

class ReadonlyPropertyError(errors.ApplicationError):
    """
    Cannot set readonly property.
    """
    def __init__(self, name):
        errors.ApplicationError.__init__(self,
                u_("Cannot set readonly property '%s'") % name)


# =============================================================================
# Namespace object
# =============================================================================

class NamespaceElement(object):
    """
    Proxy object that represents an object from an XML tree within the
    action/trigger namespace.
    """

    # -------------------------------------------------------------------------
    # Constructor
    # -------------------------------------------------------------------------

    def __init__(self, name, xml_object, get_method, set_method, methods,
            properties):
        """
        Initialize a namespace proxy object.
        """

        checktype (xml_object, GTriggerCore)

        self.__name       = name
        self.__xml_object = xml_object
        self.__get_method = get_method
        self.__set_method = set_method
        self.__methods    = methods
        self.__properties = properties

        # FIXME: gnue.forms.GFForm.GFForm.triggerSetFocus() still uses this!
        # Remove here once it is removed in Forms!
        self._object = xml_object


    # -------------------------------------------------------------------------
    # Nice string representation
    # -------------------------------------------------------------------------

    def __repr__(self):
        return self.__name


    # -------------------------------------------------------------------------
    # Getting and setting attributes
    # -------------------------------------------------------------------------

    def __getattr__(self, name):

        # Handle methods and properties. Child objects (which are stored in the
        # native __dict__ of the object) have highest priority.

        if self.__methods.has_key(name):
            return self.__methods[name]
        elif self.__properties.has_key(name):
            return self.__properties[name][0]()

    # -------------------------------------------------------------------------

    def __setattr__(self, name, value):

        # This is called when trying to write something inside a trigger object
        # namespace.  It checks to see if the var name is already linked to a
        # NamespaceElement based object and calls that objects _triggerSet if
        # it exists.
        #
        # Example: form.block1.entry1 = "foo"
        #
        # The __setattr__ will execute at the block1 and call the functions
        # that are part of the entry1 object to set it's value.
        #
        # Apart from that, this also handles properties.

        # Directly set for private variables, otherwise we will recurse in
        # __init__.
        if name.startswith('_NamespaceElement__'):
            self.__dict__[name] = value
            return

        attr = self.__dict__.get(name)

        if isinstance(attr, NamespaceElement):
            if attr.__set_method is None:
                raise NoSetValueError, name
            attr.__set_method(value)

        elif self.__properties.has_key(name):
            set_function = self.__properties[name][1]
            if set_function is None:
                raise ReadonlyPropertyError, name
            set_function(value)

        else:
            self.__dict__[name] = value


    # -------------------------------------------------------------------------
    # String and Integer values
    # -------------------------------------------------------------------------

    def __str__(self):

        # This executes at a different level than __setattr__.
        # While __setattr__ is executed in the parent object to protect its
        # namespace object links, this routine is called by the referenced
        # object.
        #
        # Example: foo = str(form.block1.entry1)
        #
        # This __str__ would execute for the entry1 object.

        if self.__get_method:
            return str(self.__get_method())
        else:
            return self.__name

    def __unicode__(self): 
        if self.__get_method: 
            return unicode(self.__get_method())
        else: 
            return self.__name

    # -------------------------------------------------------------------------

    def __int__(self):

        if self.__get_method:
            return int(self.__get_method())
        else:
            return 0

    # -------------------------------------------------------------------------

    def __cmp__(self, other):

        # Forces the system to compare the string values of NamespaceElement
        # objects rather than their instances

        return cmp(str(self), str(other))

    # -------------------------------------------------------------------------

    def __nonzero__(self):
        return True

    # -------------------------------------------------------------------------

    def __len__(self):
        return len(str(self))

    # -------------------------------------------------------------------------

    def __getitem__(self, key) :
        return str(self)[key.start:key.stop]


    # -------------------------------------------------------------------------
    # Iterator support in case the underlying GObj object supports it
    # -------------------------------------------------------------------------

    def __iter__(self):
        return iter(self.__xml_object)
