/**
   * Remove all references to the channel. 1) the map of a channel (socket) to its interests. 2) the
   * map of interests to its channels.
   *
   * <p>As each interest is removed inform all channels having a similar interest.
   *
   * @param sourceCtx
   */
  public void unregister(final ChannelHandlerContext sourceCtx) {
    final SocketAddress key = sourceCtx.channel().remoteAddress();
    majorLogger.info("unregister channel {}", key);
    final Interest.ChannelInterestSet channelInterestSet = this.channelInterestMap.remove(key);
    if (channelInterestSet == null) {
      majorLogger.warn(
          "nothing to unregister in interest map: {}", this.channelInterestMap.keySet());
      return;
    }
    final String trackingGuid = UUID.randomUUID().toString();
    majorLogger.warn("no interested channel set");

    for (final Interest interest : channelInterestSet.getInterestList()) {
      if (!this.interestedChannelMap.containsKey(interest)) {
        continue;
      }

      final Interest.InterestedChannelSet interestedChannelSet =
          this.interestedChannelMap.get(interest);
      interestedChannelSet.removeItem(sourceCtx, interest);
      majorLogger.info("unregistered interest {}", interest);
      // this.interestedChannelMap.put(interest, interestedChannelSet);

      // an implicit DISINTEREST message will be sent to all interested channels

      final MetaLinkMsg.Edit.Builder editBuilder =
          MetaLinkMsg.Edit.newBuilder()
              .addMode(MetaLinkMsg.Edit.EditMode.DISINTEREST)
              .setEditMode(MetaLinkMsg.Edit.EditMode.DISINTEREST)
              .setGuid(trackingGuid)
              .setSequence(sequenceNumber.incrementAndGet())
              .addAllTopic(interest.getTopic())
              .addOrigin(sourceCtx.channel().remoteAddress().toString())
              .addOrigin(METALINK_BRIDGE_NAME);
      final MetaLinkMsg.Edit disinterestMsg = editBuilder.build();

      majorLogger.trace("expressing disinterest to all channels except source");
      for (final Map.Entry<SocketAddress, Interest.ChannelInterestSet> entry :
          this.channelInterestMap.entrySet()) {
        final ChannelHandlerContext ctx = entry.getValue().context;
        if (isSameChannel(ctx, sourceCtx)) {
          continue;
        }
        majorLogger.trace("disinterest {} to {}", trackingGuid, ctx.channel().remoteAddress());
        ctx.write(disinterestMsg);
        ctx.flush();
        detailLogger.info("message sent:\n{}", disinterestMsg);
      }
    }
  }
  /**
   * Send the data to all channels except the incoming channel.
   *
   * <p>Write the message to the recorder file for later study and playback. This involves
   * converting the binary payload to a textual representation.
   *
   * <p>If the inbound message is an INTEREST message then it is not sent. Instead it is responded
   * to in a way indicated by the ?.
   *
   * @param sourceCtx
   * @param originalMsg
   */
  public void distribute(ChannelHandlerContext sourceCtx, final MetaLinkMsg.Edit originalMsg) {
    majorLogger.trace(
        "incoming message to distribute(): {}\n {}",
        sourceCtx.channel().remoteAddress(),
        originalMsg);
    if (originalMsg == null) {
      majorLogger.warn("attempt to distribute null message");
      return;
    }
    try {
      final SyntheticData.Recorder recorder = this.recorder.get();

      final MetaLinkMsg.Edit.Builder editBuilder = originalMsg.toBuilder();
      final MetaLinkMsg.Edit msg;
      if (originalMsg.getModeCount() < 1) {
        if (originalMsg.hasEditMode()) {
          editBuilder.addMode(originalMsg.getEditMode());
          msg = editBuilder.build();
        } else {
          majorLogger.warn("no edit mode provided, assuming POST");
          editBuilder.addMode(MetaLinkMsg.Edit.EditMode.POST);
          editBuilder.setEditMode(MetaLinkMsg.Edit.EditMode.POST);
          msg = editBuilder.build();
        }
      } else {
        if (originalMsg.hasEditMode()) {
          if (originalMsg.getEditMode() == originalMsg.getMode(originalMsg.getModeCount() - 1)) {
            msg = editBuilder.build();
          } else {
            majorLogger.info("edit mode mismatch, set the last mode");
            editBuilder.addMode(originalMsg.getEditMode());
            msg = editBuilder.build();
          }
        } else {
          majorLogger.info("edit mode provided, using the last mode");
          editBuilder.setEditMode(originalMsg.getMode(originalMsg.getModeCount() - 1));
          msg = editBuilder.build();
        }
      }

      if (null != recorder) {
        recorder.record(sourceCtx, msg);
      }
      final String trackingGuid =
          msg.hasGuid() ? msg.getGuid().toString() : UUID.randomUUID().toString();
      final String origin = (msg.getOriginCount() > 0) ? msg.getOrigin(0) : "";

      switch (msg.getEditMode()) {
        case NOTICE:
          {
            final Interest topic = new Interest(msg.getTopicList());
            final Interest.InterestedChannelSet channelSet = this.interestedChannelMap.get(topic);
            if (channelSet == null) {
              majorLogger.warn("no interested channel set");
              editBuilder
                  .addMode(MetaLinkMsg.Edit.EditMode.NOTICE)
                  .setEditMode(MetaLinkMsg.Edit.EditMode.NOTICE)
                  .addOrigin(METALINK_BRIDGE_NAME)
                  .setSequence(sequenceNumber.incrementAndGet());

              final MetaLinkMsg.Notice.Builder noticeBuilder =
                  MetaLinkMsg.Notice.newBuilder()
                      .setNoticeMode(MetaLinkMsg.Notice.NoticeMode.ACK)
                      .setCode(NO_INTEREST_PRESENT)
                      .setMsg("no interest in topic")
                      .setId(msg.getGuid());
              editBuilder.addNotices(noticeBuilder);

              final MetaLinkMsg.Edit updatedEdit = editBuilder.build();
              majorLogger.trace(
                  "notice no-interest notice {} to {}",
                  trackingGuid,
                  sourceCtx.channel().remoteAddress());
              sourceCtx.write(updatedEdit);
              sourceCtx.flush();
              detailLogger.info("update edit\n{}", updatedEdit);

              if (null != recorder) {
                recorder.record(sourceCtx, updatedEdit);
              }
              break;
            }
            majorLogger.trace("posting notice to all interested channels except source");
            boolean anyInterest = false;
            for (final ChannelHandlerContext ctx : channelSet.getContextSet()) {
              if (isSameChannel(ctx, sourceCtx)) {
                continue;
              }
              anyInterest = true;
              majorLogger.trace(
                  "notice forward {} to {}", trackingGuid, ctx.channel().remoteAddress());
              ctx.write(msg);
              ctx.flush();
              detailLogger.info("message sent:\n{}", msg);
            }

            if (!anyInterest) {
              editBuilder
                  .addMode(MetaLinkMsg.Edit.EditMode.NOTICE)
                  .setEditMode(MetaLinkMsg.Edit.EditMode.NOTICE)
                  .addOrigin(METALINK_BRIDGE_NAME)
                  .setSequence(sequenceNumber.incrementAndGet());

              final MetaLinkMsg.Notice.Builder noticeBuilder =
                  MetaLinkMsg.Notice.newBuilder()
                      .setNoticeMode(MetaLinkMsg.Notice.NoticeMode.ACK)
                      .setCode(NO_INTEREST_PRESENT)
                      .setMsg("no pending interest for topic")
                      .setId(msg.getGuid());
              editBuilder.addNotices(noticeBuilder);

              final MetaLinkMsg.Edit updatedEdit = editBuilder.build();
              majorLogger.trace(
                  "notice no interest {} to {}", trackingGuid, sourceCtx.channel().remoteAddress());
              sourceCtx.write(updatedEdit);
              sourceCtx.flush();
              detailLogger.info("message sent:\n{}", updatedEdit);

              majorLogger.trace("cannot post, no other interest \n{}", updatedEdit);
              if (null != recorder) {
                recorder.record(sourceCtx, updatedEdit);
              }
            }

            break;
          }
        case POST:
          {
            final Interest topic = new Interest(msg.getTopicList());
            final Interest.InterestedChannelSet channelSet = this.interestedChannelMap.get(topic);
            if (channelSet == null || channelSet.size() == 0) {
              majorLogger.warn("no interested channel set");
              editBuilder
                  .addMode(MetaLinkMsg.Edit.EditMode.NOTICE)
                  .setEditMode(MetaLinkMsg.Edit.EditMode.NOTICE)
                  .addOrigin(METALINK_BRIDGE_NAME)
                  .setSequence(sequenceNumber.incrementAndGet());

              final MetaLinkMsg.Notice.Builder noticeBuilder =
                  MetaLinkMsg.Notice.newBuilder()
                      .setNoticeMode(MetaLinkMsg.Notice.NoticeMode.ACK)
                      .setCode(NO_INTEREST_PRESENT)
                      .setMsg("no interest in the posted topic")
                      .setId(msg.getGuid());
              editBuilder.addNotices(noticeBuilder);

              final MetaLinkMsg.Edit updatedEdit = editBuilder.build();
              majorLogger.trace(
                  "post no-interest {} to {}", trackingGuid, sourceCtx.channel().remoteAddress());
              sourceCtx.write(updatedEdit);
              sourceCtx.flush();
              detailLogger.info("message sent:\n{}", updatedEdit);

              if (null != recorder) {
                recorder.record(sourceCtx, updatedEdit);
              }
              break;
            } else {
              majorLogger.trace("posting edit to all interested channels except source");
              for (final ChannelHandlerContext ctx : channelSet.getContextSet()) {
                if (isSameChannel(ctx, sourceCtx)) {
                  continue;
                }
                majorLogger.trace("post all {} to {}", trackingGuid, ctx.channel().remoteAddress());
                ctx.write(msg);
                ctx.flush();
                detailLogger.info("message sent:\n{}", msg);
              }
            }
            /*
                                if (! anyInterest) {
                                    editBuilder
                                            .addMode(MetaLinkMsg.Edit.EditMode.NOTICE)
                                            .setEditMode(MetaLinkMsg.Edit.EditMode.NOTICE)
                                            .addOrigin(METALINK_BRIDGE_NAME)
                                            .setSequence(sequenceNumber.incrementAndGet());

                                    final MetaLinkMsg.Notice.Builder noticeBuilder = MetaLinkMsg.Notice.newBuilder()
                                            .setNoticeMode(MetaLinkMsg.Notice.NoticeMode.ACK)
                                            .setCode(NO_INTEREST_PRESENT)
                                            .setMsg("no pending interest for topic")
                                            .setId(msg.getGuid());
                                    editBuilder.addNotices(noticeBuilder);

                                    final MetaLinkMsg.Edit updatedEdit = editBuilder.build();
                                    majorLogger.trace("post no interest {} to {}",
                                            trackingGuid, sourceCtx.channel().remoteAddress());
                                    sourceCtx.write(updatedEdit);
                                    sourceCtx.flush();
                                    detailLogger.info("message sent:\n{}", updatedEdit);

                                    majorLogger.trace("cannot post, no other interest \n{}", updatedEdit);
                                    if (null != recorder) {
                                        recorder.record(sourceCtx, updatedEdit);
                                    }
                                }
            */
            break;
          }
        case INTEREST:
          {
            final Interest interest = new Interest(msg.getTopicList());
            playback(sourceCtx, interest);

            majorLogger.trace("determine if there is a client which already is interested?");
            final Interest.InterestedChannelSet ics = this.interestedChannelMap.get(interest);
            boolean anyInterest = false;
            if (ics != null) {
              for (final ChannelHandlerContext ctx : ics.getContextSet()) {
                if (isSameChannel(ctx, sourceCtx)) {
                  continue;
                }
                anyInterest = true;
                majorLogger.trace(
                    "interest in {} from {} to {}",
                    trackingGuid,
                    ctx.channel().remoteAddress(),
                    sourceCtx.channel().remoteAddress());
                sourceCtx.write(msg);
                sourceCtx.flush();
                detailLogger.info("message sent:\n{}", msg);
              }
            }
            if (!anyInterest) {
              editBuilder
                  .addMode(MetaLinkMsg.Edit.EditMode.NOTICE)
                  .setEditMode(MetaLinkMsg.Edit.EditMode.NOTICE)
                  .addOrigin(METALINK_BRIDGE_NAME)
                  .setSequence(sequenceNumber.incrementAndGet());

              final MetaLinkMsg.Notice.Builder noticeBuilder =
                  MetaLinkMsg.Notice.newBuilder()
                      .setNoticeMode(MetaLinkMsg.Notice.NoticeMode.ACK)
                      .setCode(NO_INTEREST_PRESENT)
                      .setMsg("no pending interest for topic")
                      .setId(msg.getGuid());
              editBuilder.addNotices(noticeBuilder);

              final MetaLinkMsg.Edit updatedEdit = editBuilder.build();
              majorLogger.trace(
                  "interest notice {} to {}", trackingGuid, sourceCtx.channel().remoteAddress());
              sourceCtx.write(updatedEdit);
              sourceCtx.flush();
              detailLogger.info("message sent:\n{}", updatedEdit);

              majorLogger.trace("no other interest");
              if (null != recorder) {
                recorder.record(sourceCtx, updatedEdit);
              }
            }
            setInterest(sourceCtx, interest);
            majorLogger.trace("interest recorded");

            majorLogger.trace("expressing interest to all channels except source");
            for (final Map.Entry<SocketAddress, Interest.ChannelInterestSet> entry :
                this.channelInterestMap.entrySet()) {
              final ChannelHandlerContext ctx = entry.getValue().context;
              if (isSameChannel(ctx, sourceCtx)) {
                continue;
              }
              majorLogger.trace("interest {} to {}", trackingGuid, ctx.channel().remoteAddress());
              ctx.write(msg);
              ctx.flush();
              detailLogger.info("message sent:\n{}", msg);
            }
            break;
          }
        case DISINTEREST:
          {
            final Interest interest = new Interest(msg.getTopicList());

            majorLogger.trace("expressing disinterest to all channels except source");
            for (final Map.Entry<SocketAddress, Interest.ChannelInterestSet> entry :
                this.channelInterestMap.entrySet()) {
              final ChannelHandlerContext ctx = entry.getValue().context;
              if (isSameChannel(ctx, sourceCtx)) {
                continue;
              }
              majorLogger.trace(
                  "disinterest {} to {}", trackingGuid, ctx.channel().remoteAddress());
              ctx.write(msg);
              ctx.flush();
              detailLogger.info("message sent:\n{}", msg);
            }
            setDisinterest(sourceCtx, interest);
          }
      }
    } catch (Exception ex) {
      majorLogger.warn("problem distributing message", ex);
    }
    majorLogger.info("MESSAGE PROCESSED");
  }