##############################################################################
#
# Copyright (c) 2004, 2005 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.
#
##############################################################################
# metaclass taken from PEAK.
# Martijn doesn't pretend to understand this.
from weakref import WeakValueDictionary
from types import ClassType

def makeClass(name,bases,dict):
    """makeClass(name, bases, dict) - enhanced class creation
    """

    # Create either a "classic" Python class, an ExtensionClass, or a new-style
    # class with autogenerated metaclass, based on the nature of the base
    # classes involved

    name = str(name)  # De-unicode

    metaclasses = [getattr(b,'__class__',type(b)) for b in bases]

    if dict.has_key('__metaclass__'):
        metaclasses.insert(0,dict['__metaclass__'])

    if dict.has_key('__metaclasses__'):
        metaclasses[0:0] = list(dict['__metaclasses__'])

    metaclasses = normalizeBases(metaclasses)

    if metaclasses:

        # If we have metaclasses, it's not a classic class, so derive a
        # single metaclass, and ask it to create the class.

        if len(metaclasses)==1:
            metaclass = metaclasses[0]
        else:
            metaclass = derivedMeta(metaclasses)

        return metaclass(name,bases,dict)

    # No metaclasses, it's a classic class, so use 'new.classobj'

    from new import classobj; return classobj(name,bases,dict)

def normalizeBases(allBases):
    return minimalBases([b for b in allBases if b is not makeClass])

def minimalBases(classes):
    """Reduce a list of base classes to its ordered minimum equivalent"""

    classes = [c for c in classes if c is not ClassType]
    candidates = []

    for m in classes:
        for n in classes:
            if issubclass(n,m) and m is not n:
                break
        else:
            # m has no subclasses in 'classes'
            if m in candidates:
                candidates.remove(m)    # ensure that we're later in the list
            candidates.append(m)

    return candidates

metaReg = WeakValueDictionary()

def derivedMeta(metaclasses):
    metaclasses = tuple(metaclasses)
    derived = metaReg.get(metaclasses)

    if derived is None:
        normalized = tuple(normalizeBases(metaclasses))
        derived = metaReg.get(normalized)

        if derived is None:
            if len(normalized)==1:
                derived = normalized[0]
            else:
                derived = metaFromBases(normalized)(
                    '_'.join([n.__name__ for n in normalized]),
                    metaclasses, {}
                )
            try: metaReg[normalized] = derived
            except TypeError: pass  # Some metatypes can't be weakref'd

        try: metaReg[metaclasses] = derived
        except TypeError: pass

    return derived

def metaFromBases(bases):
    meta = tuple([getattr(b,'__class__',type(b)) for b in bases])
    if meta==bases: raise TypeError("Incompatible root metatypes",bases)
    return derivedMeta(meta)
