/** {@inheritDoc} */
  @Override
  public boolean sendMessageToSubscriber(ProtocolMessage protocolMessage, AndesContent content)
      throws AndesException {

    boolean sendSuccess;

    DeliverableAndesMetadata messageMetadata = protocolMessage.getMessage();

    if (messageMetadata.isRetain()) {
      recordRetainedMessage(messageMetadata.getMessageID());
    }

    // Should get the message from the list
    ByteBuffer message = MQTTUtils.getContentFromMetaInformation(content);
    // Will publish the message to the respective queue
    if (null != mqqtServerChannel) {
      try {

        // TODO:review - instead of getSubscribedDestination() used message destination
        mqqtServerChannel.distributeMessageToSubscriber(
            wildcardDestination,
            message,
            messageMetadata.getMessageID(),
            messageMetadata.getQosLevel(),
            messageMetadata.isPersistent(),
            getMqttSubscriptionID(),
            getSubscriberQOS(),
            messageMetadata);

        // We will indicate the ack to the kernel at this stage
        // For MQTT QOS 0 we do not get ack from subscriber, hence will be implicitly creating an
        // ack
        if (QOSLevel.AT_MOST_ONCE.getValue() == getSubscriberQOS()
            || QOSLevel.AT_MOST_ONCE.getValue() == messageMetadata.getQosLevel()) {
          mqqtServerChannel.implicitAck(messageMetadata.getMessageID(), getChannelID());
        }
        sendSuccess = true;
      } catch (MQTTException e) {
        final String error =
            "Error occurred while delivering message to the subscriber for message :"
                + messageMetadata.getMessageID();
        log.error(error, e);
        throw new AndesException(error, e);
      }
    } else {
      sendSuccess = false;
    }

    return sendSuccess;
  }
  /** {@inheritDoc} */
  @Override
  public int deliverMessageToSubscriptions(MessageDeliveryInfo messageDeliveryInfo)
      throws AndesException {

    Set<DeliverableAndesMetadata> messages = messageDeliveryInfo.getReadButUndeliveredMessages();
    int sentMessageCount = 0;
    Iterator<DeliverableAndesMetadata> iterator = messages.iterator();
    List<DeliverableAndesMetadata> droppedTopicMessagesList = new ArrayList<>();

    /**
     * get all relevant type of subscriptions. This call does NOT return hierarchical subscriptions
     * for the destination. There are duplicated messages for each different subscribed destination.
     * For durable topic subscriptions this should return queue subscription bound to unique queue
     * based on subscription id
     */
    Collection<LocalSubscription> subscriptions =
        subscriptionEngine.getActiveLocalSubscribers(
            messageDeliveryInfo.getDestination(),
            messageDeliveryInfo.getProtocolType(),
            messageDeliveryInfo.getDestinationType());

    while (iterator.hasNext()) {

      try {
        DeliverableAndesMetadata message = iterator.next();

        // All subscription filtering logic for topics goes here
        List<LocalSubscription> subscriptionsToDeliver = new ArrayList<>();

        for (LocalSubscription subscription : subscriptions) {

          /*
           * If this is a topic message, remove all durable topic subscriptions here
           * because durable topic subscriptions will get messages via queue path.
           * Also need to consider the arrival time of the message. Only topic
           * subscribers which appeared before publishing this message should receive it
           */
          if (subscription.isDurable()
              || (subscription.getSubscribeTime() > message.getArrivalTime())) {
            continue;
          }

          // Avoid sending if the selector of subscriber does not match
          if (!subscription.isMessageAcceptedBySelector(message)) {
            continue;
          }

          subscriptionsToDeliver.add(subscription);
        }

        if (subscriptionsToDeliver.size() == 0) {
          iterator.remove(); // remove buffer
          droppedTopicMessagesList.add(message);

          continue; // skip this iteration if no subscriptions for the message
        }

        /**
         * For normal non-durable topic we pre evaluate room for all subscribers and if all subs has
         * room to accept messages we send them. This means we operate to the speed of slowest
         * subscriber (to prevent OOM). If it is too slow to make others fast, make that topic
         * subscriber a durable topic subscriber.
         */
        boolean allTopicSubscriptionsHasRoom = true;
        for (LocalSubscription subscription : subscriptionsToDeliver) {
          if (!subscription.hasRoomToAcceptMessages()) {
            allTopicSubscriptionsHasRoom = false;
            break;
          }
        }
        if (allTopicSubscriptionsHasRoom) {

          message.markAsScheduledToDeliver(subscriptionsToDeliver);

          // schedule message to all subscribers
          for (LocalSubscription localSubscription : subscriptionsToDeliver) {
            MessageFlusher.getInstance().deliverMessageAsynchronously(localSubscription, message);
          }
          iterator.remove();
          if (log.isDebugEnabled()) {
            log.debug(
                "Removing Scheduled to send message from buffer. MsgId= " + message.getMessageID());
          }
          sentMessageCount++;
        } else {
          if (log.isDebugEnabled()) {
            log.debug(
                "Some subscriptions for destination "
                    + messageDeliveryInfo.getDestination()
                    + " have max unacked messages "
                    + message.getDestination());
          }
          // if we continue message order will break
          break;
        }

      } catch (NoSuchElementException ex) {
        // This exception can occur because the iterator of ConcurrentSkipListSet loads the
        // at-the-time snapshot.
        // Some records could be deleted by the time the iterator reaches them.
        // However, this can only happen at the tail of the collection, not in middle, and it would
        // cause the loop
        // to blindly check for a batch of deleted records.
        // Given this situation, this loop should break so the sendFlusher can re-trigger it.
        // for tracing purposes can use this : log.warn("NoSuchElementException thrown",ex);
        log.warn("NoSuchElementException thrown. ", ex);
        break;
      }
    }

    /**
     * Here we do not need to have orphaned slot scenario (return slot). If there are no subscribers
     * slot will be consumed and metadata will be removed. We duplicate topic messages per node
     */

    /**
     * delete topic messages that were dropped due to no subscriptions for the message and due to
     * has no room to enqueue the message. Delete call is blocking and then slot message count is
     * dropped in order
     */
    MessagingEngine.getInstance().deleteMessages(droppedTopicMessagesList);

    return sentMessageCount;
  }