##############################################################################
#
# Copyright (c) 2005,2010 Zope Foundation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""
Subscriber definitions.
"""

from logging import getLogger

import OFS.interfaces
from Acquisition import aq_base
from App.config import getConfiguration
from AccessControl import getSecurityManager
from ZODB.POSException import ConflictError

import zope.component
import zope.interface
import zope.location.interfaces
from zope.container.contained import dispatchToSublocations
from zope.lifecycleevent.interfaces import IObjectMovedEvent
from zope.lifecycleevent.interfaces import IObjectCopiedEvent

deprecatedManageAddDeleteClasses = []

LOG = getLogger('OFS.subscribers')

def compatibilityCall(method_name, *args):
    """Call a method if events have not been setup yet.

    This is the case for some unit tests that have not been converted to
    use the component architecture.
    """
    if deprecatedManageAddDeleteClasses:
        # Events initialized, don't do compatibility call
        return
    if method_name == 'manage_afterAdd':
        callManageAfterAdd(*args)
    elif method_name == 'manage_beforeDelete':
        callManageBeforeDelete(*args)
    else:
        callManageAfterClone(*args)

def maybeWarnDeprecated(ob, method_name):
    """Send a warning if a method is deprecated.
    """
    if not deprecatedManageAddDeleteClasses:
        # Directives not fully loaded
        return
    if getattr(getattr(ob, method_name), '__five_method__', False):
        # Method knows it's deprecated
        return
    if isinstance(ob, tuple(deprecatedManageAddDeleteClasses)):
        return
    class_ = ob.__class__
    LOG.debug(
        "%s.%s.%s is discouraged. You should use event subscribers instead." %
        (class_.__module__, class_.__name__, method_name))

##################################################

class ObjectManagerSublocations(object):
    """Get the sublocations for an ObjectManager.
    """
    zope.component.adapts(OFS.interfaces.IObjectManager)
    zope.interface.implements(zope.location.interfaces.ISublocations)

    def __init__(self, container):
        self.container = container

    def sublocations(self):
        for ob in self.container.objectValues():
            yield ob

# The following subscribers should really be defined in ZCML
# but we don't have enough control over subscriber ordering for
# that to work exactly right.
# (Sometimes IItem comes before IObjectManager, sometimes after,
# depending on some of Zope's classes.)
# This code can be simplified when Zope is completely rid of
# manage_afterAdd & co, then IItem wouldn't be relevant anymore and we
# could have a simple subscriber for IObjectManager that directly calls
# dispatchToSublocations.

@zope.component.adapter(OFS.interfaces.IItem,
                        OFS.interfaces.IObjectWillBeMovedEvent)
def dispatchObjectWillBeMovedEvent(ob, event):
    """Multi-subscriber for IItem + IObjectWillBeMovedEvent.
    """
    # First, dispatch to sublocations
    if OFS.interfaces.IObjectManager.providedBy(ob):
        dispatchToSublocations(ob, event)
    # Next, do the manage_beforeDelete dance
    callManageBeforeDelete(ob, event.object, event.oldParent)

@zope.component.adapter(OFS.interfaces.IItem, IObjectMovedEvent)
def dispatchObjectMovedEvent(ob, event):
    """Multi-subscriber for IItem + IObjectMovedEvent.
    """
    # First, do the manage_afterAdd dance
    callManageAfterAdd(ob, event.object, event.newParent)
    # Next, dispatch to sublocations
    if OFS.interfaces.IObjectManager.providedBy(ob):
        dispatchToSublocations(ob, event)

@zope.component.adapter(OFS.interfaces.IItem,
                        OFS.interfaces.IObjectClonedEvent)
def dispatchObjectClonedEvent(ob, event):
    """Multi-subscriber for IItem + IObjectClonedEvent.
    """
    # First, do the manage_afterClone dance
    callManageAfterClone(ob, event.object)
    # Next, dispatch to sublocations
    if OFS.interfaces.IObjectManager.providedBy(ob):
        dispatchToSublocations(ob, event)

@zope.component.adapter(OFS.interfaces.IItem, IObjectCopiedEvent)
def dispatchObjectCopiedEvent(ob, event):
    """Multi-subscriber for IItem + IObjectCopiedEvent.
    """
    # Dispatch to sublocations
    if OFS.interfaces.IObjectManager.providedBy(ob):
        dispatchToSublocations(ob, event)


def callManageAfterAdd(ob, item, container):
    """Compatibility subscriber for manage_afterAdd.
    """
    if container is None:
        return
    if getattr(aq_base(ob), 'manage_afterAdd', None) is None:
        return
    maybeWarnDeprecated(ob, 'manage_afterAdd')
    ob.manage_afterAdd(item, container)

def callManageBeforeDelete(ob, item, container):
    """Compatibility subscriber for manage_beforeDelete.
    """
    if container is None:
        return
    if getattr(aq_base(ob), 'manage_beforeDelete', None) is None:
        return
    maybeWarnDeprecated(ob, 'manage_beforeDelete')
    import OFS.ObjectManager # avoid circular imports
    try:
        ob.manage_beforeDelete(item, container)
    except OFS.ObjectManager.BeforeDeleteException:
        raise
    except ConflictError:
        raise
    except:
        LOG.error('_delObject() threw', exc_info=True)
        # In debug mode when non-Manager, let exceptions propagate.
        if getConfiguration().debug_mode:
            if not getSecurityManager().getUser().has_role('Manager'):
                raise

def callManageAfterClone(ob, item):
    """Compatibility subscriber for manage_afterClone.
    """
    if getattr(aq_base(ob), 'manage_afterClone', None) is None:
        return
    maybeWarnDeprecated(ob, 'manage_afterClone')
    ob.manage_afterClone(item)
