import threading

from hazelcast.proxy.base import Proxy
from hazelcast.util import AtomicInteger

BLOCK_SIZE = 10000


class IdGenerator(Proxy):
    """
    The IdGenerator is responsible for creating unique ids (a long) in a cluster. In theory, an
    AtomicLong.increment_and_get() could be used to provide the same functionality. The big difference is that the
    increment_and_get() requires one or more remote calls for every invocation which cause a performance and
    scalability bottleneck. The IdGenerator uses an AtomicLong under the hood, but instead of doing remote call for
    every call to new_id(), it does it less frequently. It checks out a chunk, e.g. 1..1000 and as long as it has not
    yet consumed all the ids in its chunk, then no remote call is done. IDs generated by different cluster members may
    get out of order because each member will get its own chunk. It can be that member 1 has chunk 1..1000 and member
    2 has 1001..2000. Therefore, member 2 will automatically have ids that are out of order with the ids generated by
    member 1.
    """
    def __init__(self, client, service_name, name, atomic_long):
        super(IdGenerator, self).__init__(client, service_name, name)
        self._atomic_long = atomic_long
        self._residue = BLOCK_SIZE
        self._local = -1
        self._lock = threading.RLock()

    def _on_destroy(self):
        self._atomic_long.destroy()

    def init(self, initial):
        """
        Try to initialize this IdGenerator instance with the given id. The first generated id will be 1 greater than id.

        :param initial: (long), the given id.
        :return: (bool), ``true`` if initialization succeeded, ``false`` if id is less than 0.
        """
        if initial <= 0:
            return False
        step = initial // BLOCK_SIZE
        with self._lock:
            init = self._atomic_long.compare_and_set(0, step + 1).result()
            if init:
                self._local = step
                self._residue = (initial % BLOCK_SIZE) + 1
            return init

    def new_id(self):
        """
        Generates and returns a cluster-wide unique id. Generated ids are guaranteed to be unique for the entire cluster
        as long as the cluster is live. If the cluster restarts, then id generation will start from 0.

        :return: (long), cluster-wide new unique id.
        """
        with self._lock:
            curr = self._residue
            self._residue += 1
            if self._residue >= BLOCK_SIZE:
                increment = self._atomic_long.get_and_increment().result()
                self._local = increment
                self._residue = 0
                return self.new_id()
            return self._local * BLOCK_SIZE + curr

