"""Party

:Maintainer: Ben Bangert <ben@groovie.org>
:Status: Production

A Zookeeper pool of party members. The :class:`Party` object can be
used for determining members of a party.

"""
import uuid

from kazoo.exceptions import NodeExistsError, NoNodeError


class BaseParty(object):
    """Base implementation of a party."""

    def __init__(self, client, path, identifier=None):
        """
        :param client: A :class:`~kazoo.client.KazooClient` instance.
        :param path: The party path to use.
        :param identifier: An identifier to use for this member of the
                           party when participating.

        """
        self.client = client
        self.path = path
        self.data = str(identifier or "").encode("utf-8")
        self.ensured_path = False
        self.participating = False

    def _ensure_parent(self):
        if not self.ensured_path:
            # make sure our parent node exists
            self.client.ensure_path(self.path)
            self.ensured_path = True

    def join(self):
        """Join the party"""
        return self.client.retry(self._inner_join)

    def _inner_join(self):
        self._ensure_parent()
        try:
            self.client.create(self.create_path, self.data, ephemeral=True)
            self.participating = True
        except NodeExistsError:
            # node was already created, perhaps we are recovering from a
            # suspended connection
            self.participating = True

    def leave(self):
        """Leave the party"""
        self.participating = False
        return self.client.retry(self._inner_leave)

    def _inner_leave(self):
        try:
            self.client.delete(self.create_path)
        except NoNodeError:
            return False
        return True

    def __len__(self):
        """Return a count of participating clients"""
        self._ensure_parent()
        return len(self._get_children())

    def _get_children(self):
        return self.client.retry(self.client.get_children, self.path)


class Party(BaseParty):
    """Simple pool of participating processes"""

    _NODE_NAME = "__party__"

    def __init__(self, client, path, identifier=None):
        BaseParty.__init__(self, client, path, identifier=identifier)
        self.node = uuid.uuid4().hex + self._NODE_NAME
        self.create_path = self.path + "/" + self.node

    def __iter__(self):
        """Get a list of participating clients' data values"""
        self._ensure_parent()
        children = self._get_children()
        for child in children:
            try:
                d, _ = self.client.retry(
                    self.client.get, self.path + "/" + child
                )
                yield d.decode("utf-8")
            except NoNodeError:  # pragma: nocover
                pass

    def _get_children(self):
        children = BaseParty._get_children(self)
        return [c for c in children if self._NODE_NAME in c]


class ShallowParty(BaseParty):
    """Simple shallow pool of participating processes

    This differs from the :class:`Party` as the identifier is used in
    the name of the party node itself, rather than the data. This
    places some restrictions on the length as it must be a valid
    Zookeeper node (an alphanumeric string), but reduces the overhead
    of getting a list of participants to a single Zookeeper call.

    """

    def __init__(self, client, path, identifier=None):
        BaseParty.__init__(self, client, path, identifier=identifier)
        self.node = "-".join([uuid.uuid4().hex, self.data.decode("utf-8")])
        self.create_path = self.path + "/" + self.node

    def __iter__(self):
        """Get a list of participating clients' identifiers"""
        self._ensure_parent()
        children = self._get_children()
        for child in children:
            yield child[child.find("-") + 1 :]
