# coding: utf-8
"""
Backported functions from later versions of python (mostly 2.7)

These functions are all licensed under the PSF License.
"""

import sys

version_info = sys.version_info

if version_info[:2] == (2, 6):
    from _weakref import ref

    class _IterationGuard(object):
        # This context manager registers itself in the current iterators of the
        # weak container, such as to delay all removals until the context
        # manager
        # exits.
        # This technique should be relatively thread-safe (since sets are).

        def __init__(self, weakcontainer):
            # Don't create cycles
            self.weakcontainer = ref(weakcontainer)

        def __enter__(self):
            w = self.weakcontainer()
            if w is not None:
                w._iterating.add(self)
            return self

        def __exit__(self, e, t, b):
            w = self.weakcontainer()
            if w is not None:
                s = w._iterating
                s.remove(self)
                if not s:
                    w._commit_removals()

    class WeakSet(object):

        def __init__(self, data=None):
            self.data = set()

            def _remove(item, selfref=ref(self)):
                self = selfref()

                if self is not None:
                    if self._iterating:
                        self._pending_removals.append(item)
                    else:
                        self.data.discard(item)

            self._remove = _remove

            # A list of keys to be removed
            self._pending_removals = []
            self._iterating = set()

            if data is not None:
                self.update(data)

        def _commit_removals(self):
            l = self._pending_removals
            discard = self.data.discard
            while l:
                discard(l.pop())

        def __iter__(self):
            with _IterationGuard(self):
                for itemref in self.data:
                    item = itemref()
                    if item is not None:
                        # Caveat: the iterator will keep a strong reference to
                        # `item` until it is resumed or closed.
                        yield item

        def __len__(self):
            return len(self.data) - len(self._pending_removals)

        def __contains__(self, item):
            try:
                wr = ref(item)
            except TypeError:
                return False
            return wr in self.data

        def __reduce__(self):
            return (self.__class__, (list(self),),
                    getattr(self, '__dict__', None))

        __hash__ = None

        def add(self, item):
            if self._pending_removals:
                self._commit_removals()
            self.data.add(ref(item, self._remove))

        def clear(self):
            if self._pending_removals:
                self._commit_removals()
            self.data.clear()

        def copy(self):
            return self.__class__(self)

        def pop(self):

            if self._pending_removals:
                self._commit_removals()

            while True:
                try:
                    itemref = self.data.pop()

                except KeyError:
                    raise KeyError('pop from empty WeakSet')

                item = itemref()

                if item is not None:
                    return item

        def remove(self, item):

            if self._pending_removals:
                self._commit_removals()
            self.data.remove(ref(item))

        def discard(self, item):
            if self._pending_removals:
                self._commit_removals()
            self.data.discard(ref(item))

        def update(self, other):
            if self._pending_removals:
                self._commit_removals()

            for element in other:
                self.add(element)

        def __ior__(self, other):
            self.update(other)
            return self

        def difference(self, other):
            newset = self.copy()
            newset.difference_update(other)
            return newset

        __sub__ = difference

        def difference_update(self, other):
            self.__isub__(other)

        def __isub__(self, other):
            if self._pending_removals:
                self._commit_removals()
            if self is other:
                self.data.clear()
            else:
                self.data.difference_update(ref(item) for item in other)
            return self

        def intersection(self, other):

            return self.__class__(item for item in other if item in self)

        __and__ = intersection

        def intersection_update(self, other):
            self.__iand__(other)

        def __iand__(self, other):
            if self._pending_removals:
                self._commit_removals()
            self.data.intersection_update(ref(item) for item in other)
            return self

        def issubset(self, other):
            return self.data.issubset(ref(item) for item in other)

        __le__ = issubset

        def __lt__(self, other):
            return self.data < set(ref(item) for item in other)

        def issuperset(self, other):
            return self.data.issuperset(ref(item) for item in other)

        __ge__ = issuperset

        def __gt__(self, other):
            return self.data > set(ref(item) for item in other)

        def __eq__(self, other):
            if not isinstance(other, self.__class__):
                return NotImplemented
            return self.data == set(ref(item) for item in other)

        def __ne__(self, other):
            opposite = self.__eq__(other)
            if opposite is NotImplemented:
                return NotImplemented
            return not opposite

        def symmetric_difference(self, other):
            newset = self.copy()
            newset.symmetric_difference_update(other)
            return newset

        __xor__ = symmetric_difference

        def symmetric_difference_update(self, other):
            self.__ixor__(other)

        def __ixor__(self, other):
            if self._pending_removals:
                self._commit_removals()
            if self is other:
                self.data.clear()
            else:
                self.data.symmetric_difference_update(
                    ref(item, self._remove) for item in other)
            return self

        def union(self, other):
            return self.__class__(e for s in (self, other) for e in s)

        __or__ = union

        def isdisjoint(self, other):
            return len(self.intersection(other)) == 0
