/// <summary>
        /// TBD
        /// </summary>
        /// <param name="settings">TBD</param>
        /// <exception cref="ArgumentException">TBD</exception>
        /// <returns>TBD</returns>
        public DistributedPubSubMediator(DistributedPubSubSettings settings)
        {
            if (settings.RoutingLogic is ConsistentHashingRoutingLogic)
            {
                throw new ArgumentException("Consistent hashing routing logic cannot be used by the pub-sub mediator");
            }

            _settings = settings;

            if (!string.IsNullOrEmpty(_settings.Role) && !_cluster.SelfRoles.Contains(_settings.Role))
            {
                throw new ArgumentException($"The cluster member [{_cluster.SelfAddress}] doesn't have the role [{_settings.Role}]");
            }

            //Start periodic gossip to random nodes in cluster
            _gossipCancelable = Context.System.Scheduler.ScheduleTellRepeatedlyCancelable(_settings.GossipInterval, _settings.GossipInterval, Self, GossipTick.Instance, Self);
            _pruneInterval    = new TimeSpan(_settings.RemovedTimeToLive.Ticks / 2);
            _pruneCancelable  = Context.System.Scheduler.ScheduleTellRepeatedlyCancelable(_pruneInterval, _pruneInterval, Self, Prune.Instance, Self);
            _buffer           = new PerGroupingBuffer();

            Receive <Send>(send =>
            {
                var routees = new List <Routee>();
                ValueHolder valueHolder;
                if (_registry.TryGetValue(_cluster.SelfAddress, out var bucket) &&
                    bucket.Content.TryGetValue(send.Path, out valueHolder) &&
                    send.LocalAffinity)
                {
                    var routee = valueHolder.Routee;
                    if (routee != null)
                    {
                        routees.Add(routee);
                    }
                }
                else
                {
                    foreach (var entry in _registry)
                    {
                        if (entry.Value.Content.TryGetValue(send.Path, out valueHolder))
                        {
                            var routee = valueHolder.Routee;
                            if (routee != null)
                            {
                                routees.Add(routee);
                            }
                        }
                    }
                }

                if (routees.Count != 0)
                {
                    new Router(_settings.RoutingLogic, routees.ToArray()).Route(
                        Internal.Utils.WrapIfNeeded(send.Message), Sender);
                }
                else
                {
                    IgnoreOrSendToDeadLetters(send.Message);
                }
            });
            Receive <SendToAll>(sendToAll =>
            {
                PublishMessage(sendToAll.Path, sendToAll.Message, sendToAll.ExcludeSelf);
            });
            Receive <Publish>(publish =>
            {
                string path = Internal.Utils.MakeKey(Self.Path / Internal.Utils.EncodeName(publish.Topic));
                if (publish.SendOneMessageToEachGroup)
                {
                    PublishToEachGroup(path, publish.Message);
                }
                else
                {
                    PublishMessage(path, publish.Message);
                }
            });
            Receive <Put>(put =>
            {
                if (put.Ref.Path.Address.HasGlobalScope)
                {
                    Log.Warning("Registered actor must be local: [{0}]", put.Ref);
                }
                else
                {
                    PutToRegistry(Internal.Utils.MakeKey(put.Ref), put.Ref);
                    Context.Watch(put.Ref);
                }
            });
            Receive <Remove>(remove =>
            {
                if (_registry.TryGetValue(_cluster.SelfAddress, out var bucket))
                {
                    if (bucket.Content.TryGetValue(remove.Path, out var valueHolder) && !valueHolder.Ref.IsNobody())
                    {
                        Context.Unwatch(valueHolder.Ref);
                        PutToRegistry(remove.Path, null);
                    }
                }
            });
            Receive <Subscribe>(subscribe =>
            {
                // each topic is managed by a child actor with the same name as the topic
                var encodedTopic = Internal.Utils.EncodeName(subscribe.Topic);

                _buffer.BufferOr(Internal.Utils.MakeKey(Self.Path / encodedTopic), subscribe, Sender, () =>
                {
                    var child = Context.Child(encodedTopic);
                    if (!child.IsNobody())
                    {
                        child.Forward(subscribe);
                    }
                    else
                    {
                        NewTopicActor(encodedTopic).Forward(subscribe);
                    }
                });
            });
            Receive <RegisterTopic>(register =>
            {
                HandleRegisterTopic(register.TopicRef);
            });
            Receive <NoMoreSubscribers>(msg =>
            {
                var key = Internal.Utils.MakeKey(Sender);
                _buffer.InitializeGrouping(key);
                Sender.Tell(TerminateRequest.Instance);
            });
            Receive <NewSubscriberArrived>(msg =>
            {
                var key = Internal.Utils.MakeKey(Sender);
                _buffer.ForwardMessages(key, Sender);
            });
            Receive <GetTopics>(getTopics =>
            {
                Sender.Tell(new CurrentTopics(GetCurrentTopics().ToImmutableHashSet()));
            });
            Receive <Subscribed>(subscribed =>
            {
                subscribed.Subscriber.Tell(subscribed.Ack);
            });
            Receive <Unsubscribe>(unsubscribe =>
            {
                var encodedTopic = Internal.Utils.EncodeName(unsubscribe.Topic);

                _buffer.BufferOr(Internal.Utils.MakeKey(Self.Path / encodedTopic), unsubscribe, Sender, () =>
                {
                    var child = Context.Child(encodedTopic);
                    if (!child.IsNobody())
                    {
                        child.Forward(unsubscribe);
                    }
                    else
                    {
                        // no such topic here
                    }
                });
            });
            Receive <Unsubscribed>(unsubscribed =>
            {
                unsubscribed.Subscriber.Tell(unsubscribed.Ack);
            });
            Receive <Status>(status =>
            {
                // only accept status from known nodes, otherwise old cluster with same address may interact
                // also accept from local for testing purposes
                if (_nodes.Contains(Sender.Path.Address) || Sender.Path.Address.HasLocalScope)
                {
                    // gossip chat starts with a Status message, containing the bucket versions of the other node
                    var delta = CollectDelta(status.Versions).ToImmutableList();
                    if (delta.Count != 0)
                    {
                        Sender.Tell(new Delta(delta));
                    }

                    if (!status.IsReplyToStatus && OtherHasNewerVersions(status.Versions))
                    {
                        Sender.Tell(new Status(versions: OwnVersions, isReplyToStatus: true)); // it will reply with Delta
                    }
                }
            });
            Receive <Delta>(delta =>
            {
                deltaCount += 1;

                // reply from Status message in the gossip chat
                // the Delta contains potential updates (newer versions) from the other node
                // only accept deltas/buckets from known nodes, otherwise there is a risk of
                // adding back entries when nodes are removed
                if (_nodes.Contains(Sender.Path.Address))
                {
                    foreach (var bucket in delta.Buckets)
                    {
                        if (_nodes.Contains(bucket.Owner))
                        {
                            if (!_registry.TryGetValue(bucket.Owner, out var myBucket))
                            {
                                myBucket = new Bucket(bucket.Owner);
                            }

                            if (bucket.Version > myBucket.Version)
                            {
                                _registry[bucket.Owner] = new Bucket(myBucket.Owner, bucket.Version, myBucket.Content.SetItems(bucket.Content));
                            }
                        }
                    }
                }
            });
            Receive <GossipTick>(_ => HandleGossip());
            Receive <Prune>(_ => HandlePrune());
            Receive <Terminated>(terminated =>
            {
                var key = Internal.Utils.MakeKey(terminated.ActorRef);

                if (_registry.TryGetValue(_cluster.SelfAddress, out var bucket))
                {
                    if (bucket.Content.TryGetValue(key, out var holder) && terminated.ActorRef.Equals(holder.Ref))
                    {
                        PutToRegistry(key, null); // remove
                    }
                }
                _buffer.RecreateAndForwardMessagesIfNeeded(key, () => NewTopicActor(terminated.ActorRef.Path.Name));
            });
            Receive <ClusterEvent.CurrentClusterState>(state =>
            {
                var nodes = state.Members
                            .Where(m => m.Status != MemberStatus.Joining && IsMatchingRole(m))
                            .Select(m => m.Address);

                _nodes = new HashSet <Address>(nodes);
            });
            Receive <ClusterEvent.MemberUp>(up =>
            {
                if (IsMatchingRole(up.Member))
                {
                    _nodes.Add(up.Member.Address);
                }
            });
            Receive <ClusterEvent.MemberWeaklyUp>(weaklyUp =>
            {
                if (IsMatchingRole(weaklyUp.Member))
                {
                    _nodes.Add(weaklyUp.Member.Address);
                }
            });
            Receive <ClusterEvent.MemberLeft>(left =>
            {
                if (IsMatchingRole(left.Member))
                {
                    _nodes.Remove(left.Member.Address);
                    _registry.Remove(left.Member.Address);
                }
            });
            Receive <ClusterEvent.MemberDowned>(downed =>
            {
                if (IsMatchingRole(downed.Member))
                {
                    _nodes.Remove(downed.Member.Address);
                    _registry.Remove(downed.Member.Address);
                }
            });
            Receive <ClusterEvent.MemberRemoved>(removed =>
            {
                var member = removed.Member;
                if (member.Address == _cluster.SelfAddress)
                {
                    Context.Stop(Self);
                }
                else if (IsMatchingRole(member))
                {
                    _nodes.Remove(member.Address);
                    _registry.Remove(member.Address);
                }
            });
            Receive <ClusterEvent.IMemberEvent>(_ => { /* ignore */ });
            Receive <Count>(_ =>
            {
                var count = _registry.Sum(entry => entry.Value.Content.Count(kv => !kv.Value.Ref.IsNobody()));
                Sender.Tell(count);
            });
            Receive <DeltaCount>(_ =>
            {
                Sender.Tell(deltaCount);
            });
            Receive <CountSubscribers>(msg =>
            {
                var encTopic = Internal.Utils.EncodeName(msg.Topic);
                _buffer.BufferOr(Internal.Utils.MakeKey(Self.Path / encTopic), msg, Sender, () =>
                {
                    var child = Context.Child(encTopic);
                    if (!child.IsNobody())
                    {
                        child.Tell(Count.Instance, Sender);
                    }
                    else
                    {
                        Sender.Tell(0);
                    }
                });
            });
        }
        public DistributedPubSubMediator(DistributedPubSubSettings settings)
        {
            if (settings.RoutingLogic is ConsistentHashingRoutingLogic)
                throw new ArgumentException("Consistent hashing routing logic cannot be used by the pub-sub mediator");

            _settings = settings;

            if (!string.IsNullOrEmpty(_settings.Role) && !_cluster.SelfRoles.Contains(_settings.Role))
                throw new ArgumentException(string.Format("The cluster member [{0}] doesn't have the role [{1}]", _cluster.SelfAddress, _settings.Role));

            //Start periodic gossip to random nodes in cluster
            _gossipCancelable = Context.System.Scheduler.ScheduleTellRepeatedlyCancelable(_settings.GossipInterval, _settings.GossipInterval, Self, GossipTick.Instance, Self);
            _pruneInterval = new TimeSpan(_settings.RemovedTimeToLive.Ticks / 2);
            _pruneCancelable = Context.System.Scheduler.ScheduleTellRepeatedlyCancelable(_pruneInterval, _pruneInterval, Self, Prune.Instance, Self);
            _buffer = new PerGroupingBuffer();

            Receive<Send>(send =>
            {
                var routees = new List<Routee>();

                Bucket bucket;
                ValueHolder valueHolder;
                if (_registry.TryGetValue(_cluster.SelfAddress, out bucket) && bucket.Content.TryGetValue(send.Path, out valueHolder) && send.LocalAffinity)
                {
                    var routee = valueHolder.Routee;
                    if (routee != null) routees.Add(routee);
                }
                else
                {
                    foreach (var entry in _registry)
                    {
                        if (entry.Value.Content.TryGetValue(send.Path, out valueHolder))
                        {
                            var routee = valueHolder.Routee;
                            if (routee != null) routees.Add(routee);
                        }
                    }
                }

                if (routees.Count != 0)
                {
                    new Router(_settings.RoutingLogic, routees.ToArray()).Route(
                        Internal.Utils.WrapIfNeeded(send.Message), Sender);
                }
                else
                {
                    SendToDeadLetters(send.Message);
                }
            });
            Receive<SendToAll>(sendToAll =>
            {
                PublishMessage(sendToAll.Path, sendToAll.Message, sendToAll.ExcludeSelf);
            });
            Receive<Publish>(publish =>
            {
                string path = Internal.Utils.MakeKey(Self.Path / Internal.Utils.EncodeName(publish.Topic));
                if (publish.SendOneMessageToEachGroup)
                    PublishToEachGroup(path, publish.Message);
                else
                    PublishMessage(path, publish.Message);
            });
            Receive<Put>(put =>
            {
                if (put.Ref.Path.Address.HasGlobalScope)
                {
                    Log.Warning("Registered actor must be local: [{0}]", put.Ref);
                }
                else
                {
                    PutToRegistry(Internal.Utils.MakeKey(put.Ref), put.Ref);
                    Context.Watch(put.Ref);
                }
            });
            Receive<Remove>(remove =>
            {
                Bucket bucket;
                if (_registry.TryGetValue(_cluster.SelfAddress, out bucket))
                {
                    ValueHolder valueHolder;
                    if (bucket.Content.TryGetValue(remove.Path, out valueHolder) && valueHolder.Ref != null)
                    {
                        Context.Unwatch(valueHolder.Ref);
                        PutToRegistry(remove.Path, null);
                    }
                }
            });
            Receive<Subscribe>(subscribe =>
            {
                // each topic is managed by a child actor with the same name as the topic
                var encodedTopic = Internal.Utils.EncodeName(subscribe.Topic);

                _buffer.BufferOr(Internal.Utils.MakeKey(Self.Path / encodedTopic), subscribe, Sender, () =>
                {
                    var child = Context.Child(encodedTopic);
                    if (!child.IsNobody())
                    {
                        child.Forward(subscribe);
                    }
                    else
                    {
                        NewTopicActor(encodedTopic).Forward(subscribe);
                    }
                });
            });
            Receive<RegisterTopic>(register =>
            {
                HandleRegisterTopic(register.TopicRef);
            });
            Receive<NoMoreSubscribers>(msg =>
            {
                var key = Internal.Utils.MakeKey(Sender);
                _buffer.InitializeGrouping(key);
                Sender.Tell(TerminateRequest.Instance);
            });
            Receive<NewSubscriberArrived>(msg =>
            {
                var key = Internal.Utils.MakeKey(Sender);
                _buffer.ForwardMessages(key, Sender);
            });
            Receive<GetTopics>(getTopics =>
            {
                Sender.Tell(new CurrentTopics(GetCurrentTopics().ToArray()));
            });
            Receive<Subscribed>(subscribed =>
            {
                subscribed.Subscriber.Tell(subscribed.Ack);
            });
            Receive<Unsubscribe>(unsubscribe =>
            {
                var encodedTopic = Internal.Utils.EncodeName(unsubscribe.Topic);

                _buffer.BufferOr(Internal.Utils.MakeKey(Self.Path / encodedTopic), unsubscribe, Sender, () =>
                {
                    var child = Context.Child(encodedTopic);
                    if (!child.IsNobody())
                    {
                        child.Forward(unsubscribe);
                    }
                    else
                    {
                        // no such topic here
                    }
                });
            });
            Receive<Unsubscribed>(unsubscribed =>
            {
                unsubscribed.Subscriber.Tell(unsubscribed.Ack);
            });
            Receive<Status>(status =>
            {
                // only accept status from known nodes, otherwise old cluster with same address may interact
                // also accept from local for testing purposes
                if (_nodes.Contains(Sender.Path.Address) || Sender.Path.Address.HasLocalScope)
                {
                    // gossip chat starts with a Status message, containing the bucket versions of the other node
                    var delta = CollectDelta(status.Versions).ToArray();
                    if (delta.Length != 0)
                        Sender.Tell(new Delta(delta));

                    if (!status.IsReplyToStatus && OtherHasNewerVersions(status.Versions))
                        Sender.Tell(new Status(versions: OwnVersions, isReplyToStatus: true)); // it will reply with Delta
                }
            });
            Receive<Delta>(delta =>
            {
                deltaCount += 1;

                // reply from Status message in the gossip chat
                // the Delta contains potential updates (newer versions) from the other node
                // only accept deltas/buckets from known nodes, otherwise there is a risk of
                // adding back entries when nodes are removed
                if (_nodes.Contains(Sender.Path.Address))
                {
                    foreach (var bucket in delta.Buckets)
                    {
                        if (_nodes.Contains(bucket.Owner))
                        {
                            Bucket myBucket;
                            if (!_registry.TryGetValue(bucket.Owner, out myBucket))
                                myBucket = new Bucket(bucket.Owner);

                            if (bucket.Version > myBucket.Version)
                            {
                                _registry[bucket.Owner] = new Bucket(myBucket.Owner, bucket.Version, myBucket.Content.SetItems(bucket.Content));
                            }
                        }
                    }
                }
            });
            Receive<GossipTick>(_ => HandleGossip());
            Receive<Prune>(_ => HandlePrune());
            Receive<Terminated>(terminated =>
            {
                var key = Internal.Utils.MakeKey(terminated.ActorRef);

                Bucket bucket;
                if (_registry.TryGetValue(_cluster.SelfAddress, out bucket))
                {
                    ValueHolder holder;
                    if (bucket.Content.TryGetValue(key, out holder) && terminated.ActorRef.Equals(holder.Ref))
                    {
                        PutToRegistry(key, null); // remove
                    }
                }
                _buffer.RecreateAndForwardMessagesIfNeeded(key, () => NewTopicActor(terminated.ActorRef.Path.Name));
            });
            Receive<ClusterEvent.CurrentClusterState>(state =>
            {
                var nodes = state.Members
                    .Where(m => m.Status != MemberStatus.Joining && IsMatchingRole(m))
                    .Select(m => m.Address);

                _nodes = new HashSet<Address>(nodes);
            });
            Receive<ClusterEvent.MemberUp>(up =>
            {
                if (IsMatchingRole(up.Member)) _nodes.Add(up.Member.Address);
            });
            Receive<ClusterEvent.MemberLeft>(left =>
            {
                if (IsMatchingRole(left.Member))
                {
                    _nodes.Remove(left.Member.Address);
                    _registry.Remove(left.Member.Address);
                }
            });
            Receive<ClusterEvent.MemberRemoved>(removed =>
            {
                var member = removed.Member;
                if (member.Address == _cluster.SelfAddress)
                {
                    Context.Stop(Self);
                }
                else if (IsMatchingRole(member))
                {
                    _nodes.Remove(member.Address);
                    _registry.Remove(member.Address);
                }
            });
            Receive<ClusterEvent.IMemberEvent>(_ => { /* ignore */ });
            Receive<Count>(_ =>
            {
                var count = _registry.Sum(entry => entry.Value.Content.Count(kv => kv.Value.Ref != null));
                Sender.Tell(count);
            });
            Receive<DeltaCount>(_ =>
            {
                Sender.Tell(deltaCount);
            });
        }