# -*- coding: utf-8 -*-
# Copyright 2018 gevent contributors. See LICENSE for details.
# cython: auto_pickle=False,embedsignature=True,always_allow_keywords=False

from __future__ import absolute_import
from __future__ import division
from __future__ import print_function


from weakref import WeakKeyDictionary
from weakref import ref

from heapq import heappop
from heapq import heappush

__all__ = [
    'IdentRegistry',
]

class ValuedWeakRef(ref):
    """
    A weak ref with an associated value.
    """
    # This seems entirely spurious; even on Python 2.7
    # weakref.ref descends from object
    # pylint: disable=slots-on-old-class
    __slots__ = ('value',)


class IdentRegistry(object):
    """
    Maintains a unique mapping of (small) positive integer identifiers
    to objects that can be weakly referenced.

    It is guaranteed that no two objects will have the the same
    identifier at the same time, as long as those objects are
    also uniquely hashable.
    """

    def __init__(self):
        # {obj -> (ident, wref(obj))}
        self._registry = WeakKeyDictionary()

        # A heap of numbers that have been used and returned
        self._available_idents = []

    def get_ident(self, obj):
        """
        Retrieve the identifier for *obj*, creating one
        if necessary.
        """

        try:
            return self._registry[obj][0]
        except KeyError:
            pass

        if self._available_idents:
            # Take the smallest free number
            ident = heappop(self._available_idents)
        else:
            # Allocate a bigger one
            ident = len(self._registry)

        vref = ValuedWeakRef(obj, self._return_ident)
        vref.value = ident # pylint:disable=assigning-non-slot,attribute-defined-outside-init
        self._registry[obj] = (ident, vref)
        return ident

    def _return_ident(self, vref):
        # By the time this is called, self._registry has been
        # updated
        if heappush is not None:
            # Under some circumstances we can get called
            # when the interpreter is shutting down, and globals
            # aren't available any more.
            heappush(self._available_idents, vref.value)

    def __len__(self):
        return len(self._registry)


from gevent._util import import_c_accel
import_c_accel(globals(), 'gevent.__ident')
