""" Multiple inheritance for forms and model forms.

Based on http://www.djangosnippets.org/snippets/703/ """

import inspect, types, __builtin__

############## preliminary: two utility functions #####################

def skip_redundant(iterable, skipset=None):
   "Redundant items are repeated items or items in the original skipset."
   if skipset is None: skipset = set()
   for item in iterable:
       if item not in skipset:
           skipset.add(item)
           yield item


def remove_redundant(metaclasses):
   skipset = set([types.ClassType])
   for meta in metaclasses: # determines the metaclasses to be skipped
       skipset.update(inspect.getmro(meta)[1:])
   return tuple(skip_redundant(metaclasses, skipset))

##################################################################
## now the core of the module: two mutually recursive functions ##
##################################################################

memoized_metaclasses_map = {}

def get_noconflict_metaclass(bases, left_metas, right_metas):
    """Not intended to be used outside of this module, unless you know
    what you are doing."""
    # make tuple of needed metaclasses in specified priority order
    metas = left_metas + tuple(map(type, bases)) + right_metas
    needed_metas = remove_redundant(metas)

    # return existing confict-solving meta, if any
    if needed_metas in memoized_metaclasses_map:
      return memoized_metaclasses_map[needed_metas]
    # nope: compute, memoize and return needed conflict-solving meta
    elif not needed_metas:         # wee, a trivial case, happy us
        meta = type
    elif len(needed_metas) == 1: # another trivial case
       meta = needed_metas[0]
    # check for recursion, can happen i.e. for Zope ExtensionClasses
    elif needed_metas == bases:
        raise TypeError("Incompatible root metatypes", needed_metas)
    else: # gotta work ...
        metaname = '_' + ''.join([m.__name__ for m in needed_metas])
        meta = classmaker()(metaname, needed_metas, {})
    memoized_metaclasses_map[needed_metas] = meta
    return meta

def classmaker(left_metas=(), right_metas=()):
   def make_class(name, bases, adict):
       metaclass = get_noconflict_metaclass(bases, left_metas, right_metas)
       return metaclass(name, bases, adict)
   return make_class

def multiform_metaclass():
    """
    Get metaclass for form class that inherits multiple forms, especially the
    base classes of form mix up with Form and ModelForm (metaclass mix up with
    DeclarativeFieldsMetaclass, ModelFormMetaclass).
    See http://code.djangoproject.com/ticket/7018 for related details.
    @return Function that will generate class that metaclass will do.
    """
    from django.forms import models as nmodels
    factory = classmaker()
    def new(name, bases, attrs):
        # get_declared_fields can only be called once, the result will be wrong
        # if you call it again. We call it first to hold the correct result.
        fields = nmodels.get_declared_fields(bases, attrs, True)
        newclass = factory(name, bases, attrs)
        # Restore the correct result.
        newclass.base_fields = fields
        return newclass
    return new
