from blist._blist import blist
import collections, bisect, weakref, operator, itertools, sys, threading
try: # pragma: no cover
    izip = itertools.izip
except AttributeError: # pragma: no cover
    izip = zip

__all__ = ['sortedlist', 'weaksortedlist', 'sortedset', 'weaksortedset']

class ReprRecursion(object):
    local = threading.local()
    def __init__(self, ob):
        if not hasattr(self.local, 'repr_count'):
            self.local.repr_count = collections.defaultdict(int)
        self.ob_id = id(ob)
        self.value = self.ob_id in self.local.repr_count

    def __enter__(self):
        self.local.repr_count[self.ob_id] += 1
        return self.value

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.local.repr_count[self.ob_id] -= 1
        if self.local.repr_count[self.ob_id] == 0:
            del self.local.repr_count[self.ob_id]
        return False

class _sortedbase(collections.Sequence):
    def __init__(self, iterable=(), key=None):
        self._key = key
        if key is not None and not hasattr(key, '__call__'):
            raise TypeError("'%s' object is not callable" % str(type(key)))
        if ((isinstance(iterable,type(self))
             or isinstance(self,type(iterable)))
            and iterable._key is key):
            self._blist = blist(iterable._blist)
        else:
            self._blist = blist()
            for v in iterable:
                self.add(v)

    def _from_iterable(self, iterable):
        return self.__class__(iterable, self._key)

    def _u2key(self, value):
        "Convert a user-object to the key"
        if self._key is None:
            return value
        else:
            return self._key(value)

    def _u2i(self, value):
        "Convert a user-object to the internal representation"
        if self._key is None:
            return value
        else:
            return (self._key(value), value)

    def _i2u(self, value):
        "Convert an internal object to a user-object"
        if self._key is None:
            return value
        else:
            return value[1]

    def _i2key(self, value):
        "Convert an internal object to the key"
        if self._key is None:
            return value
        else:
            return value[0]

    def _bisect_left(self, v):
        """Locate the point in the list where v would be inserted.

        Returns an (i, value) tuple:
          - i is the position where v would be inserted
          - value is the current user-object at position i

        This is the key function to override in subclasses.  They must
        accept a user-object v and return a user-object value.
        """

        key = self._u2key(v)
        lo = 0
        hi = len(self._blist)
        while lo < hi:
            mid = (lo+hi)//2
            v = self._i2key(self._blist[mid])
            if v < key: lo = mid + 1
            else: hi = mid
        if lo < len(self._blist):
            return lo, self._i2u(self._blist[lo])
        return lo, None

    def _bisect_right(self, v):
        """Same as _bisect_left, but go to the right of equal values"""

        key = self._u2key(v)
        lo = 0
        hi = len(self._blist)
        while lo < hi:
            mid = (lo+hi)//2
            v = self._i2key(self._blist[mid])
            if key < v: hi = mid
            else: lo = mid + 1
        if lo < len(self._blist):
            return lo, self._i2u(self._blist[lo])
        return lo, None

    def bisect_left(self, v):
        """L.bisect_left(v) -> index

        The return value i is such that all e in L[:i] have e < v, and
        all e in a[i:] have e >= v.  So if v already appears in the
        list, i points just before the leftmost v already there.
        """
        return self._bisect_left(v)[0]

    def bisect_right(self, v):
        """L.bisect_right(v) -> index

        Return the index where to insert item v in the list.

        The return value i is such that all e in a[:i] have e <= v,
        and all e in a[i:] have e > v.  So if v already appears in the
        list, i points just beyond the rightmost v already there.
        """
        return self._bisect_right(v)[0]

    bisect = bisect_right

    def add(self, value):
        """Add an element."""
        # Will throw a TypeError when trying to add an object that
        # cannot be compared to objects already in the list.
        i, _ = self._bisect_right(value)
        self._blist.insert(i, self._u2i(value))

    def discard(self, value):
        """Remove an element if it is a member.

        If the element is not a member, do nothing.

        """

        try:
            i, v = self._bisect_left(value)
        except TypeError:
            # Value cannot be compared with values already in the list.
            # Ergo, value isn't in the list.
            return
        i = self._advance(i, value)
        if i >= 0:
            del self._blist[i]

    def __contains__(self, value):
        """x.__contains__(y) <==> y in x"""
        try:
            i, v = self._bisect_left(value)
        except TypeError:
            # Value cannot be compared with values already in the list.
            # Ergo, value isn't in the list.
            return False
        i = self._advance(i, value)
        return i >= 0

    def __len__(self):
        """x.__len__() <==> len(x)"""
        return len(self._blist)

    def __iter__(self):
        """ x.__iter__() <==> iter(x)"""
        return (self._i2u(v) for v in self._blist)

    def __getitem__(self, index):
        """x.__getitem__(y) <==> x[y]"""
        if isinstance(index, slice):
            rv = self.__class__()
            rv._blist = self._blist[index]
            rv._key = self._key
            return rv
        return self._i2u(self._blist[index])

    def _advance(self, i, value):
        "Do a linear search through all items with the same key"
        key = self._u2key(value)
        while i < len(self._blist):
            if self._i2u(self._blist[i]) == value:
                return i
            elif key < self._i2key(self._blist[i]):
                break
            i += 1
        return -1

    def __reversed__(self):
        """L.__reversed__() -- return a reverse iterator over the list"""
        return (self._i2u(v) for v in reversed(self._blist))

    def index(self, value):
        """L.index(value) -> integer -- return first index of value.

        Raises ValueError if the value is not present.

        """

        try:
            i, v = self._bisect_left(value)
        except TypeError:
            raise ValueError
        i = self._advance(i, value)
        if i >= 0:
            return i
        raise ValueError

    def count(self, value):
        """L.count(value) -> integer -- return number of occurrences of value"""
        try:
            i, _ = self._bisect_left(value)
        except TypeError:
            return 0
        key = self._u2key(value)
        count = 0
        while True:
            i = self._advance(i, value)
            if i == -1:
                return count
            count += 1
            i += 1

    def pop(self, index=-1):
        """L.pop([index]) -> item -- remove and return item at index (default last).

        Raises IndexError if list is empty or index is out of range.

        """

        rv = self[index]
        del self[index]
        return rv

    def __delslice__(self, i, j):
        """x.__delslice__(i, j) <==> del x[i:j]

        Use of negative indices is not supported.

        """
        del self._blist[i:j]

    def __delitem__(self, i):
        """x.__delitem__(y) <==> del x[y]"""
        del self._blist[i]

