##############################################################################
#
# Copyright (c) 2003 Zope Corporation 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.
#
##############################################################################
"""Container-related interfaces

$Id: interfaces.py 40368 2005-11-25 15:09:45Z efge $
"""
__docformat__ = 'restructuredtext'

from zope.deprecation import deprecated

from zope.interface import Interface, Attribute, Invalid
from zope.component.interfaces import IView
from zope.interface.common.mapping import IItemMapping
from zope.interface.common.mapping import IReadMapping, IEnumerableMapping
from zope.app.location.interfaces import ILocation
from zope.app.event.interfaces import IObjectEvent
from zope.app.event.interfaces import IObjectModifiedEvent

deprecated('IContentContainer',
           'This interface has been deprecated. '
           'Check the "containerViews" zcml directive. '
           'The reference will be gone in 3.3')

class DuplicateIDError(KeyError):
    pass

class ContainerError(Exception):
    """An error of a container with one of its components."""

class InvalidContainerType(Invalid, TypeError):
    """The type of a container is not valid."""

class InvalidItemType(Invalid, TypeError):
    """The type of an item is not valid."""

class InvalidType(Invalid, TypeError):
    """The type of an object is not valid."""



class IContained(ILocation):
    """Objects contained in containers."""


class IItemContainer(IItemMapping):
    """Minimal readable container."""


class ISimpleReadContainer(IItemContainer, IReadMapping):
    """Readable content containers."""


class IReadContainer(ISimpleReadContainer, IEnumerableMapping):
    """Readable containers that can be enumerated."""


class IWriteContainer(Interface):
    """An interface for the write aspects of a container."""

    def __setitem__(name, object):
        """Add the given `object` to the container under the given name.

        Raises a ``TypeError`` if the key is not a unicode or ascii string.
        Raises a ``ValueError`` if key is empty.

        The container might choose to add a different object than the
        one passed to this method.

        If the object doesn't implement `IContained`, then one of two
        things must be done:

        1. If the object implements `ILocation`, then the `IContained`
           interface must be declared for the object.

        2. Otherwise, a `ContainedProxy` is created for the object and
           stored.

        The object's `__parent__` and `__name__` attributes are set to the
        container and the given name.

        If the old parent was ``None``, then an `IObjectAddedEvent` is
        generated, otherwise, an `IObjectMovedEvent` is generated.  An
        `IContainerModifiedEvent` is generated for the container.

        If the object replaces another object, then the old object is
        deleted before the new object is added, unless the container
        vetos the replacement by raising an exception.

        If the object's `__parent__` and `__name__` were already set to
        the container and the name, then no events are generated and
        no hooks.  This allows advanced clients to take over event
        generation.

        """

    def __delitem__(name):
        """Delete the named object from the container.

        Raises a ``KeyError`` if the object is not found.

        If the deleted object's `__parent__` and `__name__` match the
        container and given name, then an `IObjectRemovedEvent` is
        generated and the attributes are set to ``None``. If the object
        can be adapted to `IObjectMovedEvent`, then the adapter's
        `moveNotify` method is called with the event.

        Unless the object's `__parent__` and `__name__` attributes were
        initially ``None``, generate an `IContainerModifiedEvent` for the
        container.

        If the object's `__parent__` and `__name__` were already set to
        ``None``, then no events are generated.  This allows advanced
        clients to take over event generation.

        """


class IItemWriteContainer(IWriteContainer, IItemContainer):
    """A write container that also supports minimal reads."""


class IContentContainer(IWriteContainer):
    """Containers (like folders) that contain ordinary content."""


class IContainer(IReadContainer, IWriteContainer):
    """Readable and writable content container."""


class IOrderedContainer(IContainer):
    """Containers whose contents are maintained in order."""

    def updateOrder(order):
        """Revise the order of keys, replacing the current ordering.

        order is a list or a tuple containing the set of existing keys in
        the new order. `order` must contain ``len(keys())`` items and cannot
        contain duplicate keys.

        Raises ``TypeError`` if order is not a tuple or a list.

        Raises ``ValueError`` if order contains an invalid set of keys.
        """


class IContainerNamesContainer(IContainer):
    """Containers that always choose names for their items."""


##############################################################################
# Moving Objects

class IObjectMovedEvent(IObjectEvent):
    """An object has been moved."""

    oldParent = Attribute("The old location parent for the object.")
    oldName = Attribute("The old location name for the object.")
    newParent = Attribute("The new location parent for the object.")
    newName = Attribute("The new location name for the object.")


##############################################################################
# Adding objects

class UnaddableError(ContainerError):
    """An object cannot be added to a container."""

    def __init__(self, container, obj, message=""):
        self.container = container
        self.obj = obj
        self.message = message and ": %s" % message

    def __str__(self):
        return ("%(obj)s cannot be added "
                "to %(container)s%(message)s" % self.__dict__)


class IObjectAddedEvent(IObjectMovedEvent):
    """An object has been added to a container."""


class IAdding(IView):

    def add(content):
        """Add content object to container.

        Add using the name in `contentName`.  Returns the added object
        in the context of its container.

        If `contentName` is already used in container, raises
        ``DuplicateIDError``.
        """

    contentName = Attribute(
         """The content name, as usually set by the Adder traverser.

         If the content name hasn't been defined yet, returns ``None``.

         Some creation views might use this to optionally display the
         name on forms.
         """
         )

    def nextURL():
        """Return the URL that the creation view should redirect to.

        This is called by the creation view after calling add.

        It is the adder's responsibility, not the creation view's to
        decide what page to display after content is added.
        """

    def nameAllowed():
        """Return whether names can be input by the user."""

    def addingInfo():
        """Return add menu data as a sequence of mappings.

        Each mapping contains 'action', 'title', and possibly other keys.

        The result is sorted by title.
        """

    def isSingleMenuItem():
        """Return whether there is single menu item or not."""

    def hasCustomAddView():
        "This should be called only if there is `singleMenuItem` else return 0"


class INameChooser(Interface):

    def checkName(name, object):
        """Check whether an object name is valid.

        Raises a user error if the name is not valid.
        """

    def chooseName(name, object):
        """Choose a unique valid name for the object

        The given name and object may be taken into account when
        choosing the name.
        """


##############################################################################
# Removing objects


class IObjectRemovedEvent(IObjectMovedEvent):
    """An object has been removed from a container."""


##############################################################################
# Modifying containers


class IContainerModifiedEvent(IObjectModifiedEvent):
    """The container has been modified.

    This event is specific to "containerness" modifications, which means
    addition, removal or reordering of sub-objects.
    """


##############################################################################
# Finding objects

class IFind(Interface):
    """
    Find support for containers.
    """

    def find(id_filters=None, object_filters=None):
        """Find object that matches all filters in all sub-objects.

        This container itself is not included.
        """


class IObjectFindFilter(Interface):

    def matches(object):
        """Return True if the object matches the filter criteria."""


class IIdFindFilter(Interface):

    def matches(id):
        """Return True if the id matches the filter criteria."""
