##############################################################################
#
# Copyright (c) 2004 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (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.
#
##############################################################################
"""Adapter management

$Id: adapter.py 75894 2007-05-22 18:39:49Z jim $
"""

import weakref
from zope.interface import providedBy, Interface, ro

_marker = object
class BaseAdapterRegistry(object):

    # List of methods copied from lookup sub-objects:
    _delegated = ('lookup', 'queryMultiAdapter', 'lookup1', 'queryAdapter',
                  'adapter_hook', 'lookupAll', 'names',
                  'subscriptions', 'subscribers')

    # All registries maintain a generation that can be used by verifying
    # registries
    _generation = 0

    def __init__(self, bases=()):

        # {order -> {required -> {provided -> {name -> value}}}}
        # where "interfaces" is really a nested key.  So, for example:
        # for order == 0, we have:
        #   {provided -> {name -> valie}}
        # but for order == 2, we have:
        #   {r1 -> {r2 -> {provided -> {name -> valie}}}}
        self._adapters = []

        # {order -> {required -> {provided -> {name -> [value]}}}}
        # where the remarks about adapters above apply
        self._subscribers = []

        # Set, with a reference count, keeping track of the interfaces
        # for which we have provided components:
        self._provided = {}

        # Looup object to perform lookup.  We make this a separate object to
        # to make it easier, in the furture, to implement just the lookup
        # functionality in C.
        self._createLookup()

        # Cache invalidation data.  There are really 2 kinds of registries:
        
        #   Invalidating registries have caches that are invalidated
        #     when they or when base registies change.  An invalidating
        #     registry can only have invalidating registries as bases.

        #   Verifying registies can't rely on getting invalidation message,
        #     so have to check the generations of base registries to determine
        #     if their cache data are current

        # Base registries:
        self.__bases__ = bases
        
    def _setBases(self, bases):
        self.__dict__['__bases__'] = bases
        self.ro = ro.ro(self)
        self.changed(self)

    __bases__ = property(lambda self: self.__dict__['__bases__'],
                         lambda self, bases: self._setBases(bases),
                         )

    def _createLookup(self):
        self._v_lookup = self.LookupClass(self)
        for name in self._delegated:
            self.__dict__[name] = getattr(self._v_lookup, name)

    def changed(self, originally_changed):
        self._generation += 1
        self._v_lookup.changed(originally_changed)

    def register(self, required, provided, name, value):
        if value is None:
            self.unregister(required, provided, name, value)
            return

        required = tuple(map(_convert_None_to_Interface, required))
        name = _normalize_name(name)
        order = len(required)
        byorder = self._adapters
        while len(byorder) <= order:
            byorder.append({})
        components = byorder[order]
        key = required + (provided,)
        
        for k in key:
            d = components.get(k)
            if d is None:
                d = {}
                components[k] = d
            components = d

        if components.get(name) is value:
            return
        
        components[name] = value

        n = self._provided.get(provided, 0) + 1
        self._provided[provided] = n
        if n == 1:
            self._v_lookup.add_extendor(provided)

        self.changed(self)

    def registered(self, required, provided, name=u''):
        required = tuple(map(_convert_None_to_Interface, required))
        name = _normalize_name(name)
        order = len(required)
        byorder = self._adapters
        if len(byorder) <= order:
            return None

        components = byorder[order]
        key = required + (provided,)
        
        for k in key:
            d = components.get(k)
            if d is None:
                return None
            components = d

        return components.get(name)
        
    def unregister(self, required, provided, name, value=None):
        required = tuple(map(_convert_None_to_Interface, required))
        order = len(required)
        byorder = self._adapters
        if order >= len(byorder):
            return False
        components = byorder[order]
        key = required + (provided,)
        
        for k in key:
            d = components.get(k)
            if d is None:
                return
            components = d

        old = components.get(name)
        if old is None:
            return
        if (value is not None) and (old is not value):
            return

        del components[name]
        n = self._provided[provided] - 1
        if n == 0:
            del self._provided[provided]
            self._v_lookup.remove_extendor(provided)
        else:
            self._provided[provided] = n

        self.changed(self)

        return


    def subscribe(self, required, provided, value):
        required = tuple(map(_convert_None_to_Interface, required))
        name = u''
        order = len(required)
        byorder = self._subscribers
        while len(byorder) <= order:
            byorder.append({})
        components = byorder[order]
        key = required + (provided,)
        
        for k in key:
            d = components.get(k)
            if d is None:
                d = {}
                components[k] = d
            components = d

        components[name] = components.get(name, ()) + (value, )

        if provided is not None:
            n = self._provided.get(provided, 0) + 1
            self._provided[provided] = n
            if n == 1:
                self._v_lookup.add_extendor(provided)

        self.changed(self)

    def unsubscribe(self, required, provided, value=None):
        required = tuple(map(_convert_None_to_Interface, required))
        order = len(required)
        byorder = self._subscribers
        if order >= len(byorder):
            return
        components = byorder[order]
        key = required + (provided,)
        
        for k in key:
            d = components.get(k)
            if d is None:
                return
            components = d

        old = components.get(u'')
        if not old:
            return

        if value is None:
            new = ()
        else:
            new = tuple([v for v in old if v is not value])

        if new == old:
            return
        
        components[u''] = new

        if provided is not None:
            n = self._provided[provided] + len(new) - len(old)
            if n == 0:
                del self._provided[provided]
                self._v_lookup.remove_extendor(provided)

        self.changed(self)

    # XXX hack to fake out twisted's use of a private api.  We need to get them
    # to use the new registed method.
    def get(self, _):
        class XXXTwistedFakeOut:
            selfImplied = {}
        return XXXTwistedFakeOut