class _weaksortedbase(_sortedbase):
    def _bisect_left(self, value):
        key = self._u2key(value)
        lo = 0
        hi = len(self._blist)
        while lo < hi:
            mid = (lo+hi)//2
            n, v = self._squeeze(mid)
            hi -= n
            if n and hi == len(self._blist):
                continue
            if self._i2key(self._blist[mid]) < key: lo = mid+1
            else: hi = mid
        n, v = self._squeeze(lo)
        return lo, v

    def _bisect_right(self, value):
        key = self._u2key(value)
        lo = 0
        hi = len(self._blist)
        while lo < hi:
            mid = (lo+hi)//2
            n, v = self._squeeze(mid)
            hi -= n
            if n and hi == len(self._blist):
                continue
            if key < self._i2key(self._blist[mid]): hi = mid
            else: lo = mid+1
        n, v = self._squeeze(lo)
        return lo, v

    _bisect = _bisect_right

    def _u2i(self, value):
        if self._key is None:
            return weakref.ref(value)
        else:
            return (self._key(value), weakref.ref(value))

    def _i2u(self, value):
        if self._key is None:
            return value()
        else:
            return value[1]()

    def _i2key(self, value):
        if self._key is None:
            return value()
        else:
            return value[0]

    def __iter__(self):
        """ x.__iter__() <==> iter(x)"""
        i = 0
        while i < len(self._blist):
            n, v = self._squeeze(i)
            if v is None: break
            yield v
            i += 1

    def _squeeze(self, i):
        n = 0
        while i < len(self._blist):
            v = self._i2u(self._blist[i])
            if v is None:
                del self._blist[i]
                n += 1
            else:
                return n, v
        return n, None

    def __getitem__(self, index):
        """x.__getitem__(y) <==> x[y]"""
        if isinstance(index, slice):
            return _sortedbase.__getitem__(self, index)
        n, v = self._squeeze(index)
        if v is None:
            raise IndexError('list index out of range')
        return v

    def __reversed__(self):
        """L.__reversed__() -- return a reverse iterator over the list"""
        i = len(self._blist)-1
        while i >= 0:
            n, v = self._squeeze(i)
            if not n:
                yield v
            i -= 1

    def _advance(self, i, value):
        "Do a linear search through all items with the same key"
        key = self._u2key(value)
        while i < len(self._blist):
            n, v = self._squeeze(i)
            if v is None:
                break
            if v == value:
                return i
            elif key < self._i2key(self._blist[i]):
                break
            i += 1
        return -1

