#############################################################################
#
# 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.
#
##############################################################################

import grokcore.component
import martian
from martian.error import GrokError
from zope import component
from zope.container.interfaces import IContainer
from zope.container.interfaces import INameChooser
from zope.lifecycleevent.interfaces import IObjectAddedEvent

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')
    martian.directive(grokcore.site.install_on, default=IObjectAddedEvent)

    def execute(self, factory, config, infos, install_on, **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, install_on)

        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='',
                 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 = f'{module_info.dotted_name}.{name}'
        config.action(
            discriminator=('utility', provides, name),
            callable=grokcore.component.provideUtility,
            args=(factory, provides, name),
        )
        return True