_not_in_mapping = object()
class LookupBasePy(object):

    def __init__(self):
        self._cache = {}
        self._mcache = {}
        self._scache = {}
        
    def changed(self, ignored=None):
        self._cache.clear()
        self._mcache.clear()
        self._scache.clear()
        
    def _getcache(self, provided, name):
        cache = self._cache.get(provided)
        if cache is None:
            cache = {}
            self._cache[provided] = cache
        if name:
            c = cache.get(name)
            if c is None:
                c = {}
                cache[name] = c
            cache = c
        return cache

    def lookup(self, required, provided, name=u'', default=None):
        cache = self._getcache(provided, name)
        if len(required) == 1:
            result = cache.get(required[0], _not_in_mapping)
        else:
            result = cache.get(tuple(required), _not_in_mapping)

        if result is _not_in_mapping:
            result = self._uncached_lookup(required, provided, name)
            if len(required) == 1:
                cache[required[0]] = result
            else:
                cache[tuple(required)] = result

        if result is None:
            return default

        return result
    
    def lookup1(self, required, provided, name=u'', default=None):
        cache = self._getcache(provided, name)
        result = cache.get(required, _not_in_mapping)
        if result is _not_in_mapping:
            return self.lookup((required, ), provided, name, default)

        if result is None:
            return default

        return result

    def queryAdapter(self, object, provided, name=u'', default=None):
        return self.adapter_hook(provided, object, name, default)

    def adapter_hook(self, provided, object, name=u'', default=None):
        required = providedBy(object)
        cache = self._getcache(provided, name)
        factory = cache.get(required, _not_in_mapping)
        if factory is _not_in_mapping:
            factory = self.lookup((required, ), provided, name)

        if factory is not None:
            result = factory(object)
            if result is not None:
                return result

        return default

    def lookupAll(self, required, provided):
        cache = self._mcache.get(provided)
        if cache is None:
            cache = {}
            self._mcache[provided] = cache

        required = tuple(required)
        result = cache.get(required, _not_in_mapping)
        if result is _not_in_mapping:
            result = self._uncached_lookupAll(required, provided)
            cache[required] = result

        return result


    def subscriptions(self, required, provided):
        cache = self._scache.get(provided)
        if cache is None:
            cache = {}
            self._scache[provided] = cache

        required = tuple(required)
        result = cache.get(required, _not_in_mapping)
        if result is _not_in_mapping:
            result = self._uncached_subscriptions(required, provided)
            cache[required] = result

        return result
    