class _listmixin(object):
    def remove(self, value):
        """L.remove(value) -- remove first occurrence of value.

        Raises ValueError if the value is not present.
        """

        del self[self.index(value)]

    def update(self, iterable):
        """L.update(iterable) -- add all elements from iterable into the list"""

        for item in iterable:
            self.add(item)

    def __mul__(self, k):
        if not isinstance(k, int):
            raise TypeError("can't multiply sequence by non-int of type '%s'"
                            % str(type(int)))
        rv = self.__class__()
        rv._key = self._key
        rv._blist = sum((blist([x])*k for x in self._blist), blist())
        return rv
    __rmul__ = __mul__

    def __imul__(self, k):
        if not isinstance(k, int):
            raise TypeError("can't multiply sequence by non-int of type '%s'"
                            % str(type(int)))
        self._blist = sum((blist([x])*k for x in self._blist), blist())
        return self

    def __eq__(self, other):
        """x.__eq__(y) <==> x==y"""
        return self._cmp_op(other, operator.eq)
    def __ne__(self, other):
        """x.__ne__(y) <==> x!=y"""
        return self._cmp_op(other, operator.ne)
    def __lt__(self, other):
        """x.__lt__(y) <==> x<y"""
        return self._cmp_op(other, operator.lt)
    def __gt__(self, other):
        """x.__gt__(y) <==> x>y"""
        return self._cmp_op(other, operator.gt)
    def __le__(self, other):
        """x.__le__(y) <==> x<=y"""
        return self._cmp_op(other, operator.le)
    def __ge__(self, other):
        """x.__ge__(y) <==> x>=y"""
        return self._cmp_op(other, operator.ge)

class _setmixin(object):
    "Methods that override our base class"

    def add(self, value):
        """Add an element to the set.

        This has no effect if the element is already present.

        """
        if value in self: return
        super(_setmixin, self).add(value)

    def __iter__(self):
        it = super(_setmixin, self).__iter__()
        while True:
            item = next(it)
            n = len(self)
            yield item
            if n != len(self):
                raise RuntimeError('Set changed size during iteration')

def safe_cmp(f):
    def g(self, other):
        if not isinstance(other, collections.Set):
            raise TypeError("can only compare to a set")
        return f(self, other)
    return g

class _setmixin2(collections.MutableSet):
    "methods that override or supplement the collections.MutableSet methods"

    __ror__ = collections.MutableSet.__or__
    __rand__ = collections.MutableSet.__and__
    __rxor__ = collections.MutableSet.__xor__

    if sys.version_info[0] < 3: # pragma: no cover
        __lt__ = safe_cmp(collections.MutableSet.__lt__)
        __gt__ = safe_cmp(collections.MutableSet.__gt__)
        __le__ = safe_cmp(collections.MutableSet.__le__)
        __ge__ = safe_cmp(collections.MutableSet.__ge__)

    def __ior__(self, it):
        if self is it:
            return self
        for value in it:
            self.add(value)
        return self

    def __isub__(self, it):
        if self is it:
            self.clear()
            return self
        for value in it:
            self.discard(value)
        return self

    def __ixor__(self, it):
        if self is it:
            self.clear()
            return self
        for value in it:
            if value in self:
                self.discard(value)
            else:
                self.add(value)
        return self

    def __rsub__(self, other):
        return self._from_iterable(other) - self

    def _make_set(self, iterable):
        if isinstance(iterable, collections.Set):
            return iterable
        return self._from_iterable(iterable)

    def difference(self, *args):
        """Return a new set with elements in the set that are not in the others."""
        rv = self.copy()
        rv.difference_update(*args)
        return rv

    def intersection(self, *args):
        """Return a new set with elements common to the set and all others."""
        rv = self.copy()
        rv.intersection_update(*args)
        return rv

    def issubset(self, other):
        """Test whether every element in the set is in *other*."""
        return self <= self._make_set(other)

    def issuperset(self, other):
        """Test whether every element in *other* is in the set."""
        return self >= self._make_set(other)

    def symmetric_difference(self, other):
        """Return a new set with elements in either the set or *other*
        but not both."""

        return self ^ self._make_set(other)

    def union(self, *args):
        """Return the union of sets as a new set.

        (i.e. all elements that are in either set.)

        """
        rv = self.copy()
        for arg in args:
            rv |= self._make_set(arg)
        return rv

    def update(self, *args):
        """Update the set, adding elements from all others."""
        for arg in args:
            self |= self._make_set(arg)

    def difference_update(self, *args):
        """Update the set, removing elements found in others."""
        for arg in args:
            self -= self._make_set(arg)

    def intersection_update(self, *args):
        """Update the set, keeping only elements found in it and all others."""
        for arg in args:
            self &= self._make_set(arg)

    def symmetric_difference_update(self, other):
        """Update the set, keeping only elements found in either set,
        but not in both."""

        self ^= self._make_set(other)

    def clear(self):
        """Remove all elements"""
        del self._blist[:]

    def copy(self):
        return self[:]

