/**
   * @param replica
   * @param externalizable
   * @param tcpReplicatorBuilder
   * @param maxEntrySizeBytes used to check that the last entry will fit into the buffer, it can not
   *     be smaller than the size of and entry, if it is set smaller the buffer will over flow, it
   *     can be larger then the entry, but setting it too large reduces the workable space in the
   *     buffer.
   * @throws IOException
   */
  TcpReplicator(
      @NotNull final Replica replica,
      @NotNull final EntryExternalizable externalizable,
      @NotNull final TcpReplicatorBuilder tcpReplicatorBuilder,
      final int maxEntrySizeBytes)
      throws IOException {

    super("TcpSocketReplicator-" + replica.identifier(), tcpReplicatorBuilder, maxEntrySizeBytes);

    serverInetSocketAddress = tcpReplicatorBuilder.serverInetSocketAddress();

    heartBeatInterval = tcpReplicatorBuilder.heartBeatInterval(MILLISECONDS);

    long throttleBucketInterval = tcpReplicatorBuilder.throttleBucketInterval(MILLISECONDS);
    selectorTimeout = Math.min(heartBeatInterval, throttleBucketInterval);

    packetSize = tcpReplicatorBuilder.packetSize();
    endpoints = tcpReplicatorBuilder.endpoints();

    this.replica = replica;
    this.localIdentifier = replica.identifier();
    this.maxEntrySizeBytes = maxEntrySizeBytes;
    this.externalizable = externalizable;

    start();
  }
  /**
   * used to exchange identifiers and timestamps and heartbeat intervals between the server and
   * client
   *
   * @param key the SelectionKey relating to the this cha
   * @throws java.io.IOException
   * @throws InterruptedException
   */
  private void doHandShaking(@NotNull final SelectionKey key)
      throws IOException, InterruptedException {
    final Attached attached = (Attached) key.attachment();
    final TcpSocketChannelEntryWriter writer = attached.entryWriter;
    final TcpSocketChannelEntryReader reader = attached.entryReader;

    if (attached.remoteIdentifier == Byte.MIN_VALUE) {

      final byte remoteIdentifier = reader.identifierFromBuffer();

      if (remoteIdentifier == Byte.MIN_VALUE) return;

      attached.remoteIdentifier = remoteIdentifier;

      // we use the as iterating the activeKeys via the bitset wont create and Objects
      // but if we use the selector.keys() this will.
      selectionKeysStore[remoteIdentifier] = key;
      activeKeys.set(remoteIdentifier);

      if (LOG.isDebugEnabled()) {
        LOG.debug(
            "server-connection id={}, remoteIdentifier={}", localIdentifier, remoteIdentifier);
      }

      if (remoteIdentifier == localIdentifier) {
        throw new IllegalStateException(
            "Where are connecting to a remote "
                + "map with the same "
                + "identifier as this map, identifier="
                + localIdentifier
                + ", "
                + "please change either this maps identifier or the remote one");
      }

      attached.remoteModificationIterator =
          replica.acquireModificationIterator(remoteIdentifier, attached);

      writer.writeRemoteBootstrapTimestamp(replica.lastModificationTime(remoteIdentifier));

      // tell the remote node, what are heartbeat interval is
      writer.writeRemoteHeartbeatInterval(heartBeatInterval);
    }

    if (attached.remoteBootstrapTimestamp == Long.MIN_VALUE) {
      attached.remoteBootstrapTimestamp = reader.remoteBootstrapTimestamp();
      if (attached.remoteBootstrapTimestamp == Long.MIN_VALUE) return;
    }

    if (!attached.hasRemoteHeartbeatInterval) {

      long value = reader.remoteHeartbeatIntervalFromBuffer();

      if (value == Long.MIN_VALUE) return;

      if (value < 0) {
        LOG.error("value=" + value);
      }

      // we add a 10% safety margin to the timeout time due to latency fluctuations on the network,
      // in other words we wont consider a connection to have
      // timed out, unless the heartbeat interval has exceeded 25% of the expected time.
      attached.remoteHeartbeatInterval = (long) (value * 1.25);

      // we have to make our selector poll interval at least as short as the minimum selector
      // timeout
      selectorTimeout = Math.min(selectorTimeout, value);

      if (selectorTimeout < 0) LOG.info("");

      attached.hasRemoteHeartbeatInterval = true;

      // now we're finished we can get on with reading the entries
      attached.setHandShakingComplete();
      attached.remoteModificationIterator.dirtyEntries(attached.remoteBootstrapTimestamp);
      reader.entriesFromBuffer();
    }
  }