from enum import Enum, EnumMeta, _EnumDict
from functools import total_ordering
from collections import Iterable


__version__ = '0.6.5'
__all__ = ['MultiValueEnum', 'no_overlap', 'StrEnum', 'CaseInsensitiveStrEnum',
           'CaseInsensitiveMultiValueEnum']


class _MultiValueMeta(EnumMeta):
    def __init__(self, clsname, bases, classdict):
        for member in self.__members__.values():
            values = member._value_
            # make sure we only have tuple values, not single values
            if not isinstance(values, Iterable) or isinstance(values, str):
                raise TypeError('{} = {!r}, should be iterable, not {}!'
                    .format(member._name_, values, type(values))
                )
            for alias in values:
                # don't touch if already set, so behave like alias
                # described in python documentation
                self._value2member_map_.setdefault(alias, member)


class MultiValueEnum(Enum, metaclass=_MultiValueMeta):
    """Enum subclass where a member can be any iterable (except str).
    You can reference a member by any of its element in the associated iterable.
    """


class _CasInsensitiveMultiValueMeta(EnumMeta):
    def __init__(self, clsname, bases, classdict):
        # make sure we only have tuple values, not single values
        for member in self.__members__.values():
            value = member._value_
            if not isinstance(value, Iterable) or isinstance(value, str):
                raise TypeError('{} = {!r}, should be iterable, not {}!'
                    .format(member._name_, value, type(value))
                )
            for alias in value:
                if isinstance(alias, str):
                    alias = alias.upper()
                self._value2member_map_.setdefault(alias, member)

    def __call__(cls, value):
        """Return the appropriate instance with any of the values listed."""
        if isinstance(value, str):
            value = value.upper()
        return super().__call__(value)


class CaseInsensitiveMultiValueEnum(
    Enum, metaclass=_CasInsensitiveMultiValueMeta):
    """Same as MultiValueEnum, except when member value contains an str,
    they will be compared in a case-insensitive manner. Non-str types left
    untouched.
    """


def no_overlap(multienum):
    """Class decorator for MultiValueEnum ensuring non overlapping elements
    in member values. Other words: ensure no element in any member value is
    present in any other member value.
    """
    duplicates = []
    members = []

    for member in multienum.__members__.values():
        for prev_member in members:
            intersection = set(member._value_) & set(prev_member._value_)
            if intersection:
                duplicates.append(
                    (member._name_, prev_member._name_, intersection)
                )
        members.append(member)

    if duplicates:
        alias_details = ', '.join(["{} & {} -> {}"
                                  .format(alias, name, intersection) for
                                  (alias, name, intersection) in duplicates])
        raise ValueError('common element found in {!r}: {}'
                         .format(multienum, alias_details))
    return multienum


class _CheckTypeDict(_EnumDict):
    def __init__(self, expected_type):
        super().__init__()
        self._expected_type = expected_type

    def __setitem__(self, key, value):
        super().__setitem__(key, value)
        if not isinstance(value, self._expected_type):
            raise TypeError('{} = {!r}, should be {}!'.format(
                            key, value, self._expected_type)
            )


class _CheckTypeEnumMeta(EnumMeta):
    @classmethod
    def __prepare__(metacls, cls, bases):
        if bases[-1] == Enum:
            # basic stdlib Enum
            return _EnumDict()
        else:
            # User subclassed one of our custom Enum,
            # so the last in the mro will be that class
            # __mro__ of our custom Enum class:
            # 1. subclass (e.g. StrEnum or IntEnum)
            # 2. expected type (e.g. str)
            # 3. Enum class
            # 4. object
            our_enum_class = bases[-1]
            expected_type = our_enum_class.__mro__[1]
            return _CheckTypeDict(expected_type)


class StrEnum(str, Enum, metaclass=_CheckTypeEnumMeta):
    """Enum subclass which members are also instances of str
    and directly comparable to strings. str type is forced at declaration.
    """


class _CaseInsensitiveEnumMeta(_CheckTypeEnumMeta):
    def __init__(self, cls, bases, classdict):
        for name, member in self._member_map_.items():
            self._value2member_map_.pop(member._value_)
            member._value_ = member.value.upper()
            # need to update also
            self._value2member_map_[member._value_] = member

    def __call__(cls, value):
        return cls.__new__(cls, value.upper())


class CaseInsensitiveStrEnum(str, Enum, metaclass=_CaseInsensitiveEnumMeta):
    def __new__(cls, *args):
        args = tuple(arg.upper() for arg in args if isinstance(arg, str))
        return super().__new__(cls, *args)

    def __eq__(self, other):
        return self.upper() == other.upper()


@total_ordering
class OrderableMixin:
    """Mixin for comparable Enums. The order is the definition order
    from smaller to bigger.
    """
    # From Python manual:
    # If a class that overrides __eq__() needs to retain
    # the implementation of __hash__() from a parent class,
    # the interpreter must be told this explicitly
    def __hash__(self):
        return super().__hash__()

    def __reduce_ex__(self, proto):
        # this will never run anyway, but the Enum class needs it
        return self.__qualname__

    def __eq__(self, other):
        if self.__class__ is other.__class__:
            return self._value_ == other._value_
        return NotImplemented

    def __lt__(self, other):
        if self.__class__ is other.__class__:
            names = self.__class__._member_names_
            return names.index(self._name_) < names.index(other._name_)
        return NotImplemented

@@ -106,16 +106,47 @@
 terable%0A
+from six import with_metaclass%0A
 %0A%0A__vers
@@ -998,22 +998,21 @@
 lueEnum(
-Enum, 
+with_
 metaclas
@@ -1012,17 +1012,17 @@
 etaclass
-=
+(
 _MultiVa
@@ -1020,32 +1020,39 @@
 (_MultiValueMeta
+, Enum)
 ):%0A    %22%22%22Enum s
@@ -2116,22 +2116,21 @@
 um(%0A    
-Enum, 
+with_
 metaclas
@@ -2126,25 +2126,25 @@
 th_metaclass
-=
+(
 _CasInsensit
@@ -2156,24 +2156,31 @@
 ltiValueMeta
+, Enum)
 ):%0A    %22%22%22Sa
@@ -4393,35 +4393,29 @@
 ass StrEnum(
-str, Enum, 
+with_
 metaclass=_C
@@ -4411,17 +4411,17 @@
 etaclass
-=
+(
 _CheckTy
@@ -4426,24 +4426,36 @@
 TypeEnumMeta
+, str, Enum)
 ):%0A    %22%22%22En
@@ -5059,19 +5059,13 @@
 num(
-str, Enum, 
+with_
 meta
@@ -5069,17 +5069,17 @@
 etaclass
-=
+(
 _CaseIns
@@ -5086,32 +5086,44 @@
 ensitiveEnumMeta
+, str, Enum)
 ):%0A    def __new

Switch metaclass declarations