class sortedlist(_sortedbase, _listmixin):
    """sortedlist(iterable=(), key=None) -> new sorted list

    Keyword arguments:
    iterable -- items used to initially populate the sorted list
    key -- a function to return the sort key of an item

    A sortedlist is indexable like a list, but always keeps its
    members in sorted order.

    """

    def __repr__(self):
        """x.__repr__() <==> repr(x)"""
        if not self: return 'sortedlist()'
        with ReprRecursion(self) as r:
            if r: return 'sortedlist(...)'
            return ('sortedlist(%s)' % repr(list(self)))

    def _cmp_op(self, other, op):
        if not (isinstance(other,type(self)) or isinstance(self,type(other))):
            return NotImplemented
        if len(self) != len(other):
            if op is operator.eq:
                return False
            if op is operator.ne:
                return True
        for x, y in izip(self, other):
            if x != y:
                return op(x, y)
        return op in (operator.eq, operator.le, operator.ge)

class weaksortedlist(_listmixin, _weaksortedbase):
    """weaksortedlist(iterable=(), key=None) -> new sorted weak list

    Keyword arguments:
    iterable -- items used to initially populate the sorted list
    key -- a function to return the sort key of an item

    A weaksortedlist is indexable like a list, but always keeps its
    items in sorted order.  The weaksortedlist weakly references its
    members, so items will be discarded after there is no longer a
    strong reference to the item.

    """

    def __repr__(self):
        """x.__repr__() <==> repr(x)"""
        if not self: return 'weaksortedlist()'
        with ReprRecursion(self) as r:
            if r: return 'weaksortedlist(...)'
            return 'weaksortedlist(%s)' % repr(list(self))

    def _cmp_op(self, other, op):
        if not (isinstance(other,type(self)) or isinstance(self,type(other))):
            return NotImplemented
        for x, y in izip(self, other):
            if x != y:
                return op(x, y)
        return op in (operator.eq, operator.le, operator.ge)

class sortedset(_setmixin, _sortedbase, _setmixin2):
    """sortedset(iterable=(), key=None) -> new sorted set

    Keyword arguments:
    iterable -- items used to initially populate the sorted set
    key -- a function to return the sort key of an item

    A sortedset is similar to a set but is also indexable like a list.
    Items are maintained in sorted order.

    """

    def __repr__(self):
        """x.__repr__() <==> repr(x)"""
        if not self: return 'sortedset()'
        with ReprRecursion(self) as r:
            if r: return 'sortedset(...)'
            return ('sortedset(%s)' % repr(list(self)))

class weaksortedset(_setmixin, _weaksortedbase, _setmixin2):
    """weaksortedset(iterable=(), key=None) -> new sorted weak set

    Keyword arguments:
    iterable -- items used to initially populate the sorted set
    key -- a function to return the sort key of an item

    A weaksortedset is similar to a set but is also indexable like a
    list.  Items are maintained in sorted order.  The weaksortedset
    weakly references its members, so items will be discarded after
    there is no longer a strong reference to the item.

    """

    def __repr__(self):
        """x.__repr__() <==> repr(x)"""
        if not self: return 'weaksortedset()'
        with ReprRecursion(self) as r:
            if r: return 'weaksortedset(...)'
            return 'weaksortedset(%s)' % repr(list(self))
