/**
   * Query the FindServers service at the given endpoint URL.
   *
   * <p>The endpoint URL(s) for each server {@link ApplicationDescription} returned can then be used
   * in a {@link #getEndpoints(String)} call to discover the endpoints for that server.
   *
   * @param endpointUrl the endpoint URL to find servers at.
   * @return the {@link ApplicationDescription}s returned by the FindServers service.
   */
  public static CompletableFuture<ApplicationDescription[]> findServers(String endpointUrl) {
    UaTcpStackClientConfig config =
        UaTcpStackClientConfig.builder().setEndpointUrl(endpointUrl).build();

    UaTcpStackClient client = new UaTcpStackClient(config);

    FindServersRequest request =
        new FindServersRequest(
            new RequestHeader(null, DateTime.now(), uint(1), uint(0), null, uint(5000), null),
            endpointUrl,
            null,
            null);

    return client
        .<FindServersResponse>sendRequest(request)
        .whenComplete((r, ex) -> client.disconnect())
        .thenApply(FindServersResponse::getServers);
  }
  /**
   * Query the GetEndpoints service at the given endpoint URL.
   *
   * @param endpointUrl the endpoint URL to get endpoints from.
   * @return the {@link EndpointDescription}s returned by the GetEndpoints service.
   */
  public static CompletableFuture<EndpointDescription[]> getEndpoints(String endpointUrl) {
    UaTcpStackClientConfig config =
        UaTcpStackClientConfig.builder().setEndpointUrl(endpointUrl).build();

    UaTcpStackClient client = new UaTcpStackClient(config);

    GetEndpointsRequest request =
        new GetEndpointsRequest(
            new RequestHeader(null, DateTime.now(), uint(1), uint(0), null, uint(5000), null),
            endpointUrl,
            null,
            new String[] {Stack.UA_TCP_BINARY_TRANSPORT_URI});

    return client
        .<GetEndpointsResponse>sendRequest(request)
        .whenComplete((r, ex) -> client.disconnect())
        .thenApply(GetEndpointsResponse::getEndpoints);
  }
  public static CompletableFuture<Channel> bootstrap(UaTcpStackClient client) {
    CompletableFuture<Channel> handshake = new CompletableFuture<>();

    Bootstrap bootstrap = new Bootstrap();

    bootstrap
        .group(Stack.sharedEventLoop())
        .channel(NioSocketChannel.class)
        .option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
        .option(ChannelOption.TCP_NODELAY, true)
        .handler(
            new ChannelInitializer<SocketChannel>() {
              @Override
              protected void initChannel(SocketChannel channel) throws Exception {
                channel.pipeline().addLast(new UaTcpClientAcknowledgeHandler(client, handshake));
              }
            });

    try {
      URI uri = URI.create(client.getEndpointUrl());

      bootstrap
          .connect(uri.getHost(), uri.getPort())
          .addListener(
              (ChannelFuture f) -> {
                if (!f.isSuccess()) {
                  handshake.completeExceptionally(f.cause());
                }
              });
    } catch (Throwable t) {
      UaException failure =
          new UaException(
              StatusCodes.Bad_TcpEndpointUrlInvalid,
              "endpoint URL invalid: " + client.getEndpointUrl());

      handshake.completeExceptionally(failure);
    }

    return handshake;
  }