LookupBase = LookupBasePy

class VerifyingBasePy(LookupBasePy):

    def changed(self, originally_changed):
        LookupBasePy.changed(self, originally_changed)
        self._verify_ro = self._registry.ro[1:]
        self._verify_generations = [r._generation for r in self._verify_ro]

    def _verify(self):
        if ([r._generation for r in self._verify_ro]
            != self._verify_generations):
            self.changed(None)

    def _getcache(self, provided, name):
        self._verify()
        return LookupBasePy._getcache(self, provided, name)
    
    def lookupAll(self, required, provided):
        self._verify()
        return LookupBasePy.lookupAll(self, required, provided)

    def subscriptions(self, required, provided):
        self._verify()
        return LookupBasePy.subscriptions(self, required, provided)

VerifyingBase = VerifyingBasePy


try:
    import _zope_interface_coptimizations
except ImportError:
    pass
else:
    from _zope_interface_coptimizations import LookupBase, VerifyingBase

class AdapterLookupBase(object):

    def __init__(self, registry):
        self._registry = registry
        self._required = {}
        self.init_extendors()
        super(AdapterLookupBase, self).__init__()

    def changed(self, ignored=None):
        super(AdapterLookupBase, self).changed(None)
        for r in self._required.keys():
            r = r()
            if r is not None:
                r.unsubscribe(self)
        self._required.clear()


    # Extendors
    # ---------

    # When given an target interface for an adapter lookup, we need to consider
    # adapters for interfaces that extend the target interface.  This is
    # what the extendors dictionary is about.  It tells us all of the
    # interfaces that extend an interface for which there are adapters
    # registered.

    # We could separate this by order and name, thus reducing the
    # number of provided interfaces to search at run time.  The tradeoff,
    # however, is that we have to store more information.  For example,
    # is the same interface is provided for multiple names and if the
    # interface extends many interfaces, we'll have to keep track of
    # a fair bit of information for each name.  It's better to
    # be space efficient here and be time efficient in the cache
    # implementation.

    # TODO: add invalidation when a provided interface changes, in case
    # the interface's __iro__ has changed.  This is unlikely enough that
    # we'll take our chances for now.
    
    def init_extendors(self):
        self._extendors = {}
        for p in self._registry._provided:
            self.add_extendor(p)

    def add_extendor(self, provided):
        _extendors = self._extendors
        for i in provided.__iro__:
            extendors = _extendors.get(i, ())
            _extendors[i] = (
                [e for e in extendors if provided.isOrExtends(e)]
                +
                [provided]
                + 
                [e for e in extendors if not provided.isOrExtends(e)]
                )

    def remove_extendor(self, provided):
        _extendors = self._extendors
        for i in provided.__iro__:
            _extendors[i] = [e for e in _extendors.get(i, ())
                             if e != provided]


    def _subscribe(self, *required):
        _refs = self._required
        for r in required:
            ref = r.weakref()
            if ref not in _refs:
                r.subscribe(self)
                _refs[ref] = 1

    def _uncached_lookup(self, required, provided, name=u''):
        result = None
        order = len(required)
        for registry in self._registry.ro:
            byorder = registry._adapters
            if order >= len(byorder):
                continue

            extendors = registry._v_lookup._extendors.get(provided)
            if not extendors:
                continue

            components = byorder[order]
            result = _lookup(components, required, extendors, name, 0,
                             order)
            if result is not None:
                break

        self._subscribe(*required)

        return result

    def queryMultiAdapter(self, objects, provided, name=u'', default=None):
        factory = self.lookup(map(providedBy, objects), provided, name)
        if factory is None:
            return default

        result = factory(*objects)
        if result is None:
            return default

        return result        

    def _uncached_lookupAll(self, required, provided):
        order = len(required)
        result = {}
        for registry in reversed(self._registry.ro):
            byorder = registry._adapters
            if order >= len(byorder):
                continue
            extendors = registry._v_lookup._extendors.get(provided)
            if not extendors:
                continue
            components = byorder[order]
            _lookupAll(components, required, extendors, result, 0, order)

        self._subscribe(*required)

        return tuple(result.iteritems())

    def names(self, required, provided):
        return [c[0] for c in self.lookupAll(required, provided)]

    def _uncached_subscriptions(self, required, provided):
        order = len(required)
        result = []
        for registry in reversed(self._registry.ro):
            byorder = registry._subscribers
            if order >= len(byorder):
                continue

            if provided is None:
                extendors = (provided, )
            else:
                extendors = registry._v_lookup._extendors.get(provided)
                if extendors is None:
                    continue

            _subscriptions(byorder[order], required, extendors, u'',
                           result, 0, order)

        self._subscribe(*required)

        return result

    def subscribers(self, objects, provided):
        subscriptions = self.subscriptions(map(providedBy, objects), provided)
        if provided is None:
            result = ()
            for subscription in subscriptions:
                subscription(*objects)
        else:
            result = []
            for subscription in subscriptions:
                subscriber = subscription(*objects)
                if subscriber is not None:
                    result.append(subscriber)
        return result

