"""
Define names for built-in types that aren't directly accessible as a builtin.
"""
import sys

# Iterators in Python aren't a matter of type but of protocol.  A large
# and changing number of builtin types implement *some* flavor of
# iterator.  Don't check the type!  Use hasattr to check for both
# "__iter__" and "__next__" attributes instead.


def _f():
    pass


FunctionType = type(_f)
LambdaType = type(lambda: None)  # Same as FunctionType
CodeType = None  # TODO: Add better sentinel which can't match anything
MappingProxyType = None  # TODO: Add better sentinel which can't match anything
SimpleNamespace = None  # TODO: Add better sentinel which can't match anything


def _g():
    yield 1


GeneratorType = type(_g())


class _C:
    def _m(self):
        pass


MethodType = type(_C()._m)

BuiltinFunctionType = type(len)
BuiltinMethodType = type([].append)  # Same as BuiltinFunctionType

ModuleType = type(sys)

try:
    raise TypeError
except TypeError:
    #    tb = sys.exc_info()[2]
    TracebackType = None  # TODO: Add better sentinel which can't match anything
    FrameType = None  # TODO: Add better sentinel which can't match anything
    tb = None
    del tb

# For Jython, the following two types are identical
GetSetDescriptorType = None  # TODO: Add better sentinel which can't match anything
MemberDescriptorType = None  # TODO: Add better sentinel which can't match anything

del (
    sys,
    _f,
    _g,
    _C,
)  # Not for export


# Provide a PEP 3115 compliant mechanism for class creation
def new_class(name, bases=(), kwds=None, exec_body=None):
    """Create a class object dynamically using the appropriate metaclass."""
    meta, ns, kwds = prepare_class(name, bases, kwds)
    if exec_body is not None:
        exec_body(ns)
    return meta(name, bases, ns, **kwds)


def prepare_class(name, bases=(), kwds=None):
    """Call the __prepare__ method of the appropriate metaclass.

    Returns (metaclass, namespace, kwds) as a 3-tuple

    *metaclass* is the appropriate metaclass
    *namespace* is the prepared class namespace
    *kwds* is an updated copy of the passed in kwds argument with any
    'metaclass' entry removed. If no kwds argument is passed in, this will
    be an empty dict.
    """
    if kwds is None:
        kwds = {}
    else:
        kwds = dict(kwds)  # Don't alter the provided mapping
    if "metaclass" in kwds:
        meta = kwds.pop("metaclass")
    else:
        if bases:
            meta = type(bases[0])
        else:
            meta = type
    if isinstance(meta, type):
        # when meta is a type, we first determine the most-derived metaclass
        # instead of invoking the initial candidate directly
        meta = _calculate_meta(meta, bases)
    if hasattr(meta, "__prepare__"):
        ns = meta.__prepare__(name, bases, **kwds)
    else:
        ns = {}
    return meta, ns, kwds


def _calculate_meta(meta, bases):
    """Calculate the most derived metaclass."""
    winner = meta
    for base in bases:
        base_meta = type(base)
        if issubclass(winner, base_meta):
            continue
        if issubclass(base_meta, winner):
            winner = base_meta
            continue
        # else:
        raise TypeError(
            "metaclass conflict: "
            "the metaclass of a derived class "
            "must be a (non-strict) subclass "
            "of the metaclasses of all its bases"
        )
    return winner
