#############################################################################
#
# Copyright (c) 2006-2009 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.
#
##############################################################################

from zope import component

from zope.lifecycleevent.interfaces import IObjectAddedEvent
from zope.container.interfaces import IContainer,  INameChooser

import martian
from martian.error import GrokError

import grokcore.component
import grokcore.site
import grokcore.site.components
import grokcore.site.interfaces


class SiteGrokker(martian.ClassGrokker):
    """Grokker for subclasses of `grokcore.site.Site`."""
    martian.component(grokcore.site.components.BaseSite)
    martian.priority(500)
    martian.directive(grokcore.site.local_utility, name='infos')

    def execute(self, factory, config, infos, **kw):
        if not infos:
            return False

        infos = infos.values()
        for info in infos:
            if info.public and not IContainer.implementedBy(factory):
                raise GrokError(
                    "Cannot set public to True with grok.local_utility as "
                    "the site (%r) is not a container." %
                    factory, factory)

        # Store the list of info objects in their "natural" order on the
        # site class. They will be picked up by a subscriber doing the
        # actual registrations in definition order.
        factory.__grok_utilities_to_install__ = sorted(infos)
        adapts = (factory, IObjectAddedEvent)

        config.action(
            discriminator=None,
            callable=grokcore.component.provideHandler,
            args=(localUtilityRegistrationSubscriber, adapts),
            )
        return True


def localUtilityRegistrationSubscriber(site, event):
    """A subscriber that fires to set up local utilities.
    """
    installed = getattr(site, '__grok_utilities_installed__', False)
    if installed:
        return

    setupUtility = component.getUtility(
        grokcore.site.interfaces.IUtilityInstaller)
    for info in getattr(site.__class__, '__grok_utilities_to_install__', []):
        setupUtility(site, info.factory(), info.provides, name=info.name,
                     name_in_container=info.name_in_container,
                     public=info.public, setup=info.setup)

    # we are done. If this subscriber gets fired again, we therefore
    # do not register utilities anymore
    site.__grok_utilities_installed__ = True


@grokcore.component.provider(grokcore.site.interfaces.IUtilityInstaller)
def setupUtility(site, utility, provides, name=u'',
                 name_in_container=None, public=False, setup=None):
    """Set up a utility in a site.

    site - the site to set up the utility in
    utility - the utility to set up
    provides - the interface the utility should be registered with
    name - the name the utility should be registered under, default
      the empty string (no name)
    name_in_container - if given it will be used to add the utility
      object to its container. Otherwise a name will be made up
    public - if False, the utility will be stored in the site manager. If
      True, the utility will be storedin the site (it is assumed the
      site is a container)
    setup - if not None, it will be called with the utility as its first
       argument. This function can then be used to further set up the
       utility.
    """
    site_manager = site.getSiteManager()

    if not public:
        container = site_manager
    else:
        container = site

    if name_in_container is None:
        name_in_container = INameChooser(container).chooseName(
            utility.__class__.__name__, utility)
    container[name_in_container] = utility

    if setup is not None:
        setup(utility)

    site_manager.registerUtility(utility, provided=provides, name=name)


class ApplicationGrokker(martian.ClassGrokker):
    """Grokker for Grok application classes."""
    martian.component(grokcore.site.components.Application)
    martian.priority(500)

    def grok(self, name, factory, module_info, config, **kw):
        # XXX fail loudly if the same application name is used twice.
        provides = grokcore.site.interfaces.IApplication
        name = '%s.%s' % (module_info.dotted_name, name)
        config.action(
            discriminator=('utility', provides, name),
            callable=grokcore.component.provideUtility,
            args=(factory, provides, name),
            )
        return True