class AdapterLookup(AdapterLookupBase, LookupBase):
    pass

class AdapterRegistry(BaseAdapterRegistry):

    LookupClass = AdapterLookup

    def __init__(self, bases=()):
        # AdapterRegisties are invalidating registries, so
        # we need to keep track of out invalidating subregistries.
        self._v_subregistries = weakref.WeakKeyDictionary()

        super(AdapterRegistry, self).__init__(bases)

    def _addSubregistry(self, r):
        self._v_subregistries[r] = 1

    def _removeSubregistry(self, r):
        if r in self._v_subregistries:
            del self._v_subregistries[r]

    def _setBases(self, bases):
        old = self.__dict__.get('__bases__', ())
        for r in old:
            if r not in bases:
                r._removeSubregistry(self)
        for r in bases:
            if r not in old:
                r._addSubregistry(self)

        super(AdapterRegistry, self)._setBases(bases)

    def changed(self, originally_changed):
        super(AdapterRegistry, self).changed(originally_changed)

        for sub in self._v_subregistries.keys():
            sub.changed(originally_changed)


class VerifyingAdapterLookup(AdapterLookupBase, VerifyingBase):
    pass

class VerifyingAdapterRegistry(BaseAdapterRegistry):

    LookupClass = VerifyingAdapterLookup
    
def _convert_None_to_Interface(x):
    if x is None:
        return Interface
    else:
        return x

def _normalize_name(name):
    if isinstance(name, basestring):
        return unicode(name)

    raise TypeError("name must be a regular or unicode string")

def _lookup(components, specs, provided, name, i, l):
    if i < l:
        for spec in specs[i].__sro__:
            comps = components.get(spec)
            if comps:
                r = _lookup(comps, specs, provided, name, i+1, l)
                if r is not None:
                    return r
    else:
        for iface in provided:
            comps = components.get(iface)
            if comps:
                r = comps.get(name)
                if r is not None:
                    return r
                
    return None

def _lookupAll(components, specs, provided, result, i, l):
    if i < l:
        for spec in reversed(specs[i].__sro__):
            comps = components.get(spec)
            if comps:
                _lookupAll(comps, specs, provided, result, i+1, l)
    else:
        for iface in reversed(provided):
            comps = components.get(iface)
            if comps:
                result.update(comps)

def _subscriptions(components, specs, provided, name, result, i, l):
    if i < l:
        for spec in reversed(specs[i].__sro__):
            comps = components.get(spec)
            if comps:
                _subscriptions(comps, specs, provided, name, result, i+1, l)
    else:
        for iface in reversed(provided):
            comps = components.get(iface)
            if comps:
                comps = comps.get(name)
                if comps:
                    result.extend(comps)
